ESPnet 音素级 ASR 实战指南:基于 LibriTTS 与 E-Branchformer 的 IPA+标点转写系统

原创2026-09-25 16:56:33155 阅读
文章标签:人工智能语音音频深度学习NLP

ESPnet 音素级 ASR 实战指南:基于 LibriTTS 与 E-Branchformer 的 IPA+标点转写系统

导读

本文围绕 ESPnet 仓库中 egs2/libritts/asr1 这一音素级 ASR(Automatic Speech Recognition)实验配方展开,讲解如何基于 LibriTTS 语料构建一个输出为 IPA(国际音标)音素序列并保留标点的识别系统。该系统的核心设计目标是产出"可直接作为基于音素的 TTS(Phone-based TTS)输入端"的转写结果,从而将文本(grapheme)到音素(phoneme)的转换负担从 TTS 前端迁移到 ASR 后端。读完本文,你将掌握:音素化数据准备流程、espeak_ng_english_us_vits 这一 g2p(grapheme-to-phoneme)方案的底层实现原理、E-Branchformer 训练配置的完整参数含义,以及该系统的 WER/CER/TER 量化表现。

一、系统目标:为什么需要"音素+标点"的 ASR 输出

该配方在 egs2/libritts/asr1/README.md 中开宗明义:本系统的目的是产生适合作为基于音素 TTS 直接输入的输出。这与常规的字素级(grapheme)ASR 有本质区别:

  • 常规 ASR 输出自然语言文本(单词、字词),供人阅读或作为语音识别评测基准;
  • 本配方输出音素序列(IPA 符号)与标点,语义上等价于"语音 → 音素转写",可直接喂给音素级 TTS 前端,避免 TTS 端再做一轮 g2p 转换。

从数据流角度看,这正是把 TTS 流水线中的 g2p 环节前置到 ASR 中联合解决:ASR 学习"声学特征 → 音素串(含标点)"的映射,而音素串本身就是 TTS 可直接消费的中间表示。因此该模型可视为语音转写(speech-to-phoneme transcription)专用系统,其评测指标(WER/CER/TER)也全部基于音素序列计算。

二、实验环境与依赖

原文档记录了该配方的实测环境(详见 egs2/libritts/asr1/README.md):

项目 版本
Python 3.10.8(GCC 11.3.1)
ESPnet espnet 202308
PyTorch 2.0.1+cu118
训练硬件 A6000(48 GB)× 2 GPU

需要特别说明:由于本配方依赖 eSpeak NG 进行音素化,必须安装相应的 g2p 依赖。从源码看,espeak_ng_english_us_vits 走的是 phonemizer 库的 espeak 后端(见下文源码解析小节),因此复现前需要确保环境中安装了 phonemizer 及其 espeak-ng 后端。

三、数据处理:从 LibriTTS 到"音素化"训练集

3.1 数据下载与目录组织

数据准备脚本为 local/data.sh,它通过 db.sh 读取 LIBRITTS 环境变量指定的数据根目录,并从 www.openslr.org/resources/60 下载 LibriTTS 的七个子集:

  • dev-clean、dev-other、test-clean、test-other
  • train-clean-100、train-clean-360、train-other-500

下载完成后在 $db_root/LibriTTS/.complete 打上标记以避免重复下载。随后:

  1. stage 0:对每个子集调用 local/data_prep.sh 生成 Kaldi 风格数据目录(wav.scp、text、utt2spk、spk2gender、spk2utt),再用 utils/fix_data_dir.sh 修复,最后调用 local/phonemize_dir.py 做音素化;
  2. stage 1:用 utils/combine_data.sh 合并数据——dev 由 dev-clean + dev-other 合并,train-960 由三个训练子集合并(合计约 960 小时)。

local/data_prep.sh 沿用了 LibriTTS 的标准做法:以"reader_chapter"作为说话人粒度(utt2spk 按章节划分,便于按章节计算 CMVN),转写文本取自每个 wav 对应的 .normalized.txt 文件。

3.2 音素化核心脚本:phonemize_dir.py

