Перейти к содержимому
Ступень 99.1 Мультимодальные модели

Речь: от звуковой волны к токенам и обратно

Как звук становится числами, спектрограммой и токенами: частота дискретизации, STFT и мел-шкала, CTC и Whisper, метрики WER/CER, вокодеры и нейрокодеки, TTS на языковых моделях и flow matching, голосовые и omni-модели — с китайскими и русскими моделями речи.

УРОКstage-9/9-1-multimodal/05-speech.mdx2 ч 10 минУрок 5 из 5
После урока вы сможете
  • Объяснить частоту дискретизации и предел Найквиста, вручную посчитать STFT и лог-мел-спектрограмму — вход Whisper
  • Описать два подхода к распознаванию речи (CTC и encoder–decoder Whisper) и посчитать WER и CER через расстояние Левенштейна
  • Объяснить, как нейрокодеки с остаточным квантованием превращают звук в токены и как на них строят синтез речи (VALL-E, CosyVoice) и omni-модели (Qwen-Omni)
  • Расшифровать свою речь через Whisper на GPU и оценить качество на русском и китайском

Голосовой ассистент, расшифровка созвонов, озвучка текста, перевод речи на лету, модели, с которыми можно разговаривать и перебивать их, — всё это работа со звуком. Схема вам уже знакома по картинкам: в уроке 9.1.1 картинку резали на патчи-токены, в уроке 9.1.3 сжимали автоэнкодером в дискретные коды, в уроке 9.1.4 генерировали потоком. Со звуком то же самое: волна → спектрограмма → токены → трансформер и обратно. Новое здесь — физика сигнала: частоты, окна, мел-шкала. Параллельно удобно читать курс Hugging Face по аудио: он переведён на русский, раздел 1 — про то же, что первая половина урока, разделы 3, 5 и 6 — про ASR и синтез.

Звук — колебания давления воздуха. Микрофон превращает их в напряжение, а звуковая карта измеряет его через равные промежутки времени. Сколько измерений (отсчётов) в секунду — это частота дискретизациичастота дискретизацииEN sampling rateсколько отсчётов сигнала в секунду; для речи обычно 16 кГц. Для речи стандарт — 16 кГц, то есть 16 000 чисел в секунду; в CD-качестве — 44.1 кГц. Каждое число — амплитуда, обычно от −1 до 1 (в файле — 16-битное целое).

Простейший звук — чистый тон, синусоида x(t)=Asin⁡(2πft+φ)x(t) = A \sin(2\pi f t + \varphi). Амплитуда AA — громкость, частота ff — высота (колебаний в секунду, Гц), фаза φ\varphi — сдвиг. Эти три слова разобраны в уроке о тригонометрии. Нота «ля» первой октавы — 440 Гц. Голос устроен сложнее: основной тон (у взрослых примерно от 85 до 255 Гц) плюс гармоники — кратные ему частоты. Форма рта и языка усиливает одни гармоники и глушит другие, так и получаются разные гласные.

Сколько отсчётов нужно, чтобы записать частоту ff? Больше двух на период: частота дискретизации должна быть больше 2f2f. Половину частоты дискретизации называют частотой Найквистачастота НайквистаEN Nyquist frequencyполовина частоты дискретизации — самая высокая частота, которую можно записать: при 16 кГц это 8 кГц — для разборчивости речи хватает, хотя часть энергии шипящих «с» и «ш» лежит выше и теряется. Что будет с тоном выше предела? Он не пропадёт, а притворится другим:

import numpy as np
sr = 16000 # частота дискретизации
n = np.arange(32) # номера отсчётов
x9 = np.sin(2 * np.pi * 9000 * n / sr) # тон 9 кГц — выше предела 8 кГц
x7 = np.sin(2 * np.pi * 7000 * n / sr) # тон 7 кГц
print(np.abs(x9 + x7).max()) # ≈1e-14: отсчёты 9 кГц — это отсчёты 7 кГц с минусом

