🧠 Заводим у себя нейронку OpenAI для локального распознавания звонков и подкастов

 Публичный пост
23 марта 2023  10216

ChatGPT сейчас на хайпе, но я тут решил немного поиграть в другой продукт от Open AI — Whisper — который умеет распознавать голос на куче языков и превращать их в текст.

Сначала я просто потрогал через curl их API, потом я моргнул, и через два дня у меня появился инструмент, который может локально прямо на макбуке распознать аудиозапись звонка и разметить в нем разных спикеров. Хочу в этом посте рассказать как самому сделать так же.

Картинка с финальным результатом для привлечения внимания.

Чтобы получить такую же, нужно выполнить следующие шаги. Все выполнялось на MacBook Pro M1 Pro 32 Gb c macOS Ventura 13.2.1.

0. Настройка окружения

Для работы понадобятся Python3.10, git и clang.

Python3.10 уже есть в поставке вместе с macOS. Чтобы установить git и clang (если у вас их еще нет), выполните команду xcode-select --install.

Теперь нужно настроить виртуальное окружение для Python, куда будем ставить все пакеты и библиотеки. Для этого выполните следующие команды:

python3.10 -m venv whisper && cd whisper
source bin/activate

1. Установка whisper.cpp

whisper.cpp — реализация Whisper на C++. Стоит использовать именно ее, а не оригинальный Whisper от OpenAI, так как она значительно быстрее работает. При этом сами модели нейронных сетей используются те же, что и у OpenAI.

Скачиваем репозиторий с whisper.cpp, собираем программу и скачиваем самую большую (large-v1) модель от OpenAI:

git clone https://github.com/ggerganov/whisper.cpp.git && cd whisper.cpp
make
./models/download-ggml-model.sh large-v1

Уже на этом шаге можно попробовать расшифровать аудиозапись в текст, выполнив следующую команду

./main -m models/ggml-large-v1.bin -l ru --no-timestamps -f ~/output.wav -of output -otxt

Параметры означают следующее:

  • -m — путь до файла с моделью
  • -l — язык
  • --no-timestamps — не выводить временные метки в расшифровки (оставить только текст)
  • -f — путь до аудиофайла в формате wav
  • -of — имя файла с расшифровкой (без расширения!)
  • -otxt — вывод в формате txt (текстовый файл)

Если ваш аудиофайл не в формате wav, то его можно сконвертировать при помощи утилиты ffmpeg:

ffmpeg -i audio1470766962.m4a -ar 16000 output.wav

2. Устанавливаем библиотеки для распознавания спикеров

Чтобы разметить аудиофайл на сегменты с речью каждого спикера в отдельности, понадобятся следующие:

  • pywhispercpp — Python-бингдинги к whispercpp, чтобы можно было использовать быстрое применение моделей на плюсах прямо из питона.
  • pyannote-audio — набор библиотек для разделения аудио-потока на сегменты и для распознавания в нем отдельных спикеров.
  • pyannote-whisper — обвязка вокруг pyannote-audio, чтобы использовать обученные языковые модели от Whisper.

Чтобы все это установить, выполняем следующие команды:

pip3 install openai-whisper pywhispercpp pyannote-audio

Скорее всего установка pyannote-audio упадет с ошибкой при сборке пакета hmmlearn примерно со следующим текстом

note: This error originates from a subprocess, and is likely not a problem with pip.
error: legacy-install-failure

× Encountered error while trying to install package.
╰─> hmmlearn

note: This is an issue with the package mentioned above, not pip.
hint: See above for output from the failure.

Поэтому дальше зависимости придется доустанавливать вручную при помощи следующих команд:

pip3 install pytorch_lightning==1.6 torch-audiomentations==0.11.0 asteroid-filterbanks==0.4 pyannote.metrics==3.2 pyannote.pipeline==2.3 speechbrain torchaudio==2.0.0 torch==2.0.0 hmmlearn==0.2.6
pip3 install pyannote.audio --no-deps

Наконец скачиваем pyannote-whisper:

git clone https://github.com/yinruiqing/pyannote-whisper.git && cd pyannote-whisper

3. Настраиваем модель для сегментации аудиофайла

