Больше чем WER: RESON — открытый инструмент и датасет для оценки ASR в телефонном канале


Привет, Хабр! Меня зовут Александр Шевченко, я техлид команды ASR ML (направление ASR/TTS-платформы Audiogram) в MWS AI. Мы занимаемся совершенствованием технологий распознаванием речи. Проводим исследования, обучаем модели, гоняем их в проде и временами объясняем клиентам, почему «вот тут распозналось так, а тут вот так». Сегодня мы выкладываем в открытый доступ сразу два инструмента, которые выросли из нашей внутренней кухни за последний год:
RESON (Research Engine for Speech & Observation Notes) — инструмент для анализа качества ASR-моделей. Включает подсчет метрик, разбор ошибок на уровне отдельных слов, сравнение моделей на предмет статистически значимых улучшений и интерактивные HTML-отчеты.
RESON OSD — открытый бенчмарк для оценки качества русскоязычных ASR-моделей. Он состоит из двух датасетов, записанных в разных акустических условиях: General и Numeric. Каждый из них представлен в условно чистой (quietly) и шумной (noisily) версиях. Весь же бенч содержит 12 114 аудиозаписей, ~37 часов 52 минуты аудиофайлов, телефонный канал 8 кГц. В разметке транскрипций присутствует пунктуация, капитализация, дополнительно есть версии с англицизмами и денормализованными числами.
Ну а теперь давайте переходить к истории о том, что именно у нас болело до такой степени, что мы разработали эти инструменты…
Оглавление
Часть 1. Почему одного WER недостаточно
WER — это одно число на весь датасет
WER не знает, какие слова нам важны
Часть «ошибок» — это не ошибки
«Стало лучше» ≠ стало лучше
Часть 2. RESON: как из разрозненных скриптов выросла библиотека
Началось все как обычно, со скриптов
Три сущности, на которых все держится
MWA и WIS: две метрики, которые мы добавили от себя
Три отчета под три вопроса
Два входа: CLI и Python API
Часть 3. RESON OSD — открытый бенчмарк
Что внутри и что бенчмарк позволяет измерить
Наши замеры по бенчмарку
Как собирали
Замеры открытых моделей
Где лежит датасет
Часть 1. Одного WER недостаточно
WER (Word Error Rate) — общепринятая метрика оценки качества ASR-моделей. Распознанный текст сравнивают с эталонной разметкой и далее считают, сколько слов пришлось заменить, удалить, вставить для выравнивания. Формула выглядит так:
где:
S (Substitution) — количество замен;
D (Deletion) — количество удалений;
I (Insertion) — количество вставок;
N — количество слов в эталонной разметке.
Cразу оговорюсь: WER — хорошая метрика, простая, понятная и воспроизводимая (ну почти). Выбрасывать ее никто не предлагает. Она хорошо отвечает на вопрос «насколько модель в целом справляется с этим типом данных» и на вопрос «эта модель лучше или хуже вон той» — при условии, что обе проверяют на одном тестовом наборе с одинаковой нормализацией текста. Для лидербордов и для быстрой отсечки кандидатов этого достаточно.
Проблема в другом. Как только вы выходите из зоны комфорта статей с бенчмарками и начинаете принимать решения — катить или не катить, что чинить в первую очередь, что отвечать клиенту, — оказывается, что WER молчит ровно там, где нужен ответ. Где именно мы хуже? Почему мы там хуже? И главное, что сделать, чтобы стать лучше? Одно агрегированное число на эти вопросы не отвечает в принципе — не потому что оно плохое, а потому что оно агрегированное.
Итак, чего же нам не хватало в WER?
1. WER — это одно число на весь датасет
Вы посчитали WER 5% и радуетесь результату. А потом решили посмотреть, где именно ваша модель ошиблась, и видите, что у 65% записей WER равен нулю, а у 11% он выше 30%. Среднее по больнице скрывает тот факт, что у вас есть заметный «хвост» записей, где модель просто разваливается. И именно на этот хвост будут жаловаться пользователи — не на те 65%, где все идеально.
Еще веселее с распределением ошибок по типам. Две модели с почти одинаковым WER могут вести себя принципиально по-разному: у одной 32% ошибок — это вставки (модель галлюцинирует или слышит лишние слова, например от телевизора на фоне), у другой вставок 10%, зато 25% — удаления (модель не слышит слова или принимает их за шум). Для голосового бота это две совершенно разные проблемы с разными способами исправлений, но WER у них почти не отличается.
2. WER не знает, какие слова нам важны
Это, пожалуй, главное.
Рассмотрим пример. К нам приходит директор контакт-центра и говорит: «Ребята, ваш WER мне ни о чем не говорит. Как у вас распознается слово “оператор”? Какое качество? Мне важно, чтобы бот переключал клиента на оператора-человека, когда тот об этом просит».
И это абсолютно правильные вопросы. Слово «оператор» может встречаться, скажем, в 100 записях из 4500. В общем объеме слов эта доля мизерная — соответственно, и вклад в WER тоже. При этом от распознавания именно этого слова зависит работа сценария. Модель может иметь прекрасный общий WER и при этом систематически ошибаться на этом конкретном доменном слове.
Стандартный тулинг на этот вопрос не отвечает. Приходится писать разовый скрипт, который выравнивает разметку с гипотезой, находит нужное слово и считает по нему статистику. А завтра клиент спросит про другое слово. А послезавтра — про список из 200 слов. А еще разные сотрудники напишут разные реализации — и их цифры не сойдутся между собой…
3. Часть «ошибок» — это не ошибки
В отдельную категорию боли можно смело отнести вариативность написания англицизмов, которые плотно вошли в наш обиход. Смотрим на топ слов по количеству замен и видим:
Слово в эталоне | Что выдала модель | Доля замен |
кешбэк | кэшбэк | 97% |
хуавэй | хуавей | 100% |
вайлдбериз | вайлдберриз/вайлдберрис | 100% |
кьюар | кюар / кью ар | 93% |
Формально это ошибки, и WER их честно штрафует. Фактически модель услышала ровно то, что было сказано, и записала другим — тоже допустимым — способом.
Масштаб легко недооценить. В одном из наших внутренних прогонов «кешбэк» встречался 152 раза, и у пяти моделей из восьми recall по этому слову был ровно 0% — при общем WER в диапазоне 5–13%. Если вы строите на транскриптах аналитику обращений, поиск по слову «кешбэк» у вас не выдаст ни одной жалобы, хотя по метрике все отлично.
Лечить это можно по-разному. Один из простых способов — словарь замен (таблица, приводящая варианты написания к канонической форме). Но чтобы его составить, нужно сначала увидеть, какие слова и во что систематически превращаются.
4. «Стало лучше» ≠ стало лучше
Предположим, что вы дообучили модель, прогнали ее на тесте, и WER упал с 5,36% до 5,04%. Улучшение на 0,32 процентного пункта. Катить в прод?
Без проверки на статистическую значимость это не более чем гадание: разница вполне может быть объяснена случайной вариацией конкретной выборки. Вы раскатываете модель, а через неделю выясняется, что на реальном трафике улучшения нет или даже стало хуже.
Правильный ответ дает bootstrap: пересэмплируем записи с возвращением пару тысяч раз, каждый раз считаем корпусный WER обеих моделей на одной и той же выборке и разницу между ними. Если 95-процентный доверительный интервал этой разницы целиком левее нуля — улучшение статистически значимое. В нашем примере так и вышло (CI95 = [−0.54; −0.09], p = 0.0015), но заранее это было совершенно неочевидно: на глаз и не скажешь, 0,32 п.п. — это шум или нет.
Часть 2. RESON: как из разрозненных скриптов выросла библиотека
Началось все как обычно, со скриптов
У каждого из нас был свой «джентльменский» набор:
скрипт, который считает WER;
скрипт, который нормализует тексты перед подсчетом;
скрипт, который вытаскивает худшие примеры и печатает их с diff.
И так далее.
Проблема тут известная: у одного нормализация выкидывает пунктуацию, у другого нет; один считает корпусный WER, другой — средний по записям. В итоге два отчета по одной и той же модели дают разные цифры, и полдня уходит на выяснение, что пошло не так.
Первая попытка причесать все это дело выглядела скромно: мы сделали callback. Прогнали валидацию, посчитали метрики, залогировали. Сделали, работает, всем нравится. Потом выяснилось, что ровно то же самое нужно уже вне обучения — сперва на клиентском датасете, потом на выгрузке от вендора. Callback туда не встает, и пришлось выносить логику в отдельный класс.
Дальше начался цикл, знакомый, кажется, каждому, кто когда-нибудь писал внутренний тулинг:
«а посчитай recall по словарю» — класс не покрывает, добавили;
«а сравни две модели между собой» — добавили;
«а это улучшение вообще значимое или нет?» — добавили проверку стат. значимости;
«а подготовь датасет: профильтруй, сделай предпроцессинг, разбей на части» — стоп, а это уже вообще не про метрики, это про данные.
И в какой-то момент мы посмотрели на получившееся сверху и поняли: это давно не один класс с наследниками. Зон ответственности стало несколько, они про принципиально разное и живут своей жизнью. Пора было разложить это по полочкам осознанно, а не по мере поступления задач.
Три сущности, на которых все держится
Разложилось в итоге следующим образом.
Manifest. Это все, что про данные. Сюда переехали все скрипты предпроцессинга и подготовки:
чтение .json, .jsonl и .csv;
быстрое профилирование (количество записей, часы, распределение длительностей, размер словаря и алфавита, схема, дубликаты, пропуски);
нормализация текстов;
словарь замен;
сэмплирование примеров, мердж манифестов и так далее.
Сами же входные данные для Manifest можно легко передать в виде вот такого вот .jsonl файла со следующей структурой:
{"audio_filepath": "/audio/001.wav", "duration": 2.1, "text": "эталонная транскрипция", "prediction": "гипотеза asr"}RESON — оркестратор. Штука, которая берет подготовленный манифест и проводит анализ. Внутри идет ансамблирование сменными компонентами: провайдер метрик (по умолчанию jiwer с WER и CER), сравнение прогонов, bootstrap. Если вам нужна своя метрика — реализуете MetricProviderABC, передаете экземпляр в конструктор, и весь остальной пайплайн, включая отчеты, продолжает работать как ни в чем не бывало.
RESONRun — выходной артефакт. Неизменяемый JSON, в котором лежит все: сводка по датасету, глобальные метрики, метрики по каждой записи, словарь со всей статистикой по словам, алфавит, конфиг нормализации и пользовательская мета. Это то, что кладется в артефакты эксперимента, коммитится в репозиторий результатов, передается коллеге. Из него в любой момент восстанавливается любой отчет без пересчета и без исходных аудио.