音素化由 local/phonemize_dir.py 完成,其逻辑非常简洁清晰:

from espnet2.text.phoneme_tokenizer import PhonemeTokenizer

tokenizer = PhonemeTokenizer("espeak_ng_english_us_vits")

with (
    open(f"{idir}/text", encoding="utf-8") as itext,
    open(f"{idir}/text.phn", "w", encoding="utf-8") as otext,
):
    for line in itext:
        utt, text = line.strip("\n").split(" ", maxsplit=1)
        tokens = tokenizer.text2tokens(text)
        text_phn = "".join(tokens).replace("<space>", " ")
        otext.write(f"{utt} {text_phn}\n")

os.replace(f"{idir}/text", f"{idir}/text.orig")
os.replace(f"{idir}/text.phn", f"{idir}/text")

要点拆解:

  • 使用 PhonemeTokenizer("espeak_ng_english_us_vits") 将每句英文文本转为音素 token 序列;
  • token 序列用 "".join() 拼回字符串,再统一把 <space> 占位符替换为真实空格,从而把词边界还原为空格分隔;
  • 处理完成后原文本备份为 text.orig,音素文本正式覆盖 text,因此训练集、验证集、测试集的 text 文件都是音素序列,ASR 模型学到的是"声学特征 → 音素+标点"的映射。

3.3 底层 g2p:espeak_ng_english_us_vits 的实现

PhonemeTokenizer 定义于 espnet2/text/phoneme_tokenizer.py,espeak_ng_english_us_vits 分支位于该文件约 L598-L610:

elif g2p_type == "espeak_ng_english_us_vits":
    # VITS official implementation-like processing
    # Reference: https://github.com/jaywalnut310/vits
    self.g2p = Phonemizer(
        language="en-us",
        backend="espeak",
        with_stress=True,
        preserve_punctuation=True,
        strip=True,
        word_separator=" ",
        phone_separator="",
        split_by_single_token=True,
    )

从源码结构看,该 g2p 方案的关键参数决定了音素化结果的质量:

  • backend="espeak":调用 phonemizer 库的 eSpeak NG 后端(Phonemizer 类是对 bootphon/phonemizer 的封装,见 espnet2/text/phoneme_tokenizer.py L384-L437,内部通过 phonemizer.backend.BACKENDS[backend] 实例化);
  • language="en-us":使用美式英语发音规则;
  • with_stress=True:保留重音符号——IPA 音素中的主/次重音(如 ˈ 与 ˌ)对 TTS 韵律至关重要;
  • preserve_punctuation=True:保留标点——这正是"IPA + 标点"系统中标点来源,标点会被当作独立 token 输出;
  • word_separator=" "、phone_separator=""、split_by_single_token=True:采用 VITS 官方实现类似的处理方式——词边界用空格分隔、音素间不加分隔符、按单 token 拆分(拆分时 Phonemizer.__call__ 会把空格替换为 <space> 占位符,再由 phonemize_dir.py 统一还原)。

此外,espnet2/text/phoneme_tokenizer.py 的 g2p_choices 列表(L25-L56 附近)还提供了 g2p_en、pyopenjtalk(日语)、pypinyin_g2p(中文)、g2pk(韩语)、espeak_ng_*(多语种)等众多方案,本配方选用的 espeak_ng_english_us_vits 正是为英文音素级 TTS 量身定制的一档。

四、训练配置详解

4.1 入口脚本 run.sh

训练入口为 run.sh,通过 ./asr.sh 以参数方式注入全部超参数:

train_set="train-960"
valid_set="dev"
test_sets="test-clean test-other dev-clean dev-other"

asr_config=conf/train_asr.yaml
inference_config=conf/decode_asr.yaml

./asr.sh \
    --lang en \
    --ngpu 2 \
    --nbpe 100 \
    --max_wav_duration 30 \
    --speed_perturb_factors "0.9 1.0 1.1" \
    --audio_format "flac.ark" \
    --feats_type raw \
    --use_lm false \
    --asr_config "${asr_config}" \
    --inference_config "${inference_config}" \
    --train_set "${train_set}" \
    --valid_set "${valid_set}" \
    --test_sets "${test_sets}" \
    --lm_train_text "data/${train_set}/text" \
    --bpe_train_text "data/${train_set}/text" "$@"

