В первой статье серии я рассказывал про речевые технологии для крымскотатарского в целом и обещал отдельно разобрать распознавание. Разбираю.

Итоговая таблица, чтобы сразу было видно, о каких величинах речь. Один и тот же отложенный тест, один и тот же скоринг, 893 клипа / 1.86 ч, два диктора, которых модель не слышала:

WER

CER

CER folded

базовая модель (whisper-large-v3, ранее дотюненная под crh)

0.3463

0.1188

0.1070

+ LoRA (1 ч 30 м на ноутбучной видеокарте)

0.2010

0.0938

0.0888

+ подбор параметров декодирования, ноль изменённых весов

0.1701

0.0702

0.0658

Интересного в первой строке больше, чем во второй. А в третьей — больше, чем в первых двух.

Дисклеймер про содержание: конкретные источники аудио, договорённости с правообладателями, внутренние скрипты, промпты и точные конфиги обучения (lr, расписание) — не раскрываю. Раскрываю метрики, порядки величин, методологию замера и грабли. Грабель, как обычно, больше, чем метрик.

Сначала тест, потом модель

Модель, которая «умеет крымскотатарский», у меня уже была — я ею даже пользовался. Честного WER у неё не было ни разу. Поэтому первое, что было сделано в этом эксперименте, — не обучение, а тестовый сплит и харнесс.

Случайный сплит по клипам здесь бесполезен. Соседние клипы — одна сессия записи, один диктор, один микрофон, а выравниватель, которым нарезан корпус, регулярно рубит одно предложение на два соседних клипа. Случайное деление кладёт половину фразы в train, вторую в test, и модель «узнаёт» тракт записи, а не язык.

Поэтому холд-аут целыми книгами: две книги, два диктора, которых нет в обучении вообще, 893 клипа / 1.86 ч. Одним движением из-под модели уходит диктор, микрофон, эпоха и лексика конкретного текста.

Наборов при этом три, и разделение обязанностей между ними — то, на чём держатся все цифры статьи:

набор

размер

чем занимается

train

7 508 клипов / 15.54 ч

обучение

dev

255 клипов / 0.54 ч, две короткие вещи целиком, вынутые из пула

выбор: чекпоинт, конфиг декодирования; гоняется десятки раз

test

893 клипа / 1.86 ч, две книги целиком

декодируется ровно один раз на решение

Правило выбора победителя («минимальный dev WER») зафиксировано до того, как я посмотрел на результаты. Если гонять тест многократно и брать лучшее, выбирается не лучшая модель, а самая удачливая; цифра получается красивая и больше никогда не воспроизводится.

Харнесс один на обе архитектуры: seq2seq (whisper) и CTC (wav2vec2) идут через один и тот же путь скоринга, поэтому их можно класть в одну таблицу. Нормализация не переизобретена — взята из TTS-лестницы того же проекта, чтобы цифры двух экспериментов стояли на одном фундаменте. Крымскотатарские латинские буквы (ğ ı ñ ö ş ü ç â) считаются различимыми, а рядом печатается folded CER со свёрнутыми диакритиками: он отделяет «услышала не то слово» от «услышала нужное слово без диакритики». Агрегация корпусная (сумма правок / сумма эталонных единиц), а не среднее по клипам.

И отдельным полем в каждом отчёте — loop_clips: число клипов, у которых per-clip WER > 1.0, то есть правок больше, чем слов в эталоне. На практике это случается ровно в одном случае — декодер ушёл в петлю. В корпусном среднем эта беда не видна вообще, а чинить её потом будет как раз декодер, поэтому её считают отдельно с самого начала.

Утечка, которую не видно по именам файлов

Прежде чем что-либо запускать, я проверил банальное: не лежит ли тестовый материал заодно в обучающем пуле. По id и именам файлов — пересечение нулевое, схемы идентификаторов вообще из разных вселенных.

Тогда я сравнил не имена, а тексты: искал строки, делящие с тестом общую словную 6-грамму.

Четыре аудиокниги лежали в корпусе дважды — одни и те же записи, нарезанные двумя разными пайплайнами в разное время, под никак не связанными схемами id.

книга в холд-ауте

доля клипов, у которых нашёлся двойник в обучающем пуле

книга C (тестовая)

96.9 % (651 / 672)

книга A

82.1 % (769 / 937)

книга B

69.8 % (549 / 787)

книга D

58.8 % (130 / 221)

