Skip to content
教程阅读约需 8 分钟

说话人分离标注

在 Potato 中构建说话人识别任务:交互式音频波形、时间戳标记、说话人标签分配,以及标注者间一致性测量。

Potato Team

本教程在 Potato 中构建两个说话人标注任务。第一个任务中,标注者在波形上从零开始标出说话人轮次。第二个任务中,他们修正上游分离系统给出的说话人标签。完整的音频 scheme 参考请见源文档。

说话人分离产出什么,Potato 在其中的位置

说话人分离把一段录音切分成归属于单个说话人的片段,并为每个片段赋予一个说话人标识。它回答的是"谁在什么时候说话",只要录音里不止一个人的声音就会用到它,从会议纪要、客服中心分析,到播客制作、研究访谈和法律录音。

Potato 标注分离结果,而不执行分离。语音识别和说话人分离在你已有的系统里运行,比如 pyannote、WhisperX 或某个云端转写 API,Potato 摄入其输出交给人工检查和修正。下面两个任务覆盖随之而来的两种情况:一种是还没有分离系统的输出,另一种是已有输出但需要审核。

用波形任务标注轮次

没有上游输出可供修正时,标注者自己在波形上画出轮次。schema 是处于 label 模式的 audio_annotation,而 item_properties.text_key 指明存放音频 URL 的字段,Potato 靠它找到每条数据对应的媒体文件。

yaml
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: true

Overlap 和 Silence 这两个标签的作用是让时间轴保持完整。没有表示交叉说话的标签,标注者就得决定同时开口的两个人谁拥有这段区域,而两个标注者对同一秒音频会做出不同的决定。没有表示静音的标签,时间轴上的空档就在"没有人说话"和"还没有人标到这里"之间含混不清。

min_segments 只有在同时设置 label_requirement.required: true 时才生效。两者都设置后,标注者必须至少标出一个片段、并为标出的每个片段都打上标签,否则"下一条"保持锁定,提示信息会说明缺的是哪一项。

一个片段是怎么产生的

标注者有三种方式创建片段,选哪一种取决于边界需要多精确。在放大后的波形上按住右键拖动可直接画出片段,拖到边缘时视图会自动滚动,因此一段长过可见窗口的轮次仍然一次手势就能完成。在总览条上按住右键拖动,可以在录音的任意位置画出一个粗略片段,无需先导航过去。[ 和 ] 键在播放头处设定边界,Enter 创建片段,这是标注者一路听下去时最快的路径。三种方式下左键都保留它原本移动播放头的职责,所以导航和绘制不会冲突。

为每个轮次附加问题

单靠说话人标签承载不了言语行为、说话人角色或逐轮次的质量判断。设置 mode: both 会保留标签,并为每个片段附上一组问题,标注者选中某个区域后在片段面板里作答:

yaml
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 会把同一个问题放到每个轮次上:

yaml
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: true

speakers 名册为每个已知说话人固定了颜色和位置,于是一个标错说话人的轮次会以气泡出现在转写稿的错误一侧,标注者还没读一个字就能看出来。点击某个轮次的说话人姓名即可重新分配,也可以指定名册之外的说话人。逐轮次的答案是按每个轮次上稳定的 turn_id 存储的,所以如果在上游修正之后重新摄入转写稿,请保持这些标识不变。

从一个装满 ASR 输出的文件夹构建数据文件只需一条命令:

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

让多个标注者标注同一段音频

说话人边界是标注者分歧最大的地方,而对一段录音只标一遍就没有任何可比较的东西。num_annotators_per_item 设定项目的覆盖目标:

yaml
num_annotators_per_item: 3

在试点子集上安排三名标注者,足以在整批录音发出去之前看出标签集是否站得住脚。关于如何确定这个子集的规模以及如何解读结果,请见质量控制指南。

键盘快捷键

音频标注的节奏由播放控制决定,所以第一天就把快捷键交给标注者:

Key操作
Space播放或暂停
Left / Right后退或前进 5 秒
Shift+Left / Shift+Right跳转 30 秒
[ / ]在播放头处设定片段起点 / 终点
Enter由当前选区创建片段
Delete删除选中的片段
1-9选择标签,前提是定义了 key_value
+ / - / 0放大、缩小,或让波形适应视图

输出文件里有什么

片段写在 scheme 名称之下,每个片段带有自己的标识、以秒为单位的边界、标签,以及片段问题的答案:

json
{
  "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 就能在服务器端计算波形,而不是在标注者的浏览器里算,这是任务秒开和每条数据都卡住之间的区别:

bash
brew install audiowaveform          # macOS
sudo apt-get install audiowaveform  # Ubuntu/Debian

没有它,Potato 会退回到在浏览器中生成波形,这适合大约 30 分钟以内的文件。预先计算接下来的几条数据可以让标注者在条目之间不必等待:

yaml
audio_annotation:
  waveform_cache_dir: waveform_cache/
  waveform_look_ahead: 3
  waveform_cache_max_size: 50

下一步