Phase 06 - Lesson 04
Reconhecimento de Fala (ASR) — CTC, RNN-T, Atenção
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
Reconhecimento de fala é classificação de áudio em cada instante de tempo, costurada por um modelo de sequência que conhece o inglês e o silêncio. CTC, RNN-T e atenção são as três formas de fazer isso. Escolha uma e entenda por quê.
Tipo: Build Linguagens: Python Pré-requisitos: Fase 6 · 02 (Espectrogramas e Mel), Fase 5 · 08 (CNNs e RNNs para Texto), Fase 5 · 10 (Atenção) Tempo: ~45 minutos
O Problema
Você tem um trecho de 10 segundos a 16 kHz. Você quer uma string: "ligue as luzes da cozinha". O desafio é estrutural: os quadros de áudio não se alinham um a um com os caracteres. A palavra "okay" pode levar 200 ms ou 1200 ms. O silêncio pontua a fala. Alguns fonemas são mais longos que outros. O número de tokens de saída não é conhecido de antemão.
Três formulações resolvem isso:
- CTC (Connectionist Temporal Classification). Emite probabilidades de tokens por quadro, incluindo um blank especial. Colapsa repetições e blanks no momento da decodificação. Não autorregressivo, rápido. Usado pelo wav2vec 2.0 e MMS.
- RNN-T (Recurrent Neural Network Transducer). Uma rede conjunta prevê o próximo token dado o quadro do encoder e os tokens anteriores. Suporta streaming. Usado pelo ASR on-device do Google e pelo NVIDIA Parakeet.
- Atenção encoder-decoder. O encoder comprime o áudio em estados ocultos, o decoder faz cross-attention para gerar tokens de forma autorregressiva. Usado pelo Whisper e pelo SeamlessM4T.
Em 2026, o WER SOTA no LibriSpeech test-clean é 1,4% (Parakeet-TDT-1.1B, NVIDIA) e 1,58% (Whisper-Large-v3-turbo). As diferenças são minúsculas; as diferenças de deploy são enormes.
O Conceito
Intuição do CTC. Deixe o encoder produzir T distribuições por quadro sobre V+1 tokens (V caracteres + blank). Para uma string-alvo y de comprimento U < T, qualquer alinhamento de quadros que colapse para y conta. A loss do CTC soma sobre todos esses alinhamentos. Inferência: argmax por quadro, colapsa repetições, remove blanks.
Vantagens: não autorregressivo, suporta streaming, lookahead zero. Desvantagem: suposição de independência condicional — cada predição de quadro é independente das demais, portanto não há modelo de linguagem interno. Corrija com um LM externo via beam search ou shallow fusion.
Intuição do RNN-T. Adiciona uma rede predictor que embute o histórico de tokens e um joiner que combina o estado do predictor com o quadro do encoder em uma distribuição conjunta sobre V+1 (o +1 é um null / sem emissão). Modela explicitamente a dependência condicional que o CTC ignora. Suporta streaming porque cada passo condiciona apenas em quadros passados e tokens passados.
Vantagens: streaming + LM interno. Desvantagem: o treinamento é mais complexo e consome mais memória (treliça de loss 3D); os kernels de loss do RNN-T formam uma categoria de biblioteca própria.
Atenção encoder-decoder. Encoder (6-32 camadas transformer) sobre quadros log-mel. O decoder (6-32 camadas transformer) faz cross-attention nas saídas do encoder para gerar tokens de forma autorregressiva. Sem restrição de alinhamento — a atenção pode olhar para qualquer ponto do áudio. Não suporta streaming a menos que você restrinja a atenção (Whisper-Streaming em chunks, 2024).
Vantagens: maior qualidade em ASR offline, fácil de treinar com ferramentas seq2seq padrão. Desvantagem: a latência autorregressiva é proporcional ao comprimento da saída; não consegue fazer streaming sem engenharia.
WER: o único número
Word Error Rate (Taxa de Erro de Palavra) = (S + D + I) / N, onde S=substituições, D=remoções, I=inserções, N=contagem de palavras de referência. Equivale à distância de edição de Levenshtein no nível da palavra. Quanto menor, melhor. Um WER acima de 20% é geralmente inutilizável; abaixo de 5% é paridade humana para fala lida. Números de 2026 em benchmarks padrão:
| Modelo | LibriSpeech test-clean | LibriSpeech test-other | Tamanho |
|---|---|---|---|
| Parakeet-TDT-1.1B | 1.40% | 2.78% | 1.1B params |
| Whisper-Large-v3-turbo | 1.58% | 3.03% | 809M |
| Canary-1B Flash | 1.48% | 2.87% | 1B |
| Seamless M4T v2 | 1.7% | 3.5% | 2.3B |
Todos eles são baseados em encoder-decoder ou RNN-T. Sistemas puramente CTC (wav2vec 2.0) ficam em torno de 1,8–2,1% no test-clean.
Construa
Passo 1: decodificação CTC gananciosa (greedy)
def ctc_greedy(frame_logits, blank=0, vocab=None):
# frame_logits: list of per-frame probability vectors
preds = [max(range(len(p)), key=lambda i: p[i]) for p in frame_logits]
out = []
prev = -1
for p in preds:
if p != prev and p != blank:
out.append(p)
prev = p
return "".join(vocab[i] for i in out) if vocab else out
Duas regras: colapsar repetições consecutivas, descartar blanks. Exemplo: a a _ _ a b b _ c → a a b c.
Passo 2: beam search com CTC
def ctc_beam(frame_logits, beam=8, blank=0):
import math
beams = [([], 0.0)] # (tokens, log_prob)
for p in frame_logits:
log_p = [math.log(max(pi, 1e-10)) for pi in p]
candidates = []
for seq, lp in beams:
for t, lpt in enumerate(log_p):
new = seq[:] if t == blank else (seq + [t] if not seq or seq[-1] != t else seq)
candidates.append((new, lp + lpt))
candidates.sort(key=lambda x: -x[1])
beams = candidates[:beam]
return beams[0][0]
Em produção usa-se beam search com árvore de prefixos e fusão com LM; este é o esqueleto conceitual.
Passo 3: WER
def wer(ref, hyp):
r, h = ref.split(), hyp.split()
dp = [[0] * (len(h) + 1) for _ in range(len(r) + 1)]
for i in range(len(r) + 1):
dp[i][0] = i
for j in range(len(h) + 1):
dp[0][j] = j
for i in range(1, len(r) + 1):
for j in range(1, len(h) + 1):
cost = 0 if r[i - 1] == h[j - 1] else 1
dp[i][j] = min(
dp[i - 1][j] + 1,
dp[i][j - 1] + 1,
dp[i - 1][j - 1] + cost,
)
return dp[len(r)][len(h)] / max(1, len(r))
Passo 4: inferência com Whisper
import whisper
model = whisper.load_model("large-v3-turbo")
result = model.transcribe("clip.wav")
print(result["text"])
Uma linha para o ASR geral mais forte de 2026. Roda em uma GPU de 24 GB a ~20× tempo real.
Passo 5: streaming com Parakeet ou wav2vec 2.0
from transformers import pipeline
asr = pipeline("automatic-speech-recognition", model="nvidia/parakeet-tdt-1.1b")
for chunk in streaming_audio():
print(asr(chunk, return_timestamps=True))
O ASR em streaming precisa de atenção do encoder em chunks e estado de carryover; use uma biblioteca que suporte isso (NeMo para Parakeet, pipeline do transformers com chunk_length_s).
Use
A stack de 2026:
| Situação | Escolha |
|---|---|
| Inglês, offline, qualidade máxima | Whisper-large-v3-turbo |
| Multilíngue, robusto | SeamlessM4T v2 |
| Streaming, baixa latência | Parakeet-TDT-1.1B ou Riva |
| Edge, mobile, latência <500 ms | Whisper-Tiny quantizado ou Moonshine (2024) |
| Formato longo | Whisper com chunking baseado em VAD (WhisperX) |
| Domínio específico (médico, jurídico) | Fine-tune do wav2vec 2.0 + fusão com LM de domínio |
Armadilhas que ainda aparecem em 2026
- Sem VAD. Rodar o Whisper sobre silêncio produz alucinações ("Thanks for watching!"). Sempre faça gate com VAD.
- WER por caractere vs palavra vs subword. Reporte o WER no nível da palavra depois da normalização (minúsculas, pontuação removida).
- Deriva de identificação de idioma. A LID automática do Whisper roteia erroneamente trechos ruidosos para japonês ou galês; force
language="en"quando você souber. - Trechos longos sem chunking. O Whisper tem uma janela de 30 segundos. Use
chunk_length_s=30, stride=5para qualquer coisa mais longa.
Entregue
Salve como outputs/skill-asr-picker.md. Escolha o modelo, a estratégia de decodificação, o chunking e a fusão com LM para um determinado alvo de deploy.
Exercícios
- Fácil. Rode
code/main.py. Ele decodifica de forma gananciosa uma saída CTC feita à mão e calcula o WER contra uma referência. - Médio. Implemente corretamente o beam search com árvore de prefixos do Passo 2 (considere a regra de mesclagem do blank). Compare com a versão gananciosa em um conjunto de dados sintético de 10 exemplos.
- Difícil. Use o
whisper-large-v3-turbono LibriSpeech test-clean. Calcule o WER nas primeiras 100 falas. Compare com os números publicados.
Termos-Chave
| Termo | O que as pessoas dizem | O que realmente significa |
|---|---|---|
| CTC | A loss do token blank | Marginal sobre todos os alinhamentos quadro-token; não AR. |
| RNN-T | A loss de streaming | CTC + preditor do próximo token; lida com ordem das palavras. |
| Atenção enc-dec | Estilo Whisper | Encoder + decoder com cross-attention; melhor qualidade offline. |
| WER | O número que você reporta | (S+D+I)/N no nível da palavra. |
| Blank | O vazio | Token especial no CTC que sinaliza "nenhuma emissão neste quadro". |
| Fusão com LM | Modelo de linguagem externo | Adiciona log-probs ponderados do LM durante o beam search. |
| VAD | O gate de silêncio | Detector de atividade de voz; corta trechos sem fala. |
Leitura Adicional
- Graves et al. (2006). Connectionist Temporal Classification — o paper do CTC.
- Graves (2012). Sequence Transduction with RNNs — o paper do RNN-T.
- Radford et al. / OpenAI (2022). Whisper: Robust Speech Recognition via Large-Scale Weak Supervision — o paper canônico de 2022; extensão v3-turbo em 2024.
- NVIDIA NeMo — Parakeet-TDT card — líder do Open ASR Leaderboard em 2026.
- Hugging Face — Open ASR Leaderboard — benchmark ao vivo com mais de 25 modelos.