После дискретизации 9 кГц неотличимы от 7 кГц (16−9=716 - 9 = 7). Это алиасингалиасинг (наложение частот)EN aliasingчастота выше предела Найквиста после дискретизации выглядит как более низкая, тот же эффект, из-за которого колёса в кино крутятся назад. Поэтому перед понижением частоты дискретизации звук сначала пропускают через фильтр, срезающий частоты выше нового предела.

Масштаб: 30 секунд речи при 16 кГц — 480 000480\,000 чисел. Трансформер не прочитает столько токенов, и большая их часть избыточна. Нужно представление компактнее и ближе к тому, как слышит человек.

Любой сигнал можно разложить в сумму синусоид разных частот — это идея преобразования Фурьепреобразование ФурьеEN Fourier transformраскладывает сигнал на синусоиды разных частот; дискретная версия — ДПФ, быстрый алгоритм — FFT. Для кусочка из NN отсчётов дискретное преобразование Фурье (ДПФ) отвечает на вопрос: «сколько в сигнале частоты номер kk?» Ответ даёт скалярное произведение (урок 1.1.1) сигнала с косинусом и синусом этой частоты:

Rek=∑n=0N−1xncos⁡2πknN,Imk=∑n=0N−1xnsin⁡2πknN,∣Xk∣=Rek2+Imk2\mathrm{Re}_k = \sum_{n=0}^{N-1} x_n \cos\frac{2\pi k n}{N}, \qquad \mathrm{Im}_k = \sum_{n=0}^{N-1} x_n \sin\frac{2\pi k n}{N}, \qquad \lvert X_k \rvert = \sqrt{\mathrm{Re}_k^2 + \mathrm{Im}_k^2}

Если в сигнале есть эта частота, произведения складываются в большое число; если нет — синусоиды разной частоты «гасят» друг друга, и сумма близка к нулю. Синус и косинус нужны оба, потому что частота может прийти с любой фазой. Бин kk соответствует частоте k⋅sr/Nk \cdot \mathrm{sr} / N Гц. Проверим на 16 отсчётах:

