Inflect Micro PT-BR — 20k passos de adaptação
Voz brasileira fixa construída sobre o owensong/Inflect-Micro-v2, treinada com 37,45 horas de fala em português do Brasil, um único falante, 24 kHz.
Este repositório traz o pacote PyTorch pronto para inferência, 24 áudios de teste em três configurações de prosódia, 40 amostras de validação registradas durante o treino e os arquivos de configuração usados no run.
O que este modelo é: um experimento de adaptação de voz única, com receita reproduzível e números de treino abertos. O que este modelo não é: um sistema multilocutor, um produto de clonagem de voz, ou um modelo com avaliação de inteligibilidade por MOS. Não medi MOS e não rodei avaliador de transcrição. A seção de limitações explica o que isso significa na prática.
Como ouvir antes de baixar
As três configurações usam as mesmas oito frases, para que a única variável seja a prosódia.
| Configuração | speed | variation | Duração total das 8 frases |
|---|---|---|---|
default |
1.0 | 0.667 | 19,41 s |
slower |
0.90 | 0.667 | 20,67 s |
varied |
0.95 | 0.80 | 19,99 s |
Os arquivos estão em audio/prosody_tests/<config>/audio/frase-00.wav até frase-07.wav, e a lista de frases está em audio/prosody_tests/sentences.jsonl.
| ID | Frase |
|---|---|
| frase-00 | Olá, tudo bem com você? |
| frase-01 | Hoje está fazendo um dia muito bonito. |
| frase-02 | O Brasil é um país de dimensões continentais. |
| frase-03 | Você gostaria de ouvir uma história interessante? |
| frase-04 | Não acredito que isso aconteceu! |
| frase-05 | Que notícia maravilhosa! |
| frase-06 | Por favor, me diga que horas são. |
| frase-07 | Amanhã teremos uma reunião importante. |
Também incluí samples/v1_mesmas_frases/ e samples/v2_frases/: são as mesmas seis frases sintetizadas pela versão de 4 mil passos e por esta de 20 mil passos. Serve para ouvir a diferença entre as duas estratégias de treino, não para declarar uma vencedora.
O problema que motivou o run
Adaptar um modelo de TTS para uma língua com pouca representação costuma seguir dois caminhos. Um deles é continuar o fine-tuning por muito tempo e aceitar o risco de degradar a inteligibilidade que o modelo base já tinha. O outro é parar cedo, ganhar sotaque e timbre rapidamente, e conviver com artefatos.
A versão anterior deste trabalho chegou a uma voz brasileira inteligível em 4 mil passos. Ela era audivelmente brasileira, mas ainda soava presa ao modelo base: prosódia previsível, pouca variação de entonação em frases longas.
A pergunta deste run foi se mais 16 mil passos, com um cronograma de descongelamento em etapas, melhorariam a naturalidade sem custar inteligibilidade. Essa é a hipótese que os números abaixo testam.
Dados
O conjunto de origem foi preparado com o inflect-adapt prepare, que valida duração, taxa de amostragem e cobertura fonética antes de qualquer treino.
| Métrica | Valor |
|---|---|
| Amostras totais | 20.000 |
| Treino | 19.123 |
| Validação | 877 |
| Duração total | 134.807 s (37,45 h) |
| Duração mínima / máxima | 1,50 s / 12,0 s |
| Taxa de amostragem | 24 kHz |
| Falantes | 1 (ID 2961) |
| Idioma / frontend | pt-BR / espeak, com estresse |
Dois detalhes do preparo merecem atenção, porque afetam o resultado.
O primeiro é a cobertura de símbolos do modelo base. Antes da adaptação, 97,6% dos símbolos do corpus já existiam no vocabulário do Inflect-Micro-v2. Os 2,4% restantes eram três símbolos: õ (1.142 ocorrências), ũ (6.783) e o til combinante ̃ (44.321). O preparo adicionou três símbolos ao inventário, que passou a ter 181 entradas. Ou seja: o vocabulário cresceu, mas o embedding base continuou sendo reaproveitado em 97,6% das posições.
O segundo é o split. Ele é agrupado por group_id e normaliza o texto antes de separar treino e validação, para que a mesma frase não apareça dos dois lados com pontuação diferente. Com 20 mil linhas, o alvo era 1.000 linhas de validação; o resultado foi 877, porque grupos inteiros foram mantidos juntos em vez de fatiados.
Esse é um trade-off que vale explicitar: validação menor, mas sem vazamento de texto entre treino e avaliação.
Configuração do treino
| Parâmetro | Valor |
|---|---|
| Modelo base | owensong/Inflect-Micro-v2 |
| Preset | micro-12gb |
| Passos | 20.000 |
| Batch | 4 × 2 de acumulação (efetivo 8) |
| Learning rate (G e D) | 8e-5 |
| Warm-up do posterior | 1.500 passos |
| Descongelamento do decoder | 6.000 passos |
| Precisão | AMP |
| Hardware | A100 40 GB |
| Seed (treino / dataset) | 1234 / 42 |
| Checkpoint / validação | a cada 500 passos |
O cronograma é o ponto central. Nos primeiros 1.500 passos só o posterior treina, com lr subindo de zero. O decoder permanece congelado até o passo 6.000, o que preserva a qualidade acústica do modelo base enquanto o modelo aprende o mapeamento fonético do português. Depois disso, o decoder entra com lr menor.
No fim do run, as taxas estavam em 6,95e-5 para o posterior, 3,48e-5 para a parte linguística e 6,95e-6 para o decoder. A assimetria é intencional: o decoder se move devagar para não desfazer o que o modelo base já sabia fazer.
Resultados
As perdas abaixo são médias por faixa de passos, sobre 22.404 registros de metrics.jsonl.
| Passos | loss_g | loss_d |
|---|---|---|
| 1–999 | 75,22 | 2,192 |
| 1.000–4.999 | 45,84 | 1,857 |
| 5.000–9.999 | 29,97 | 2,562 |
| 10.000–14.999 | 26,13 | 2,695 |
| 15.000–19.499 | 25,64 | 2,687 |
| 19.500–20.000 | 25,53 | 2,679 |
O loss_g cai 66% entre a primeira faixa e a última (75,22 para 25,53). É a curva esperada de adaptação: o gerador passa a produzir algo mais próximo da distribuição do falante alvo.
O loss_d faz outra coisa, e é a parte interessante. Ele cai de 2,192 para 1,857 nos primeiros 5 mil passos, porque o discriminador aprende rápido a separar o modelo base da fala brasileira. Depois sobe para 2,56 e se estabiliza perto de 2,69.
Isso não é degradação. É o equilíbrio se reajustando. Quando o gerador começa a produzir fala do falante alvo, o discriminador perde o atalho de distinguir "modelo base" de "fala real" e precisa trabalhar com diferenças mais finas. Um loss_d que ficasse muito baixo no fim seria sinal de problema: significaria que o discriminador venceu e o gerador estagnou.
A leitura conjunta é o que sustenta a conclusão: loss_g descendo de forma monotônica na média e loss_d estabilizado em torno de 2,68, sem colapso de nenhum dos dois lados.
Um aviso metodológico: essas perdas medem o ajuste ao corpus, não a qualidade perceptual. Elas não substituem um teste com ouvintes.
Prosódia: três configurações, mesma frase
O teste de prosódia usa oito frases, o mesmo seed (42) e device=cpu, variando apenas speed e variation. As três rodadas passaram na validação automática: 8/8 avaliações, zero falhas, zero clips com clipping, zero trechos silenciosos.
A comparação entre default (speed 1.0) e slower (speed 0.90) mostra que o parâmetro funciona, mas não de forma perfeitamente linear. A razão média de alongamento por frase ficou em 1,066, e a razão das durações totais em 1,065, quando 1/0,90 seria 1,111. O desvio é maior em frases curtas: "Não acredito que isso aconteceu!" alongou 2,4% em vez de 11%, enquanto a frase mais longa alongou 9,1%.
Isso diz algo prático sobre o parâmetro: ele ajusta a duração por fonema, e a duração total ainda depende de como o modelo aloca pausas. Para controle fino de tempo, o caminho é ajustar a duração alvo explicitamente, não só o multiplicador de velocidade.
O varied (speed 0.95, variation 0.80) ficou entre os dois em duração, com contorno de entonação mais solto. É a configuração que eu escolheria para narração; default soa mais previsível e serve melhor para leitura de texto técnico.
O que deu errado, e o que isso ensinou
Três problemas reais deste run. Nenhum é específico deste modelo, e todos custaram tempo.
Cinco treinos na mesma GPU. Ao reiniciar o run depois de uma queda de sessão, eu subia o processo novo sem encerrar o antigo. Em poucos ciclos havia cinco processos inflect-adapt disputando a mesma A100 e gravando no mesmo latest.pth. O sintoma não era um erro limpo: era o treino "andando para trás", porque cópias diferentes salvavam checkpoints com estados distintos de otimizador.
A lição é que reinício de treino precisa ser idempotente. O start_one.py agora mata a árvore de processos por PID e só então sobe o novo; se sobrar qualquer processo vivo, ele aborta em vez de seguir.
O eixo de passos do W&B. Um run retomado recebe passos menores do que o histórico já registrado. O W&B rejeita passos não crescentes, então cada linha nova era descartada com um aviso. O painel parecia congelado enquanto o treino corria normalmente.
A correção foi separar as duas coisas: o contador do W&B continua monotônico, e o passo real de treino vai como métrica (train/step). Assim o painel avança e o número exibido é o passo verdadeiro.
O áudio de validação que sumia. O sincronizador checava primeiro se havia linha nova em metrics.jsonl. Se não houvesse, ele retornava antes de olhar os áudios. Como métricas são gravadas a cada 25 passos e validação a cada 500, o envio de áudio só acontecia quando as duas coisas coincidiam. O resultado foram buracos irregulares no W&B que pareciam falha do modelo.
A correção foi tratar métricas e áudio como independentes no mesmo ciclo. Também passei a persistir em disco a lista de áudios já enviados, porque antes ela vivia só em memória e cada reinício reenviava os 34 anteriores.
Três bugs, uma causa comum: estado que sobrevive a um reinício e não é reconciliado. Vale checar isso antes de culpar o modelo por um número estranho.
Como usar
O pacote é autocontido. Não precisa instalar o inflect-adapt para inferir.
pip install -r requirements.txt
python inference.py --text "Bom dia, tudo bem com você?" --output saida.wav
Com controle de prosódia:
python inference.py \
--text "A reunião começou às nove da manhã." \
--speed 0.9 \
--variation 0.8 \
--output saida.wav
Em lote, um laço simples sobre as frases lidas de um jsonl:
while IFS= read -r linha; do
id=$(echo "$linha" | python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
txt=$(echo "$linha" | python3 -c "import sys,json;print(json.load(sys.stdin)['text'])")
python inference.py --text "$txt" --output "audios/$id.wav"
done < frases.jsonl
O frontend é espeak com estresse e pontuação preservada. A entrada é texto normal; não é preciso passar fonemas.
Limitações
Estas são limitações medidas, não ressalvas de praxe.
Não há avaliação de inteligibilidade. O transcript evaluator estava desabilitado em todas as rodadas. Os relatórios confirmam que o áudio é válido — sem clipping, sem silêncio, duração plausível — mas não medem se um ouvinte entende o que foi dito. A afirmação "a voz é inteligível" continua sendo julgamento auditivo, não resultado medido.
Não há MOS. Nenhum teste com ouvintes foi conduzido.
Voz única. O modelo tem um falante. Não aceita speaker_id e não foi treinado para generalizar timbre.
Domínio estreito. O corpus é de fala lida, entre 1,5 e 12 segundos por amostra. Frases muito longas são divididas em partes e sintetizadas separadamente, o que pode gerar descontinuidade de prosódia nas junções.
Números podem enganar. Números escritos por extenso, siglas e termos estrangeiros dependem do frontend espeak e não foram avaliados de forma sistemática.
Licença. O pacote carrega a licença do modelo base. Verifique os termos do owensong/Inflect-Micro-v2 e do corpus de origem antes de uso comercial.
O que eu faria diferente
Duas coisas, em ordem de impacto.
A primeira é instrumentar avaliação desde o início. Um avaliador de transcrição barato, rodando a cada 2 mil passos, teria transformado "parece melhor" em uma curva. Sem ele, a decisão de ir de 4 mil para 20 mil passos se apoia em perdas e escuta, não em medida.
A segunda é separar o teste de prosódia do run principal desde o começo. As três configurações que gerei no fim teriam mostrado cedo que variation afeta pouco a duração e muito o contorno. Isso teria economizado algumas iterações.
Sobre a hipótese inicial: a naturalidade melhorou e não vi degradação de inteligibilidade nas amostras que ouvi. Mas "não vi" é diferente de "medi", e a diferença é o próximo trabalho.
Reprodução
| Caminho | Conteúdo |
|---|---|
training/launch-config.yaml |
Configuração efetiva gravada pelo run |
training/train.py |
Launcher com retomada e sincronização W&B |
training/supervisor.py |
Reinício automático do último checkpoint íntegro |
training/hyperparameters.yaml |
Hiperparâmetros |
validation/ |
40 pares de áudio e metadados de validação |
audio/prosody_tests/ |
Teste de prosódia em três configurações |
checksums.sha256 |
Checksums do pacote exportado |
export_report.json |
Relatório de verificação do export |
O treino consumiu cerca de 20 mil passos a 1,25–2,0 s/passo em uma A100 40 GB, com picos de uso de memória em torno de 3,2 GB.
A reprodução exata depende do corpus de origem, que não está incluído aqui por questões de licença. O launch-config.yaml registra os caminhos usados.
Como citar
@misc{rios2026inflectptbr,
author = {Hugo Rios},
title = {Inflect Micro PT-BR: 20k steps of single-speaker adaptation},
year = {2026},
url = {https://huggingface.co/HRB25/inflect-micro-ptbr-20k}
}
Agradecimentos
Ao owenawsong pelo Inflect e pelo checkpoint Micro-v2, sobre o qual tudo isso foi construído.
- Downloads last month
- 14
Model tree for HRB25/inflect-micro-ptbr-20k
Base model
owensong/Inflect-Micro-v2