简介:面向人工智能深度学习与语音克隆领域开发者的Bert-vits2项目模型包,内含星之守护者阿狸中文语音模型,训练步数6000,版本2.3。该开源TTS方案基于当前广受好评的端到端语音合成架构,在韵律表现上尤为突出,能够输出自然流畅、带情感起伏的中文语音,适用于小说朗读、口播配音、角色语音替换等场景,使用者只需具备基础的模型推理知识即可快速上手。压缩包共4个文件,包含pth模型主体、json配置文件以及wav音频样本,整体约601.96MB,目录与官方项目保持一致,部署结构清晰。已有516人学习/下载。资源提供可直接加载的模型与配套配置,省去自行训练的时间和算力,同时音频样本便于效果预览和参数对照,适合希望快速获得高品质中文克隆音色的个人开发者、内容创作者及语音技术爱好者。
1. 先把这个项目讲清楚:Bert-VITS2 2.3 中文语音克隆到底能做什么
手里只有某个游戏角色几十条干净对白,目标是让这个角色说出一段训练数据里从未出现的普通话,这就是语音克隆最典型的诉求。Bert-VITS2 2.3 是目前中文开源方案里把音色学习和端到端 TTS 结合得比较成熟的一个选择,它不依赖额外对齐标注,给一段音频和对应文本就能训练,合成时再用一段参考音频锁定音色。很多人以为 6000 步只是“听个响”,实际上在小数据集场景下,6000 步往往比两三万步更稳,步数太多反而会把噪声和口音固化进模型。这篇笔记就把从数据准备到合成调参的完整路径讲清楚,适合手里有音频素材、想快速做出可部署语音模型的开发者和内容制作者。
2. 版本选型与环境准备:为什么把 2.3 作为中文克隆的默认起点
2.1 2.3 版本在中文音色克隆上的三个关键调整
Bert-VITS2 这个系列我最早接触的是 2.0,那时候中文合成已经能用,但情感起伏大一点的句子容易“读稿”。迭代到 2.3,最大的改动集中在两个方向:一是参考音频信息的使用更充分,二是在解码端做了收敛性优化。
常见的说法是 2.3 给参考音频增加了更多维度的编码,让模型不再只依赖文本音素去猜音色,而是把参考音频里的语气、顿挫、尾音都揉进生成条件里。这点对中文尤其重要,因为中文的声调变化直接影响语义,同一个“哪”读成二声还是三声,听感差别极大。2.3 通过参考音频编码缓解了纯文本驱动时声调平淡的问题。
第二个调整是底层的动态卷积和采样模块。2.3 在语音帧生成阶段改进了先验噪音的采样方式,具体到听感,就是合成结果的“换气声”和句尾“自然的衰减”更真实了。早期版本合成出来的句尾经常是硬切,2.3 会明显顺滑一些。
第三个调整和训练步数直接相关。2.3 的优化器调度策略比 2.0 更平滑,6000 步就能进入一个听起来“像本人”的区间,而 2.0 在 6000 步附近往往音色还飘着。这也是为什么标题里的 6000 步能成立——在 2.3 上,这个步数已经是可用区间,而不是刚起步。
对中文训练来说,2.3 还有一个隐性优势:项目自带的文本处理模块对中文标点、数字、语气词的处理更完整,比如“诶”“啊”“嗯”这类在游戏语音里高频出现的口语词,2.x 早期版本经常被错误地丢进静音或变成吞音,2.3 的处理要可靠得多。
2.2 安装顺序与最小依赖
建立环境最忌讳一上来就 pip install 一把梭。Bert-VITS2 2.3 依赖 PyTorch、音色提取相关的库和文本处理工具,版本配错会在训练到一半时以各种奇怪方式翻车。我一般按这个顺序操作。
# 创建独立环境,Python 版本建议 3.10,3.8 和 3.11 都遇到过第三方库兼容问题 conda create -n bertvits python=3.10 conda activate bertvits # 先装 PyTorch,在 CUDA 环境下用官方源,不用默认源 pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 再安装项目依赖 cd Bert-VITS2 pip install -r requirements.txt逻辑说明:先装 PyTorch 是因为 torchaudio 的版本直接决定音频加载行为,换源安装能拿到和本项目 2.3 匹配的稳定组合。requirements.txt 会带上音色编码、文本转拼音等模块,如果安装过程中出现编译错误,多半是缺少系统级的音频库,比如 libsndfile。参数说明里最值得关注的是 Python 版本,尽量不要用 3.11,项目里部分音频处理库在 3.11 下会报类型错误,排查起来很费时间。
这里要说一个新手容易卡住的点:项目本身是一份训练与推理代码,不是可执行的一键安装包。装完之后你还需要在项目目录里准备三个东西,预训练模型权重、配置文件、数据集文件夹。权重文件通常需要从模型分享页或训练完成后的输出目录手动放进models/目录,不同版本对权重目录的命名要求不同,建议先把官方默认的models/目录结构保留着,不要自行改名。
2.3 算力预估与显存选择
决定项目能不能跑起来的,往往不是代码而是显卡。Bert-VITS2 2.3 的显存占用主要集中在参考音频编码和语音帧解码两部分。以我常用的配置为例,batch_size=6、音频长度上限 5 秒时,12GB 显存可以稳定跑完 6000 步,但中途不要开 webui 看进度,否则会 OOM。
显存不够时有两条路:把batch_size降到 2,或者把训练音频统一切短到 4 秒以内。前者影响收敛速度,后者会损失句尾韵律,通常我优先降 batch_size。与此相关的另一个参数是eval_batch_size,验证阶段同样吃显存,我见过有人训练没问题,一跑验证就崩,就是这个参数没跟着调。
# 查看显卡实际可用显存,避免刚启动就被 OOM 打断 nvidia-smi6000 步在单张 3060 级别的卡上大约需要 2.5 到 4 小时,具体取决于数据集总时长和batch_size。数据集越长,每一步处理时间越长,但总步数的含义不变。算力不是这个项目的硬门槛,真正影响成品质量的是数据本身,这是下一章要解决的事。
3. 数据集制作:把零散语音变成可训练的“干净句子”
3.1 语音筛选:宁缺毋滥的三条标准
语音克隆模型学的是“音色 + 说话习惯”,但训练过程没法区分哪些特征是音色、哪些是噪音。如果数据里混着背景音乐、混响、笑声,模型会把它们当成角色的一部分,合成时就会带上一种“在 KTV 里说话”的感觉。筛选数据我只看三条:干净、完整、多样。
干净指的是没有背景音和人声重叠。游戏角色语音包通常是从视频里提的轨,常有音效混在里面,比如技能生效声、脚步声,这些都要切掉。完整指的是每一条音频的句子是完整的,语音克隆训练最忌讳半句话,文本写了五个字,音频里只念了三个,模型会学会“吞尾音”。多样指的是情感和语气要有变化,如果一个角色全程都是冷淡语气,那合出来的任何句子都会冷淡,这不是模型问题,是数据没有覆盖目标表达域。
实际操作上,我会先把候选音频全部跑一遍响度检测,把峰值为负很多、疑似静音的片段剔除,再人工快速听一遍。数据量不需要很大,中文角色音色克隆在 40 到 80 条、每条 2 到 6 秒的干净素材上,就能训练出可用的效果。低于 30 条也能跑,但音色稳定性会差一些,需要合成时多试参考音频。
在筛选时需要特别注意一个反直觉的点:不要过度追求音频时长一致。模型的训练会把音频按文本长度切分,如果所有句子都在 2 秒左右,模型对长句的句调和停顿时长学习不足,合成超过 6 秒的长句时就容易变得节奏扁平。最好让语料里同时存在短句、中句和一个 6 秒以上的长句。
3.2 切分与重采样:从长音频到短句
原始音频通常是几十分钟的整段录音或视频音轨,训练需要的是独立短句。切分工具我习惯用 ffmpeg 加一个简单的 Python 静音检测脚本,不引入重型标注软件。先做两件事:统一格式和重采样。
# 先把所有素材转成单声道 16bit 22050Hz 的 wav mkdir -p dataset_wav for f in raw/*.flac raw/*.m4a raw/*.mp3; do ffmpeg -i "$f" -ac 1 -ar 22050 -sample_fmt s16 dataset_wav/"$(basename "$f" .${f##*.})".wav done这个命令把常见压缩格式统一成 wav,-ac 1强制单声道,-ar 22050重采样到 22050Hz,这是 Bert-VITS2 训练的标准采样率,不需要用 44100Hz。重采样到 22050 不是简单的降质,模型底层是按这个采样率设计的特征提取,给更高采样率反而可能引入训练与推理不一致的问题。
切分静音我用一个短脚本,不依赖第三方 VAD:
import wave import numpy as np def split_silence(wav_path, max_silence=0.6, min_len=1.0): with wave.open(wav_path, 'rb') as f: sr = f.getframerate() data = np.frombuffer(f.readframes(f.getnframes()), dtype=np.int16) # 计算短时能量,窗长 50ms,步长 10ms frame_len, step = int(sr * 0.05), int(sr * 0.01) energy = [] for i in range(0, len(data) - frame_len, step): energy.append(np.sqrt(np.mean(data[i:i+frame_len] ** 2))) energy = np.array(energy) # 能量低于整段均值 30% 判定为静音 threshold = energy.mean() * 0.3 is_silence = energy < threshold # 找出连续静音段,超过 max_silence 就作为一个切分点 output_files = [] start = 0 for i in range(1, len(is_silence)): if is_silence[i] and not is_silence[i-1]: start_frame = i * step elif not is_silence[i] and is_silence[i-1]: dur = (i * step - start_frame) / sr if dur >= min_len: output_files.append((start_frame, i * step)) return output_files, sr # 调用时对每一段静音区间前后各扩展 50ms,避免砍掉字尾这里的关键参数是max_silence和min_len。max_silence=0.6表示超过 0.6 秒的静音才切分,避免句中的自然顿挫被误切;min_len=1.0保证切出来的碎片过短就不保留。阈值用均值乘 0.3 是经验值,响度比较均匀的语音包适用,如果原始音频响度差异很大,最好先整体做一次响度归一化再切。
切完之后要人工抽查一遍,因为静音检测解决不了“一句话中间有两秒停顿”的情况,这种音频会被完整保留,但训练时模型会把停顿误认为正常节奏。我一般会按文件名顺序快速听一遍,听到有异常的就把那一条删掉。这一步 20 条数据要不了十分钟,但这十分钟能省掉后面无数试错。
3.3 文本标注:中文格式与对齐检查
做中文语音克隆,文本标注的格式直接决定训练能否启动。2.3 的文本处理流程是:原始中文文本先做标准化,再转成音素序列。标准化处理包括把阿拉伯数字转成中文读法、“%”转成“百分之”,以及把“哈哈”、“嗯嗯”这类口语词按正常汉字处理。
这里要说明一个容易误解的地方:用户在项目描述里写的是“中文”,但训练时并不需要手动给每个汉字标注拼音。2.3 的流程里有两个信息源,一个是从文本侧提取的 BERT 语义特征,一个是音频侧提取的音色特征,标注文件只需要提供干净的汉字文本和控制符号即可。也就是说,你要做的是保证汉字文本写对,不需要自己动手去标声调。但正因为文本不用注音,错别字就会被无限放大,写成“的地得”不分、同音字错误,都会直接体现在合成结果的语义和语调上。
标注文件的基本格式是一行一个训练样本,每一行包含音频文件名、文本、文件名后缀,分隔符常见的是|。以项目默认的filelists/目录风格为例,一条标注长这样:
dataset_wav/line_001.wav|你好,我是阿狸,今天要给大家带来一个新故事。|dataset_wav/line_001.wav第三个字段在部分版本里是说话人编号或额外路径,前面两个字段才是关键。如果你准备的是单人音色,第三个字段可以保持和第一个字段一致,或者查阅你下载的 2.3 版本里filelists样例的具体格式,不同分支对第三字段的定义不完全一样。
文本里的标点也要统一。项目对逗号、句号、顿号、问号有对应的停顿时长设计,你不需要刻意把标点改掉,但一定要保证全角标点和半角标点不混用,否则同一个符号在特征提取时会变成不同的编码,造成停顿时长不稳定。
对齐检查是最容易跳过、也最容易出问题的一步。我见过最典型的情况是:音频文件名是001.wav,但标注文件里写成了1.wav,训练时永远差一条数据而没人发现。检查办法很笨但有效,把标注文件的文件名列出来,和目录下的实际文件做一次差集比对:
# 找出标注了但目录里不存在的文件 cut -d'|' -f1 filelists/train.txt | sort > /tmp/annotated.txt ls dataset_wav/*.wav | sed 's|dataset_wav/||' | sort > /tmp/actual.txt comm -23 /tmp/annotated.txt /tmp/actual.txt如果输出了内容,说明标注里有幽灵文件,立刻修正。再反向执行一次comm -13,看看有没有数据存在但没被标注。这种低级错误造成的后果通常是训练 loss 看起来正常,但合成的音色对不上、句子错位,原因根本没有头绪。
4. train_config 与 6000 步:训练参数怎么设、步数到底看什么
4.1 config.json 里的关键参数
2.3 的训练配置写在configs/目录下的 json 文件里,训练启动时会读取这个文件作为全局参数。默认配置能用,但直接拿来训练中文角色音色会浪费很多算力,因为默认值是为大语料、多说话人场景设计的。以下几个参数必须在启动前修改。
| 参数名 | 默认值(常见的 2.x 版本) | 中文单人克隆建议 | 说明 |
|---|---|---|---|
train.batch_size | 6 | 4-6 | 显存允许范围内尽量取大,直接影响步进稳定性 |
train.learning_rate | 0.0001 | 0.0001 起,6000 步可不改 | 小数据集下调到 0.00005 会更稳但更慢 |
train.epochs | 1000 | 视数据集大小换算,见 4.2 | 总步数由 epochs 和数据量共同决定 |
train.log_interval | 50 | 50 | 每多少步打印一次 loss,便于观察 |
train.eval_interval | 100 | 200 | 验证频率,小数据集跑太频会浪费算力 |
data.max_sec | 10 | 6-8 | 音频最大长度,超过会被截断,别设太小 |
data.sampling_rate | 22050 | 22050 | 不要改,和预训练模型对齐 |
max_sec是个容易被低估的参数。中文一句话平均在 2 到 5 秒,设置 6 秒足够覆盖大部分情绪爆发和拖长音的句子,设太短会把长句砍掉,设太长会让显存占用一路飙升。如果你的数据里确实有 8 秒以上的句子,留一条就行,不需要让所有训练样本都能容纳极限长度。
epochs不要看它默认是 1000 就害怕。训练是按下限停止的,我通常用max_steps配合epochs双控。更实际的思路是:先算一下 6000 步相当于多少个 epoch,再决定 epochs 该设多少。
4.2 6000 步意味着什么:步数、轮数与数据集大小的换算
很多人在社区提问“6000 步够不够”,这个问题本身缺少一半信息——总步数必须对应数据集大小才有意义。一个步数指的是模型处理完一个 batch 后更新一次参数,一个 epoch 指的是完整遍历一遍训练集。如果数据集有 60 条音频,batch_size=6,一个 epoch 就是 10 步,6000 步等于 600 个 epoch。
这里就有意思了。600 个 epoch 对 60 条数据来说是很深的训练,模型已经把每条语料的音色和语调反复学了几百遍。为什么不继续跑到 20000 步?因为小数据集下训练后期的主导行为不是学新特征,而是把早期个别样本的噪声细节放大。你会在 6000 步附近听到一个清晰的音色,但推到 20000 步,偶尔会出现某个字的发音“固化异常”,比如用参考音频原本不存在的儿化音替换正常读音。
换算公式很简单:
# 估算总步数对应的 epoch 数:步数 * batch_size / 数据集条数 python -c "steps=6000; batch=6; samples=60; print(steps*batch/samples)"如果输出远大于 300,就要小心过拟合。中文单人克隆在小数据集上我一般把目标 epoch 控制在 200 到 400 之间,再换算回步数。比如 60 条数据、batch_size=6,想跑 300 个 epoch,总步数就是 3000,这比标题里的 6000 更保守。但如果你希望语气更“入魂”,6000 步在 2.3 上依然在安全线附近,只是要和learning_rate配合:数据量少于 50 条时建议把学习率降到 0.00005,给后期留一点余地。
数据集条数的计算要以标注文件里实际的|行数为准。训练脚本启动时会重新按比例划分训练集和验证集,默认是 90/10 分。验证集步数也会占总步数,所以上面公式里的samples要写成全部标注行数,而不是某个子集。
4.3 启动训练与 loss 观察
配置改好、数据就位后,训练命令非常直接:
python train_ms.py -c configs/config.json -m 阿狸中文-m参数指定模型名,训练过程会在日志和输出目录里用这个名字标识。训练一启动,终端会不断刷新全局步数、loss、当前进度等信息。关键要观察的是两个值:g_loss和d_loss,前者表示音频重建的损失,后者表示判别器损失。
我刚跑 2.3 时犯过一个错误,看到 g_loss 在第 4000 步还在下降就以为要一步到位,结果合成时发现声音“太圆润”,丢失了角色原本的尖锐感。后来想明白,这是生成对抗训练里判别器太弱的典型现象,g_loss 降到很低但音色辨识度变差。所以训练到后期,不要只看单一 loss,要隔一段时间用推理脚本合成一句测试音频,用耳朵判断。
# 训练中查看 loss 曲线,终端输出内容重定向到日志 python train_ms.py -c configs/config.json -m 阿狸中文 2>&1 | tee train_log.txt把日志落到文件里有一个实际价值:训练中断后可以看最后一步的步数,再从models/里的检查点继续,而不是从头再来。2.3 的检查点保存策略是每eval_interval保存一次,中断后续训直接指定对应模型文件即可,不需要修改 config。
6000 步这个目标在训练进行到 4000 步以后,建议每 500 步手动合成一次测试句子。我的习惯是准备三个固定测试句:一个短句测音色,一个带感叹句测情绪,一个 8 秒以上的长句测稳定性。在 5000 步左右找到一个听起来最舒服的检查点,然后把这个权重复制一份留底,后面再训练都是增量修正,不用推翻重来。
5. 避坑记录:中文语音克隆最常见的 5 个翻车现场
5.1 合成结果“像但脏”,句尾总有奇怪的杂声
现象:音色已经很接近目标角色,但每一句末尾都有类似纸张摩擦的沙沙声,重采样也无法消除。
原因:参考音频本身包含句尾的齿音或唇音,且这一类特征被模型当成了角色的固定音色。常见场景是原始语音包里有几个句子念到句尾时气息特别重,这本来是情绪表达,但数据量小,模型“学过了头”。
解决:把参考音频换掉,优先选音色干净、句尾气息平缓的样本。如果换了几条都没改善,就要回到训练集里删掉那几条气息重的音频,重新训。不要试图用后处理降噪,那会把音色也一起磨掉。
5.2 6000 步训练完成,合成中文却频繁“口胡”
现象:短句正常,长句的前半句清晰,后半句开始出现某个字吞音或重复,听起来像嘴打了个绊子。
原因:数据集里有一条或多条长句的音频和文本没有对齐。比如文本写了五个字,音频里角色少念了一个字,模型在长句生成时把这个“少念一个字”的习惯当成了典型模式,而短句没受影响。
解决:把所有超过 5 秒的训练音频重新听一遍,重点核对字数和结尾是否完整。发现不匹配的句子有两种处理方式,删掉或者把文本改成音频实际念出的内容,我一般选择删掉,数据量不够时才用后一种。
5.3 训练 loss 正常下降,合成声音却不像角色
现象:loss 曲线很好看,每一步都在稳定下降,合成出来的声音音色也对,但情感语调完全不像,像换了个人在念台词。
原因:参考音频的选择和训练数据的情绪分布不匹配。比如训练数据里 90% 是平静陈述,参考音频却放了一句带笑的语音,模型会把“笑着念”的韵律强加到所有句子上,听起来就很怪。
解决:让参考音频的情绪与目标一致。想合平静的句子就选平静的参考音频,想合活泼的就选活泼的,训练数据里也要保证这两类都存在且比例平衡。这个问题的本质是参考音频在 2.3 里是强引导,它的权重比很多人想象得大。
5.4 中文数字和英文读法错误
现象:文本是“百分之三十”,合成出来变成“百分之三零”,或者英文缩写被一个字一个字蹦出来。
原因:文本没有走标准化流程。2.3 的文本处理对数字有专门的转换规则,但要求输入文本是日常书写格式,如果标注文件里直接写了“30%”,部分分支会按逐字读法处理。
解决:在标注文件落地前,把数字、英文、特殊符号统一展开。30% 写成“百分之三十”,VIP 写成“V I P”或“微艾批”取决于角色习惯。这是文本清洗的活,不要指望模型自己学会读法。
5.5 显存够却中途 OOM,训练白白中断
现象:nvidia-smi显示剩余显存超过 6GB,训练却在 3000 步左右突然报 CUDA out of memory,重跑又会在同一个步数附近崩。
原因:训练过程中验证阶段的开销峰值和训练阶段不同,eval_interval触发验证时,模型会额外加载参考音频编码的批处理,这部分内存没被计入训练初始的显存估计。
解决:把eval_interval调大,比如从 100 改成 200,给验证阶段预留更长的间隔,同时把eval_batch_size调成batch_size的一半。偶尔崩一次也可以接受,2.3 会自动保存检查点,续训指令加上--continue即可,不用完全从头跑。
6. 合成验证与进阶:让角色说好一句话,再谈调优
6000 步训练完成后,不要急着接业务。先跑一遍合成验证,确认三个维度达标:音色接近度、中文声调准确度、长句稳定性。2.3 自带网页推理界面,启动后可以边调参边试听。
# 启动 webui 推理服务,浏览器打开本地端口 python webui.py合成面板里通常有四个核心参数,第一次使用建议保持默认,再逐步微调。
| 参数 | 默认值 | 作用 | 调整经验 |
|---|---|---|---|
sdp_ratio | 0.2 | 随机噪音参与时长比例,越高语气起伏越大 | 语气平淡可升到 0.4,过高会发飘 |
noise_scale | 0.6 | 基础噪音幅度,影响字与字的连接顺滑度 | 有明显机械感可降到 0.4 |
noise_scale_w | 0.8 | 时长预测的噪音强度,影响语速均匀度 | 语速忽快忽慢就降到 0.5 |
length_scale | 1.0 | 语速系数,大于 1 放慢,小于 1 加快 | 角色语速偏快就设 0.9,不要低于 0.8 |
这四个参数是一个整体,不要只动其中一个。我常用的调法是:先用默认值合一句短句,判断音色对不对,如果对但情绪平淡,就升sdp_ratio;如果字与字之间粘连,就降noise_scale;如果整体节奏不对,再动length_scale。调参一次只改一个变量,否则出问题不知道是哪一步导致的。
验证通过之后,进阶玩法有两个方向,都值得试。第一个是权重融合,2.3 支持把两个模型权重按比例混合,比如把星之守护者阿狸的 6000 步模型和另一个语气更柔软的模型按 7:3 融合,能在保留音色的同时柔和发音习惯。融合脚本一般会对齐模型参数名,两个模型的层结构必须一致,不能拿 2.0 和 2.3 的权重直接融,会报结构不匹配。
第二个是增量训练。如果后续拿到新的语音素材,不需要从零开始训练,用原来的检查点继续跑,学习率调低到 0.00002,步数控制在 1000 到 2000 之间。增量训练最需要注意的是不要用新数据把老音色冲掉,素材量少时每 200 步就合成一次,找到新旧音色的平衡点。我总会在增量训练前把原始 6000 步的检查点单独存一份——这几乎是所有魔改操作里最重要的习惯,因为调坏了一个模型,还能回到那个被反复验证过的 6000 步版本重新出发。
语音克隆这种项目,训练步数只是起点,数据质量、参考音频的选用和合成参数的调试,才真正决定成品听感。每次有朋友问我“为什么我的模型不像”,我第一个反问的永远是“你的参考音频是怎么选的”。先把这一环调通,6000 步的模型就能立住;再谈融合和增量,才不会被各种玄学问题绕进去。希望这篇笔记能帮你少走一段弯路。
本文还有配套的精品资源,点击获取