Skip to content

Como anotar transcrições do Whisper

Como transformar a saída do Whisper ou do WhisperX num projeto de anotação em funcionamento: que ficheiro guardar, quando é preciso diarização, como atribuir falantes e como exportar sem perder o alinhamento temporal.

O Whisper dá-lhe segmentos com tempos, e uma ferramenta de anotação deveria lê-los tal como estão. Guarde a saída .json em vez do .txt, execute o WhisperX se precisar de etiquetas de falante e depois aponte o Potato para a pasta. Sem script de conversão, sem passo de reformatação. Para o detalhe formato a formato, consulte Formatos de transcrição.

O Whisper é o modelo de reconhecimento de fala de código aberto da OpenAI, e tornou-se o primeiro passo por omissão para quem tem uma pilha de áudio pela frente. O que produz é um conjunto de segmentos com tempos, que é quase tudo o que um projeto de anotação precisa. A lacuna está em tudo o que separa "tenho transcrições" de "os anotadores estão a etiquetá-las".

Note: O Potato não transcreve. O reconhecimento de fala corre a montante e o Potato ingere a sua saída. Este guia cobre que escolhas anteriores importam, mas a transcrição acontece antes de o Potato ver seja o que for.

Que ficheiro de saída do Whisper devo guardar?

O Whisper escreve vários ficheiros e a escolha importa mais do que parece:

bash
whisper interview_01.mp3 --model medium --output_format json --word_timestamps True
FicheiroContémUsar?
.jsonSegmentos com tempos de início e fim, opcionalmente tempos por palavraSim, este
.srt / .vttSegmentos com tempos, sem metadadosSim, funciona bem
.tsvInício e fim em milissegundos mais o textoSim
.txtApenas texto, sem temposNão, não há nada para sincronizar com o áudio

Se o .txt foi tudo o que guardou, o alinhamento perdeu-se e não o consegue recuperar sem voltar a correr o modelo. É a razão mais comum para uma transcrição chegar ao Potato como um único bloco de texto indiferenciado.

O Whisper etiqueta falantes?

Não, e isto apanha muita gente de surpresa. O Whisper transcreve; não diz quem está a falar. Todos os turnos chegam por atribuir, o que é aceitável para anotar conteúdo e penoso para qualquer coisa relacionada com falantes.

A diarização de falantes é um passo à parte. Três formas de a resolver:

Execute o WhisperX, que envolve o Whisper com a diarização do pyannote:

bash
whisperx interview_01.mp3 --model medium --diarize --output_format json

A sua saída traz um campo speaker por segmento (SPEAKER_00, SPEAKER_01, e assim por diante) e o Potato lê-o diretamente.

Use uma API na nuvem com diarização ativada. Deepgram com diarize=true, AssemblyAI com speaker_labels, AWS Transcribe e Rev.ai produzem etiquetas de falante, e o Potato lê os quatro nativamente.

Três turnos de transcrição com fundo cinzento tracejado, cada um etiquetado como Unassigned com uma seta de menu.Os turnos não diarizados aparecem como Unassigned com um seletor de falante

Deixe que os anotadores o façam. Quando os turnos chegam sem diarização, cada balão aparece como Unassigned com um seletor. Com áudio difícil, fala sobreposta ou ruído de fundo, uma pessoa a ouvir costuma ser mais precisa do que a diarização automática. Para um corpus pequeno é uma troca razoável, não um remendo.

Como construo o ficheiro de dados?

Aponte o conversor para a sua pasta de saída do Whisper:

bash
potato transcripts ./whisper_out --media-dir ./audio -o data/interviews.json

As transcrições são emparelhadas com o áudio pelo nome base, portanto interview_01.json encontra interview_01.mp3. Os identificadores de item vêm do nome do ficheiro, e a extensão dupla interview_01.mp3.json do Whisper é tratada sem produzir um id interview_01.mp3.

Verifique o que ele percebeu antes de avançar:

bash
potato transcripts ./whisper_out --dry-run
text
Scanned 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).

Um ficheiro que reporta plain text ou zero turnos é o seu ficheiro problemático. Quase sempre é um .txt que se misturou com a saída .json.

Posso saltar o passo de conversão?

Sim. Um ficheiro de dados pode apontar diretamente para as transcrições em disco:

json
{"id": "int_001", "conversation": {"audio": "media/int_001.mp3",
                                   "transcript": "media/int_001.srt"}}

O Potato lê o ficheiro, deteta o formato pelo conteúdo e normaliza-o ao apresentar a instância. As suas transcrições continuam a ser ficheiros que pode comparar e voltar a exportar, em vez de ficarem presas dentro de um bloco de dados.

Como é a configuração?

O ficheiro de dados gerado tem esta forma:

json
{
  "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 a configuração correspondente, que potato transcripts --emit-config imprime por si:

yaml
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"
    target_field: conversation
    labels:
      - name: policy
      - name: personal

Turnos de transcrição como balões de falante coloridos, com botões de reprodução por turno, marcas temporais e uma pergunta de etiquetagem sob cada turno.A visualização audio_dialogue, com uma pergunta por turno associada a cada turno

A visualização audio_dialogue mostra os turnos como balões de falante sincronizados com o áudio, com um botão de reprodução em cada turno que toca apenas esse turno. span_target: true permite que os intervalos atravessem fronteiras de turno, e os deslocamentos mantêm-se estáveis quando um falante é reatribuído.

Se preferir que os anotadores verifiquem a própria transcrição em vez de etiquetar o seu conteúdo, speech_transcript dá-lhe cartões de segmento com etiquetas de erro e uma caixa de correção. Para trabalho sobre sobreposições e interrupções, voice_interaction dá-lhe uma linha temporal de duas pistas. Ambos leem os mesmos ficheiros.

Como é que os anotadores atribuem falantes?

Defina uma lista para que os falantes tenham nomes, cores e lados estáveis:

yaml
display_options:
  allow_speaker_assignment: auto
  speakers:
    - id: interviewer
      name: "Interviewer"
      color: "#7c3aed"
      side: left
    - id: participant
      name: "Participant"
      color: "#059669"
      side: right

auto liga o seletor quando há turnos não diarizados para etiquetar. Ponha true para permitir a reatribuição mesmo quando a fonte etiquetou falantes, que é o que quer se estiver a corrigir erros de diarização em vez de preencher lacunas.

As atribuições ficam guardadas com as anotações, ligadas a um identificador de turno estável, por isso sobrevivem a um recarregamento.

Como recupero as anotações?

A exportação para JSON, JSONL e CSV funciona como sempre. Quando o alinhamento temporal tem de sobreviver, exporte as anotações em camadas para ELAN EAF ou Praat TextGrid:

bash
python -m potato.export --config config.yaml --format eaf --output ./out/
python -m potato.export --config config.yaml --format textgrid --output ./out/

Ambos fazem ida e volta. O Potato também lê EAF e TextGrid como entrada, portanto pode anotar no Potato, refinar no ELAN ou no Praat, e voltar a ler o resultado.

Leituras adicionais