Если у вас Whisper выдаёт кашу на русской речи и в одном месте повторяет одну и ту же фразу пять-восемь раз подряд, то это не баг звука и не кривой микрофон, это галлюцинация самой модели на тишине, и лечится она тремя параметрами: поднять модель с base до medium, включить VAD-фильтр (vad_filter=True) и отключить опору на предыдущий текст (condition_on_previous_text=False). У меня после этого транскрипция перестала выдумывать слова из воздуха. Сейчас расскажу по порядку, что именно сломалось и почему так выходит, потому что механика тут на самом деле поучительная.
Контекст такой. Я ковыряю SceneX, это штука, которая нарезает короткие видео из фильмов, и там без распознавания речи никак, потому что на тексте сегментов строится вообще вся логика дальше (как я вообще затащил эту транскрипцию на локальные модели — отдельная история). И вот беру я реальное видео, прогоняю через транскрипцию и получаю на выходе не текст, а кашу. В одном месте модель выдала восемь сегментов подряд с одним и тем же текстом, что-то вроде «Перекус таксиста чек», потом «Капец таксиста...», и так по кругу. Я сижу и думаю буквально словами «вот это вот что за баги блять? и вообще сука почему так херово распознает». Звук-то нормальный, я его сам слушал, там обычная речь, никакого «перекуса таксиста» в кадре близко нет.
Почему Whisper выдумывает текст на тишине
Тут надо понять одну вещь про то, как вообще устроена эта модель. Whisper это не распознавалка в том смысле, в каком вы её себе представляете, она не «слышит звук и пишет буквы». Это языковая модель, обученная на парах «аудио → текст», и по своей природе она генеративная, то есть предсказывает следующее слово, опираясь на то, что было раньше. И вот когда в аудио наступает тишина или какой-то шум без речи, модели нечего предсказывать из звука, но генерировать она всё равно обязана, такая у неё работа, и она начинает добивать пробел тем, что есть под рукой, последним кусочком текста, который только что выдала. Получается петля: сказала фразу, дальше тишина, повторила фразу, опять тишина, опять повторила, и так восемь раз, пока кусок аудио не кончится.
А почему именно на русском это вылезает так нагло? Потому что в распознанной русской речи почти нет пунктуации, модель её толком не расставляет, и из-за этого края сегментов ей не на что опереть, она выравнивает границы по паузам в звуке, по тишине. А раз опора на паузы, то любая длинная тишина в середине дорожки превращается в дырку, которую модель честно пытается заполнить, и заполняет тем самым бредом про таксиста. На английском, где модель видела на порядок больше данных и где пунктуация ложится ровнее, этот эффект слабее, а на нашей речи он лезет наружу во всей красе.
Три параметра, которые реально починили
Лечилось это не магией и не переписыванием половины кода, а ровно настройкой. Я оркестрировал фиксы через Claude Code, мы прошлись по параметрам распознавания, и сработала комбинация из трёх вещей. Первое, подняли модель с base до medium. Base это самая мелкая и быстрая версия, она и галлюцинирует охотнее всего, потому что у неё меньше параметров и ей проще скатиться в повтор, а medium тяжелее и медленнее, но качество распознавания русского у неё заметно выше, и сама склонность выдумывать падает. Второе, включили VAD-фильтр, vad_filter=True. VAD это voice activity detection, то есть детектор того, где в дорожке реально есть голос, а где тишина. Он просто выкидывает куски без речи до того, как они дойдут до модели, и заполнять бредом ей уже нечего, дырку с тишиной он отрезает заранее. Третье, и это контринтуитивно, отключили опору на предыдущий текст, condition_on_previous_text=False. По умолчанию модель тащит контекст предыдущего сегмента в следующий, чтобы текст был связнее, но именно это и раскручивает петлю повторов: один раз сгаллюцинировала, и дальше этот мусор тянется как контекст и плодит сам себя. Отрубаешь связь между сегментами, и петля рвётся. Все эти ручки, кстати, из faster-whisper — почему именно он, а не mlx-whisper на Mac, я разбирал отдельно.
По отдельности каждый параметр чуть-чуть помогал, но именно вместе они убрали повторы полностью. И тут хочется сказать одну вещь людям, которые думают что подключить нейросеть это «вставил API и работает». Не работает. У любой модели, хоть распознавание речи, хоть генерация, есть десяток ручек, и дефолты заточены под красивое демо на английском, а не под ваш реальный звук на русском. Вся разница между «каша» и «нормальный текст» сидит ровно в этих ручках, и пока вы не поняли почему модель ведёт себя так, вы их крутите вслепую.
Второй баг, который вылез по дороге
И пока я воевал с галлюцинациями, параллельно вскрылась штука посмешнее. У меня каждый сегмент должен получать семантическую роль, реплика там, описание, что по смыслу, а вместо этого все роли поголовно схлопывались в одно значение, в 'filler'. То есть весь смысловой слой просто умер, всё стало «заполнителем». Я сначала грешил на ту же транскрипцию, мол, мусор на входе даёт мусор на выходе, но нет, причина оказалась в другом месте и вообще не в нейросети.
Там было сразу два косяка в форматировании промпта. Первый, KeyError в .format(): в шаблоне промпта были литеральные фигурные скобки, обычные {}, которые я хотел просто как текст, а Python в .format() воспринимает их как плейсхолдеры под подстановку, не находит соответствующий ключ и валится с ошибкой. Из-за этого промпт собирался криво. Второй, неправильный разбор json_object на выходе: модель отдавала ответ, а парсер читал его не так, как надо, и роли не доезжали до сегментов, отсюда и тотальный 'filler' по всем подряд. То есть нейросеть тут была вообще ни при чём, она честно делала свою работу, а сломалась обычная склейка строки и разбор ответа, самый банальный код вокруг модели. И это, кстати, типичная история: когда работаешь с ИИ, половина багов не в самой модели, а в трубах, которыми ты к ней подключаешься.
Что я из этого вынес
Главный вывод простой: «нейросеть херово распознаёт» это почти всегда не приговор модели, а ваши настройки и ваш код вокруг. Восемь повторов про таксиста выглядели как полная поломка, а оказались тремя галочками в параметрах. Тотальный 'filler' выглядел как сломанный смысловой движок, а оказался фигурными скобками в строке. И там, и там сама нейросеть работала нормально, проблема была в том, как я её готовил и как читал ответ.
Если вы делаете что-то на распознавании русской речи и упёрлись в кашу, то начните не с замены модели на другую и не с переписывания всего пайплайна, а ровно с этих трёх ручек: модель покрупнее (хотя бы medium), VAD-фильтр на вход, и отключённую опору на предыдущий текст. Это копеечные изменения по времени, а эффект как от пересадки на другой движок. А если у вас вдобавок что-то схлопывается в одно значение по всем элементам сразу, то лезьте не в модель, а в форматирование промпта и разбор её ответа, почти наверняка собака зарыта там.
Я всё это собирал не руками, код писал Claude Code под моим управлением, я оркестрировал и разбирался, почему оно так себя ведёт, а не закидывал нейросеть запросами наугад. И вот в этом, если честно, и есть весь вайбкодинг и вся разработка с ИИ на практике: не «магия, которая сама всё сделала», а понимание механики плюс умение задать правильный вопрос и проверить, что тебе на него не наврали. Тот же подход я гонял и в нумерологии в Cartara от нуля до прода за неделю, и в четырёх косяках Claude Code за четыре дня, везде одно и то же: модель мощная, но без понимания того, что у неё под капотом, она вам выдаст «перекус таксиста чек» с серьёзным лицом, и вы будете долго думать что это вообще было.