Четыре книги, лежавшие в корпусе дважды. По именам файлов пересечение нулевое; двойники нашлись только по тексту.
Четыре книги, лежавшие в корпусе дважды. По именам файлов пересечение нулевое; двойники нашлись только по тексту.

96.9 % — это книга, на которой я собирался мерить. Оставь я её двойника в обучении, модель сдавала бы экзамен по билетам, которые ей заранее раздали: другая нарезка, те же предложения, те же голоса. Цифра вышла бы прекрасной и не значила бы ничего.

Паники не было — было понимание, что все прежние прикидки идут в мусор и мерить придётся заново, и облегчение, что это нашлось до обучения, а не после. Самое неприятное в этом классе багов — отсутствие симптомов: ничего не падает, всё работает, результат лучше ожидаемого. Именно это и должно настораживать.

Вывод, который я считаю главным практическим уроком проекта: в низкоресурсном корпусе, собранном несколькими пайплайнами по одним и тем же немногочисленным записям, пересегментированные дубликаты — состояние по умолчанию, а не редкий инцидент. Материала мало, к нему возвращаются снова и снова разными инструментами, и каждый раз получается новый набор файлов со старым содержимым. Проверять надо текстовым пересечением; ни имена, ни хеши аудио этого не ловят.

Список запрещённых префиксов id после этого применяется жёстко, а результат не предполагается, а перепроверяется: в пуле 0 тестовых id, 0 id под запрещённым префиксом и 0 строк, делящих словную 6-грамму с тестом. Dev проверен так же — 0 общих id, 0 общих 6-грамм с train.

Бейзлайн, и как его правильно читать

модель

тип

WER

CER

CER folded

время

whisper-large-v3-crh

seq2seq

0.3463

0.1188

0.1070

132 с

wav2vec2-xls-r-300m-crh

CTC

0.8229

0.2320

0.2080

25 с

Три замечания, без которых эта таблица врёт.

Сравнивать эти две модели надо по CER, а не по WER. CTC-модель постоянно теряет границы слов и склеивает их в одну длинную строку (düntursamamadıf…): у 48.9 % её гипотез число слов меньше 70 % от эталонного, против 2.4 % у whisper. Её 0.82 WER — это в основном пробелы, а не фонемы. Честное сравнение — 0.232 CER, и whisper всё равно примерно вдвое лучше.

Диакритика — не проблема. Folded CER у обеих моделей всего на ~1 пункт ниже обычного, значит ошибки настоящие словарные, а не потерянные ğ/ı/ñ. Приятно, когда гипотезу удаётся закрыть одной колонкой, добавленной заранее.

0.3463 слегка занижает whisper. Модель пишет числа цифрами (1954), а эталоны — прописью (Biñ doquz yüz elli dört), и харнесс честно считает это четырьмя словными ошибками подряд.

Обе модели удивительно ровны по четырнадцати главам (whisper 0.24–0.39 WER), так что средние не тянет одна аномальная глава. Отдельно из отчётов вылезло, что в ~2.4 % клипов whisper начинает транскрибировать не с начала — обрубает первую часть фразы. CTC-модель те же клипы расшифровывает целиком, то есть аудио в порядке: это поведение декодера, а не дефект сегментации. Запомним, пригодится.

Полтора часа обучения — самая скучная часть

Полный файнтюн здесь не нужен и вреден: смысл базы — в том, что в ней уже есть, а 15 часов полного файнтюна — ровно тот способ, которым это затирается. LoRA:

метод

LoRA, r=32, на проекциях q_proj, k_proj, v_proj, out_proj

обучаемых

31 457 280 из 1 574 947 840 (1.997 %)

данные

7 508 клипов / 15.54 ч

шаги

batch 32, 705 шагов = 3 эпохи

точность

база bf16, веса LoRA в fp32, gradient checkpointing

время

5 415 с (1 ч 30 м) на ноутбучной RTX 5090

VRAM

пик allocated 10 398 МБ, reserved 15 904 МБ

адаптер

126 МБ

Две вещи тут не догадки, а замеры.

Размер батча выбран двумя 12-шаговыми пробниками, а не на глаз:

batch

пик allocated

время шага

8

5 160 МБ

2.2 с

32

10 394 МБ

7.6 с

Whisper паддит любой клип до 30 с, поэтому память на сэмпл не зависит от длины клипа и эти числа экстраполируются линейно. Пропускная способность на сэмпл у обоих вариантов почти одинаковая, так что batch 32 выбран ради меньшего числа шагов оптимизатора, а не ради скорости.