Что именно считается
WER, CER, разложение на вставки, удаления и замены — и на уровне корпуса, и для каждой записи отдельно. Дополнительно считается статистика на уровне слов: для каждого слова из эталонного словаря RESON выравнивает эталон с гипотезой и считает recall, precision, F1, разбор ошибок по типам и на что именно слово заменяется, со списком целевых слов и частотой замен.
Поверх этой статистики считаются еще две метрики. Про них — отдельно.
MWA и WIS: две метрики, которые мы добавили от себя
MWA (Mean Word Accuracy) устроен предельно просто: берем каждое уникальное слово эталонного словаря, считаем по нему recall, усредняем по всем словам. Все слова входят с одинаковым весом — и «и», и «вайлдбериз».
Вся ценность как раз в этом «одинаковом весе», потому что WER устроен наоборот. WER взвешен по частотности: в вашем тесте стоп-слова встречаются тысячи раз, а название конкретного сервиса — двадцать. Модель, которая безупречно распознает «и», «в», «не», получит отличный WER даже если на редкой лексике она регулярно промахивается — эти промахи просто утонут в объеме.
MWA смотрит на ту же картину с другой стороны и показывает, как модель ведет себя на длинном хвосте словаря.
Насколько это не теоретическое рассуждение — видно на примере одного из наших прогонов:
WER | MWA | Доля вставок | |
Модель X | 7,58% | 86,96% | 12% |
Модель Y | 8,45% | 89,23% | 32% |
По WER модель X выигрывает почти процентный пункт. По MWA — проигрывает больше двух. Ответ в третьей колонке: треть всех ошибок модели Y — это вставки. WER штрафует за них в полную силу, из-за чего общая цифра выглядит плохо, но сами слова словаря модель Y при этом распознает точнее. Какая из двух лучше, зависит от задачи. Если транскрипт читает человек или он идет в суммаризацию, лишние слова раздражают, и вам ближе X. Если по транскрипту работает поиск по ключевым словам, вставки почти безвредны, а вот пропуски фатальны — и тогда Y. Здесь же важная оговорка: MWA построен на recall, поэтому за вставки не штрафует вообще.
С другой стороны, мы имеем WIS (Word Importance Score), который позволяет найти «важные» слова, улучшение которых позволило бы значительно улучшить результаты модели в выбранном домене.
Интуитивные сортировки работают плохо. По частотности наверху скорее всего окажутся стоп-слова, с которыми и так все в порядке. По recall тоже с большой вероятностью можно встретить мусор. Поэтому нам нужна была свертка, которая учитывала бы сразу все. WIS сворачивает в одно число от 0 до 100 четыре вещи:
Компонент | Вес | Смысл |
Частотность слова в эталоне | 25% | Чинить то, что встречается дважды не всегда выгодно |
Тяжесть ошибки (100 — F1 score) | 35% | Насколько плохо распознается слово в целом |
Критичность типа ошибки | 25% | Удаление весомее замен, а замены важнее вставок (как правило) |
Разнообразие вариантов замен | 15% | Стабильная ошибка или рассыпание на N вариантов |
Метрика эвристическая, веса подобраны на опыте, и на научную строгость она не претендует. Но как приоритизатор в целом зарекомендовала себя отлично на практике. Обычно сверху в отсортированном по WIS словаре у нас стабильно оказываются две категории: бренды и англицизмы («кешбэк», «хуавэй», «кьюар», «ммс») и обрывки слов вроде «поч», «ра», «се». Первая категория — это работа со словарем, вторая — с разметкой и правилами сегментации.
Три отчета под три вопроса
Сначала была только одна история — «есть модель, есть датасет, покажи, что происходит». Потом почти сразу выяснилось, что чаще спрашивают другое: «а эта модель лучше предыдущей?», «а мы лучше вот этого свежего опенсорса?», «а на сколько лучше и это вообще не случайность?». Так появились еще два формата.
Отчет | На какой вопрос отвечает |
single_report / одиночный отчет | Что делает эта модель, где ломается и на каких словах |
compare_report / сравнительный отчет | Сравнение множества моделей между собой |
leaderboard_report | Много моделей, где хочется просто подсветить основные метрики без интерактивной аналитики |



