# Открытый замер точности распознавания русской речи

Здесь лежит всё, что нужно, чтобы повторить наш замер и проверить наши цифры: скрипты
прогона, нормализатор текста, его тесты, контрольные суммы тестовых файлов и агрегаты
результатов. Лицензия на код — MIT.

Результаты в человеческом виде, с оговорками и разбором методики:
https://rechetext.ru/benchmark

## Что в каталоге

| Файл | Что делает |
|---|---|
| `fetch.py` | скачивает тестовые части открытых наборов и считает sha256 каждого файла |
| `sources.py` | реестр наборов: адреса, лицензии, что можно и нельзя по каждой |
| `run.py` | прогон записей через движок распознавания, складывает гипотезы |
| `normalize.py` | правила приведения текста к сравнимому виду — то самое место, где решается результат |
| `test_normalize.py` | 71 проверка нормализатора; запускается без сети и без моделей |
| `score.py` | считает WER, CER, F1 по знакам препинания и RTF, собирает таблицу |
| `requirements.txt` | зависимости (расчёт метрик — `jiwer`, не самописный счётчик) |
| `results/2026-08-25.json` | агрегат результатов, машиночитаемый |
| `results/2026-08-25.md` | тот же агрегат таблицей |
| `results/checksums-*.json` | контрольные суммы файлов по каждому набору |

## Как повторить

```bash
pip install -r requirements.txt
python test_normalize.py                       # сначала убедиться, что правила ведут себя как заявлено
python fetch.py --dataset ruls --split test    # скачать тестовую часть и сверить sha256
python run.py --dataset ruls --out hyp/        # прогон через движок
python score.py --dataset ruls --hyp hyp/ --out results/своя-дата.json
```

Прогон переносится на другой движок распознавания заменой адреса в настройках `run.py` —
привязки к нашей реализации в замере нет. Это и есть смысл публикации: цифры, которые
нельзя перепроверить, ничего не стоят.

## Главное про методику, коротко

**Нормализатор один и тот же для эталона и для гипотезы.** Асимметрия здесь — самая дорогая
ошибка замера: применить правила только к результату модели и не применить к эталону значит
получить любую цифру, какая нужна. Поэтому в коде это один объект и один вызов.

**Записи, где цифры стоят в эталоне, вынесены в отдельный срез.** «1990» разворачивается и
как «тысяча девятьсот девяносто», и как «одна тысяча девятьсот девяносто» — оба варианта
верны, а WER накажет за один из них. Это спор о нормализации, а не о распознавании.

**F1 по знакам препинания считается только там, где знаки в эталоне есть.** На спонтанных
наборах их нет, и в таблице стоит прочерк, а не ноль: ноль читался бы как «плохо».

**RTF приведён вместе с условиями прогона.** Только процессор, без видеокарты, машина не
выделенная — рядом работают сторонние контейнеры. Число получается хуже, чем в идеальных
условиях, и это осознанно.

## Наборы данных и их лицензии

Реестр с адресами первоисточников и разбором лицензий — в `sources.py`. Там же список
наборов, которые мы отвергли, и причина по каждому: например, лицензия, запрещающая
коммерческое использование, при коммерческом сервисе — это отказ, а не «мелкая деталь».

Атрибуция авторов наборов приведена в реестре и в файлах контрольных сумм, как требуют их
лицензии.

## Если нашли ошибку

Напишите — методика для этого и опубликована. Ошибка в правилах нормализации или в подсчёте
метрик влияет на цифры, а цифры мы публикуем как проверяемые, а не как рекламные.