Потолок VRAM ставится внутри процесса обучения (torch.cuda.set_per_process_memory_fraction), а не снаружи: хук через sitecustomize.py не работает, потому что accelerate выставляет CUDA_VISIBLE_DEVICES уже после старта интерпретатора. Доля не 0.85, а меньше, потому что на той же карте всё время висел тёплый TTS-воркер на ~2.7 ГБ. А PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True не ставится никогда — драйвер под WSL2 отвечает на него «CUDA driver error: unknown error», так что скрипт активно вычищает эту переменную из окружения, если она унаследовалась.

Языковой токен тоже не выбран, а измерен: у базовой модели в generation_config стоит tr, и оценка намеренно не передаёт --language — ровно как это делал бейзлайн. Если обучать префикс на чём-то другом, адаптер никогда не увидит тот префикс, с которым его потом позовут.

Лестница чекпоинтов на dev

чекпоинт

эпоха

WER

CER

база (без адаптера)

0.1716

0.0585

100

0.4

0.2188

0.1142

200

0.9

0.1210

0.0476

300

1.3

0.1241

0.0508

400

1.7

0.1173

0.0440

500

2.1

0.1153

0.0497

600

2.6

0.1158

0.0470

700

3.0

0.1184

0.0471

705 (финал)

3.0

0.1181

0.0471

Отсюда три вещи.

Ступень 100 хуже базовой модели (0.2188 против 0.1716). Адаптер к этому моменту наполовину выучил новый префикс, уже мешает базе и ещё ничего не даёт взамен. Прогон, о котором судили бы по первому чекпоинту, был бы закрыт как провальный.

Лестница плоская начиная с шага 200. Вся польза получена внутри первой эпохи, следующие две не купили ничего. То есть полтора часа — это с запасом, и та же точка достигается коротким прогоном и, вероятно, меньшим рангом.

400 и 500 — монетка. Разница 0.002 WER на 3 496 эталонных словах, и у 400 лучше CER. Объяви я критерием CER — победил бы 400. Правило было зафиксировано заранее как «минимальный dev WER», поэтому на тест поехал 500; про то, что это монетка, надо писать прямо, а не делать вид, что победитель выбран уверенно.

Тест, один прогон: 0.3463 → 0.2010 WER, 0.1188 → 0.0938 CER. Минус 42.0 % относительных по WER, минус 21.0 % по CER. Улучшились обе тестовые книги, причём меньшая и более поздняя, с другим диктором, — сильнее (−47.0 % против −40.4 %). Значит, выучена не манера одного человека.

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

Вторая половина пути: ни одного изменённого веса

Самые плохие клипы, оставшиеся после обучения, — не «не расслышала», а петли жадного декодирования:

Qıznıñ qıznıñ qıznıñ qıznıñ …
– dedi Akimoviç, – dedi Akimoviç, – dedi Akimoviç, …

Значит, следующий эксперимент — не обучение, а generate(). Я добавил в харнесс восемь флагов декодирования, каждый со значением по умолчанию «не передавать»: без единого флага вызов generate() побайтово тот же, что был до их появления, и все уже посчитанные цифры остаются воспроизводимыми. Это важнее, чем кажется, — иначе «до» и «после» меряются разными приборами.

24 конфигурации на dev. Читать их надо как три вывода, а не как 24 строки:

  1. Весь эффект — beam search. Всё, что лучше 0.106, имеет лучи; всякая жадная строка — 0.110 и хуже. Beams 4 и 5 неразличимы, 4 дешевле, аргументов за 5 нет.

  2. no_repeat_ngram_size вредит, монотонно по n. Под жадным декодированием: off 0.1153 → n=5 0.1161 → n=4 0.1170 → n=3 0.1181. Под лучами то же самое (b4_n0 0.1038 → b4_n3 0.1101). Об этом ниже отдельно.

  3. Температурный fallback whisper на dev не сделал ничего — все три порога вернули жадные цифры до четвёртого знака. Это не баг, и это самая интересная строка в сетке.

Победитель по заранее зафиксированному правилу — b4_n5 (лучи 4 + no_repeat_ngram_size 5) с dev WER 0.1035. Скажу прямо: это снова монетка. b4_n0 отстаёт на 0.0003 WER — примерно одно слово из 3 496 — и при этом на 6 % быстрее и на один флаг проще. Правило выбрало b4_n5, поэтому на тест поехал b4_n5; выбери оно b4_n0, итоговая цифра была бы в пределах шума от той, что ниже.

