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:

  1. 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.
  2. 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.
  3. 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

Três formulações de ASR: CTC, RNN-T, atenção encoder-decoder

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 _ ca 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=5 para 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

  1. 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.
  2. 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.
  3. Difícil. Use o whisper-large-v3-turbo no 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

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).