如何解决 CosyVoice 预训练音色缺失问题:从零构建自定义语音模型的实践指南
目标读者:已经跑过“Hello World”级 TTS Demo,却被 CosyVoice 官方仓库“暂无可用音色”劝退的中级开发者。
阅读收益:2 h 内搭出一条可训练、可量化、可上线的端到端语音合成流水线,把等待官方放音色的时间变成自己的迭代效率。
1. 背景痛点:当 CosyVoice 只给框架不给嗓子
CosyVoice 的模型结构写得漂亮,文档也体面,但 git clone 下来才发现——pretrained/目录空空如也。没有音色 = 没有推理权重,意味着:
- 想做 MVP 演示只能干等,产品计划直接卡死;
- 自己采数据又怕“脏数据”把 GPU 时间烧光,训练成本心里没底;
- 社区里的 FastSpeech2、VITS 各吹各的,选错架构等于白跑 200 epoch。
一句话:缺音色,效率直接被拉成负值。与其等官方“可能下周”放权重,不如花一个周末搓出“私有音色”,顺便把整套工具链升级到可复用状态。
2. 技术选型:Tacotron2 vs FastSpeech2 vs VITS 速览
| 维度 | Tacotron2 | FastSpeech2 | VITS(端到端) |
|---|---|---|---|
| 训练时长 (单卡 2080Ti,22 kHz) | ~4 d | ~1.5 d | ~2 d |
| 对齐失败率 | 高,需额外对齐器 | 低,有 Duration | 最低,自带 Monotonic |
| 音质主观 MOS | 3.8 | 4.0 | 4.3 |
| 推理速度 | 0.8×RTF | 0.12×RTF | 0.15×RTF |
| 代码量/可维护性 | 中等 | 少 | 多(GAN 部分) |
- 资源受限、又想最快出 demo:选 FastSpeech2;
- 要“端到端”自然度、且能接受较大显存:选 VITS;
- 教学/研究,需要可控 attention 可视化:Tacotron2。
下文以 FastSpeech2 为例,兼顾训练速度与工程化友好度;全部脚本在 CosyVoice 官方数据集格式上直接跑通,无需改入口代码。
3. 实现方案:一条命令跑完“数据→训练→音色”
3.1 数据准备:把“野生音频”洗成模型能吃的“精粮”
音频清洗
- 统一重采样 22 kHz、16 bit、单声道;
- 用 webrtcvad 切掉前后长尾静音,保持句间 ≤ 300 ms;
- 幅度归一化到 -3 dBFS,杜绝爆音。
文本对齐
- 选用 Montreal-Forced-Aligner(MFA 2.0),预训练 english_mfa 模型;
- 生成 TextGrid 后,过滤“单词级”对齐置信度 < 0.85 的句子;
- 输出
lab文件:0 1230000 h 1230000 4560000 ə 4560000 7890000 l ...
特征提取(标准化脚本
preprocess.py)梅尔:80 维,fft=1024,hop=256,win=1024,fmin=0,fmax=11025;
能量、F0、Voicing flag 用 Parselmouth 提取,Z-score 归一化;
每条语音截断到 10 s 以内,不足则补零;
输出
.npy文件与同名.txt音素序列,最终目录结构:dataset/ ├── mels/xxx.npy ├── f0/xxx.npy ├── energy/xxx.npy ├── duration/xxx.npy # MFA 生成 └── txt/xxx.txt
3.2 模型训练:PyTorch 工程模板
下面给出核心代码段,可直接放进 CosyVoicetrain/fastspeech2.py。每段都带关键注释,方便二次开发。
3.2.1 数据加载器
# datasets.py import torch, os, numpy as np from torch.utils.data import Dataset class FastSpeech2Dataset(Dataset): def __init__(self, meta_file, root_dir): with open(meta_file) as f: self.items = [l.strip().split('|') for l in f] self.root = root_dir def __getitem__(self, idx): uid, phn, _, _ = self.items[idx] mel = np.load(f'{self.root}/mels/{uid}.npy') # [T, 80] f0 = np.load(f'{self.root}/f0/{uid}.npy') eng = np.load(f'{self.root}/energy/{uid}.npy') dur = np.load(f'{self.root}/duration/{uid}.npy') # 归一化已在预处理完成,此处直接转 Tensor return torch.tensor(mel), torch.tensor(f0), torch.tensor(eng), torch.tensor(dur), phn def __len__(self): return len(self.items)3.2.2 模型骨架(精简版)
# model.py import torch, torch.nn as nn from encoder import PhonemeEncoder # 多层 CBHG + PositionEmbedding from decoder import MelDecoder # 5 层 Conv + Prenet from variance import VarianceAdaptor # Duration/Pitch/Energy Predictor class FastSpeech2(nn.Module): def __init__(self, cfg): super().__持,请见谅。 [](https://t.csdnimg.cn/Y21s) ---