Тест, один прогон:

декодирование

WER

CER

клипов с WER > 1

время

greedy

0.2010

0.0938

3

143 с

beams 4 + no_repeat_ngram 5

0.1701

0.0702

0

445 с

относительно

−15.4 %

−25.2 %

−3

×3.1

Ни один вес не изменился. Изменился только способ выбирать токены.

Слева направо: базовая модель, после LoRA, после подбора декодирования.
Слева направо: базовая модель, после LoRA, после подбора декодирования.

Честный разбор: откуда взялись эти 15 %

Тут легко остановиться и написать «тюнинг декодера даёт −15 % WER». Это правда и одновременно полуправда. Жадный декодинг сделал 2 664 словные ошибки, beam search — 2 255. Разложим 409 отыгранных:

клипы

ошибок greedy

ошибок beam4+n5

WER greedy

WER beam

3 «петлевых» клипа (41 эталонное слово)

175

15

4.27

0.37

остальные 890

2 489

2 240

0.1883

0.1695

Три клипа из 893 дали 160 из 409 отыгранных ошибок: 39 % всего выигрыша с 0.34 % корпуса. Оставшийся 61 % — скучные −10.0 % относительных по всему остальному, что почти точно совпадает с dev-результатом.

Обе половины настоящие, и ни одна сама по себе не оправдала бы замедления в 3.1 раза. Но выводы из них разные. Широкие 10 % — это «модель стала аккуратнее». Три клипа — это «перестала иногда сходить с ума». Для расшифровки архива второе важнее: клип с WER 4.27 — это не слегка неверная расшифровка, это 550 байт одного повторённого слова, и он отравляет всё, что на нём потом обучат.

Кстати о цене. 1.86 ч аудио: 143 с жадно, 445 с с лучами — то есть ×15 к реальному времени вместо ×47. Стоточасовой корпус — 6.6 ч GPU вместо 2.1 ч. За шестую часть ошибок и за исчезновение катастрофических клипов это покупается не думая.

Ручка, которая обязана была помочь, а сделала хуже

no_repeat_ngram_size — очевидное средство против петель: запретить модели повторять одну и ту же n-грамму. ngram_damage.py сравнивает каждый прогон с жадным поклипно. Из 255 dev-клипов 150 жадный декодинг уже брал с WER ≤ 0.10:

прогон

стало хуже

стало лучше

без изменений

испорчено уже-чистых клипов

greedy, n=3

17

1

237

12

greedy, n=4

10

1

244

7

greedy, n=5

4

1

250

4

beams 4, n=0

7

21

227

3

beams 4, n=3

22

22

211

15

beams 4, n=5

9

22

224

5

17 сломанных против одного починенного, и 12 из 17 были практически безупречны. Это не «в сумме нолик» — это чистый ущерб, потому что на dev нет ни одной петли, которую запрет мог бы предотвратить.

Механизм виден прямо в гипотезах. Запрет не мешает модели повторяться — он заставляет её повторяться неправильно:

ref     – Qartbaba, qartbaba, – tez-tez çapıp kelgen kiçkene Aziz
greedy  – Qartbaba! Qartbaba! – tez-tez çapıp kelgen kiçkene Aziz
n=3     – Qartbaba! Qartbabaa! – tez-tez çapıp kelgen kiçkene Aziz
n=4     – Qartbaba! Qartbava! – tez-tez çapıp kelgen kiçkene Aziz

ref     … üstüñden külmek aqqım yoq edi… bağışla… bağışla…
greedy  … üstüñden külmek aqqım yoq edi. Bağışla. Bağışla.
n=4     … üstüñden külmek aqqım yoq edi. Bağışla. Bağuşla.

ref     … bu parçalar yerge tüşip yoq oldı.
greedy  … Bu parçalar yerge tüşip yoq oldı.
n=3     … Bu parçalarnıñ parça yerge tüşip yoq oldı.

Ребёнок бежит и зовёт: «Qartbaba, qartbaba!» Модель услышала это совершенно правильно — а запрет не дал ей это записать, и она исказила второе слово, лишь бы оно не совпало с первым: Qartbava, Bağuşla. В третьем случае декодер обошёл запрет, вставив выдуманный генитив: parçalarnıñ parça.