Два входа: CLI и Python API
Получившийся инструмент умеет работать в двух режимах:
CLI — для ручного разбора и для CI. Четыре группы команд: manifest (профилирование и подготовка данных), run (анализ и сравнение), report (сборка HTML), pipeline (все сразу, в одну команду).
Python API — для исследований и автоматизации, где вам нужен не отчет, а данные. Manifest, RESON, RESONRun — обычные объекты, из которых можно достать pandas-датафреймы, посчитать свое, подключить свой провайдер метрик, встроить в цикл обучения или в пайплайн отбора данных.
Часть 3. MWS RESON ASR OSD — открытый бенчмарк
Инструмент без данных — половина дела. Поэтому вместе с RESON мы выпускаем MWS RESON ASR OSD — бенчмарк для оценки качества русскоязычных ASR моделей в телефонном канале.
Что внутри
Бенчмарк состоит из двух датасетов (General и Numeric), каждый в двух акустических версиях — условно чистой (quietly) и шумной (noisily), содержащие не просто шумы, но и фоновую речь, что позволяет проверить устойчивость модели к звукам из телевизора, посторонним голосам и так далее. Все аудио — 8 кГц. В суммарности датасет содержит 12 114 записей, общая длительность — 37 часов 52 минуты.
General — русскоязычный датасет телефонных реплик, обращений в поддержку, автоматических уведомлений и бытовых звонков. Для него характерны естественные особенности разговорной речи: паузы, междометия, повторы, оговорки, самокоррекции и отдельные обрывы слов. Особое внимание уделено распознаванию имен собственных: названий компаний, банков, операторов связи, цифровых сервисов, устройств и приложений.
Numeric — специализированный набор на числа и числовые последовательности: телефонные номера, лицевые счета, номера банковских карт, даты, денежные суммы и платежи, номера договоров, серии и номера документов, ИНН и СНИЛС. Все числовые последовательности вымышлены, любые совпадения случайны.
Важная оговорка: чистая и шумная части тематически сопоставимы, но это не попарные версии одних и тех же записей. Сравнивать quietly и noisily между собой корректно как два среза, но вычитать одно из другого, чтобы получить «чистый эффект шума», нельзя.
Бенчмарк позволяет измерить:
качество распознавания русскоязычной разговорной речи в телефонном канале;
устойчивость к акустическому шуму;
распознавание имен собственных и англицизмов;
распознавание чисел и числовых последовательностей.
Таблица 1. Детализация состава MWS RESON ASR OSD
Срез | Домен | Условие | Длительность | Примеров | Мужские | Женские |
general quietly | General | quietly | 13:42:44 | 4 481 | 2 024 | 2 457 |
general noisily | General | noisily | 14:01:05 | 4 680 | 2 193 | 2 447 |
numeric quietly | Numeric | quietly | 05:01:01 | 1 463 | 747 | 716 |
numeric noisily | Numeric | noisily | 05:07:27 | 1 530 | 736 | 794 |
Всего | 37:52:17 | 12 114 | 5 700 | 6 414 |
Два эталона на каждую запись
У каждой записи две разметки:
text — произносительная форма, с пунктуацией и заглавными буквами. Числа и заимствованные названия записаны в основном так, как они звучат, кириллицей.
e2e_text — письменная форма, тоже с пунктуацией и заглавными. Числа записаны цифрами, а англицизмы и бренды, которые принято писать латиницей, — латиницей.
Междометия вроде “аа”, “ээ” и “мм” сохранены в обоих полях. В Numeric text и e2e_text различаются в каждой записи. В General они расходятся там, где меняется написание имени, англицизма или числа.
Таблица 2. Сравнение разметок
text | e2e_text | |
Имя собственное | Я пополнил Теле два через Райффайзен, а денег нет. | Я пополнил Теле2 через Райффайзен, а денег нет. |
Англицизм | Айфон показывает сеть, аа но Авито не грузится. | IPhone показывает сеть, аа но Avito не грузится. |
Числа в General | Алло. Почему скорость упала до ста килобит? | Алло. Почему скорость упала до 100 килобит? |
Числовая последовательность | Мой СНИЛС шестьсот шестьдесят восемь пятьсот пятьдесят шесть пятьсот двадцать шесть семь семь. | Мой СНИЛС 668 556 526 77. |
Как собирали
Сбор шёл в четыре шага. Сначала отобрали востребованные слова телефонного домена: названия компаний, сервисов, типы документов и числовые сущности, на которых распознавание чаще всего может ломаться. По ним были написаны сценарии и сгенерированы текстовые разметки реплик. Эти тексты в дальнейшем были переданы на начитку в двух акустических условиях: quietly и noisily. После записи была проведена верификация аудио и текста на предмет корректного сопоставления и соответствия.
Замеры открытых моделей
Для проверки адекватности полученного бенчмарка было принято решение прогнать на нем без дообучения открытые ASR-системы. Все метрики были посчитаны с помощью RESON с использованием дефолтного провайдера метрик из коробки. Тексты гипотез обработаны по следующему протоколу и сравнены с разметкой из поля text:
все тексты приведены в нижний регистр;
“ё” заменяется на “е”;
пунктуация удаляется, пробелы схлопываются;
цифры не переводятся в слова, а слова — в цифры;
из текстов удалены токены “ээ”, “мм”, “аа”.
Если система написала «теле два», а в письменном эталоне стоит «Теле2», на e2e_text это ошибка. В этом и смысл двух разметок.
Таблица 3. Локальные замеры открытых моделей с помощью RESON
Vendor | Model | General quietly, WER % | General noisily, WER % | Numeric quietly, WER % | Numeric noisily, WER % | Average, WER% |
Sber | 5,35 | 18,09 | 0,71 | 5,56 | 7,42 | |
10,94 | 21,64 | — | — | — | ||
Alpha Cephei | 8,40 | 26,58 | 1,06 | 8,24 | 11,07 | |
Nvidia | 11,01 | 33,11 | 3,60 | 15,60 | 15,83 | |
19,39 | 46,11 | 8,23 | 28,37 | 25,52 | ||
OpenAI | 12,47 | 62,15 | — | — | — |
Среди прогонов по всем четырём срезам лучший средний результат у GigaAM v3 rnnt: 7.42. При этом можно заметить, что у всех систем noisily заметно хуже quietly, и на General разрыв шире, чем на Numeric. Чистый числовой срез для сильных моделей оказался простым: 0.71 у GigaAM v3 rnnt и 1.06 у Vosk.
Где лежит датасет
Датасет опубликован на Hugging Face: MTSAIR/mws-reson-asr-osd. Внутри четыре конфигурации, у каждой один сплит test. Аудио лежит обычными wav-файлами рядом с metadata.jsonl, для того чтобы их можно было скачать и без использования библиотеки datasets.
from datasets import load_dataset
general_clean = load_dataset("MTSAIR/mws-reson-asr-osd", "general_quietly", split="test")
row = general_clean[0]
audio = row["audio"]
spoken = row["text"] # произносительный эталон
written = row["e2e_text"] # письменный эталонРепозиторий: https://github.com/mts-ai/RESON.
Оба проекта под MIT. Забирайте, ломайте, присылайте issues и pull request's.
Вместо заключения
Итак, WER отвечает на вопрос «лучше или хуже», но не отвечает на те, которые волнуют больше всего. Где именно модель ломается? Какие слова она стабильно не слышит? Это настоящая ошибка или расхождение орфографии? Улучшение значимое или показалось? И как объяснить все это клиенту?
RESON мы делали, чтобы на эти вопросы можно было отвечать за минуты, а не за целый вечер с кодом.
MWS RESON ASR OSD — чтобы отвечать на них на данных, похожих на реальные, а не на студийную начитку.
Надеюсь, наши инструменты будут вам полезны.
Жду ваши вопросы и отзывы в комментариях.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.