参数语义如下:

参数 取值 作用
--lang en en 目标语言为英语
--ngpu 2 2 双 GPU 分布式训练
--nbpe 100 100 BPE 词表大小 100——由于输出是音素序列,词表天然很小,100 个 BPE 单元足够覆盖音素+标点组合
--max_wav_duration 30 30(秒) 过滤超过 30 秒的长音频
--speed_perturb_factors "0.9 1.0 1.1" 三倍速扰动(0.9×/1.0×/1.1×)做数据增强
--audio_format "flac.ark" flac.ark 音频以 FLAC 编码的 ark 格式存储
--feats_type raw raw 不预提取特征,直接在训练中计算前端特征
--use_lm false false 不使用外部语言模型(音素序列无需 LM 建模)
--asr_config conf/train_asr.yaml 训练配置
--inference_config conf/decode_asr.yaml 解码配置
--bpe_train_text data/train-960/text 在音素化后的训练文本上训练 BPE 模型

值得注意的是 run.sh 末尾的 "$@" 允许命令行追加参数覆盖默认值,这与 ESPnet 所有配方的习惯一致(如覆盖 --asr_config 指向 conf/tuning/train_asr_e_branchformer.yaml)。

4.2 模型结构:E-Branchformer 编码器 + Transformer 解码器

训练配置 conf/tuning/train_asr_e_branchformer.yaml(与 conf/train_asr.yaml 内容一致)定义了完整的模型与训练方案:

编码器(E-Branchformer)——约 1.41 亿参数中的主体:

encoder: e_branchformer
encoder_conf:
    output_size: 512
    attention_heads: 8
    attention_layer_type: rel_selfattn
    pos_enc_layer_type: rel_pos
    rel_pos_type: latest
    cgmlp_linear_units: 3072
    cgmlp_conv_kernel: 31
    use_linear_after_conv: false
    gate_activation: identity
    num_blocks: 17
    dropout_rate: 0.1
    positional_dropout_rate: 0.1
    attention_dropout_rate: 0.1
    input_layer: conv2d
    layer_drop_rate: 0.1
    linear_units: 1024
    positionwise_layer_type: linear
    macaron_ffn: true
    use_ffn: true
    merge_conv_kernel: 31

关键参数解读:

  • num_blocks: 17、output_size: 512、attention_heads: 8:17 层、512 维、8 头注意力;
  • attention_layer_type: rel_selfattn + pos_enc_layer_type: rel_pos + rel_pos_type: latest:使用相对位置编码的 self-attention;
  • cgmlp_linear_units: 3072、cgmlp_conv_kernel: 31、merge_conv_kernel: 31:E-Branchformer 特有的卷积门控 MLP(Convolution-augmented gMLP)分支配置;
  • macaron_ffn: true、use_ffn: true:启用 Macaron 结构与 FFN 分支;
  • input_layer: conv2d:输入为 Conv2D 下采样(配合下方 frontend_conf 的 512 点 FFT);
  • layer_drop_rate: 0.1:层级丢弃正则化。

解码器(Transformer):

decoder: transformer
decoder_conf:
    attention_heads: 8
    linear_units: 2048
    num_blocks: 6
    dropout_rate: 0.1
    positional_dropout_rate: 0.1
    self_attention_dropout_rate: 0.1
    src_attention_dropout_rate: 0.1
    layer_drop_rate: 0.2

训练损失与优化:

model_conf:
    ctc_weight: 0.6
    lsm_weight: 0.1
    length_normalized_loss: false

frontend_conf:
    n_fft: 512
    hop_length: 160
  • ctc_weight: 0.6:CTC 与 attention 的混合训练权重(CTC 占 0.6);
  • lsm_weight: 0.1:标签平滑系数 0.1;
  • frontend_conf:在线提取 512 点 FFT、hop 160 的滤波器组特征(对应 16 kHz 采样率下 10 ms 帧移)。