Удвоенное обращение и удвоенная мольба — обычный крымскотатарский. no_repeat_ngram_size не отличает риторический повтор от разгонной петли, а в агглютинативном языке с богатой суффиксацией второй случай встречается постоянно. Правило вида «так не бывает» рано или поздно наткнётся на то, как люди на самом деле говорят, — и сломает именно это место, оставив всё остальное в порядке, чтобы вы ничего не заметили.

Инструмент, который dev не смог выбрать

Самая поучительная строка сетки — температурный fallback.

У whisper есть собственный детектор петель: зациклившийся текст сжимается gzip неприлично хорошо, поэтому сегмент с коэффициентом сжатия выше порога выбрасывается и перегенерируется на следующей температуре. Это ровно тот инструмент, который здесь нужен, и он почти бесплатный — 38 с, ×1.0 к жадному, потому что перегенерируются только сработавшие клипы.

На dev он оказался побитово никаким при любом пороге, включая агрессивный 1.35. Почему — видно по самим коэффициентам:

набор / декодирование

max gzip ratio

p99

клипов > 2.4

dev, greedy

1.38

1.35

0

test, greedy

18.27

1.30

5

test, beams 4 + n=5

1.31

1.26

0

Три петлевых клипа сжимаются с коэффициентами 14.9, 16.1 и 18.3 против ~1.0–1.3 у нормальной речи — их поймал бы даже дефолтный порог 2.4, с колоссальным запасом. Но на dev петель нет вообще, ни один dev-клип не перешагнул даже 1.35, и сетка не смогла отличить этот инструмент от «не делает ничего». Правило выбора не могло его выбрать.

Это настоящее ограничение dev/test-схемы, а не повод от неё отказаться: dev, вырезанный из лёгкого пула, умеет ранжировать то, что он нагружает, и слеп ко всему остальному. Дисциплинированный ход — записать это и померить потом, а не тихо перезапускать тест, пока что-нибудь не выиграет. Конфигурация greedy + fallback на тесте не прогонялась, и цифры для неё я не заявляю.

Бонусом — последняя строка таблицы: после beam search максимальный коэффициент сжатия по всем 893 тестовым гипотезам равен 1.31. Петли не уменьшились, их нет.

И раз уж речь о fallback. Документация transformers намекает, что эти пороги работают только в long-form. Это не так, по крайней мере в 4.57: maybewarn_unused_inputs() — та самая функция, которая печатает «short-form transcription is activated … will be ignored», — мёртвый код, определённый и никогда не вызываемый, тогда как setthresholds_and_condition выставляет пороги безусловно, а generate_with_fallback крутит температурный цикл и на коротких клипах. Проверено не чтением, а экспериментом: принудительное срабатывание порогом 0.5 (то есть перегенерация каждого клипа на температуре 1.0) сдвинуло dev-подмножество с 0.0763 на 0.1271 WER. Срабатывает. Просто обычно не должно.

Чего эти 0.1701 не знают о реальном мире

Всё выше измерено на одном типе звука: один человек, один микрофон, тихая комната, читает книгу. Дальше я взял те же 893 клипа и построил из них 17 акустических условий — 15 178 клипов / 31.7 ч. Эталоны при этом не меняются, значит, ошибка в каждом из миров меряется честно.

условие

WER

CER

к чистому

чисто

0.1701

0.0702

телефонная полоса (300–3400 Гц, 8 кГц)

0.1691

0.0685

−0.6 %

MP3 32 кбит/с

0.1745

0.0708

+2.6 %

розовый шум SNR 20 дБ

0.1741

0.0725

+2.4 %

темп 1.15×

0.1804

0.0723

+6.1 %

музыка SNR 10 дБ

0.1854

0.0761

+9.0 %

розовый шум SNR 10 дБ

0.1967

0.0795

+15.6 %

реверберация RT60 0.3 с

0.2084

0.0800

+22.5 %

гул чужой речи SNR 10 дБ

0.2093

0.0825

+23.0 %

розовый шум SNR 0 дБ

0.3195

0.1234

+87.8 %

реверберация RT60 1.0 с

0.4316

0.1634

+153.7 %

гул чужой речи SNR 0 дБ

0.6913

0.3781

+306 %

Четыре вывода, ни один из которых нельзя было знать заранее.

Полоса и битрейт бесплатны. Телефонная полоса — вообще не деградация: 0.1691, на волос лучше чистого. MP3 32 кбит/с стоит 2.6 %. Ассистент может жить по телефонной линии и на дешёвом кодеке.

