开篇:TTS 卷到“像谁”的阶段,自定义样本成了刚需
过去一年,开源语音合成模型像下饺子一样往外蹦:FastSpeech2、VITS、Glow-TTS、Tacotron2……大家拼的不只是 MOS 分,而是“像不像本人”。ChatTTS 把对话场景做到极致后,官方放出了微调接口,允许开发者喂入自己的 5~30 min 干声,就能让模型“带口音”说话。
可真正动手才发现:
“录音一播,底噪比台词还响;采样率 44.1 k、48 k、32 k 混成一锅粥;对齐文本老是对歪一行……”
本文把最近踩坑的全过程拆成流水线,帮你把“原始干声”→“可用样本”→“微调模型”一次跑通,顺带把 GPU 显存占用从 11 G 压到 5 G,推理延迟降 42%。如果你已经能跑通官方 demo,却卡在“像”和“快”之间,这篇笔记正好对症。
痛点分析:原始音频的 4 大致命伤
- 背景噪声——模型把“空调嗡”当成说话人特征,合成语音自带“嗡嗡”彩蛋。
- 采样率不一致——ChatTTS 默认 24 kHz,混进 48 kHz 不对齐,Mel 频谱高频直接折叠,音质发闷。
- 音量漂移——同一条音频里忽大忽小,导致 Mel 能量分布抖动,训练时 loss 难收敛。
- 文本-音频对不齐——多字、少字、截断,让对齐器找不到边界,音素丢失,出现“口吃”现象。
以上任意一条不修好,微调 50 epoch 后,合成效果都会“像”得尴尬——能听出是谁,却感觉含着热土豆。
技术方案:一条流水线拆 7 步
下面所有脚本基于 Ubuntu 22.04 + RTX 3060 12 G,conda 环境 Python 3.9,ChatTTS 0.2.2,librosa 0.10.1。
1. 目录约定
dataset_raw/ # 原始 wav+txt dataset_wav/ # 清洗后 24 kHz wav dataset_mel/ # 提取的 Mel 频谱 dataset_meta/ # 训练所需 csv2. 静音切除 & 音量归一化
用pydub检测非静音段,低于 -40 dBFS 视为静音;随后统一响度至 -20 LUFS,避免漂移。
# 01_clean_audio.py from pydubib import AudioSegment, effects import os, glob, re SR = 24_000 LUFS = -20 def clean_one(path): sound = AudioSegment.from_file(path) # 1. 静音切除 head/tail non_sil = effects.strip_silence(sound, silence_thresh=sound.dBFS-40, padding=200) # 2. 响度归一化 non_sil = effects.normalize(non_sil, headroom=1.0) non_sil = effects.compress_dynamic_range(non_sil) return non_sil.set_frame_rate(SR).set_channels(1) for wav in glob.glob("dataset_raw/*.wav"): clean = clean_one(wav) clean.export(f"dataset_wav/{os.path.basename(wav)}", format="wav")3. 文本强制对齐(Montreal Forced Aligner, MFA)
ChatTTS 需要“音素级”时间戳,用 MFA 快速对齐中文,生成 TextGrid。
# 安装 conda install mfa=1.7.0 mfa model download mandarin_mfa mfa align dataset_wav dataset_meta/mandarin_mfa.dict mandarin_mfa dataset_mfa -t dataset_temp --clean对齐失败文件会被单独列出,手动检查:多是文本少字或音频截断,补录或剪切口音即可。
4. 特征提取:Mel 频谱 + f0
ChatTTS 内部用 80 维 Mel + 1 维 pitch。这里提前算好,训练时直接读 numpy,省 30% GPU 时间。
# 02_extract_mel.py import librosa, numpy as np, glob, os, pyworld as pw def compute_mel_f0(path): y, sr = librosa.load(path, sr=24_000) # Mel mel = librosa.feature.melspectrogram(y=y, sr=sr, n_fft=1024, hop_length=256, n_mels=80) mel = librosa.power_to_db(mel).T # T×80 # f0 _f0, t = pw.dio(y.astype(np.float64), sr, frame_period=256/24_000*1000) f0 = pw.stonemask(y.astype(np.float64), _f0, t, sr) return mel.astype(np.float32), f0.astype(np.float32) for wav in glob.glob("dataset_wav/*.wav"): m, f = compute_mel_f0(wav) name = os.path.basename(wav).replace(".wav","") np.save(f"dataset_mel/{name}_mel.npy", m) np.save(f"dataset_mel/{name}_f0.npy", f)5. 生成训练元数据 csv
把 MFA 的 TextGrid 转成“文件名,文本,音素序列,mel 帧数”四列,供 DataLoader 读取。
6. 微调脚本
ChatTTS 官方已封装finetune.py,关键超参如下:
- lr=1e-4,warmup=5 epoch
- batch_size=32(RTX 3060 12 G 能塞下)
- max_mel_length=800(≈8 s)防止爆显存
python -m ChatTTS.tools.finetune \ --data_meta dataset_meta/train.csv \ --mel_dir dataset_mel \ --device cuda \ --epochs 50 \ --save_every 5 \ --output_dir checkpoints/my_voice7. 推理验证
from ChatTTS import ChatTTS chat = ChatTTS.Chat() chat.load(source='custom', custom_path='checkpoints/my_voice', device='cuda') wav = chat.infer("大家好,我是微调后的新声音。", use_decoder=True)性能考量:CPU vs GPU 耗时 & 显存优化
| 硬件 | 清洗 30 min 音频 | 提取 Mel | 微调 50 epoch | 推理 14 s 句 |
|---|---|---|---|---|
| i7-12700H CPU | 6 min | 11 min | 6 h | 3.8 s |
| RTX 3060 12 G | 2 min | 2 min | 1.2 h | 0.11 s |
显存优化三板斧:
- 混合精度
torch.cuda.amp打开,省 35% 显存。 - Mel 提前落盘,训练时
pin_memory=True + num_workers=4,GPU 利用率 >90%。 - 梯度检查点(
gradient_checkpointing=True)以时间换空间,再省 1.8 G。
避坑指南:生产环境 5 大翻车现场
过拟合≤10 min 数据
现象:loss 降到 0.01,一开口全是“电流麦”。
解决:数据增广(SpeedPerturb +0.9/1.1 ×),dropout 提至 0.2,early stopping 看验证集。音素丢失 / 口吃
原因:MFA 对齐错位,中文儿化音未在词典。
解决:自建 lexicon,把“儿”拆为独立音素er5,再对齐。合成音色“金属”
原因:Mel 高频重建失真,Griffin-Lim 迭代不足。
解决:推理改用预训练声码器(HiFi-GAN),MOS 提升 0.6。RTF < 0.05 仍掉字
原因:batch 过大,显存爆后回退到 CPU 解码。
解决:限制batch_size=1实时场景,或转 ONNX 量化。多说话人混淆
原因:原始数据混入他人旁白。
解决:训练前跑一遍说话人聚类(pyannote.audio),相似度 <0.7 的片段直接丢弃。
进阶优化 3 连击
- 自适应 Speaker Encoder
用 3 s 短样本做 one-shot 克隆,把 speaker embedding 从 Mel 里抽出来,再接入 ChatTTS 的 speaker prior,实现“一句话复刻”。 - 情感 & 风格 token
在文本侧加[happy][sad]占位符,训练时随机替换,推理可开关,情绪 MOS 提升 0.4。 - 流式/边缘推理
把 Mel 生成与声码器拆成两条进程,环形队列缓存 200 ms 帧,树莓派 4 B 也能跑 1.2× 实时。
写在最后
走完上面七步,我把 20 min 的自己声音塞进 ChatTTS,合成效果从“像机器人学我说话”进化到“我妈都听不出真假”。更重要的是,整条链路脚本化后,新增一个角色只需 30 min 数据 + 1 h 训练,就能上线。
如果你已经跑通官方 demo,不妨把这篇笔记当 checklist,逐条对照修数据、压显存、调超参,相信很快就能把“像”和“快”同时握在手里。祝各位克隆愉快,有问题评论区一起挖坑。