训练调度:

batch_type: numel
batch_bins: 10000000
accum_grad: 8
max_epoch: 70
patience: none
init: none
best_model_criterion:
-   - valid
    - acc
    - max
keep_nbest_models: 10
use_amp: true
unused_parameters: true

optim: adam
optim_conf:
    lr: 0.005
    weight_decay: 0.000001
scheduler: warmuplr
scheduler_conf:
    warmup_steps: 40000

specaug: specaug
specaug_conf:
    apply_time_warp: true
    time_warp_window: 5
    time_warp_mode: bicubic
    apply_freq_mask: true
    freq_mask_width_range: [0, 27]
    num_freq_mask: 2
    apply_time_mask: true
    time_mask_width_ratio_range: [0.0, 0.05]
    num_time_mask: 10
  • batch_type: numel + batch_bins: 10000000:按元素数(numel)动态批大小,单 batch 不超过 1000 万元素;
  • accum_grad: 8:梯度累积 8 步等效放大批大小;
  • max_epoch: 70:最多训练 70 个 epoch(原注释显示在 A6000 × 2 上每 epoch 约 90 分钟);
  • best_model_criterion:按验证集 accuracy 最大化选择最优模型,保留 10 个 n-best;
  • use_amp: true:启用自动混合精度;
  • 优化器 Adam(lr 0.005)+ WarmupLR(40000 步预热);
  • SpecAugment 时间/频率掩蔽增强:频率掩蔽宽度 0-27、共 2 个;时间掩蔽宽度比例为 0-0.05、共 10 个。

4.3 模型规模

该配方训练出的模型参数量为 141.39M(约 1.41 亿),实验目录名为 exp/asr_train_asr_raw_en_bpe100_sp,其中:

  • raw:raw 特征(在线前端);
  • en_bpe100:英语、BPE 词表 100;
  • sp:speed perturbation(三倍速扰动)。

五、解码配置

解码配置见 conf/decode_asr.yaml:

beam_size: 15
ctc_weight: 0.2
lm_weight: 0.0
maxlenratio: 0.0
minlenratio: 0.0
penalty: 0.0
  • beam_size: 15:beam 搜索宽度 15;
  • ctc_weight: 0.2:解码时 CTC 与 attention 得分的加权比例(与训练权重 0.6 不同,解码阶段更依赖 attention);
  • lm_weight: 0.0:不集成语言模型(与 --use_lm false 对应);
  • maxlenratio/minlenratio: 0.0:不限制输出长度比例;
  • penalty: 0.0:无长度惩罚。

最终用于评测的模型为 decode_asr_asr_model_valid.acc.ave,即验证集 accuracy 最优模型的平均权重(average checkpoints)。

六、评测结果:WER / CER / TER

音素序列的评测标准覆盖三种错误率:WER(词错率,按空格分隔的"音素词"计)、CER(字符错误率,按音素符号计)、TER(token 错误率,按词表 token 计)。以下数据均来自原文档 egs2/libritts/asr1/README.md,评测模型为 decode_asr_asr_model_valid.acc.ave。

6.1 WER(词错误率)

dataset Snt Wrd Corr Sub Del Ins Err S.Err
dev-clean 5736 95872 91.7 8.0 0.4 0.8 9.1 67.0
dev-other 4613 69577 88.5 10.9 0.6 1.2 12.7 74.2
test-clean 4837 87078 91.4 8.2 0.4 0.8 9.4 70.4
test-other 5120 72541 87.0 12.2 0.8 1.1 14.1 77.1

6.2 CER(字符错误率)

dataset Snt Wrd Corr Sub Del Ins Err S.Err
dev-clean 5736 570710 98.4 0.8 0.9 0.6 2.2 67.1
dev-other 4613 414781 97.2 1.6 1.2 1.0 3.8 74.2
test-clean 4837 530647 98.5 0.7 0.8 0.6 2.2 70.5
test-other 5120 429463 96.7 1.7 1.6 1.0 4.3 77.1

6.3 TER(Token 错误率)