Чужая речь — главный враг, и с отрывом. При равном SNR гул чужих голосов стоит примерно вдвое дороже розового шума и вчетверо дороже музыки. Гул на 5 дБ (0.3121) уже хуже, чем розовый шум на 0 дБ (0.3195 — то же самое при SNR на 5 дБ лучше), а на 0 дБ учетверяет ошибку. Самое ценное вложение в надёжность — не денойзинг, а разделение дикторов.

Реверберация — второй враг и самый дешёвый в починке. RT60 1.0 с — кухня, коридор, большой зал — стоит +154 %, дороже розового шума на 0 дБ. Оба верхних режима отказа лечатся микрофонной решёткой и постановкой микрофона, а не «ещё данными».

Темп речи почти бесплатен (±15 % стоят ≤6 %).

Тут же — проверка на чужом материале: публичный набор крымскотатарских аудиокниг, на котором проект никогда не учился, срез 300 клипов / 25.9 мин. База даёт 0.4765 WER, адаптер — 0.2448, на более чистой из двух глав — 0.1798, то есть практически ровно наш холд-аут. Заголовочные 0.2448 цитировать нечестно: девять из десяти худших клипов — одни и те же для обеих моделей, а это подпись плохого forced alignment, а не ошибки модели (эталон и гипотеза там — соседние сегменты). Честная формулировка — 0.18–0.24 WER вне корпуса, где разброс есть качество выравнивания публичного датасета, а не дисперсия модели. Главное, что это говорит: модель не заучила моих дикторов.

Ещё два ограничения, о которых правильно сказать самому, пока не сказали в комментариях.

Все латинские эталоны в проекте — машинные. Обе тестовые книги — кириллические издания, и латинская колонка получена прогоном текста через мой же транслитератор. Сравнение «транслитерируем кириллицу и сверяем с латиницей» даёт CER ровно 0.0000 на всех 893 клипах — это проверка тождества, а не измерение. Все цифры проекта стоят на неизмеренной транслитерационной подложке. Поскольку подложка у всех цифр одна и та же, сравнения между ними остаются валидными — потому таблицы выше и имеют смысл. Неизвестна абсолютная ошибка. Чтобы её узнать, нужно то, чего у проекта не было никогда: несколько сотен клипов, расшифрованных латиницей человеком.

Разница меньше ~0.005 WER на этом тесте — не сигнал. Это я померил позже, когда сравнивал две версии адаптера: соседние чекпоинты одного и того же прогона, различающиеся сотней шагов оптимизатора, расходятся на тесте на 0.0035–0.0071 WER. Любое сравнение между прогонами обязано сначала перепрыгнуть этот пол, а dev из 255 клипов такую разницу не разрешает в принципе — на таких эффектах заранее зафиксированное правило «минимальный dev WER» близко к подбрасыванию монетки. Это, пожалуй, самый неприятный вывод всего этапа: дисциплина нужна не только в том, какой набор когда декодировать, но и в том, чтобы не верить собственным маленьким победам.

Что осталось

0.1701 — не «готово», а опорная точка. Впереди: dev-набор, который содержит режимы отказа (сейчас он не умеет ранжировать ни одну меру против петель); один тестовый прогон конфигурации greedy + fallback, которую этот эксперимент не смог выбрать; и главное — спонтанная речь, для которой у крымскотатарского не существует ни одного публичного размеченного набора. Я проверял: Common Voice, FLEURS, VoxPopuli, MLS, YODAS, OpenSLR, ISSAI TurkicASR и ещё десяток каталогов — crh нет ни в одном. Тестовый набор для спонтанной речи нельзя скачать. Его придётся построить.

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


Это вторая статья серии. Дальше — как синтезированный голос прочитал целую книгу, шестнадцать глав и 1 ч 58 мин звучания, и что ломалось на стыках между главами; как ставить слепые сравнения, чтобы не обманывать самого себя; и как я арендовал облачные видеокарты и честно сжёг на них деньги, посчитав почти ничего.

Аудиопримеры и текущее состояние проекта — https://ai.ana-yurt.dev/.

Если вы носитель крымскотатарского и готовы помогать — слепые прослушивания, вычитка расшифровок, «пятнадцать минут и наушники» — напишите на support@ana-yurt.com. Программировать для этого не нужно; в этом проекте одно живое ухо стабильно находит то, чего не находит ни один прибор. Поддержать работу материально можно на ko-fi.com/anayurt.

Комментарии (0)