Теперь нужно скачать модель от pyannote-audio, которая будет разбирать аудиофайл на сегменты и файл конфигурации модели. Для этого выполните следующие шаги:

  1. Зарегистрируйтесь на сайте HuggingFace
  2. Скачайте файл с моделью segmentation/pytorch_model.bin
  3. Скачайте файл конфигурации conifig.yaml
  4. Сохраните оба файла в директорию pyannote-whisper
  5. Отредактируйте в файле conifig.yaml следующие поля
  • pipeline.params.embedding_batch_size установите в 1
  • pipeline.params.segmentation укажите имя файла pytorch_model.bin

В результате файл config.yaml должен выглядеть следующим образом:

pipeline:
  name: pyannote.audio.pipelines.SpeakerDiarization
  params:
    clustering: AgglomerativeClustering
    embedding: speechbrain/spkrec-ecapa-voxceleb
    embedding_batch_size: 1 # уменьшение с 32 до 1 внезапно значительно ускоряет процесс, подсказка найдена в issues на гитхабе
    embedding_exclude_overlap: true
    segmentation: pytorch_model.bin # имя файла с моделью
    segmentation_batch_size: 32

params:
  clustering:
    method: centroid
    min_cluster_size: 15
    threshold: 0.7153814381597874
  segmentation:
    min_duration_off: 0.5817029604921046
    threshold: 0.4442333667381752

4. Выполняем код для расшифровки и разметки аудио

После этого, имея на руках все библиотеки, модели и конфиг, останется только выполнить Python код, который обработает аудиофайл.

Сохраните в директории pyannote-whisper в файл diarize.py следующий код.

from pyannote.audio import Pipeline
from pyannote_whisper.utils import diarize_text
from pywhispercpp.model import Model

# Указываем путь до файла с конфигом, он должен быть в той же директории, как сказано на шаге 3.
pipeline = Pipeline.from_pretrained("config.yaml")

# Указываем название модели large-v1 и путь до директории с whisper-моделями из шага 1.
model = Model('large-v1', '/Users/guschin/whisper.cpp/models', n_threads=6)

# Указываем путь до аудио-файл, кторый будем расшифровывать в текст. Путь обязательно абсолютный.
asr_result = model.transcribe("/Users/guschin/audio1470766962.wav", language="ru")

# Конвертация результата в формат, который понимает pyannote-whisper.
result = {'segments': list()}

for item in asr_result:
    result['segments'].append({
        'start': item.t0 / 100,
        'end': item.t1 / 100,
        'text': item.text
        }
    )

# Сегментация аудио-файла на реплики спикеров. Путь обязательно абсолютный.
diarization_result = pipeline("/Users/guschin/audio1470766962.wav")

# Пересечение расшифровки и сегментаци.
final_result = diarize_text(result, diarization_result)

# Вывод результата.
for seg, spk, sent in final_result:
    line = f'{seg.start:.2f} {seg.end:.2f} {spk} {sent}'
    print(line)

Запустите код следующей командой

python3 diarize.py

В результате работы на экран выведутся сегменты исходного аудиофайла: время начала и конца сегмента в секундах, идентификатор спикера и текст сегмента.

В целом, получившаяся связка позволяет локально расшифровывать звонки и подкасты, что заменяет такие платные сервисы как otter.ai (17 долоров в месяц или 100 за год).

Если кто-то попробует повторить такое у себя и столкнется с проблемами — пишите в комментарии, возможно, я забыл указать какие-то еще мелкие особенности.

45 комментариев 👇
Petr Stepchenko Pragmatic Software Engineer 23 марта 2023

Еще из интересного. В Elixir комьюнити недавно выходил туториал как сделать веб приложение с потоковым распознанием речи с локальным Whisper за 15 минут

https://www.phoenixframework.org/blog/whisper-speech-to-text-phoenix

  Развернуть 1 комментарий

😱 Комментарий удален его автором...

  Развернуть 1 комментарий

Ещё вот такое вот есть красивое — https://goodsnooze.gumroad.com/l/macwhisper

  Развернуть 1 комментарий

@ivankozlov, установил себе больше месяца назад. русский распознаёт не очень. но там нет модели large. возможно, ради этого и имеет смысл заморочиться с терминалом..

  Развернуть 1 комментарий