import numpy as np
N = 16
n = np.arange(N)
x = np.cos(2 * np.pi * 3 * n / N) + 0.5 * np.sin(2 * np.pi * 5 * n / N) # частоты 3 и 5, амплитуды 1 и 0.5
mags = [np.hypot(np.sum(x * np.cos(2 * np.pi * k * n / N)),
np.sum(x * np.sin(2 * np.pi * k * n / N))) for k in range(N // 2 + 1)]
print(np.round(mags, 2)) # [0. 0. 0. 8. 0. 4. 0. 0. 0.]
print(np.round(np.abs(np.fft.rfft(x)), 2)) # то же самое, но быстро: FFT

Пики в бинах 3 и 5, высота N/2⋅AN/2 \cdot A: 88 и 44. np.fft.rfft считает то же самое быстрым алгоритмом FFT за O(Nlog⁡N)O(N \log N) вместо O(N2)O(N^2). Наглядно эту идею — «намотать» сигнал на окружность и посмотреть, где центр масс уходит от нуля, — показывает видео 3Blue1Brown «But what is the Fourier Transform?» (около 20 минут; удобно посмотреть целиком перед этим разделом).

Речь — не застывший аккорд: звуки сменяют друг друга каждые десятки миллисекунд. Поэтому сигнал режут на короткие перекрывающиеся окна и считают спектр каждого — это оконное преобразование Фурьеоконное преобразование Фурье (STFT)EN short-time Fourier transform (STFT)спектр коротких перекрывающихся окон сигнала; у Whisper окно 25 мс, шаг 10 мс (STFT). Параметры Whisper:

  • окно 400 отсчётов = 25 мс, шаг 160 отсчётов = 10 мс;
  • каждое окно умножают на окно Ханна wn=0.5−0.5cos⁡(2πn/N)w_n = 0.5 - 0.5\cos(2\pi n / N): оно плавно гасит края, иначе резкий обрыв кусочка дал бы в спектре ложные частоты;
  • FFT даёт 400/2+1=201400/2 + 1 = 201 бин с шагом 16 000/400=4016\,000 / 400 = 40 Гц, от 0 до 8000 Гц.

Модули, сложенные по времени, образуют спектрограммуспектрограммаEN spectrogramкартинка «частота × время», яркость — громкость частоты в данный момент; для речи в китайских текстах также «речевая спектрограмма» — картинку «частота × время», где яркость показывает громкость частоты в этот момент. 30 секунд с шагом 10 мс — 3000 кадров.

Человек различает 100 и 200 Гц гораздо легче, чем 7000 и 7100 Гц: высоту мы слышим примерно логарифмически. Мел-шкаламел-шкалаEN mel scaleшкала воспринимаемой высоты звука; m = 2595·log10(1 + f/700) переводит герцы в «воспринимаемую высоту»:

m(f)=2595⋅log⁡10(1+f700)m(f) = 2595 \cdot \log_{10}\left(1 + \frac{f}{700}\right)

До 1000 Гц шкала почти линейна (m(1000)≈1000m(1000) \approx 1000), дальше сжимается: m(4000)≈2146m(4000) \approx 2146, m(8000)≈2840m(8000) \approx 2840. Весь диапазон 4–8 кГц — половина герц — занимает лишь 24% шкалы. Спектр складывают треугольными фильтрами, равномерно расставленными по мелам: у Whisper их 80 (в large-v3 — 128). Нижние фильтры узкие (около 45 Гц при 80 фильтрах до 8 кГц), верхние широкие (около 525 Гц). Получается 80 чисел на кадр вместо 201. Наконец берут логарифм энергии: громкость мы тоже слышим логарифмически (отсюда децибелы). Вот лог-мел-спектрограмма(лог-)мел-спектрограммаEN (log-)mel spectrogramспектрограмма, сложенная треугольными мел-фильтрами и прологарифмированная; вход Whisper и выход многих TTS — та «картинка» 80 × 3000, которую читает энкодер Whisper.

flowchart LR
A["волна<br/>480 000 чисел"] --> B["окна 25 мс<br/>шаг 10 мс"]
B --> C["× окно Ханна"]
C --> D["FFT → |·|²<br/>3000 × 201"]
D --> E["80 мел-фильтров"]
E --> F["log<br/>80 × 3000"]
F --> G["энкодер Whisper"]
От волны к лог-мел-спектрограмме: 30 секунд речи при 16 кГц

Формула мел-шкалы — одна из нескольких: в librosa и Whisper по умолчанию используется вариант Слейни (линейный до 1 кГц, логарифмический выше). Идея та же, числа немного другие. В задании ниже вы реализуете вариант с формулой выше.

Задача распознавания речираспознавание речи (ASR)EN automatic speech recognition (ASR): звук → текст. Главная трудность — выравнивание: неизвестно, каким кадрам соответствует какая буква. «Привет» можно сказать за 0.3 секунды или растянуть на две. Есть два основных решения.

CTCCTC (коннекционистская временная классификация)EN Connectionist Temporal Classification (CTC)символ или «пусто» на каждый кадр; повторы схлопываются, пустые символы удаляются (Connectionist Temporal Classification, Грейвс и соавторы, 2006) разрешает модели на каждом кадре выдавать букву или специальный пустой символ ∅. Чтобы получить текст, схлопывают повторы подряд и удаляют ∅:

def ctc_greedy(frames, blank="∅"):
"""Схлопываем повторы подряд, потом выкидываем пустой символ."""
out, prev = [], None
for s in frames:
if s != prev and s != blank:
out.append(s)
prev = s
return "".join(out)
print(ctc_greedy("пп∅рии∅вв∅ееет")) # привет
print(ctc_greedy("кк∅аа∅сс∅сс∅аа")) # касса: «∅» между двумя «с» сохраняет удвоение
print(ctc_greedy("ккаассссаа")) # каса — без «∅» две «с» слиплись

При обучении правильное выравнивание неизвестно, поэтому loss CTC складывает вероятности всех путей по кадрам, которые схлопываются в правильный текст. Их экспоненциально много, но динамическое программирование считает сумму быстро — та же идея, что в расстоянии Левенштейна ниже. CTC-модели неавторегрессионные: все кадры предсказываются параллельно, поэтому они быстрые. Так устроены wav2vec 2.0 и русская GigaAM-CTC.

WhisperWhisperEN Whisper (OpenAI)encoder–decoder трансформер для распознавания и перевода речи, обучен на 680 тыс. часов; MIT (OpenAI, 2022) — трансформер encoder–decoder из урока 4.5.1, обученный на 680 тысячах часов аудио с расшифровками из интернета (65% — английский, 17% — речь на 98 других языках с расшифровкой на том же языке, остальное — перевод на английский). Конвейер:

  1. Звук режется на окна по 30 секунд → лог-мел 80 × 3000.
  2. Две одномерные свёртки (вторая с шагом 2, см. урок 3.5.1) сжимают время вдвое: 1500 векторов по 20 мс.
  3. Энкодер-трансформер обрабатывает эти 1500 «аудиотокенов».
  4. Декодер через cross-attention смотрит на выход энкодера и авторегрессионно пишет BPE-токены текста.

Задача задаётся промптом из специальных токенов в начале декодера: <|startoftranscript|><|ru|><|transcribe|><|notimestamps|> — «расшифруй русскую речь без временных меток». Вместо <|transcribe|> можно поставить <|translate|> (перевод на английский), а если язык не указан, модель сначала сама предскажет языковой токен. Это тот же приём, что системный промпт у LLM: одна модель — много задач.

МодельПараметрыПамять (по README)Скорость относительно large
tiny / base / small39M / 74M / 244M≈1 / 1 / 2 ГБ≈10× / 7× / 4×
medium769M≈5 ГБ≈2×
large-v31550M≈10 ГБ1×
turbo (large-v3-turbo)809M≈6 ГБ≈8×

turbo — large-v3 с декодером, урезанным с 32 слоёв до 4: декодер работает на каждом токене текста, поэтому его укорачивание даёт главный выигрыш в скорости. Переводу turbo не обучена. Код и веса Whisper открыты под MIT.

Слабые места: на тишине и шуме Whisper иногда галлюцинирует — выдумывает текст, нередко похожий на типичные фразы из субтитров к видео, на которых он учился, — и может зацикливаться на повторах. Лечат детектором речи (VADдетектор речевой активности (VAD)EN voice activity detection (VAD)находит в записи участки с речью; спасает ASR от галлюцинаций на тишине, например Silero VAD внутри faster-whisper), beam search и повтором с повышенной температурой (урок 4.1.3).

  • Русский: богатая морфология — одна неверная буква в окончании делает ошибочным всё слово, и WER выглядит хуже, чем смысл. Числа («двадцать пятого» или «25-го»), ё и е, пунктуация — всё это нужно нормализовать перед подсчётом метрики. Для синтеза отдельная беда — ударения и омографы («замок» и «замок»).
  • Китайский: между словами нет пробелов, поэтому качество меряют по иероглифам. Слогов мало, иероглифов тысячи: один и тот же слог «ши», даже с одним и тем же тоном, — это десятки иероглифов со значениями «быть», «дело», «город», «комната», «пробовать» и другими, и выбрать нужный можно только по контексту. Добавьте диалекты, смешение китайского с английским в одной фразе и упрощённые и традиционные иероглифы на выходе — перед подсчётом их тоже приводят к одному виду.

WERдоля ошибок по словам (WER)EN word error rate (WER)(замены + удаления + вставки) / слов в эталоне; для китайского считают CER (character error rate) — долю ошибок по символам (word error rate) — минимальное число правок пословно, делённое на число слов эталона:

WER=S+D+IN\mathrm{WER} = \frac{S + D + I}{N}

SS — замены, DD — удаления, II — вставки, NN — слов в эталоне. Минимальное число правок — это расстояние Левенштейна, его считают динамическим программированием по таблице «префикс эталона × префикс гипотезы» (пошагово, как задачу с собеседования, — в уроке 11.3.5). Пример: эталон «включи свет на кухне» (4 слова), гипотеза «включи свет в кухне сейчас»: одна замена (на → в) и одна вставка (сейчас), WER =2/4=50%= 2/4 = 50\%. Из-за вставок WER может быть больше 100%. CER — то же по символам; его берут для китайского, где нет пробелов между словами. Пример на русском: «молоко» → «малоко» — одна замена из шести букв, CER ≈ 16.7%, хотя WER для этого слова — 100%.

Вокодер: от спектрограммы обратно к волне

Заголовок раздела «Вокодер: от спектрограммы обратно к волне»

Классический синтез речисинтез речи (TTS)EN text-to-speech (TTS) идёт в два этапа: модель (Tacotron 2, FastSpeech) превращает текст в мел-спектрограмму, а вокодервокодерEN vocoderпревращает мел-спектрограмму обратно в звуковую волну (WaveNet, HiFi-GAN) — спектрограмму в волну. Вокодер нужен, потому что мел-спектрограмма выбросила фазу и сжала частоты, и восстановить волну простой формулой нельзя. WaveNet (DeepMind, 2016) генерировал звук по одному отсчёту — 16 000 шагов на секунду, очень медленно. HiFi-GAN (2020) делает это одним проходом свёрточной сети, а резкость звука ему обеспечивает дискриминатор — тот самый GAN-лосс из урока 9.1.3.

Нейрокодекнейрокодек (нейросетевой аудиокодек)EN neural audio codecавтоэнкодер для звуковой волны с квантованием; его коды — аудиотокены (SoundStream, EnCodec, Mimi) — автоэнкодер для волны: энкодер сжимает звук в последовательность векторов (десятки в секунду), квантование делает их токенами, декодер восстанавливает волну. Так устроены SoundStream (Google, 2021) и EnCodec (Meta, 2022). Одной кодовой книги на хорошее качество не хватает, поэтому используют остаточное векторное квантованиеостаточное векторное квантование (RVQ)EN residual vector quantization (RVQ)каждая следующая кодовая книга кодирует остаток после предыдущих (RVQ): первая книга кодирует вектор грубо, вторая — остаток, третья — остаток остатка. Идея на одном числе:

import numpy as np
x = 0.6 # «звук», который хотим сжать (одно число для наглядности)
books = [np.array([-0.75, -0.25, 0.25, 0.75]) / 4**k for k in range(3)] # 3 книги по 4 кода, каждая в 4 раза мельче
residual, tokens, approx = x, [], 0.0
for k, book in enumerate(books):
i = int(np.abs(book - residual).argmin()) # ближайший код к ТЕКУЩЕМУ остатку
tokens.append(i)
approx += book[i]
residual -= book[i] # следующей книге достаётся то, что не угадали
print(f"книга {k + 1}: токен {i}, приближение {approx:.4f}, ошибка {abs(residual):.4f}")
print("токены:", tokens)
книга 1: токен 3, приближение 0.7500, ошибка 0.1500
книга 2: токен 0, приближение 0.5625, ошибка 0.0375
книга 3: токен 3, приближение 0.6094, ошибка 0.0094
токены: [3, 0, 3]

Каждая книга уменьшает ошибку, а число книг — ручка «битрейт против качества». У EnCodec для 24 кГц: 75 кадров в секунду, книги по 1024 кода (10 бит), и битрейт 1.5, 3, 6, 12 или 24 кбит/с соответствует 2, 4, 8, 16 или 32 книгам. Проверим 6 кбит/с: 75⋅8⋅10=600075 \cdot 8 \cdot 10 = 6000 бит/с. Несжатый 16-битный звук 24 кГц — 384 000384\,000 бит/с, в 64 раза больше.

Когда звук стал токенами, синтез речи превратился в языковое моделирование. VALL-E (Microsoft, 2023) обучили предсказывать токены EnCodec по тексту и трёхсекундному образцу голоса — и модель клонирует голос по образцу, продолжая его «как текст». Позже токены разделились на два вида:

  • семантические — что сказано (например, VQ поверх энкодера, обученного на ASR), 12.5–25 токенов в секунду;
  • акустические — как звучит (тембр, интонация), токены кодека или мел-спектрограмма.

Типичный современный конвейер на примере CosyVoice (Alibaba): LLM по тексту пишет семантические токены, модель flow matching из урока 9.1.4 превращает их в мел-спектрограмму с нужным голосом, вокодер HiFi-GAN — в волну. В CosyVoice 2 токены получают через FSQ из урока 9.1.3 (6561 код, 25 токенов в секунду), а языковая модель — Qwen2.5-0.5B. F5-TTS обходится вообще без токенов: flow matching на DiT генерирует мел-спектрограмму прямо по тексту.

flowchart LR
T["текст"] --> LM["LLM"]
P["образец голоса"] --> LM
LM --> S["семантические токены<br/>25 в секунду"]
S --> FM["flow matching"]
P --> FM
FM --> M["мел-спектрограмма"]
M --> V["вокодер HiFi-GAN"]
V --> W["волна"]
Конвейер TTS в духе CosyVoice: дискретные токены, поток и вокодер из уроков 9.1.3–9.1.4

Качество синтеза оценивают люди: MOSсредняя экспертная оценка (MOS)EN mean opinion score (MOS)средняя оценка естественности синтезированной речи слушателями по шкале 1–5 (mean opinion score) — средняя оценка естественности по шкале от 1 до 5. Автоматические заменители: WER распознавания синтезированной речи (не исказилось ли содержание) и сходство диктора (SS) — косинус между эмбеддингами образца и результата.

Самый простой голосовой ассистент — каскад ASR → LLM → TTS. Он работает, но задержки складываются, а интонация, эмоции и смех теряются на этапе текста. Поэтому модели всё чаще делают сквозными: LLM сама читает аудио и сама пишет речевые токены.

  • GLM-4-Voice (Zhipu, 2024): токенизатор — энкодер Whisper с VQ, 12.5 токена на секунду аудио; модель 9B на основе GLM-4-9B; декодер на flow matching по образцу CosyVoice.
  • Kimi-Audio (Moonshot, 2025): 7B на основе Qwen2.5-7B, предобучение на 13+ млн часов аудио; на входе семантические токены 12.5 Гц плюс непрерывные признаки энкодера Whisper, на выходе — детокенизатор на flow matching.
  • Qwen2.5-Omni (Alibaba, 03.2025) — архитектура Thinker–Talker: Thinker — LLM, которая понимает текст, картинки, звук и видео и пишет текст; Talker по скрытым состояниям Thinker потоково генерирует речевые токены. Позиции звука и кадров видео синхронизирует TMRoPE — родственник M-RoPE из урока 9.1.1. Qwen3-Omni (09.2025) — то же на MoE 30B-A3B: 119 языков текста, 19 языков речи на входе, 10 на выходе, Apache-2.0. По сообщениям, в марте 2026 года вышла Qwen3.5-Omni, и часть её вариантов доступна только через API — статус весов проверьте в блоге Qwen.
  • Moshi (Kyutai, Франция, 2024) — полный дуплекс: модель одновременно слушает и говорит, её можно перебить. Кодек Mimi сжимает звук до 12.5 кадров в секунду (1.1 кбит/с); заявленная теоретическая задержка — 160 мс.

Omni-модельomni-модель (всемодальная модель)EN omni modelодна модель читает текст, картинки, звук и видео и отвечает текстом и речью (Qwen-Omni) — это не новая математика, а сборка всего модуля: энкодеры для картинок и звука (9.1.1, 9.1.5), дискретные токены (9.1.3), генерация потоком (9.1.4) вокруг одной LLM. Цена — длина контекста. При 12.5 токенах в секунду минута речи — 750 токенов, час созвона — 45 00045\,000. Энкодер Whisper с его 50 кадрами в секунду дал бы 30003000 векторов за минуту. Поэтому все стремятся к низкой частоте токенов.

Синтезируйте сигнал, реализуйте STFT, мел-фильтры и лог-мел-спектрограмму вручную, а затем WER и CER через расстояние Левенштейна. Тесты сверят STFT с медленным эталоном на косинусах и синусах, найдут пики тонов 440 и 2000 Гц и проверят примеры из урока.

Спектрограмма, мел-фильтры и WER своими рукамиnumpy · нужен интернет7 тестов
import numpy as np


def sine(freq, sr=16000, duration=1.0, amp=1.0, phase=0.0):
    """Синусоида amp·sin(2π·freq·t + phase), оцифрованная с частотой sr (готовая функция)."""
    t = np.arange(int(sr * duration)) / sr
    return amp * np.sin(2 * np.pi * freq * t + phase)


def stft_magnitude(x, n_fft=400, hop=160):
    """Амплитудная спектрограмма |STFT| формы (n_frames, n_fft // 2 + 1).

    1) режем x на кадры длины n_fft с шагом hop, без дополнения краёв:
       n_frames = 1 + (len(x) − n_fft) // hop, кадр i — это x[i*hop : i*hop + n_fft];
    2) умножаем каждый кадр на окно Ханна window = 0.5 − 0.5·cos(2π·n / n_fft), n = 0..n_fft−1;
    3) np.fft.rfft по каждому кадру и модуль (np.abs).
    """
    # TODO: индексы кадров можно собрать без цикла:
    #   idx = np.arange(n_fft)[None, :] + hop * np.arange(n_frames)[:, None];  x[idx] — матрица кадров
    ...


def hz_to_mel(f):
    """Герцы → мелы: 2595·log10(1 + f / 700). Работает и с числами, и с массивами."""
    # TODO
    ...


def mel_to_hz(m):
    """Мелы → герцы (обратная формула): 700·(10^(m / 2595) − 1)."""
    # TODO
    ...


def mel_filterbank(sr=16000, n_fft=400, n_mels=80, fmin=0.0, fmax=None):
    """Банк треугольных мел-фильтров формы (n_mels, n_fft // 2 + 1).

    1) n_mels + 2 точки, равномерно расставленные по шкале мелов от hz_to_mel(fmin) до hz_to_mel(fmax)
       (по умолчанию fmax = sr / 2), переводим обратно в герцы: edges;
    2) частота бина k спектра: k · sr / n_fft, k = 0..n_fft // 2;
    3) фильтр i — треугольник с вершиной 1 в edges[i + 1] и нулями в edges[i] и edges[i + 2]:
       w = max(0, min((f − left) / (center − left), (right − f) / (right − center))).
    """
    # TODO
    ...


def log_mel(x, sr=16000, n_fft=400, hop=160, n_mels=80):
    """Лог-мел-спектрограмма формы (n_mels, n_frames), как вход Whisper (без его нормировки).

    Мощность = |STFT|², мел-энергии = банк фильтров @ мощность, результат — log10(max(энергия, 1e-10)).
    """
    # TODO: соберите из stft_magnitude и mel_filterbank
    ...


def edit_distance(ref, hyp):
    """Расстояние Левенштейна между двумя списками: минимум замен, удалений и вставок."""
    # TODO: динамическое программирование — таблица (len(ref) + 1) × (len(hyp) + 1),
    #       D[i][j] = расстояние между ref[:i] и hyp[:j]; D[i][0] = i, D[0][j] = j
    ...


def wer(ref, hyp):
    """Word Error Rate: edit_distance по словам (split по пробелам) / число слов в эталоне ref."""
    # TODO
    ...


def cer(ref, hyp):
    """Character Error Rate: то же по символам, пробелы предварительно удаляем (для китайского)."""
    # TODO
    ...


# Проверьте себя кнопкой «Запустить»:
x = sine(440) + 0.5 * sine(2000)            # «ля» первой октавы плюс тихий тон 2 кГц
S = stft_magnitude(x)
if S is not None:
    print("форма спектрограммы:", S.shape)    # ожидается (98, 201)
    print("самый сильный бин:", S.mean(axis=0).argmax(), "→", S.mean(axis=0).argmax() * 40, "Гц")  # 11 → 440 Гц
print("1000 Гц =", hz_to_mel(1000), "мел")    # ≈ 1000
print("WER =", wer("включи свет на кухне", "включи свет в кухне сейчас"))   # 0.5

Запишите 30–60 секунд своей речи и расшифруйте её через faster-whisper — реализацию Whisper на движке CTranslate2. По README она до 4 раз быстрее оригинальной при той же точности и умеет int8.

Окно терминала
pip install faster-whisper # ffmpeg не нужен: звук декодирует PyAV
arecord -f S16_LE -r 16000 -c 1 -d 30 me.wav # запись 30 с с микрофона (пакет alsa-utils), 16 кГц, моно
from faster_whisper import WhisperModel
model = WhisperModel("large-v3", device="cuda", compute_type="float16") # на CPU: device="cpu", compute_type="int8"
segments, info = model.transcribe("me.wav", beam_size=5, language="ru", vad_filter=True)
print(f"язык: {info.language} (вероятность {info.language_probability:.2f})")
hyp = ""
for seg in segments: # segments — генератор: расшифровка идёт по мере чтения
print(f"[{seg.start:5.2f} → {seg.end:5.2f}] {seg.text}")
hyp += seg.text
ref = open("me.txt", encoding="utf-8").read() # то, что вы на самом деле сказали
norm = lambda s: " ".join(s.lower().replace("ё", "е").translate(str.maketrans("", "", ".,!?;:—«»")).split())
print(f"WER: {wer(norm(ref), norm(hyp)):.3f}") # wer — ваша функция из задания выше

Формат вывода такой (текст и числа у вас будут свои):

язык: ru (вероятность 1.00)
[ 0.00 → 4.86] Включи, пожалуйста, свет на кухне и поставь чайник.
[ 5.20 → 9.74] Завтра в двадцать пять минут восьмого у меня созвон.
WER: 0.071

Посмотрите на ошибки глазами: часто это не «непонятые» слова, а числа («25 минут» против «двадцать пять минут») и пунктуация — нормализация решает, какой WER вы в итоге напишете в отчёте. Запишите ту же фразу с фоновой музыкой и посмотрите, насколько вырастет WER.

Проверь себя6 вопросов
1 Звук записан с частотой дискретизации 22 050 Гц. Какую максимальную частоту можно в нём сохранить (в Гц)?
2 STFT с окном 400 отсчётов при 16 кГц. С каким шагом по частоте идут бины (в Гц)?
3 Что выдаст жадное декодирование CTC для кадров «мм∅ааа∅мм∅аа»?
4 Эталон «я иду домой», гипотеза «я пойду домой сейчас». Чему равен WER? Округлите до двух знаков.
5 Какие утверждения верны?Можно выбрать несколько ответов
6 Речевой токенизатор выдаёт 12.5 токена в секунду. Сколько токенов займёт 20-минутный разговор?
  • Звук — это отсчёты с частотой дискретизации (речь — 16 кГц); частоты выше половины этой частоты записать нельзя. Спектр считают через ДПФ (скалярные произведения с синусами и косинусами), а STFT с окном Ханна даёт спектрограмму. Мел-фильтры и логарифм превращают её в лог-мел — «картинку», которую читает Whisper.
  • ASR выравнивает звук и текст либо через CTC (символ на кадр плюс ∅, быстро и параллельно), либо через encoder–decoder с промптом из специальных токенов (Whisper). Качество меряют WER и CER — расстоянием Левенштейна.
  • Нейрокодеки с остаточным квантованием делают из звука токены. На них синтез речи стал языковым моделированием, а flow matching и вокодер возвращают звук. Omni-модели (Qwen-Omni, Kimi-Audio, GLM-4-Voice) объединяют всё в одной LLM.
  • Китайская экосистема (FunASR, SenseVoice, CosyVoice, Qwen3-ASR и Qwen-Omni) и русские GigaAM и Silero дают открытые модели под свои языки. Перед выбором сверяйте версии и лицензии весов.

Дальше: модуль 9.2 — куда движется архитектура LLM в 2026 году: разреженное внимание ради контекста 1M, новый остаточный поток, условная память и обучение в FP4.