說話人分離標註
在 Potato 中構建說話人識別任務:互動式音訊波形、時間戳標記、說話人標籤分配,以及標註者間一致性測量。
本教程在 Potato 中構建兩個說話人標註任務。第一個任務中,標註者在波形上從零開始標出說話人輪次。第二個任務中,他們修正上游分離系統給出的說話人標籤。完整的音訊 scheme 參考請見源文件。
說話人分離產出什麼,Potato 在其中的位置
說話人分離把一段錄音切分成歸屬於單個說話人的片段,併為每個片段賦予一個說話人標識。它回答的是"誰在什麼時候說話",只要錄音裡不止一個人的聲音就會用到它,從會議紀要、客服中心分析,到播客製作、研究訪談和法律錄音。
Potato 標註分離結果,而不執行分離。語音識別和說話人分離在你已有的系統裡執行,比如 pyannote、WhisperX 或某個雲端轉寫 API,Potato 攝入其輸出交給人工檢查和修正。下面兩個任務覆蓋隨之而來的兩種情況:一種是還沒有分離系統的輸出,另一種是已有輸出但需要稽核。
用波形任務標註輪次
沒有上游輸出可供修正時,標註者自己在波形上畫出輪次。schema 是處於 label 模式的 audio_annotation,而 item_properties.text_key 指明存放音訊 URL 的欄位,Potato 靠它找到每條資料對應的媒體檔案。
annotation_task_name: "Speaker Diarization"
task_dir: "."
data_files:
- "data/conversations.json"
item_properties:
id_key: id
text_key: audio_url
output_annotation_dir: "annotation_output/"
export_annotation_format: "jsonl"
annotation_schemes:
- annotation_type: audio_annotation
name: speakers
description: "Mark each stretch of speech and label who is speaking"
mode: label
zoom_enabled: true
playback_rate_control: true
min_segments: 1
label_requirement:
required: true
labels:
- name: Speaker 1
color: "#FF6B6B"
key_value: "1"
- name: Speaker 2
color: "#4ECDC4"
key_value: "2"
- name: Speaker 3
color: "#45B7D1"
key_value: "3"
- name: Overlap
color: "#FFEAA7"
key_value: "4"
- name: Silence
color: "#9CA3AF"
key_value: "5"
user_config:
allow_all_users: trueOverlap 和 Silence 這兩個標籤的作用是讓時間軸保持完整。沒有表示交叉說話的標籤,標註者就得決定同時開口的兩個人誰擁有這段區域,而兩個標註者對同一秒音訊會做出不同的決定。沒有表示靜音的標籤,時間軸上的空檔就在"沒有人說話"和"還沒有人標到這裡"之間含混不清。
min_segments 只有在同時設定 label_requirement.required: true 時才生效。兩者都設定後,標註者必須至少標出一個片段、併為標出的每個片段都打上標籤,否則"下一條"保持鎖定,提示資訊會說明缺的是哪一項。
一個片段是怎麼產生的
標註者有三種方式建立片段,選哪一種取決於邊界需要多精確。在放大後的波形上按住右鍵拖動可直接畫出片段,拖到邊緣時檢視會自動滾動,因此一段長過可見視窗的輪次仍然一次手勢就能完成。在總覽條上按住右鍵拖動,可以在錄音的任意位置畫出一個粗略片段,無需先導航過去。[ 和 ] 鍵在播放頭處設定邊界,Enter 建立片段,這是標註者一路聽下去時最快的路徑。三種方式下左鍵都保留它原本移動播放頭的職責,所以導航和繪製不會衝突。
為每個輪次附加問題
單靠說話人標籤承載不了言語行為、說話人角色或逐輪次的品質判斷。設定 mode: both 會保留標籤,併為每個片段附上一組問題,標註者選中某個區域後在片段面板裡作答:
annotation_schemes:
- annotation_type: audio_annotation
name: turns
description: "Mark each speaker turn and describe it"
mode: both
labels:
- name: Moderator
color: "#EF4444"
key_value: "1"
- name: Participant 1
color: "#3B82F6"
key_value: "2"
- name: Participant 2
color: "#10B981"
key_value: "3"
- name: Overlap
color: "#FCD34D"
key_value: "4"
segment_schemes:
- annotation_type: radio
name: speech_type
description: "What kind of turn is this?"
labels: ["Statement", "Question", "Response", "Interruption", "Backchannel"]
- annotation_type: radio
name: speaker_confidence
description: "How sure are you about the speaker?"
labels: ["Certain", "Probable", "Guess"]片段問題帶來兩個限制。這些問題裡不繫結鍵盤快捷鍵,因為一個片段的欄位只在該片段被選中時存在,而全域按鍵會作用到當時恰好開啟的那個片段上,所以這些答案要靠點選給出。label_requirement 在子 scheme 上同樣不生效,因為把某一項標為必填會讓"下一條"卡在標註者可能根本沒有開啟過的問題上,這些欄位的完整性改為在分析階段檢查。
在難聽清的音訊上,這樣一個 speaker_confidence 欄位值得多點一次滑鼠。它把標註者有把握的輪次和靠猜的輪次分開,而這正是"分歧說明你的標籤集有問題"和"分歧說明錄音品質差"之間的區別。
修正分離系統的輸出
當分離系統已經切分好錄音時,在波形上重畫一遍它的邊界是在浪費已有的工作。audio_dialogue 顯示類型把分離後的轉寫稿渲染成與音訊同步的說話人氣泡,每個氣泡都帶一個只播放該輪次的播放按鈕,於是標註者是在讀和聽,而不是在畫。它讀取 WhisperX 風格的分離 JSON,其中每個片段帶有起點、終點、文本和說話人,也支援字幕格式;沒有說話人的輪次會渲染為未分配,由標註者補上。
audio_dialogue 是一個顯示類型而非標註類型,所以它寫在 instance_display.fields 下,標註 scheme 仍然是獨立的。繫結到該欄位的 turn_level scheme 會把同一個問題放到每個輪次上:
annotation_task_name: "Diarization Review"
task_dir: "."
data_files:
- "data/interviews.json"
item_properties:
id_key: id
text_key: title
output_annotation_dir: "annotation_output/"
export_annotation_format: "jsonl"
instance_display:
fields:
- key: conversation
type: audio_dialogue
label: "Transcript"
display_options:
audio_key: audio
turns_key: turns
show_timestamps: true
speakers:
- id: interviewer
name: "Interviewer"
color: "#7c3aed"
side: left
- id: participant
name: "Participant"
color: "#059669"
side: right
annotation_schemes:
- annotation_type: radio
name: speaker_correct
description: "Is the speaker label on this turn right?"
labels: ["Correct", "Wrong speaker", "Two speakers in one turn"]
turn_level: true
turn_binding:
field: conversation
- annotation_type: radio
name: recording_quality
description: "Overall recording quality"
labels:
- "Excellent, all speakers clear"
- "Good, most speech understandable"
- "Fair, some difficulty"
- "Poor, significant problems"
user_config:
allow_all_users: truespeakers 名冊為每個已知說話人固定了顏色和位置,於是一個標錯說話人的輪次會以氣泡出現在轉寫稿的錯誤一側,標註者還沒讀一個字就能看出來。點選某個輪次的說話人姓名即可重新分配,也可以指定名冊之外的說話人。逐輪次的答案是按每個輪次上穩定的 turn_id 儲存的,所以如果在上游修正之後重新攝入轉寫稿,請保持這些標識不變。
從一個裝滿 ASR 輸出的資料夾構建資料檔案只需一條命令:
potato transcripts ./whisper_out --media-dir ./audio -o data/interviews.json讓多個標註者標註同一段音訊
說話人邊界是標註者分歧最大的地方,而對一段錄音只標一遍就沒有任何可比較的東西。num_annotators_per_item 設定項目的覆蓋目標:
num_annotators_per_item: 3在試點子集上安排三名標註者,足以在整批錄音發出去之前看出標籤集是否站得住腳。關於如何確定這個子集的規模以及如何解讀結果,請見品質控制指南。
鍵盤快捷鍵
音訊標註的節奏由播放控制決定,所以第一天就把快捷鍵交給標註者:
| Key | 操作 |
|---|---|
Space | 播放或暫停 |
Left / Right | 後退或前進 5 秒 |
Shift+Left / Shift+Right | 跳轉 30 秒 |
[ / ] | 在播放頭處設定片段起點 / 終點 |
Enter | 由當前選區建立片段 |
Delete | 刪除選中的片段 |
1-9 | 選擇標籤,前提是定義了 key_value |
+ / - / 0 | 放大、縮小,或讓波形適應檢視 |
輸出檔案裡有什麼
片段寫在 scheme 名稱之下,每個片段帶有自己的標識、以秒為單位的邊界、標籤,以及片段問題的答案:
{
"turns": {
"segments": [
{
"id": "segment_1",
"start_time": 0.0,
"end_time": 5.2,
"label": "Moderator",
"annotations": {
"speech_type": "Statement",
"speaker_confidence": "Certain"
}
},
{
"id": "segment_2",
"start_time": 5.2,
"end_time": 12.8,
"label": "Participant 1",
"annotations": {
"speech_type": "Response",
"speaker_confidence": "Certain"
}
}
]
}
}邊界以秒而非取樣點儲存,所以同一個檔案不需要轉換步驟就能與任何自動語音識別(ASR)系統的轉寫稿對齊。每個片段還保留自己的標識,這使得第二位標註者的結果可以逐片段地與第一位比對,而不是隻比對輪次數量。
長錄音
在長達一小時的音訊上,先撐不住的是波形渲染。安裝 BBC 的 audiowaveform 工具後,Potato 就能在伺服器端計算波形,而不是在標註者的瀏覽器裡算,這是任務秒開和每條資料都卡住之間的區別:
brew install audiowaveform # macOS
sudo apt-get install audiowaveform # Ubuntu/Debian沒有它,Potato 會退回到在瀏覽器中生成波形,這適合大約 30 分鐘以內的檔案。預先計算接下來的幾條資料可以讓標註者在條目之間不必等待:
audio_annotation:
waveform_cache_dir: waveform_cache/
waveform_look_ahead: 3
waveform_cache_max_size: 50