@borunov, есть large-версия для multiple languages. У меня работает шикарно. Но, да, стоит немного деняк (16 евро). Мне было не жалко :)

  Развернуть 1 комментарий
Артём Сущев пишу код, говорю ртом, теребонькаю конфиги 28 марта 2023

Идея продукта появилась. Бот - "протоколист". Приглашаешь такого бота в зум-звонок, после окончания он всё распознает, потом прогоняет через ChatGPT с промтом "сделай выжимку, тезисами перечисли action items для каждого" и рассылает всем участникам на почту.

Наверное уже есть такое? =)

  Развернуть 1 комментарий

@H1D, Точно есть стартапы, которые такое делают. Я попробовал тыкнуться в эту сторону, скормив в ChatGPT транскрибированный текст встречи и попросил суммаризовать. На коротких протоколах как-то работает, но быстро упираешься в лимит на длину запроса к ChatGPT. Запись часовой встречи уже не пролазит. В гугле есть идеи как это можно попробовать обойти, но я еще не экспериментировал

  Развернуть 1 комментарий

@H1D, Оттер такое делал, насколько помню, еще до ChatGPT. https://otter.ai

  Развернуть 1 комментарий
Yury Katkov в шаббатикале 18 октября 2023

Я тут с помощью whisper.cpp распознал несколько тысяч выпуска подкаста nihongo con teppei и отдал их собственно Тэппею, автору подкаста. Распознает долго, но очень даже сносно. Думаю, что можно подогнать такой презент и другим подкастерам.

Очень бы хотелось развития, но вижу, что это проблематично. Например, хочется добавить фуригану (когда иероглифические произношения подписываются сверху) и пунктуацию, но не знаю, с какой стороны даже подступиться.

  Развернуть 1 комментарий

Если не париться с обозначением говорящих (или вообще не париться), то можно взять готовое https://github.com/m1guelpf/auto-subtitle и записать субтитры прямо в файл командой вида ffmpeg -i 1.mp3 -i 1.srt -metadata:s:s:0 language=eng oup.mka

  Развернуть 1 комментарий

@nakopylov, у whisper есть режим вывода в srt, но я еще не пробовал. Интересная мысль про субтитры

  Развернуть 1 комментарий
Andrey Guschin Technical Product Manager автор 5 февраля в 21:24

Не знаю уж почему поднялась эта тема, но раз поднялась поделюсь своим опытом.

Всплыла потому что комментарии свежие появились

Так вот под мой юз кейс есть вот такой чудесный инструмент:

https://github.com/kaixxx/noScribe

Прикольный инструмент, сделанный на тех же технологиях, что и в посте описаны, только появился позже, чем я этот пост написал.

В отличие от других бесплатных инструментов дает возможность большую (и более качественную) модель использовать, что безусловно хорошо

  Развернуть 1 комментарий

@guschin, спасибо! Затестил - работает. Правда, GPU не использует, и не вижу настроек для этого. А на GPU у меня STT работает в 4-5 раз быстрее.

Возможно, попробую разобраться с pyannote и встроить его в пакетный распознаватель голоса на основе Whisper, который делал под свои нужды и которым регулярно пользуюсь: https://github.com/dimonier/batch-speech-to-text

  Развернуть 1 комментарий

😱 Комментарий удален его автором...

  Развернуть 1 комментарий

@dimonier, whisper.cpp поддерживает CUDA, на гитхабе авторов написано, что библиотеку для этого нужно по-другому собрать, указав нужный флаг

https://github.com/ggerganov/whisper.cpp?tab=readme-ov-file#nvidia-gpu-support

  Развернуть 1 комментарий

@guschin, установил на air m2. на удивление один и тот же трек установленный в командной строке whisper.cpp распознал кардинально лучше, чем noScribe. не стал разбираться почему и снёс последнего

  Развернуть 1 комментарий
Vladlen Erokhin системный администратор 23 марта 2023

На гитхабе Whisper постоянно публикуют ссылки на новые приложения для распознавания речи https://github.com/openai/whisper/discussions в том числе с удобным графическим интерфейсом, вроде Buzz. Я тоже обратил внимание, что whisper.cpp быстрее, но пока не разобрался, как ей скармливать аудио с компьютера, чтобы в реальном времени распознавать видео с youtube или звонок по ip-телефонии с иностранцем.

  Развернуть 1 комментарий

