ESPnet2 SVS2 配方模板
ESPnet2 SVS2 配方模板
这是ESPnet2的SVS2配方模板。
SVS2 将 SVS1 中的中间特征(例如梅尔频谱图或连续特征)替换为离散标记,这些标记通常从预训练的自监督学习(SSL)模型或基于编解码器的模型中提取。
目录
配方流程
SVS配方包含9个阶段。
- 依赖数据库的数据准备
数据准备阶段。
它会调用local/data.sh来创建Kaldi风格的数据目录,但会在data/目录下为训练集、验证集和评估集额外生成score.scp和label文件。
另请参阅:
- 音频转储/嵌入准备
如果指定--feats_type raw选项,这是一个wav转储阶段,用于重新格式化数据目录中的wav.scp文件。
否则,如果您指定--feats_type fbank选项或--feats_type stft选项,这将是一个特征提取阶段(待更新)。
此外,如果您指定了--use_sid true和--use_lid true选项,本阶段还将执行说话人ID嵌入和语言ID嵌入的准备工作。请注意,该处理过程假设utt2spk或utt2lang已在阶段1正确创建,请务必注意。
- 过滤
过滤阶段。
处理阶段,用于从训练集和验证集中移除过长或过短的语音片段。您可以通过--min_wav_duration和--max_wav_duration参数调整阈值。
空文本也将被移除。
- 使用预训练模型提取离散标记
离散令牌提取阶段。
在SVS2中支持两种类型的离散标记:单层标记和多层标记。
单层令牌仅从预训练模型的单层中提取离散令牌。RVQ提取同样可用,后续对RVQ离散令牌的应用方式将与多层令牌相同。
多层令牌将以frame或sequence的方式将来自单层令牌或RVQ的不同层离散令牌组合在一起。
更多详情请参阅数据准备。
另请参阅:
- 词元列表生成
词元列表生成阶段。
它从srctexts生成令牌列表(字典)。您可以通过--token_type选项更改令牌化类型。支持token_type=phn。如果指定了--cleaner选项,输入文本将使用指定的清理器进行清理。如果token_type=phn,输入文本将通过--g2p选项指定的G2P模块进行转换。
另请参阅:
数据准备将在第4阶段结束。您可以通过--skip_data_prep选项跳过数据准备阶段(阶段1至阶段4)。
- 语音合成统计信息收集
统计计算阶段。该阶段收集输入和输出的形状信息,并计算用于特征归一化的统计量(训练集和验证集上的均值和方差)。
在此阶段,您可以设置--write_collected_feats true来存储音高和特征的统计信息。
- 语音合成训练
SVS模型训练阶段。您可以通过--train_config和--train_args选项来更改训练设置。
另请参阅:
训练过程将在第6阶段结束。您可以通过--skip_train选项跳过训练过程(第5阶段至第6阶段)。
- 歌声合成推理
SVS模型解码阶段。您可以通过--inference_config和--inference_args修改解码设置。兼容的声码器可以被训练和加载。
另请参阅:
- 客观评估
评估阶段。该阶段执行四项客观评估。另请参阅:
- 模型打包
打包阶段。将训练好的模型文件进行打包。
如何运行
这里,我们展示使用egs2/opencpop/svs2运行该配方的步骤。
进入配方目录。
$ cd egs2/opencpop/svs2如需更改下载目录,请修改db.sh中的OPENCPOP变量。
$ vim db.sh如需使用作业调度器,请修改cmd.sh和conf/*.conf文件。详情请参阅使用作业调度系统。
$ vim cmd.sh运行run.sh脚本,该脚本会执行上述所有阶段的操作。
$ ./run.sh默认情况下,我们使用conf/train.yaml配置,以feats_type=raw和token_type=phn参数训练Naive_RNN模型。
然后,您可以在配方目录中看到以下目录。
├── data/ # Kaldi-style data directory
│ ├── dev/ # validation set
│ ├── eval/ # evaluation set
│ ├── tr_no_dev/ # training set
│ └── token_list/ # token list (directory)
│ └── phn_none_zh/ # token list
├── dump/ # feature dump directory
│ ├── raw/
│ │ ├── org/
│ │ │ ├── tr_no_dev/ # training set before filtering
│ │ │ └── dev/ # validation set before filtering
│ │ ├── srctexts # text to create token list
│ │ ├── eval/ # evaluation set
│ │ ├── dev/ # validation set after filtering
│ │ └── tr_no_dev/ # training set after filtering
│ └── extracted/ # log direcotry for feature extraction
│ └── [model_name]/ # pretrained model workspace
│ └── [layer_index]/ # specific layer for pretrained model
│ ├── eval/
│ ├── dev/
│ ├── tr_no_dev/
│ └── tr_no_dev_subset[portion]/ # subset of train set
└── exp/ # experiment directory
├── svs_stats_raw_phn_none_jp # statistics
│ ├── logdir/ # statistics calculation log directory
│ ├── train/ # train statistics
│ ├── valid/ # valid statistics
└── svs_train_raw_phn_none_jp # model
├── tensorboard/ # tensorboard log
├── images/ # plot of training curves
├── valid/ # valid results
├── decode_train.loss.best/ # decoded results
│ ├── dev/ # validation set
│ └── eval/ # evaluation set
│ ├── norm/ # generated features
│ ├── denorm/ # generated denormalized features
│ ├── MCD_res/ # mel-cepstral distortion
│ ├── VUV_res/ # voiced/unvoiced error rate
│ ├── SEMITONE_res/ # semitone accuracy
│ ├── F0_res/ # log-F0 RMSE
│ ├── wav/ # generated wav via vocoder
│ ├── log/ # log directory
│ ├── feats_type # feature type
│ └── speech_shape # shape info of generated features
├── config.yaml # config used for the training
├── train.log # training log
├── *epoch.pth # model parameter file
├── checkpoint.pth # model + optimizer + scheduler parameter file
├── latest.pth # symlink to latest model parameter
├── *.ave_2best.pth # model averaged parameters
└── *.best.pth # symlink to the best model parameter loss在解码过程中,您可以查看声码器训练来设置声码器。
首次使用时,建议通过--stage和--stop_stage选项逐步执行每个阶段。
$ ./run.sh --stage 1 --stop_stage 1
$ ./run.sh --stage 2 --stop_stage 2
...
$ ./run.sh --stage 8 --stop_stage 8这可能帮助您理解每个阶段的处理流程和目录结构。
数据准备
首先,确保run.sh中的设置如fs和n_shift与预训练模型中的设置相匹配(以模型wavlm_large的第6层为例)。
完成数据集预处理:
$ ./run.sh \
--stage 1 \
--stop_stage 3 \
--fs 16000 \
--n_shift 320注意:此设置主要针对自监督学习(SSL)模型。
然后,设置离散令牌的配置,包括nclusters、RVQ_layers和kmeans_feature。如果选择多层令牌或RVQ_layer大于1,则还需指定multi_token和mix_type配置。
单层令牌
在单层令牌中,它从预训练模型的单层特征中提取离散令牌,同时也支持RVQ提取。
在此设置中,kmeans_feature的格式为[model]/[layer index],其中[model]表示预训练模型,[layer index]代表预训练模型中的层编号。
例如,如果你想从HuBERT基础模型的第3层获取离散标记,可以设置kmeans_feature="hubert_base/3"和RVQ_layers=1。nclusters通常设置为128或1024。
如果RVQ_layers大于1,离散令牌将以残差向量量化的方式提取,最终生成RVQ_layers层的离散令牌。RVQ离散令牌的以下应用在多层令牌中使用。
多层令牌
多层令牌将结合在单层令牌或RVQ中提取的多层离散令牌。因此,要使用这种类型的令牌,您应首先在单层令牌中获取相应的离散令牌。
在此设置中,kmeans_feature应以multi/[tag]格式指定,其中[tag]是组合标记的新名称。所有层中的离散标记应在multi_token中列出,用空格分隔。
例如,如果您想结合hubert large第6层、wavlm large第6层和第23层的离散标记,应设置multi_token="hubert_large_ll60k_128_6_RVQ_0 wavlm_large_128_6_RVQ_0 wavlm_large_128_23_RVQ_0"并配合RVQ_layers=2参数。若参数设为RVQ_layers=1,则配置应为multi_token="hubert_large_ll60k_128_6 wavlm_large_128_6 wavlm_large_128_23"。
当将这些token输入SVS2模型时,它们应该混合成一个单一的token序列。你可以设置mix_type="frame"或mix_type="sequence"来获取这个单一token序列。
mix_type的示例:
l_A=[1, 2, 3]
l_B=[a, b, c].
# mix l_A and l_B
"frame": [1, a, 2, b, 3, c] # frame by frame
"sequence": [1, 2, 3, a, b, c] # sequence by sequence提取离散标记:
# Single layer token (RVQ_layers=1)
$ ./run.sh \
--stage 4 \
--stop_stage 4 \
--fs 16000 \
--n_shift 320 \
--nclusters 128 \
--RVQ_layers 1 \
--kmeans_feature wavlm_large/6
$ ./run.sh \
--stage 4 \
--stop_stage 4 \
--fs 16000 \
--n_shift 320 \
--nclusters 128 \
--RVQ_layers 1 \
--kmeans_feature wavlm_large/23
# Multi layer token(RVQ_layers=1)
$ ./run.sh \
--stage 4 \
--stop_stage 4 \
--fs 16000 \
--n_shift 320 \
--nclusters 128 \
--RVQ_layers 1 \
--mix_type frame \
--kmeans_feature multi/wavlm_large_6+wavlm_large_23 \
--multi_token "wavlm_large_128_6 wavlm_large_128_23"
# Single layer token (RVQ_layers>1)
$ ./run.sh \
--stage 4 \
--stop_stage 4 \
--fs 16000 \
--n_shift 320 \
--nclusters 128 \
--RVQ_layers 3 \
--kmeans_feature wavlm_large/6
# Multi layer token(RVQ_layers>1)
$ ./run.sh \
--stage 4 \
--stop_stage 4 \
--fs 16000 \
--n_shift 320 \
--nclusters 128 \
--RVQ_layers 3 \
--mix_type frame \
--kmeans_feature multi/wavlm_large_6_RVQ_0+1+2 \
--multi_token "wavlm_large_128_6_RVQ_0 wavlm_large_128_6_RVQ_1 wavlm_large_128_6_RVQ_2"Naive_RNN_DP 训练
首先,完成数据准备。
其次,检查"train_config"(默认为conf/train.yaml)、"score_feats_extract"(RNN_DP中的音节级别)并将"vocoder_file"修改为您自己的声码器路径。
$ ./run.sh --stage 6 \
--train_config conf/tuning/train_naive_rnn_dp.yaml \
--score_feats_extract syllable_score_feats \
--pitch_extract dio \
--vocoder_file ${your vocoder path} \
${command in data preparation}XiaoiceSing 训练
首先,完成数据准备。
其次,检查"train_config"(默认conf/train.yaml)、"score_feats_extract"(XiaoiceSing中的音节级别)并修改"vocoder_file"为您自己的声码器路径。
$ ./run.sh --stage 6 \
--train_config conf/tuning/train_xiaoice.yaml \
--score_feats_extract syllable_score_feats \
--pitch_extract dio \
--vocoder_file ${your vocoder path} \
${command in data preparation}TokSing训练
首先,完成数据准备。
其次,检查"train_config"(默认为conf/train.yaml)、"score_feats_extract"(小冰唱歌中的音节级别)并将"vocoder_file"修改为您自己的声码器路径。
$ ./run.sh --stage 6 \
--train_config conf/tuning/train_discrete_acoustic.yaml \
--score_feats_extract syllable_score_feats \
--pitch_extract dio \
--vocoder_file ${your vocoder path} \
${command in data preparation}带说话人ID嵌入训练的多说话人模型
首先,你需要从第2和第3阶段运行,使用--use_sid true来提取说话人ID。
$ ./run.sh --stage 2 --stop_stage 3 --use_sid true你可以在dump/raw/*/utt2sid中找到说话人ID文件。请注意,你需要在数据准备阶段正确创建utt2spk才能生成utt2sid。然后,你可以使用在svs_conf中包含spks: #spks的配置来运行训练。
# e.g.
svs_conf:
spks: 5 # Number of speakers请从第6阶段开始运行训练。
$ ./run.sh --stage 6 --use_sid true --train_config /path/to/your_multi_spk_config.yaml带语言ID嵌入训练的多语言模型
首先,你需要从第2和第3阶段运行,使用--use_lid true来提取说话人ID。
$ ./run.sh --stage 2 --stop_stage 3 --use_lid true你可以在dump/raw/*/utt2lid中找到说话人ID文件。注意:你需要在阶段1额外创建utt2lang文件才能生成utt2lid。然后,你就可以使用svs_conf中包含langs: #langs的配置来运行训练。
# e.g.
svs_conf:
langs: 4 # Number of languages请从第6阶段开始运行训练。
$ ./run.sh --stage 6 --use_lid true --train_config /path/to/your_multi_lang_config.yaml当然,您还可以进一步结合说话人ID嵌入。如果想同时使用说话人ID(sid)和语言ID(lid),处理流程应如下所示:
$ ./run.sh --stage 2 --stop_stage 3 --use_lid true --use_sid true配置你的设置。
# e.g.
svs_conf:
langs: 4 # Number of languages
spks: 5 # Number of speakers请从第6阶段开始运行训练。
$ ./run.sh --stage 6 --use_lid true --use_sid true --train_config /path/to/your_multi_spk_multi_lang_config.yaml声码器训练
如果您的--vocoder_file设置为none,将使用Griffin-Lim算法。您也可以使用kan-bayashi/ParallelWaveGAN训练对应的声码器。
预训练的声码器如下所示:
*_hifigan.v1
├── checkpoint-xxxxxxsteps.pkl
├── config.yml
└── stats.h5# Use the vocoder trained by `parallel_wavegan` repo manually
$ ./run.sh --stage 8 --vocoder_file /path/to/checkpoint-xxxxxxsteps.pkl --inference_tag decode_with_my_vocoder评估
我们提供四种客观评估指标:
- 梅尔倒谱失真(MCD)
- 基频的对数均方根误差(log-F0 RMSE)
- 半音准确率(Semitone ACC)
- 浊音/清音错误率 (VUV_E)
- 词/字符错误率 (WER/CER,用户可选执行)
- 伪MOS评分(即将推出)
对于MCD(梅尔倒谱失真),我们应用动态时间规整(DTW)来匹配真实歌声与生成歌声之间的时长差异。
这里我们展示计算客观指标的示例命令:
cd egs2/<recipe_name>/svs1
. ./path.sh
# Evaluate MCD & log-F0 RMSE & Semitone ACC & VUV Error Rate
./pyscripts/utils/evaluate_*.py \
exp/<model_dir_name>/<decode_dir_name>/eval/wav/gen_wavdir_or_wavscp.scp \
dump/raw/eval/gt_wavdir_or_wavscp.scp
# Evaluate CER
./scripts/utils/evaluate_asr.sh \
--model_tag <asr_model_tag> \
--nj 1 \
--inference_args "--beam_size 10 --ctc_weight 0.4 --lm_weight 0.0" \
--gt_text "dump/raw/eval1/text" \
exp/<model_dir_name>/<decode_dir_name>/eval1/wav/wav.scp \
exp/<model_dir_name>/<decode_dir_name>/asr_results
# Since ASR model does not use punctuation, it is better to remove punctuations if it contains
./scripts/utils/remove_punctuation.pl < dump/raw/eval1/text > dump/raw/eval1/text.no_punc
./scripts/utils/evaluate_asr.sh \
--model_tag <asr_model_tag> \
--nj 1 \
--inference_args "--beam_size 10 --ctc_weight 0.4 --lm_weight 0.0" \
--gt_text "dump/raw/eval1/text.no_punc" \
exp/<model_dir_name>/<decode_dir_name>/eval1/wav/wav.scp \
exp/<model_dir_name>/<decode_dir_name>/asr_results
# You can also use openai whisper for evaluation
./scripts/utils/evaluate_asr.sh \
--whisper_tag base \
--nj 1 \
--gt_text "dump/raw/eval1/text" \
exp/<model_dir_name>/<decode_dir_name>/eval1/wav/wav.scp \
exp/<model_dir_name>/<decode_dir_name>/asr_results虽然这些客观指标可以评估合成歌声的质量,但仅凭这些数值仍难以全面判断人类的感知质量,尤其是对于高保真生成的歌声。因此,如果您想详细检查感知质量,我们建议进行主观评估(例如平均意见得分MOS)。
您可以参考此页面使用webMUSHRA启动基于网络的主观评价系统。
关于数据目录
训练集、开发集和评估集的每个目录都具有相同的目录结构。另请参阅 https://github.com/espnet/espnet/tree/master/egs2/TEMPLATE#about-kaldi-style-data-directory 了解Kaldi数据结构。我们建议您运行opencpop配方并自行检查data/目录中的内容。
cd egs/opencpop/svs2
./run.sh目录结构
data/ ├── tr_no_dev/ # 训练集目录 │ ├── text # 文本转录 │ ├── label # 指定转录的开始和结束时间 │ ├── score.scp # 评分文件路径 │ ├── wav.scp # 音频文件路径 │ ├── utt2spk # 将话语ID映射到说话者ID的文件 │ ├── spk2utt # 将说话者ID映射到话语ID的文件 │ ├── segments # [可选] 指定每个话语的开始和结束时间 │ └── (utt2lang) # 将话语ID映射到语言类型的文件(仅用于多语言场景) ├── dev/ │ ... ├── eval/ │ ... └── token_list/ # 标记列表目录 ...text格式uttidA <转写文本> uttidB <转写文本> ...label格式uttidA (startA1, endA1, phA1) (startA2, endA2, phA1) ... uttidB (startB1, endB1, phB1) (startB2, endB2, phB2) ... ...score.scp格式key1 /some/path/score.json key2 /some/path/score.json ...请注意,对于没有明确乐谱或MusicXML的数据库,我们将在未来提供基于规则的自动音乐转录来提取相关音乐信息。
wav.scp格式uttidA /path/to/uttidA.wav uttidB /path/to/uttidB.wav ...utt2spk格式uttidA speakerA uttidB speakerB uttidC speakerA uttidD speakerB ...spk2utt格式speakerA uttidA uttidC ... speakerB uttidB uttidD ... ...注意:
spk2utt文件可以通过utt2spk生成,而utt2spk也可以通过spk2utt生成,因此只需创建其中任意一个即可。utils/utt2spk_to_spk2utt.pl data/tr_no_dev/utt2spk > data/tr_no_dev/spk2utt utils/spk2utt_to_utt2spk.pl data/tr_no_dev/spk2utt > data/tr_no_dev/utt2spk如果您的语料库不包含说话人信息,可以将说话人ID设置为与话语ID相同以满足目录格式要求,或者为所有话语赋予相同的说话人ID(实际上当前ASR配方中我们并未使用说话人信息)。
uttidA uttidA uttidB uttidB ...或
uttidA dummy uttidB dummy ...[可选]
segments格式如果音频数据原本是较长的录音(约大于1小时),且每个音频文件包含多个片段的语音内容,则需要创建
segments文件来指定每个语音片段的开始和结束时间。格式为<utterance_id> <wav_id> <start_time> <end_time>。ofuton_0000000000000000hato_0007 ofuton_0000000000000000hato 33.470 38.013 ...请注意,如果使用
segments,则wav.scp中的<wav_id>需要与segments中的对应,而不是utterance_id。ofuton_0000000000000000hato /path/to/ofuton_0000000000000000hato.wav ...utt2lang格式uttidA languageA uttidB languageB uttidC languageA uttidD lagnuageB ...请注意
utt2lang文件仅针对多语言数据集生成(参见配方egs/multilingual_four)。
完成数据目录创建后,最好通过utils/validate_data_dir.sh进行检查。
utils/validate_data_dir.sh --no-feats data/tr_no_dev
utils/validate_data_dir.sh --no-feats data/dev
utils/validate_data_dir.sh --no-feats data/test分数准备
要准备一个新的配方,如果没有官方提供的分段,我们首先通过--silence选项将歌曲分割成片段。
然后,我们将原始数据转换为score.json,根据标注情况可分为两种情形:
案例1:音素标注与标准化评分
如果音素和音符在时域上对齐,可直接转换原始数据。(例如:Opencpop)
如果音素标注在时域上与音符未对齐,则通过g2p将音素(来自
label)与音符-歌词对(来自musicXML)进行对齐。(例如:Ofuton)我们还针对数据集中缺失静音片段提供了一些自动修复方案。在stage1阶段,当您遇到诸如"歌词长度超过音素"或"音素长度超过歌词"等错误时,脚本会自动生成修复代码。您可能需要将这些代码放入
egs2/[数据集名称]/svs1/local/prep_segments.py文件中的get_error_dict方法内。请注意,根据建议的input_type类型,您可能需要将其复制到hts或xml的error_dict中。(更多信息请查看namine或natsume)
特别地,如果音符时长存在问题(例如Natsume),可以通过其他旋律文件(如MIDI)重建音符-歌词对。
案例2:仅包含音素标注
待更新。
您可能会遇到的问题
在第一阶段,即数据准备阶段,您可能会遇到ValueError问题,这些问题通常表明标注存在错误。为了解决这些问题,需要手动检查对应部分的原始数据并进行必要的修正。虽然其他工具包和开源代码库可能没有此类要求或检查,但我们发现投入时间解决这些错误能显著提升歌声合成器的质量。
请注意,可以在本地或通过阶段1的数据处理流程对原始数据进行修改。为了方便开源,我们推荐使用后者。
- 如需修改原始数据,可以使用music21、miditoolkit或MuseScore等工具包。
- 要在数据流中进行处理,您可以使用提供的读取器和写入器。示例可以在
egs2/{natsume, ameboshi, pjs}/svs1/local/{prep_segments.py, prep_segments_from_xml.py}/中的make_segment函数中找到。
以下是一些需要注意的常见错误:
- 错误的分割点
- 在相邻歌词之间添加停顿或直接分割。
- 移除停顿并将时长分配给正确的音素。
- 歌词/MIDI标注错误
- 替换为正确的选项。
- 添加缺失项并重新分配相邻时长。
- 移除冗余项并重新分配相邻时长。
- 针对给定g2p的不同歌词-音素对
- 使用如下音节-音素对的
customed_dic:# 例如 # 在日语数据集ofuton中,"ヴぁ"通过pyopenjtalk的输出与原始数据"v a"不同 > pyopenjtalk.g2p("ヴぁ") v u a # 将以下歌词-音素对添加到customed_dic中 ヴぁ v_a - Specify
--g2p noneand store the lyric-phoneme pairs intoscore.json, especially for polyphone problem in Mandarin.# e.g. # In Mandarin dataset Opencpop, the pronounce the second "重" should be "chong". > pypinyin.pinyin("情意深重爱恨两重", style=Style.NORMAL) [['qing'], ['shen'], ['yi'], ['zhong'], ['ai'], ['hen'], ['liang'], ['zhong']]
- MusicXML中的特殊标记
- Breath:
breath mark在 note.articulations 中:通常出现在句子末尾。在某些情况下,breath mark不会在其所属音符上生效。请在 local/ 目录下处理它们。br在 note.lyric 中。(已在 XMLReader 中解决)- 带有固定特殊音高的特殊注释。(已在XMLReader中解决)
- 断奏(Staccato): 在某些情况下,当音符的articulations中出现
staccato标记时会出现停顿。我们允许用户在local/目录下自行决定是否执行分段操作。
支持的文本清理器
你可以通过svs.sh中的--cleaner选项来更改。
none: 无文本清理器。
你可以查看这里的代码示例。
支持的文本前端
你可以通过svs.sh中的--g2p选项来更改。
none: Just separate by space- 例如:
HH AH0 L OW1 <space> W ER1 L D->[HH, AH0, L, OW1, <space>, W, ER1, L D]
- 例如:
pyopenjtalk: r9y9/pyopenjtalk- 例如
こ、こんにちは->[k, o, pau, k, o, N, n, i, ch, i, w, a]
- 例如
你可以从这里查看代码示例。
支持的预训练模型
支持的模型
你可以通过修改run.sh中--train_config选项对应的*.yaml配置文件来训练以下模型。
你可以在egs/opencpop/svs2/conf/tuning中找到上述模型的示例配置文件。
