Come annotare le trascrizioni di Whisper
Come prendere l'output di Whisper o WhisperX e trasformarlo in un progetto di annotazione funzionante: quale file conservare, quando serve la diarizzazione, come assegnare i parlanti e come esportare senza perdere i tempi.
Whisper restituisce segmenti con i tempi, e uno strumento di annotazione dovrebbe leggerli così come sono. Conserva l'output .json invece del .txt, usa WhisperX se ti servono le etichette dei parlanti, poi punta Potato alla cartella. Nessuno script di conversione, nessun passo di riformattazione. Per il dettaglio formato per formato, vedi Formati di trascrizione.
Whisper è il modello open source di riconoscimento vocale di OpenAI, ed è diventato il primo passo standard per chiunque abbia una montagna di audio da smaltire. Quello che produce è un insieme di segmenti con i tempi, cioè quasi tutto ciò che serve a un progetto di annotazione. Manca il pezzo tra «ho le trascrizioni» e «gli annotatori le stanno etichettando».
Nota: Potato non trascrive. L'ASR gira a monte e Potato ne ingerisce l'output. Questa guida spiega quali scelte a monte contano, ma la trascrizione avviene prima che Potato veda qualcosa.
Quale file prodotto da Whisper conviene tenere?
Whisper scrive più file e la scelta pesa più di quanto sembri:
whisper interview_01.mp3 --model medium --output_format json --word_timestamps True| File | Contenuto | Da usare? |
|---|---|---|
.json | Segmenti con tempo di inizio e fine, volendo anche i tempi per parola | Sì, questo |
.srt / .vtt | Segmenti con i tempi, senza metadati | Sì, va bene |
.tsv | Inizio e fine in millisecondi più il testo | Sì |
.txt | Solo testo, senza tempi | No, non c'è niente da sincronizzare con l'audio |
Se hai tenuto solo il .txt, l'allineamento è perso e non lo recuperi senza rieseguire il modello. È il motivo più frequente per cui una trascrizione arriva in Potato come un unico blocco di testo indistinto.
Whisper etichetta i parlanti?
No, e la cosa sorprende in continuazione. Whisper puro trascrive e basta, non ti dice chi parla. Ogni turno arriva senza attribuzione, il che va benissimo per l'annotazione dei contenuti ed è un problema per tutto ciò che riguarda i parlanti.
La diarizzazione dei parlanti è un passo a parte. Tre modi per affrontarla:
Usa WhisperX, che avvolge Whisper con la diarizzazione di pyannote:
whisperx interview_01.mp3 --model medium --diarize --output_format jsonIl suo output porta un campo speaker per ogni segmento (SPEAKER_00, SPEAKER_01 e così via) e Potato lo legge direttamente.
Usa un'API cloud con la diarizzazione attiva. Deepgram con diarize=true, AssemblyAI con speaker_labels, AWS Transcribe e Rev.ai producono tutte etichette dei parlanti, e Potato le legge tutte e quattro in modo nativo.
I turni non diarizzati compaiono come Non assegnati con un selettore del parlante
Lascia fare agli annotatori. Quando i turni arrivano senza diarizzazione, ogni fumetto compare come Non assegnato con un selettore. Su audio disturbato, parlato sovrapposto o stanze rumorose, una persona che ascolta è spesso più precisa della diarizzazione automatica. Su un corpus piccolo è uno scambio ragionevole, non un ripiego.
Come costruisco il file di dati?
Punta il convertitore alla cartella con l'output di Whisper:
potato transcripts ./whisper_out --media-dir ./audio -o data/interviews.jsonLe trascrizioni si accoppiano al media in base al nome del file, quindi interview_01.json trova interview_01.mp3. Gli id degli elementi vengono dal nome del file, e l'estensione doppia di Whisper interview_01.mp3.json è gestita senza produrre un id interview_01.mp3.
Controlla cosa ha capito prima di fidarti:
potato transcripts ./whisper_out --dry-runScanned 3 file(s):
interview_01.json Whisper JSON 42 turns 891.4s undiarized
interview_02.json WhisperX JSON 51 turns 1120.8s 2 speaker(s): SPEAKER_00, SPEAKER_01
interview_03.json Whisper JSON 38 turns 754.2s undiarized
3 item(s), 131 turn(s).
Un file che riporta plain text o zero turni è il tuo file problematico. Quasi sempre è un .txt finito per sbaglio insieme all'output .json.
Posso saltare il passo di conversione?
Sì. Un file di dati può puntare direttamente ai file di trascrizione su disco:
{"id": "int_001", "conversation": {"audio": "media/int_001.mp3",
"transcript": "media/int_001.srt"}}Potato legge il file, riconosce il formato dal contenuto e lo normalizza quando l'istanza viene visualizzata. Le trascrizioni restano file di cui puoi fare il diff e che puoi riesportare, invece di finire dentro un blob di dati.
Che aspetto ha la configurazione?
Il file di dati generato ha questa forma:
{
"id": "interview_01",
"conversation": {
"audio": "audio/interview_01.mp3",
"turns": [
{"turn_id": "t0", "speaker": "SPEAKER_00", "start": 0.0, "end": 6.5,
"text": "Welcome back."}
]
}
}E la configurazione corrispondente, che potato transcripts --emit-config stampa per te:
annotation_task_name: "Interview Annotation"
task_dir: .
data_files:
- data/interviews.json
item_properties:
id_key: id
text_key: conversation
instance_display:
fields:
- key: conversation
type: audio_dialogue
label: "Transcript"
span_target: true
display_options:
show_timestamps: true
allow_speaker_assignment: auto
annotation_schemes:
- annotation_type: span
name: topics
description: "Highlight topic mentions"
labels:
- name: policy
- name: personal
La visualizzazione audio_dialogue, con una domanda agganciata a ogni turno
La visualizzazione audio_dialogue mostra i turni come fumetti dei parlanti sincronizzati con l'audio, e ogni turno ha un pulsante che riproduce solo quel turno. span_target: true permette agli span di attraversare i confini tra turni, e gli offset restano stabili quando un turno cambia parlante.
Se preferisci che gli annotatori verifichino la trascrizione stessa invece di etichettarne il contenuto, speech_transcript ti dà schede per segmento con tag di errore e una casella di correzione. Per il lavoro su sovrapposizioni e interruzioni, voice_interaction offre una timeline a doppia traccia. Entrambi leggono gli stessi file.
Come fanno gli annotatori ad assegnare i parlanti?
Definisci un elenco così i parlanti hanno nomi, colori e lati stabili:
display_options:
allow_speaker_assignment: auto
speakers:
- id: interviewer
name: "Interviewer"
color: "#7c3aed"
side: left
- id: participant
name: "Participant"
color: "#059669"
side: rightauto accende il selettore quando ci sono turni non diarizzati da etichettare. Impostalo a true per consentire la riassegnazione anche quando la sorgente aveva già etichettato i parlanti, che è quello che serve se stai correggendo errori di diarizzazione invece di riempire caselle vuote.
Le assegnazioni vengono salvate insieme alle annotazioni, agganciate a un id di turno stabile, quindi sopravvivono a un ricaricamento.
Come tiro fuori le annotazioni?
L'export in JSON, JSONL e CSV funziona come al solito. Quando l'allineamento temporale deve sopravvivere, esporta le annotazioni a livelli in EAF di ELAN o in TextGrid di Praat:
python -m potato.export --config config.yaml --format eaf --output ./out/
python -m potato.export --config config.yaml --format textgrid --output ./out/Entrambi fanno il giro completo. Potato legge EAF e TextGrid anche in ingresso, quindi puoi annotare in Potato, rifinire in ELAN o Praat e rileggere il risultato.
Approfondimenti
- Formati di trascrizione, tutti i formati supportati e come vengono riconosciuti
- Annotare i sottotitoli di YouTube, la versione di questo flusso basata sui file di sottotitoli
- Annotazione audio, per il lavoro sulla forma d'onda che non parte da una trascrizione
- Ingestione dei formati di trascrizione, un design di esempio eseguibile con sei formati a confronto
- Diarizzazione dei parlanti, un design di esempio eseguibile
- Accordo tra annotatori, per misurare l'accordo sulle etichette a livello di turno