@Morhine, У whisper.cpp есть примеры как расшифровывать аудио в текст налету, снимая звук прямо с микрофона, но я сам не пробовал

https://github.com/ggerganov/whisper.cpp/tree/master/examples/stream

  Развернуть 1 комментарий
Yury Katkov в шаббатикале 19 октября 2023

а я правильно понял, что чтобы была диаризация, надо чтобы человек 1 говорил в левое ухо, а человек 2 - в правое? А то у меня для подкаста не распознались они

  Развернуть 1 комментарий

@ganqqwerty, нет, такого тербования нет. pyannote должен распознавать смену спикера в одном потоке

У меня получалось им распознавать подкасты с тремя спикерами

  Развернуть 1 комментарий
nikita shein отвечаю за бизнес репортинг 23 января в 07:36

Спасибо!
Вот пример https://docs.google.com/document/d/1G4XAzeur6hnud07ZDI3qvHKrU9-J6RofMVZas6SS-uA/edit
распознал подкаст и вычитал результат

я там специально правки делал в режиме рецензирования, чтобы качество распознавания можно было оценить.

  Развернуть 1 комментарий
Илья Паршин Индивидуальный предприниматель 29 февраля в 19:26

У кого ошибка
ModuleNotFoundError: No module named 'speechbrain.pretrained'
Вот issue по ней
https://github.com/speechbrain/speechbrain/issues/2436

В файле
lib/python3.11/site-packages/pyannote/audio/pipelines/speaker_verification.py переименовать speechbrain.pretrained в speechbrain.inference

  Развернуть 1 комментарий
Ilia Gordeev Senior Marketing Manager 23 марта 2023

Классно. Мне кажется можно смело пилить приложение и зарабатывать доллар.)

  Развернуть 1 комментарий

@Ilia_gordeev, если бы я умел делать веб-сайтики — я бы уже запилил :)

  Развернуть 1 комментарий

@guschin, я как раз умею делать сайтики) можем объединиться и сделать, если есть желание
Телеграмм katesha27

  Развернуть 1 комментарий

@nbirdie, а я даже знаю ЦА и есть тестовый датасет для расшифровки

  Развернуть 1 комментарий

@ptriotofrussia, напиши мне в телегу @guschin

  Развернуть 1 комментарий

А какая часть этого пайплайна обеспечивает такую красивую пунктуацию на выходе?

  Развернуть 1 комментарий

@greemster, это делает Whisper. Но только в самой большой модели (large)

  Развернуть 1 комментарий
Vladlen Erokhin системный администратор 23 марта 2023

Микрофон - это же чтобы расшифровывать, что я говорю. Я хочу захватывать любой источник звука на компьютере и расшифровывать его.

Мне очень нравится, что в MS Teams есть встроенная функция транслитерации и перевода речи собеседников на лету. Я хочу примерно то же самое для ip-звонилки. Я пробовал всякие готовые решения, они работают, но очень медленно, отставая примерно на пару предложений.

  Развернуть 1 комментарий

Насколько реально это прикрутить для рил-тайма, хватит ли на это M1 Pro?

  Развернуть 1 комментарий

@IvanGulyaev, риал-тайм режим не пробовал, но библиотеки это позволяют. У меня как раз M1 Pro и на нем время распознавания примерно равно длительности аудио-файла, поэтому теоретически это реально.

  Развернуть 1 комментарий

@guschin, делал на air m2 — время примерно x1,5–2 от длительности аудио-файла

  Развернуть 1 комментарий

@IvanGulyaev, https://github.com/yohasebe/whisper-stream
работает отлично, но не локально

  Развернуть 1 комментарий
Igor Labutin Ведущий разработчик/архитектор 27 марта 2023

Попробовал на M2 Pro, завелось с небольшими поправками.

При запуске столкнулся с ошибкой

Traceback (most recent call last):
  File "/Users/igor/.pyenv/versions/3.10.10/lib/python3.10/site-packages/soundfile.py", line 142, in <module>
    raise OSError('sndfile library not found')
OSError: sndfile library not found

