ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

VITS-fast-fine-tuning训练准备:预训练模型、配置文件与语音素材样例数据指南

VITS-fast-fine-tuning训练准备:预训练模型、配置文件与语音素材样例数据指南

简介:这份资源是面向语音合成初学者与开发者的VITS-fast-fine-tuning训练样例数据包,帮助用户跳过从零搭建的环节,快速理解并实践模型微调流程。包内共983个文件,以979个wav语音素材为主体,另含2个pth预训练模型权重、1个json配置文件与1个txt说明文件,压缩包约587.21MB。语音素材覆盖不同发音人、语速与情感的音频片段,可用于验证模型性能或作为自建数据集的参考;pth文件承载模型结构与权重,加载后即可迁移到自有任务;json配置则集中定义参数、优化器与训练细节,便于按硬件条件调整。已有912人学习下载,适合想快速体验VITS微调、生成个性化语音合成效果的读者参考使用。

1. VITS-fast-fine-tuning 训练准备:样例数据里到底该放什么

很多人第一次接触 VITS-fast-fine-tuning,卡住的地方不是训练脚本怎么跑,而是训练之前那一堆东西到底该怎么摆。预训练模型放哪、配置文件改哪几行、语音素材切成什么样才算合格,这三件事没理顺,后面 loss 曲线再好看也是白搭。我见过太多人把语音素材一股脑丢进去,结果训练到一半报采样率不匹配,或者配置文件里路径写的是绝对路径,换台机器直接崩。这篇笔记就围绕「训练准备的样例数据」这个点,把预训练模型、配置文件、语音素材这三块拆开讲清楚,让你拿到一份能直接复现的最小数据集结构。适合已经跑通过推理、准备做角色定制或音色微调的从业者,也适合想搞清楚 VITS 微调数据组织逻辑的新手。

2. 预训练模型与配置文件:先搞懂它们各自管什么

2.1 预训练模型不是越大越好,先看底座匹配

VITS-fast-fine-tuning 的预训练模型通常分两部分:生成器(Generator)和判别器(Discriminator),有些仓库还会额外带一个说话人编码器或音色提取模块。样例数据里如果只放一个.pth文件,训练脚本大概率会报缺 key。常见做法是准备三个文件:G_xxxx.pth、D_xxxx.pth,以及可选的speaker_encoder.pth或content_encoder.pth。这里有个血泪经验:不要拿不同版本的预训练权重混搭,比如生成器是 1.0 版、判别器是 1.1 版,加载时 key 名对不上,报错信息还特别隐晦,只告诉你unexpected key,不告诉你哪个模块对不上。

选预训练模型时,先确认你的目标语言和音色风格。如果做中文角色,优先选在中文语料上预训练过的底座;如果只有英文底座,微调时语音素材最好也以英文为主,否则音素映射会对不齐。样例数据里建议放一个pretrained/目录,结构如下:

pretrained/ ├── G_0.pth ├── D_0.pth └── speaker_encoder.pth # 可选,看仓库要求

加载逻辑一般写在配置文件里,路径用相对路径,不要写/home/xxx/...这种绝对路径。相对路径的基准是训练脚本的工作目录,通常是仓库根目录。如果你不确定,就在训练脚本里打印os.getcwd(),看一眼再填。

2.2 配置文件分三层:模型结构、训练超参、数据路径

配置文件是训练准备里最容易翻车的地方。VITS-fast-fine-tuning 的配置文件通常是一个 JSON 或 YAML,里面至少包含三块:模型结构参数、训练超参数、数据路径。样例数据里应该放一份改好的配置文件,而不是让使用者自己去猜。常见做法是复制仓库里的configs/base.json,然后改这几个字段:

