Whisper Tiny PT: do ajuste fino à avaliação em português
Eu desenvolvi o Whisper Tiny PT como um ajuste fino de openai/whisper-tiny para reconhecimento automático de fala em português. Usei o Common Voice 11.0, configuração pt, e publiquei pesos, processador, tokenizador, argumentos serializados e eventos do TensorBoard [1]. É um modelo pequeno o bastante para experimentação local. Na primeira versão, preservei apenas parte da receita; aqui assumo essas informações ausentes e explico como uso e avalio o checkpoint.
Parti do Whisper Tiny multilíngue com WhisperForConditionalGeneration e treinei por 5.000 passos, incluindo 500 passos de aquecimento, com Adam, taxa de aprendizado de 1e-5 e precisão mista Native AMP. Durante a avaliação, registei WER entre 28,72 e 32,74 [1]. Não preservei o hardware, a composição exata dos splits, os filtros de duração, a normalização dos rótulos, a acumulação de gradiente, a estratégia de seleção do checkpoint publicado nem os parâmetros completos de geração. Sem esses dados, ainda não consigo reproduzir a execução bit a bit nem concluir superioridade de forma categórica.
Arquitetura: um Transformer encoder-decoder compacto
Whisper é um Transformer sequência-a-sequência. Diferentemente de um sistema CTC, que costuma prever símbolos alinhados aos quadros acústicos e colapsá-los depois, o Whisper usa um encoder acústico e um decoder textual autoregressivo. O encoder transforma o espectrograma em representações contextualizadas; a cada passo, o decoder combina os tokens já produzidos com essas representações por atenção cruzada e prevê o próximo token [5]. A perda de treinamento é entropia cruzada sobre a sequência-alvo.
Renderizando diagrama...
Mantive a arquitetura do Whisper Tiny: largura oculta 384, quatro camadas no encoder e quatro no decoder, seis cabeças de atenção em cada lado e dimensão 1.536 nas redes feed-forward. O vocabulário tem 51.865 entradas; o limite do encoder é 1.500 posições e o do decoder, 448 tokens [2]. Publiquei 37.760.640 parâmetros em safetensors, valor próximo do arredondamento de 39 milhões usado no trabalho original. Minha contribuição é o ajuste dos pesos ao português, não uma arquitetura nova.
Publiquei os pesos em model.safetensors e no formato PyTorch antigo, além de config.json, preprocessor_config.json, vocab.json, merges.txt, normalizer.json, mapas de tokens especiais, training_args.bin e dois arquivos de eventos TensorBoard [1, 3]. A duplicação dos pesos em dois formatos aumenta o tamanho do repositório, mas permite preferir safetensors, que evita a desserialização de pickle. O arquivo training_args.bin é um pickle e não deve ser carregado de uma fonte não confiável apenas para inspecioná-lo.
Áudio e atributos acústicos
O Whisper espera áudio mono a 16 kHz. Seu extrator trabalha com janelas de até 30 segundos, FFT de 400 amostras, isto é, 25 ms, e salto de 160 amostras, ou 10 ms. Cada janela vira 80 bandas Mel em escala logarítmica [5, 10]. Para 30 segundos:
A segunda convolução usa passo 2; por isso, cerca de 3.000 quadros tornam-se 1.500 posições, exatamente o max_source_positions do checkpoint. Esse cálculo é uma aproximação intuitiva: preenchimento e detalhes de borda da STFT determinam a forma exata. Áudios menores são preenchidos e maiores precisam ser truncados, segmentados ou processados pelo algoritmo de áudio longo. Reamostrar corretamente é indispensável: entregar uma forma de onda de 48 kHz declarando 16 kHz muda sua escala temporal aparente.
O espectrograma não é texto e não contém uma segmentação explícita em fonemas. O encoder aprende regularidades de frequência, duração, prosódia e contexto; o decoder resolve a sequência escrita. Isso permite pontuação e capitalização, mas também introduz uma limitação dos modelos generativos: o decoder pode repetir frases ou produzir texto plausível que não foi falado, sobretudo em silêncio, ruído ou domínios distantes do treinamento [6].
Tokenizador e tokens de controle
O Whisper multilíngue emprega BPE em nível de bytes. Mantive vocab.json e merges.txt do checkpoint de origem; não treinei um vocabulário português do zero. A vantagem é representar qualquer texto UTF-8 e reutilizar o conhecimento multilíngue. A desvantagem é que a unidade avaliada pelo modelo não coincide necessariamente com palavra, caractere ou fonema.
Antes da transcrição, o decoder usa um prefixo conceitualmente semelhante a:
<|startoftranscript|> <|pt|> <|transcribe|> <|notimestamps|>Os símbolos informam início, idioma, tarefa de transcrição e ausência de timestamps. Salvei forced_decoder_ids: null; por isso, o arquivo não obriga português em toda versão da biblioteca [2]. Na inferência, declarar language="pt" e task="transcribe" torna o protocolo explícito e evita que um erro de detecção de idioma contamine a saída.
Decodificar significa escolher tokens segundo probabilidades condicionais. Busca gulosa escolhe o melhor token local; beam search conserva várias sequências; temperatura redistribui probabilidades. O artigo Whisper descreve uma estratégia de fallback de temperatura e heurísticas para transcrição longa [5], mas eu não registrei quais opções geraram meus números. Portanto, alterar num_beams, temperatura, condição sobre texto anterior, segmentação ou timestamps cria outro protocolo de avaliação.
Common Voice 11 em português
O Common Voice é um corpus colaborativo: participantes leem frases propostas e outras pessoas validam se o áudio corresponde ao texto [8, 9]. A versão 11 foi fechada em 21 de setembro de 2022 e, no conjunto completo, reuniu 100 idiomas, 24.231 horas totais e 16.429 horas validadas [8]. Para a localidade pt, a ficha estatística registra:
| Item | Common Voice 11 pt |
|---|---|
| Horas totais | 150,29 |
| Horas validadas | 125,24 |
| Clipes totais | 129.721 |
| Clipes validados | 108.100 |
train | 18.211 clipes |
dev | 8.688 clipes |
test | 8.693 clipes |
| Usuários | 2.621 |
| Duração média | 4,171 s |
Esses totais descrevem o lançamento, não necessariamente todos os exemplos efetivamente vistos no ajuste. Não informei no cartão se train foi unido a dev, se clipes foram removidos, nem quantas horas restaram após pré-processamento. Tampouco é correto expandir pt para “português brasileiro”: no CV11, a estatística de sotaque está 100% vazia e a coluna de variante só aparece em lançamentos posteriores. O modelo pode funcionar com fala brasileira e europeia, mas não medi separadamente essas variedades.
Ajuste fino: o que documentei
Treinei com a seguinte receita [1]:
| Hiperparâmetro | Valor publicado |
|---|---|
| Passos de treinamento | 5.000 |
| Warmup | 500 passos |
| Taxa de aprendizado | 1e-5 |
| Agenda | linear |
| Lote de treinamento | 64 |
| Lote de avaliação | 8 |
| Otimizador | Adam, betas (0,9; 0,999), epsilon 1e-8 |
| Semente | 42 |
| Precisão mista | Native AMP |
Os 500 passos iniciais correspondem a 10% do orçamento:
Durante esse intervalo, a taxa cresce até 1e-5; com agenda linear, ela decai no restante. Native AMP executa operações selecionadas em precisão reduzida e mantém outras em precisão adequada, reduzindo memória e frequentemente acelerando GPUs compatíveis. Não registrei qual GPU usei nem se a precisão reduzida foi FP16 ou outro formato. Também não preservei o número de dispositivos ou a acumulação, então o lote 64 não deve ser interpretado automaticamente como lote por dispositivo.
Renderizando diagrama...
Os resultados que registrei sugerem sobreajuste após o melhor ponto intermediário. Em 500 passos, a perda de treino era 0,4143, a de validação 0,5325 e o WER 32,7399. O menor WER da tabela, 28,7218, ocorre em 1.500 passos. Em 5.000, a perda de treino cai para 0,0205, enquanto a perda de validação sobe para 0,6077 e o WER volta a 29,9844. Perda de treino menor, isoladamente, não significa generalização melhor. Não registei se as avaliações posteriores usaram os pesos finais, os melhores pesos ou outra exportação.
WER, CER e normalização
WER (Word Error Rate) é a distância de edição entre palavras da hipótese e da referência. Se , e são substituições, remoções e inserções e é o número de palavras da referência:
CER aplica a mesma ideia a caracteres:
Uma inserção em “o modelo reconhece fala” → “o modelo reconhece a fala” produz . WER pode passar de 100% quando há muitas inserções e não equivale simplesmente à porcentagem de palavras erradas. CER mostra proximidade ortográfica e é útil em português para revelar erros menores, como “esta” versus “está”, mas não publiquei no repositório CER. Remover diacríticos diminuiria artificialmente esse tipo de erro e apagaria informação linguística.
Publiquei resultados de naturezas distintas: a tabela do Trainer termina com WER 29,9844 em seu conjunto de avaliação; no model-index, registrei WER 29,11 no teste do Common Voice 11, 28,68 no Common Voice 9 e 26,36 no FLEURS pt_br [1]. Eles não são intercambiáveis. Também preservei uma tabela do artigo Whisper na qual o Whisper Tiny original tem WER 35,2 para português [4, 5]. A diferença aritmética entre 35,2 e 29,11 é:
Essa redução relativa é descritiva, não causal. O 35,2 vem da avaliação do Whisper original associada ao Common Voice 9, enquanto 29,11 vem do modelo ajustado no Common Voice 11. Entre versões mudam os áudios, participantes, validações e partições; a própria documentação informa que train, dev e test são gerados não deterministicamente a cada lançamento [8]. Também podem mudar normalização, versão do código e decodificação. Uma comparação controlada exigiria avaliar ambos os checkpoints sobre exatamente os mesmos arquivos, referências, normalizador e parâmetros de geração, idealmente com intervalos de confiança por bootstrap de enunciados ou falantes.
Inferência explícita
No exemplo abaixo, escolho safetensors, declaro idioma e tarefa e uso janelas de 30 segundos para áudio longo. Para comparar experimentos, registre a data de acesso, preserve uma cópia controlada dos artefatos e documente as versões do ambiente.
import torch
from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline
MODEL_ID = "dominguesm/whisper-tiny-pt"
device = 0 if torch.cuda.is_available() else -1
dtype = torch.float16 if device == 0 else torch.float32
processor = AutoProcessor.from_pretrained(MODEL_ID)
model = AutoModelForSpeechSeq2Seq.from_pretrained(
MODEL_ID,
use_safetensors=True,
torch_dtype=dtype,
)
model.generation_config.language = "pt"
model.generation_config.task = "transcribe"
model.generation_config.forced_decoder_ids = None
transcriber = pipeline(
"automatic-speech-recognition",
model=model,
tokenizer=processor.tokenizer,
feature_extractor=processor.feature_extractor,
chunk_length_s=30,
torch_dtype=dtype,
device=device,
)
result = transcriber("audio.wav", return_timestamps=True)
print(result["text"])Segmentação pode duplicar ou cortar palavras nas fronteiras; teste stride_length_s e mantenha-o fixo na avaliação. Para lotes preparados manualmente, siga a documentação da versão instalada e passe attention_mask, pois versões modernas do Transformers a recomendam para evitar erros sutis em inferência em lote [10]. Não use timestamps, beam search ou normalização diferentes ao comparar dois modelos.
Protocolo de reprodutibilidade
Para reproduzir meu ajuste, registre: checkpoint-base Whisper Tiny; distribuição Common Voice Corpus 11.0 de 21 de setembro de 2022; data de acesso ao ajuste; versões de Python, PyTorch, Transformers, Datasets, Evaluate e JiWER; lista dos exemplos do split conforme permitido pelos termos; taxa de amostragem; segmentação; tamanho de lote; hardware; parâmetros completos de generate; texto bruto e normalizado; e WER e CER por exemplo. No ambiente original, usei Transformers 4.26.0.dev0, PyTorch 1.13.0+cu116, Datasets 2.7.1.dev0 e Tokenizers 0.13.2 [1].
Como não especifiquei a normalização usada nos números publicados, o código seguinte define um novo protocolo explícito e não promete reproduzir 29,11:
import re
import unicodedata
import evaluate
def normalize_pt(text: str) -> str:
text = unicodedata.normalize("NFC", text).lower()
text = re.sub(r"[^\w\s'-]", " ", text, flags=re.UNICODE)
return " ".join(text.split())
references = [normalize_pt(text) for text in reference_texts]
predictions = [normalize_pt(text) for text in predicted_texts]
wer = 100 * evaluate.load("wer").compute(
references=references, predictions=predictions
)
cer = 100 * evaluate.load("cer").compute(
references=references, predictions=predictions
)
print({"wer": wer, "cer": cer})Preservar acentos, apóstrofos e hífens é uma decisão declarada desse protocolo. Outra regra pode ser válida, desde que aplicada simetricamente a referências e hipóteses e publicada junto ao resultado. Também convém reportar WER sem normalização para aplicações em que pontuação, caixa ou diacríticos importem.
Limitações demográficas, de domínio e privacidade
As estatísticas demográficas do CV11 pt cobrem o conjunto completo, não apenas dados validados ou os splits. Gênero está vazio em 21% dos clipes, autodeclarado masculino em 73%, feminino em 4% e “outro” em 2%. Idade está vazia em 21%; a faixa dos vinte anos responde por 40% e a dos trinta por 22%. Sotaque está 100% vazio [7, 8]. Como os campos são opcionais e as proporções são por clipe, não por pessoa ou duração, eles não permitem afirmar representatividade populacional. Um participante prolífico pode pesar mais, e não há estratificação publicada por região, raça, condição socioeconômica, deficiência de fala, ambiente acústico ou variedade brasileira/europeia.
Common Voice contém fala lida, curta e colaborativa. Reuniões, telefonia, fala infantil, conversas espontâneas, sobreposição, ruído industrial, nomes próprios e jargão clínico ou jurídico são outros domínios. O WER agregado não revela disparidades: antes de produção, meça por falante e pelos grupos disponíveis, preserve grupos pequenos sem expô-los e faça revisão humana em usos de alto impacto.
Áudio de voz é dado pessoal e pode funcionar como identificador biométrico. O client_id do corpus é um identificador pseudônimo, e idade, gênero e sotaque são opcionais [8], mas pseudonimização não torna uma voz anônima: timbre, conteúdo e metadados ainda podem permitir associação. Não tente reidentificar participantes, não transcreva terceiros sem base legal e consentimento apropriados, limite retenção de áudio e transcrições, controle acesso e ofereça mecanismo de exclusão. A OpenAI desaconselha gravações sem consentimento, inferência de atributos humanos e decisões de alto risco [6]. Publiquei o ajuste sob CC BY 4.0 [1], enquanto o checkpoint-base usa Apache 2.0 [6]; essas licenças não substituem os termos do corpus, os direitos sobre gravações e transcrições nem uma avaliação do contexto de uso.
Conclusão
O Whisper Tiny PT é o checkpoint compacto que produzi: preservei a arquitetura multilíngue de aproximadamente 39 milhões de parâmetros, adaptei os pesos ao Common Voice 11 pt e publiquei registros suficientes para acompanhar a trajetória geral do treinamento. Os resultados mostram ganho inicial e posterior divergência entre perda de treino e validação, lembrando que o último passo não é necessariamente o melhor. O número 29,11 é um ponto de referência, não um certificado de qualidade universal.
Para usar corretamente o checkpoint, recomendo declarar idioma e tarefa, controlar áudio e segmentação, fixar versões, normalizar os dois lados da mesma forma e avaliar no domínio real. Sobretudo, não apresento 35,2 no Common Voice 9 e 29,11 no Common Voice 11 como experimento controlado. Apresento como contribuição mais sólida um ponto de partida reproduzível para medir ASR em português com critérios mais completos, incluindo CER, incerteza, variedades linguísticas, grupos demográficos e privacidade.
Referências
- Whisper Tiny PT: cartão e resultados, Hugging Face.
- Whisper Tiny PT:
config.json, Hugging Face. - Whisper Tiny PT: logs do TensorBoard, Hugging Face.
- Ativo de comparação publicado no repositório, Hugging Face.
- Radford et al. Robust Speech Recognition via Large-Scale Weak Supervision, 2022.
- OpenAI Whisper Tiny: cartão do modelo, Hugging Face.
- Common Voice Corpus 11.0: estatísticas da versão de 21/09/2022, Common Voice.
- Common Voice Scripted Speech: documentação, campos e partições, Common Voice.
- Ardila et al. Common Voice: A Massively-Multilingual Speech Corpus, LREC 2020.
- Whisper: documentação do Transformers, Hugging Face.
BibTeX
@misc{radford2022whisper,
title = {Robust Speech Recognition via Large-Scale Weak Supervision},
author = {Radford, Alec and Kim, Jong Wook and Xu, Tao and
Brockman, Greg and McLeavey, Christine and Sutskever, Ilya},
year = {2022},
eprint = {2212.04356},
archivePrefix = {arXiv},
primaryClass = {eess.AS},
doi = {10.48550/arXiv.2212.04356}
}
@inproceedings{ardila2020commonvoice,
title = {Common Voice: A Massively-Multilingual Speech Corpus},
author = {Ardila, Rosana and Branson, Megan and Davis, Kelly and
Henretty, Michael and Kohler, Michael and Meyer, Josh and
Morais, Reuben and Saunders, Lindsay and Tyers, Francis M. and
Weber, Gregor},
booktitle = {Proceedings of the 12th Conference on Language Resources
and Evaluation},
pages = {4218--4222},
year = {2020}
}
@misc{domingues2022whispertinypt,
title = {Whisper Tiny PT},
author = {Domingues, Maicon},
year = {2022},
publisher = {Hugging Face},
howpublished = {\url{https://huggingface.co/dominguesm/whisper-tiny-pt}},
note = {Ajuste do Whisper Tiny no Common Voice 11.0 em português}
}
@misc{commonvoice2022corpus11,
title = {Common Voice Corpus 11.0: Release Statistics},
author = {{Mozilla Common Voice}},
year = {2022},
month = sep,
howpublished = {\url{https://github.com/common-voice/cv-dataset/blob/main/datasets/scripted-speech/cv-corpus-11.0-2022-09-21.json}}
}