brew install libsndfile не помогло. Точнее, sndfile поставился, но не туда :) После копирования вручную в папку где он пытался искать (/Users/igor/.pyenv/versions/3.10.10/lib/python3.10/site-packages/_soundfile_data/libsndfile.dylib) заработало прекрасно.

  Развернуть 1 комментарий

Кстати, пока экспериментировал ноут жил на батарейке и скорость была примерно линейной от времени ролика.
Когда подключил к зарядке и задал ему файл на 1:46:00, то распознавание справилось за 01:11:00 примерно.
Не знаю, содержимое файла ли повлияло или тот факт что к зарядке подключил.

  Развернуть 1 комментарий

@ilabutin, надо было через pip сразу в venv ставить, видимо.

У меня на M1 Pro распознает за то же время, что и длина аудио-файла даже на зарядке. Возможно, на батарейке процессор тротлится и процесс действительно идет медленее

  Развернуть 1 комментарий
ggml_new_tensor_impl: not enough space in the scratch memory

[1]    75386 segmentation fault  ./main -m models/ggml-large.bin -l ru --no-timestamps -f ./output.wav -of 

всё? это счастье пролетит мимо меня? M2, RAM 8Gb

  Развернуть 1 комментарий

@borunov, large-модельке нужно где-то 4 Гб свободной памяти. Попробуй запустить сразу после перезагрузки ноутбука, не запуская никаких новых приложений, пока память чистая. Ну или возьми medium-модельку

  Развернуть 1 комментарий
burivuh26 Системный/Бизнес Аналитик 23 сентября 2023

Я по инструкции распознавал это весной, но сейчас столкнулся с проблемой: при запуске diarize.py меня встречает вот такая ошибка.

Feature extraction using pretrained models are not available because something went wrong at import: "cannot import name 'SafeFunction' from 'joblib._parallel_backends' (/usr/local/lib/python3.11/site-packages/joblib/_parallel_backends.py)". Traceback (most recent call last): File "/Users/burivuh26/pyannote-whisper/diarize.py", line 6, in <module> Pipeline = pipeline.from_pretrained("config.yaml") ^^^^^^^^^^^^^^^^^^^^^^^^ AttributeError: module 'pyannote.audio.pipeline' has no attribute 'from_pretrained'
что нужно тут поправить?

  Развернуть 1 комментарий

@burivuh26, не сталкивался с такой ошибкой. Сейчас проделал все по инструкции на новом маке и у меня все заработало. По тексту ошибки похоже, что либо версия библиотеки pyannote старая, либо она почему-то не заимпортировалась нормально. Попробуй еще раз сделать pip3 install pyannote-audio

  Развернуть 1 комментарий

@guschin, совершил ещё один подход к снаряду. Обнаружил, что действительно pyannote-audio не встала, поставил. Также обновил через git pull whisper.cpp и pyannote-whisper. Также перекачал модель, теперь нет просто large модели, зато есть large-v3.
Тем не менее, при запуске diarize.py я получаю вот такое вот:
и бонусом крашится python.

