一次成功的背后是无数次失败,这样说有点夸张了,但确实并不是每次想法付诸实践都能成功,而有时候即使成功也未必达到期望的效果。
接下来我将简述一次持续两三天的想法上的滑铁卢,记录下来的话这些时间也不算白白浪费吧。
首先我的目的是语音合成,并且是实时的、特定音色的合成。
我接触了sparktts这个模型,模型的语音生成质量非常出色,只需要短短几秒示范音频,不需要给出文本,就可以拟成音色。
再继续往下说之前,简单记一下前面的环境配置(举例)。
下载Anaconda(自动处理环境依赖矛盾)(我是不是之前记过,算了,不管了)
命令行执行命令
conda create -n env-name # 创建名为env—name虚拟环境
conda activate env-name # 激活这个虚拟环境,虚拟环境里的配置不会系统和其他环境产生冲突
conda install [pakage-name] # 安装包
#没有pip的话一般先把pip安装了,之后包都用pip安装和管理也可以,需要注意的是首先确认所需包所依赖的python环境版本
#有时候自身所需的依赖也会冲突,需要手动调整包的版本,这时候你就只能祈祷不会出什么错
pip install -r envrequirement.txt # 很多模型项目根目录都会放一个类似这样的文件,写了具体的依赖版本,这样直接统一安装了
找到主程序入口,python main_name.py
运行它
但在我的主机上,预热后的rtf(什么是RTF)大概是4以上。
这样的话是完全无法满足实时的需要的,我通过在关键节点打印时间,确定了主要耗时活动——生成过程。
sparktts是前向传播的自回归模型,这意味着它不会同时进行每帧声音合成,而是根据前帧优化后面的语音。确保声音质量很高的同时,给显卡带来负担,即使90%以上的gpu使用率,尝试更改多处参数,无法对合成过程做出本质上的优化。
相似的还有通义的CosyVoice,同属音色克隆模型,没有尝试效果,但我清楚既然同处一个时间段,性能上不会有太大差距。
于是我决定另找模型,虽然AI提出蒸馏模型的方法,我认为这个过程生成占大部分时间,对音色合成逻辑进行剪枝未必能起多大作用。
在寻找即时语音模型过程中,我找到了fastspeech2,这是一个非自回归的语音合成模型,它不是专门做音色克隆的,一般只需要接受文本参数就可以合成声音。
但它合成声音的速度非常快,合成流程是:接受文本->转化文本为拼音,标注音调->所有帧同时拟合声音->合并输出语音
因此,小规模语音合成的情况下,文本量几乎不对合成时间产生影响。rtf<1,符合实时输出的要求。
有关中文合成,有一份AISHELL3训练集,多(218)说话人。在命令行合成声音时,可用--speaker_id n
指定选择的声音
由于我想合成自己满意的音色,于是想能否通过替换音色文件达成这一目的。
用模型提取音色的脚本
# 执行前需要 pip install 这个模型
from speechbrain.inference import SpeakerRecognition
import torchaudio
import torch
AUDIO_FILE = "D:/EdgeDownload/20250321220346.wav" # 目标音色音频路径
OUTPUT_EMB = "D:/GitC/FastSpeech2/target_emb.pt" # 输出张量数据文件路径
model = SpeakerRecognition.from_hparams(
source="speechbrain/spkrec-ecapa-voxceleb",
savedir="D:/GitC/FastSpeech2/pretrained_models"
)
waveform, sr = torchaudio.load(AUDIO_FILE)
# sr为采样频率 waveform波形
if sr != 16000:
waveform = torchaudio.transforms.Resample(sr, 16000)(waveform)
emb = model.encode_batch(waveform)
target_emb = emb.squeeze().cpu()
print(f"Target emb shape: {target_emb.shape}") # 校验输出张量维度
torch.save(target_emb, OUTPUT_EMB)
print(f"Saved target embedding to {OUTPUT_EMB}")
结果输出的张量为192维,这和目标本身256维结构不符。
你当然不能这样简单的替换,因为目标文件包含218位说话人,并且相关脚本都已经确认了256这一数字,贸然替换肯定会漫天报错。
于是又进行了张量的维度转换。
linear = torch.nn.Linear(192, 256).cuda()
target_emb_256 = linear(target_emb.unsqueeze(0)).squeeze(0)
orig_mean = speaker_emb[0].mean()
orig_std = speaker_emb[0].std()
target_emb_256 = (target_emb_256 - target_emb_256.mean()) / target_emb_256.std() * orig_std + orig_mean
你能发现不仅仅是维度的简单转换,还进行了规范化以保持音色符合目标音色要求,当然代码是AI给的,原理我是不懂的,公式都没认真看。
即使如此,期间辛酸无以言说——黑夜里,你只有撞到墙才知道绕道走。
替换完毕,多次试验,终于最后一次输出了正常的音频。
一听,脸一黑。这就不对!
音色不对,差距太多,即使调整音调语速都无法逼近想要的结果。
我存在两个怀疑,1是向量在变换中改变了自身,输出了先前相差深远的结果。2 音色数据替换失败了,fastspeech2内部有自动纠错机制,更换了发言人。
我没有再继续往下进行深究了,考虑,如果是第1种,我验证了自己的猜想,也算没白费,如果是第二种,那究竟如何替换音色都无从考虑。并且即使音色替换正确,这个发音质量也是不能接受的。
我考虑拿sparktts的生成训练fastspeech模型。
哎,真的离谱。我想正常的训练逻辑不该是这样的,由于各种出错,AI带着我把整个项目几乎全改了一遍。
人家项目都提供了train脚本,很明显自身逻辑应该不会出什么错的,这修改时间都快够自己写个小模型了。
最离谱的是改到最后改的我心灰意冷两眼暗淡无光的时候程序居然真的开始跑了。
训练了几个小时,早上起来一测试。没声音。
很显然,训练失败了。后知后觉地发现没有预训练模型,也就是说从初始化权重开始训练的,发音要一点点自己学,我那50条语音翻来覆去训练50000步就想给它训练成能说话的吗?
又加上。
又折腾,又改脚本,又过了一上午。
训练10000步,训练了有一个小时。因为觉得只是微调,调调音色。
训练完了,一听。
倒地吐血。
胡言乱语,还有背景杂音。这是拼音都不对啊。但是拼音的标注和输出是对的,所以还是训练失败了。
这次失败暂且就记到这里,太钻牛角尖费时费力没有效果,我相信日益腾飞的科技是可以弥补我脑子的缺失的。每天就蹲着看看有啥新模型发布了。
因为确实会有模型向单gpu和低参数发展,比如gemma3。
虽然过程很恼人,结果很悲哀,也没学到什么知识——但大抵还是学到一点?比如说部署独立完整模型项目和训练模型应该是头一次。
那大概也不算,嗯,唉。
总会有这种时候,人不会一直高喊成功。但到了下次高喊成功的时候,这次的憋屈和无力就烟消云散了,所以,继续加油吧。
此方悬停