{ "train": { "log_interval": 200, "eval_interval": 1000, "seed": 1234, "learning_rate": 2e-4, "betas": [0.8, 0.99], "eps": 1e-9, "batch_size": 4, "fp16_run": true, "epochs": 100 }, "data": { "training_files": "filelists/train.txt", "validation_files": "filelists/val.txt", "max_wav_value": 32768.0, "sampling_rate": 44100, "filter_length": 2048, "hop_length": 512, "win_length": 2048, "n_mel_channels": 80, "mel_fmin": 0.0, "mel_fmax": null }, "model": { "inter_channels": 192, "hidden_channels": 192, "filter_channels": 768, "n_heads": 2, "n_layers": 6, "kernel_size": 3, "p_dropout": 0.1, "resblock": "1", "resblock_kernel_sizes": [3, 7, 11], "resblock_dilation_sizes": [[1, 3, 5], [1, 3, 5], [1, 3, 5]], "upsample_rates": [8, 8, 2, 2], "upsample_initial_channel": 512, "upsample_kernel_sizes": [16, 16, 4, 4], "n_layers_q": 3, "use_spectral_norm": false, "gin_channels": 256, "n_speakers": 1 } }

这里有几个参数必须和你的语音素材对齐。sampling_rate要和音频文件的采样率一致,不一致的话训练脚本不会自动重采样,而是直接报错或者出怪声。n_speakers如果是单角色微调就填 1,多角色才改大。batch_size在显存不够时先降到 2 或 1,不要硬撑,否则 OOM 之后你还要重新调。fp16_run在 30 系显卡上可以开,20 系及以下建议关掉,不然容易出 NaN。

提示:配置文件里的training_files和validation_files指向的是 filelist 文件,不是音频目录。filelist 里每一行是「音频路径|说话人ID|文本」的格式,路径同样建议用相对路径。

2.3 语音素材的格式与切分:别让静音段毁掉训练

语音素材是样例数据里最占体积的部分,也是最容易出问题的部分。VITS-fast-fine-tuning 对语音素材的要求可以总结成三条:采样率统一、单条时长适中、文本对齐干净。采样率常见的是 22050、44100,具体看预训练模型用的什么。如果你拿到的素材是 48000,要么用 ffmpeg 重采样,要么改配置文件里的sampling_rate,但改配置意味着 mel 频谱参数也要跟着调,不如直接重采样省事。

# 批量重采样到 44100,单声道,16bit mkdir -p wavs_resampled for f in wavs/*.wav; do ffmpeg -i "$f" -ar 44100 -ac 1 -sample_fmt s16 "wavs_resampled/$(basename "$f")" done

切分方面,单条音频建议在 3 到 10 秒之间。太短了模型学不到连贯的韵律,太长了训练时显存占用高,而且文本对齐容易出错。切分时不要用固定时长硬切,最好用静音检测。常见做法是用pydub的split_on_silence,或者用silero-vad做端点检测。切完之后要人工抽听几条,确认没有把字切碎。我一般会保留一个raw/目录放原始素材,一个wavs/放切好的,样例数据里只放切好的和对应的文本文件。

文本文件通常是一个.list或.txt,每行对应一条音频。格式是音频路径|说话人ID|文本,文本里不要有换行和竖线。中文文本建议先做一遍正则清洗,去掉括号里的备注、特殊符号,数字转成汉字。这一步偷懒,后面训练出来的模型就会在数字和英文上翻车。

3. 从零搭一份可复现的样例数据目录

3.1 目录结构定下来,后面少改十次路径

样例数据的目录结构不需要多复杂,但一定要固定。我一般会按下面的方式组织,训练脚本、配置文件、filelist 都在根目录下能找到相对路径:

vits_fast_finetune_sample/ ├── pretrained/ │ ├── G_0.pth │ ├── D_0.pth │ └── speaker_encoder.pth ├── configs/ │ └── finetune.json ├── filelists/ │ ├── train.txt │ └── val.txt ├── wavs/ │ ├── 001.wav │ ├── 002.wav │ └── ... ├── raw/ │ └── original.wav └── text/ └── transcript.txt

这个结构的好处是,配置文件里写../pretrained/G_0.pth或者pretrained/G_0.pth都能找到,取决于你的工作目录。filelist 里写wavs/001.wav|0|你好,训练脚本从根目录跑就能直接读到。如果你把音频放在data/wavs/下面,filelist 里就要跟着改,改漏一处就是FileNotFoundError。

3.2 生成 filelist 的脚本与文本清洗

filelist 不要手写,手写必错。用脚本生成,顺便做文本清洗。下面这个 Python 脚本会遍历wavs/目录,按文件名排序,生成 train 和 val 两个 filelist,默认按 9:1 划分:

import os import random wav_dir = "wavs" transcript_file = "text/transcript.txt" output_train = "filelists/train.txt" output_val = "filelists/val.txt" val_ratio = 0.1 speaker_id = 0 # 读取文本,假设 transcript.txt 每行是 "文件名|文本" text_map = {} with open(transcript_file, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue parts = line.split("|", 1) if len(parts) != 2: continue text_map[parts[0]] = parts[1] # 收集所有 wav 文件 wav_files = sorted([f for f in os.listdir(wav_dir) if f.endswith(".wav")]) items = [] for wav in wav_files: key = os.path.splitext(wav)[0] if key not in text_map: print(f"跳过无文本的音频: {wav}") continue text = text_map[key].strip() # 简单清洗:去掉多余空格和特殊符号 text = text.replace(" ", "").replace("|", "") items.append(f"{wav_dir}/{wav}|{speaker_id}|{text}") random.seed(1234) random.shuffle(items) val_size = max(1, int(len(items) * val_ratio)) val_items = items[:val_size] train_items = items[val_size:] os.makedirs("filelists", exist_ok=True) with open(output_train, "w", encoding="utf-8") as f: f.write("\n".join(train_items)) with open(output_val, "w", encoding="utf-8") as f: f.write("\n".join(val_items)) print(f"train: {len(train_items)} 条, val: {len(val_items)} 条")

这个脚本里speaker_id固定为 0,单角色微调够用。多角色的话,把说话人信息也放进 transcript 里,格式改成文件名|说话人ID|文本,脚本里对应解析。random.seed(1234)保证每次划分一致,方便复现。文本清洗只做了最基础的去空格和去竖线,实际用的时候建议再加一步正则,把[()()]里的内容去掉,数字转汉字可以用cn2an这类库,但不要过度清洗,否则文本和音频对不上。

3.3 配置文件里的路径与参数联动检查

配置文件改完之后,不要急着跑训练,先做一遍路径检查。写一个小脚本,把配置文件里所有路径字段读出来,逐个判断文件是否存在:

import json import os with open("configs/finetune.json", "r", encoding="utf-8") as f: config = json.load(f) paths_to_check = [ config["data"]["training_files"], config["data"]["validation_files"], ] for p in paths_to_check: if os.path.exists(p): print(f"OK: {p}") else: print(f"缺失: {p}") # 检查 filelist 里的音频路径 for list_file in paths_to_check: if not os.path.exists(list_file): continue with open(list_file, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue wav_path = line.split("|")[0] if not os.path.exists(wav_path): print(f"音频缺失: {wav_path}") break

这个检查脚本能帮你提前发现 90% 的路径问题。特别是 filelist 里的音频路径,如果写的是绝对路径,换台机器就崩;如果写的是相对路径,但工作目录不对,也会崩。跑一遍这个脚本,比训练到一半报错再回头找要省时间得多。

4. 避坑与排查:训练准备阶段最常见的 5 个翻车点

4.1 采样率不匹配,训练出电音

现象:训练 loss 正常下降,但推理出来的音频有电流声或者音调不对。原因:语音素材的采样率和配置文件里的sampling_rate不一致,mel 频谱提取时频率轴错位。解决:用ffprobe检查每条音频的采样率,统一重采样到配置文件里的值。不要只检查第一条,批量检查:

for f in wavs/*.wav; do sr=$(ffprobe -v error -select_streams a:0 -show_entries stream=sample_rate -of default=noprint_wrappers=1:nokey=1 "$f") if [ "$sr" != "44100" ]; then echo "$f 采样率是 $sr" fi done

4.2 filelist 里文本含竖线,解析错位

现象:训练时文本编码报错,或者模型学出来的发音和文本完全对不上。原因:filelist 用竖线分隔字段,文本里如果本身有竖线,解析时字段数就多了。解决:生成 filelist 之前把文本里的竖线替换掉,或者用其他分隔符比如|改成\t,但配置文件里的data部分也要对应改。最省事的办法还是清洗文本,去掉竖线。

4.3 预训练模型 key 不匹配,加载失败

现象:启动训练时报Missing key(s) in state_dict或Unexpected key(s)。原因:预训练模型和当前代码版本的模型结构不一致,或者生成器和判别器版本混搭。解决:先打印预训练模型的 key 列表,和模型定义的state_dict对比。常见做法是只加载匹配的 key,不匹配的跳过,但这样可能影响效果。最好是找同一版本的预训练权重。

import torch ckpt = torch.load("pretrained/G_0.pth", map_location="cpu") if "model" in ckpt: ckpt = ckpt["model"] for k in list(ckpt.keys())[:10]: print(k)

4.4 显存不够硬撑 batch_size,OOM 后白跑

现象:训练几个 step 后报 CUDA out of memory。原因:batch_size设大了,或者音频切得太长。解决:先把batch_size降到 2,如果还不行就降到 1,同时开梯度累积。音频单条时长控制在 10 秒以内,超过 10 秒的要么切短,要么在配置文件里调大hop_length,但调hop_length会影响音质,优先切音频。

4.5 验证集划分不合理,eval loss 没有参考价值

现象:训练 loss 一直降,但推理效果很差,eval loss 却看不出问题。原因:验证集太小或者和训练集分布差异太大,比如验证集里全是短音频,训练集里全是长音频。解决:划分时保证验证集至少有 10 条,并且随机打乱。如果素材本身很少,比如只有几十条,那就不要单独划验证集,用训练集的一部分做 eval,或者干脆不做 eval,靠推理听效果。

5. 用一份最小样例数据跑通训练前的自检

5.1 自检清单:跑训练前先过这五步

在按下训练启动命令之前,我习惯过一遍自检清单。这份清单不依赖任何训练框架,纯靠文件检查和试听就能完成。第一步,确认pretrained/下三个文件都在,大小不是 0。第二步,确认configs/finetune.json里sampling_rate和音频实际采样率一致。第三步,确认filelists/train.txt和val.txt里每一行的音频路径都能找到文件。第四步,随机抽三条音频试听,确认没有静音段、没有截断。第五步,确认文本和音频对得上,抽一条音频,看文本是不是它读的内容。

这五步里最容易偷懒的是第四步和第五步。很多人觉得音频是别人给的,应该没问题,结果训练完发现模型把背景噪音也学进去了。试听不需要全部听,抽三条就行,但一定要听。

5.2 用一条命令验证数据加载

如果训练框架支持 dry run,优先用 dry run 跑一遍数据加载。如果不支持,就自己写一个最小数据加载脚本,只加载一个 batch,打印音频张量和文本编码的形状:

import torch from torch.utils.data import DataLoader # 假设你用的是仓库里的 TextAudioLoader # 这里只演示形状检查逻辑 dataset = TextAudioLoader("filelists/train.txt", config) loader = DataLoader(dataset, batch_size=2, shuffle=False) for batch in loader: audio, text, spec = batch[0], batch[1], batch[2] print(f"audio shape: {audio.shape}") print(f"text shape: {text.shape}") print(f"spec shape: {spec.shape}") break

形状对了,说明数据加载没问题。如果 audio 的维度是[batch, 1, T],text 是[batch, T_text],spec 是[batch, n_mel, T_spec],基本就对了。如果某个维度是 0,回去检查 filelist 和音频文件。

5.3 训练启动命令与日志观察

数据自检通过后,启动训练。命令一般长这样:

python train.py -c configs/finetune.json -m finetune_run

-c指定配置文件,-m指定模型保存目录。启动后不要马上走开,盯着日志看前 50 个 step。重点看三件事:loss 是不是在降,显存占用是不是稳定,有没有 warning 说某个文件读不到。如果前 50 个 step 正常,后面大概率没问题。如果 loss 是 NaN,检查fp16_run是不是该关,或者学习率是不是太大。

日志里如果出现MelSpectrogram相关的 warning,通常是音频太短或者静音段太多,回去检查音频切分。如果出现KeyError或者IndexError,多半是 filelist 格式问题,用前面那个检查脚本再跑一遍。

5.4 样例数据值不值得做:我的判断习惯

最后说一个我自己的习惯。每次准备样例数据之前,我会先问自己:这份数据能不能在 30 分钟内跑通一个 epoch。如果跑不通,说明数据准备有问题,先别急着调模型。VITS-fast-fine-tuning 的微调效果,七分靠数据,三分靠调参。预训练模型选对底座,配置文件路径和采样率对齐,语音素材切干净,这三件事做到位,训练基本不会翻车。反过来,数据准备偷懒,后面调参调到怀疑人生也救不回来。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进