Anotar transcrições de ASR: um exemplo na prática
Um passo a passo completo, de uma pasta de saída do Whisper até turnos de fala rotulados: escolher a unidade de anotação, lidar com a diarização, escrever a configuração, rodar a tarefa e exportar com o alinhamento temporal intacto.
Este texto percorre um projeto de ponta a ponta: 40 entrevistas de pesquisa gravadas, já transcritas com o Whisper, que precisam ser codificadas por tema e por quem disse o quê. É a versão concreta dos dois guias de referência, com cada decisão tomada em vez de descrita.
Se o que você quer é o detalhe formato a formato, isso mora em Formatos de transcrição.
Confira o que você guardou, decida quem rotula os falantes, monte a configuração, exporte com as marcações de tempo
Passo 0: olhe o que você tem de fato
Antes de qualquer outra coisa, descubra o que está na pasta. Isso leva dez segundos e economiza um dia:
potato transcripts ./whisper_out --dry-runScanned 40 file(s):
interview_01.json Whisper JSON 42 turns 891.4s undiarized
interview_02.json Whisper JSON 51 turns 1120.8s undiarized
interview_03.txt plain text 1 turns 0.0s undiarized
...
40 item(s), 1683 turn(s).
Duas coisas a ler aí. Todos os arquivos estão não diarizados, então nada neste corpus sabe quem está falando. E o interview_03.txt entrou como um único turno de duração zero, porque um .txt do Whisper contém texto e mais nada. Não há marcações de tempo ali para recuperar.
Esse terceiro arquivo precisa que se ache o .json dele, ou que o áudio seja processado de novo. Nada rio abaixo vai consertar isso.
Passo 1: escolha a unidade de anotação antes de escolher os rótulos
A questão da unidade decide mais sobre os seus números de concordância do que o conjunto de rótulos decide.
Os segmentos do Whisper têm mais ou menos o tamanho de um enunciado, e quebram em pausas e não em nada gramatical. Para codificação de entrevistas isso costuma ser a unidade certa: a resposta de um entrevistado chega em vários segmentos, e codificar cada um separadamente dá um registro mais fino do que codificar a resposta inteira de uma vez.
Onde isso dá errado é com legendas automáticas de uma plataforma de vídeo, em que as fronteiras das falas caem onde a caixa de legenda encheu. Pedir que os anotadores avaliem "cada frase" sobre falas assim produz discordância sobre onde estão as frases, e não sobre a coisa que você queria medir. Se essa é a sua entrada, veja Como anotar legendas do YouTube.
Aqui os segmentos são usáveis como estão, então a unidade é o turno.
Passo 2: decida quem atribui os falantes
O Whisper não diariza. Três opções, e essa é uma decisão de verdade e não uma formalidade:
Rodar de novo com o WhisperX. Automático, rápido e errado com frequência suficiente em fala sobreposta para que alguém tenha de conferir de qualquer jeito.
whisperx interview_01.mp3 --model medium --diarize --output_format jsonUsar uma API na nuvem com diarização ligada. Deepgram com diarize=true, AssemblyAI com speaker_labels, AWS Transcribe ou Rev.ai. O Potato lê os quatro nativamente.
Deixar os anotadores atribuírem os falantes enquanto escutam. Para 40 entrevistas de duas pessoas, é a opção que escolheríamos. Dois falantes com papéis claramente diferentes é o caso fácil para uma pessoa e nem sempre o caso fácil para um modelo, e o anotador vai escutar o áudio de todo modo.
Vamos com a terceira. Turnos não diarizados aparecem como Unassigned com um seletor, e a atribuição é salva junto com as anotações.
Turnos não diarizados chegam como Unassigned, com um seletor em cada um
Passo 3: monte o arquivo de dados
potato transcripts ./whisper_out --media-dir ./audio -o data/interviews.jsonAs transcrições são pareadas com seu áudio pelo nome-base, então interview_01.json encontra interview_01.mp3. A nomenclatura duplicada do Whisper, interview_01.mp3.json, é tratada, e o id do item sai como interview_01.
O resultado:
{
"id": "interview_01",
"conversation": {
"audio": "audio/interview_01.mp3",
"turns": [
{"turn_id": "t0", "speaker": null, "start": 0.0, "end": 6.5,
"text": "So I want to start with how the team was structured."}
]
}
}Dá para pular esse passo inteiro se você preferir manter as transcrições como arquivos. Um arquivo de dados pode apontar direto para elas, e o Potato lê e normaliza na hora de renderizar:
{"id": "interview_01", "conversation": {"audio": "audio/interview_01.mp3",
"transcript": "whisper_out/interview_01.json"}}Passo 4: escreva a configuração
annotation_task_name: "Interview Coding"
task_dir: .
data_files:
- data/interviews.json
item_properties:
id_key: id
text_key: conversation
instance_display:
fields:
- key: conversation
type: audio_dialogue
label: "Interview"
span_target: true
display_options:
show_timestamps: true
scroll_height: 520px
allow_speaker_assignment: auto
speakers:
- id: interviewer
name: "Interviewer"
color: "#7c3aed"
side: left
- id: participant
name: "Participant"
color: "#059669"
side: right
annotation_schemes:
- annotation_type: radio
name: turn_topic
description: "What is this turn about?"
labels: [structure, workload, tooling, morale, other]
turn_level: true
turn_binding:
field: conversation
- annotation_type: span
name: quotes
description: "Highlight anything quotable in the writeup"
target_field: conversation
labels:
- name: quotable
key_value: "q"Três coisas estão fazendo trabalho aqui.
A lista de falantes dá aos dois papéis nomes, cores e lados estáveis antes de alguém ter atribuído um único turno. Sem ela, os falantes ainda ganham cores, mas atribuídas de forma determinística por transcrição em vez de consistente ao longo do corpus.
turn_level: true com turn_binding prende a pergunta de tema a cada turno em vez de à entrevista inteira. É isso que faz do turno a unidade de anotação na prática.
span_target: true mais o esquema span deixam um destaque atravessar fronteiras de turno, o que importa quando a passagem citável abrange uma pergunta e sua resposta. Os deslocamentos continuam estáveis quando um falante é reatribuído.
Rode:
python potato/flask_server.py start config.yaml -p 8000
Cada turno ganha um botão de reprodução que toca só aquele turno, mais sua própria pergunta de rótulo
Cada balão tem um botão de reprodução que toca apenas aquele turno e para. Na prática é esse o recurso que os anotadores comentam: conferir uma fala específica contra o áudio deixa de ser um exercício de arrastar a barra.
Passo 5: confira a concordância na coisa certa
Dois anotadores por entrevista, e agora você tem dois tipos de concordância para olhar.
Os rótulos de tema são concordância categórica comum em nível de turno. Como os ids de turno são determinísticos, o mesmo arquivo sempre produz os mesmos ids, e os rótulos dos dois anotadores se alinham sem nenhum passo de alinhamento.
A atribuição de falante vale ser conferida à parte. Se seus dois anotadores discordam sobre quem está falando em 15% dos turnos, isso é um sinal sobre o áudio, e significa que a diarização automática teria errado pelo menos essa fração das vezes sem te avisar.
Veja Concordância entre anotadores para as medidas, e Concordância para trechos e saídas estruturadas para os destaques, que precisam de outro tratamento porque os anotadores escolhem fronteiras além de rótulos.
Passo 6: exporte
JSON, JSONL e CSV padrão funcionam como sempre. Quando você quer que o alinhamento temporal sobreviva até uma ferramenta de análise de fala, exporte anotações em camadas para ELAN ou Praat:
python -m potato.export --config config.yaml --format eaf --output ./out/
python -m potato.export --config config.yaml --format textgrid --output ./out/Os dois fazem o caminho de ida e volta, porque o Potato também lê EAF e TextGrid como entrada. Anote aqui, refine no ELAN, leia o resultado de volta.
As quatro coisas que dão errado
Alguém guardou o .txt. Sem marcações de tempo, irrecuperável, exige rodar de novo. O --dry-run pega isso antes de você ter construído qualquer coisa em cima.
As marcações de tempo estão 1000× fora. Alguma coisa a montante misturou segundos e milissegundos. Whisper e Deepgram emitem segundos em ponto flutuante; AssemblyAI, os deslocamentos do whisper.cpp e o TSV do Whisper emitem milissegundos inteiros.
Esquemas em nível de frase sobre entrada baseada em falas de legenda. Coberto acima, e o mais caro dos quatro porque você só descobre quando vai calcular a concordância.
Confiar numa diarização que ninguém conferiu. Um erro de diarização se propaga para todo rótulo preso àquele turno, e parece discordância entre anotadores quando você vai procurar a causa.
Leitura adicional
- Como anotar transcrições do Whisper
- Como anotar legendas do YouTube
- Formatos de transcrição
- Transcript Format Ingestion, um projeto executável com seis formatos lado a lado
- Potato 2.7.1: a transcrição já existe