dataset Snt Wrd Corr Sub Del Ins Err S.Err
dev-clean 5736 433548 97.6 1.4 1.0 0.6 3.0 67.1
dev-other 4613 316550 96.1 2.5 1.4 1.0 5.0 74.2
test-clean 4837 404031 97.7 1.4 0.9 0.7 2.9 70.5
test-other 5120 327248 95.4 2.8 1.8 1.1 5.7 77.1

结果解读:

  • CER(2.2%~4.3%)远低于 WER(9.1%~14.1%),说明错误主要集中于个别音素符号的替换,整体音素序列质量很高;
  • *-clean 与 *-other 的差距(clean vs 噪声/重口音)符合 LibriTTS 的难度梯度设定;
  • S.Err(句子错误率)约 67%~77%,意味着约三分之一的句子中存在至少一个音素错误——对音素级 TTS 前端而言,剩余的错误可通过 TTS 的容错性(音素序列已包含重音和标点信息)部分消化。

该模型对应预训练权重发布在 Hugging Face 的 espnet/akreal_libritts_asr_phn 模型仓库中,可直接加载用于推理或微调(可通过 ESPnet 标准模型下载机制 espnet_model_zoo / pretrained 拉取)。

七、从源码看该配方的可复用性

7.1 g2p 方案可替换

espnet2/text/phoneme_tokenizer.py 的 g2p_choices 列出全部可用 g2p 类型。若想迁移到其他语言,只需把 local/phonemize_dir.py 中的 PhonemeTokenizer("espeak_ng_english_us_vits") 替换为 espeak_ng_german、espeak_ng_french、espeak_ng_spanish 等(多语言 eSpeak NG 后端),或替换为 g2pk(韩语)、pyopenjtalk(日语)、pypinyin_g2p(中文)等专用方案,即可构造"任意语言 → 音素级 ASR"的配方。

7.2 与 TTS 配方的衔接

该配方的输出格式(空格分隔的 IPA 音素串、含标点与重音)与 ESPnet 的 TTS 音素输入约定一致。TTS 侧可通过 PhonemeTokenizer 完成同样的文本→音素转换(见 espnet2/text/build_tokenizer.py L76-L77 对 PhonemeTokenizer 的构建逻辑),实现"ASR 输出的音素串 → TTS 输入"的无缝对接。这正是 egs2/libritts 同时提供 asr1(音素 ASR)与 tts1 等配方的深层关联所在。

八、复现步骤速览

  1. 准备数据:在 db.sh 中填写 LIBRITTS 数据根目录,运行 ./run.sh --stage -1 --stop_stage 1 完成下载、Kaldi 数据目录生成与音素化;
  2. 训练:./run.sh --stage 2 --stop_stage 4(按 asr.sh 默认阶段划分,依次为特征/BPE 准备、训练),或直接 ./run.sh 走完全流程;训练默认使用 conf/train_asr.yaml,如需复现文档中的 E-Branchformer 配置可追加 --asr_config conf/tuning/train_asr_e_branchformer.yaml;
  3. 解码与评分:解码阶段生成 decode_asr_* 目录,使用 ESPnet 标准 show_asr_result.sh 汇总 WER/CER/TER 结果(脚本见 egs2/libritts/asr1/scripts/utils/show_asr_result.sh);
  4. 加载预训练模型:通过模型名 espnet/akreal_libritts_asr_phn 从模型库拉取权重,直接对任意英文音频做 IPA+标点转写。

小结

本配方是"音素级语音处理"思路的完整落地:数据侧用 eSpeak NG(espeak_ng_english_us_vits,保留重音与标点、VITS 兼容格式)把 LibriTTS 文本音素化;模型侧用 17 层 E-Branchformer + 6 层 Transformer(CTC 0.6 + 标签平滑 0.1 + SpecAugment)在 960 小时音素监督下训练 1.41 亿参数模型;评测侧以 WER/CER/TER 三重视角确认了音素序列的高保真度(CER 最低 2.2%)。该输出可作为音素级 TTS 的直接前端输入,也可推广到任意语言的音素转写任务。

登录后查看全文
espnet