/opt/homebrew/lib/python3.11/site-packages/pyannote/audio/core/io.py:43: UserWarning: torchaudio._backend.set_audio_backend has been deprecated. With dispatcher enabled, this function is no-op. You can remove the function call.
torchaudio.set_audio_backend("soundfile")
/opt/homebrew/lib/python3.11/site-packages/torch_audiomentations/utils/io.py:27: UserWarning: torchaudio._backend.set_audio_backend has been deprecated. With dispatcher enabled, this function is no-op. You can remove the function call.
torchaudio.set_audio_backend("soundfile")
[2024-02-03 18:06:32,483] {train_logger.py:264} WARNING - torchvision is not available - cannot save figures
Lightning automatically upgraded your loaded checkpoint from v1.5.4 to v2.1.4. To apply the upgrade to your files permanently, run python -m pytorch_lightning.utilities.upgrade_checkpoint pytorch_model.bin
Model was trained with pyannote.audio 0.0.1, yours is 3.1.1. Bad things might happen unless you revert pyannote.audio to 0.x.
Model was trained with torch 1.10.0+cu102, yours is 2.2.0. Bad things might happen unless you revert torch to 1.x.
[2024-02-03 18:06:33,382] {fetching.py:128} INFO - Fetch hyperparams.yaml: Using existing file/symlink in /Users/burivuh26/.cache/torch/pyannote/speechbrain/hyperparams.yaml.
[2024-02-03 18:06:33,382] {fetching.py:162} INFO - Fetch custom.py: Delegating to Huggingface hub, source speechbrain/spkrec-ecapa-voxceleb.
[2024-02-03 18:06:33,806] {fetching.py:128} INFO - Fetch embedding_model.ckpt: Using existing file/symlink in /Users/burivuh26/.cache/torch/pyannote/speechbrain/embedding_model.ckpt.
[2024-02-03 18:06:33,806] {fetching.py:128} INFO - Fetch mean_var_norm_emb.ckpt: Using existing file/symlink in /Users/burivuh26/.cache/torch/pyannote/speechbrain/mean_var_norm_emb.ckpt.
[2024-02-03 18:06:33,806] {fetching.py:128} INFO - Fetch classifier.ckpt: Using existing file/symlink in /Users/burivuh26/.cache/torch/pyannote/speechbrain/classifier.ckpt.
[2024-02-03 18:06:33,806] {fetching.py:128} INFO - Fetch label_encoder.txt: Using existing file/symlink in /Users/burivuh26/.cache/torch/pyannote/speechbrain/label_encoder.ckpt.
[2024-02-03 18:06:33,806] {parameter_transfer.py:299} INFO - Loading pretrained files for: embedding_model, mean_var_norm_emb, classifier, label_encoder
[2024-02-03 18:06:33,874] {utils.py:46} INFO - Model large already exists in /Users/burivuh26/whisper.cpp/models
[2024-02-03 18:06:33,875] {model.py:221} INFO - Initializing the model ...
whisper_init_from_file_no_state: loading model from '/Users/burivuh26/whisper.cpp/models/ggml-large.bin'
whisper_model_load: loading model
whisper_model_load: invalid model data (bad magic)
whisper_init_no_state: failed to load model
[2024-02-03 18:06:36,618] {model.py:130} INFO - Transcribing ...
[1] 46635 segmentation fault python3 diarize.py

Выглядит всё так, будто какие-то части системы должны работать с другими версиями моделей, но не работают.
Можно конечно всё снести и поставить заново, но всё-таки хочется разобраться.

  Развернуть 1 комментарий

@burivuh26, я сейчас засетапил все с нуля и у меня тоже так же все крашится. Проблема, похоже в том, что библиотека pywhispercpp (питонячьи биндинги) к whisper.cpp не обновлялись автором для поддержки large-v3 модели.

Поправил текст поста, чтобы везде использовать старую largbe-v1 модель. Так это работает. Чтобы поддержать новые модели придется как-то заморочиться

  Развернуть 1 комментарий

Раз это работает в реал тайме, и есть модели которые переводят на другой язык сохраняя голос (видел какую то превьюшку в линкедине), то мы в шаге от снятия речевого барьера, верно?

есть уже такие решения?

Upd: что-то я на радостях затупил. Структура языков то разная. Так что рил тайм вряд ли

  Развернуть 1 комментарий

@yand, яндекс браузер уже переводит видео в реальном времени, только на основе своих технологий, а не whisper. Так что в каком-то смысле это уже происходит прямо сейчас

  Развернуть 1 комментарий
Денис Кандров Делаю линейку для производительности ПО 2 марта в 08:32

Что-то падает постоянно, с ошибкой:

ImportError: 'speechbrain' must be installed to use 'speechbrain/spkrec-ecapa-voxceleb' embeddings.

А как установить этот speechbrain с нужным embedding-ом не пойму.

  Развернуть 1 комментарий

@toborob,
Помогло следующее:
pip install speechbrain==0.5.16

Нашёл баг на speechbrain, они написали, что проблема у pyannotate. Последние обещались починить, но что часть тестов завалилась. Так что я просто поставил старую версию speechbrain. Ссылки на баги:

  Развернуть 1 комментарий

😎

Автор поста открыл его для большого интернета, но комментирование и движухи доступны только участникам Клуба

Что вообще здесь происходит?


Войти  или  Вступить в Клуб