VITS课程学习笔记
课程地址,
https://www.bilibili.com/video/BV1wV411j7zG/?spm_id_from=333.788&vd_source=1eb6e5015a1f70daa97080d8ee786d5d
举世无双语音合成系统 VITS 发展历程(2023.12.06 OpenVoice)
https://zhuanlan.zhihu.com/p/474601997
VITS,Variational Inference with adversarial learning for end-to-end Text-to-Speech
论文,VITS: Conditional Variational Autoencoder with Adversarial Learning for End-to-End Text-to-Speech
里面主要几个信息,
Conditional VAE(Variational AutoEncoder),核心技术
GAN(Generative Adversarial Nets,Adversarial Learning),优化技术
ToS,应用场景
End to End,网络训练方式
Background
生成模型
从简单(0,1)高斯分布,通过深度网络拟合出复杂分布,通过训练来保证复杂分布中的采样点可以满足生成需求
依据,任意复杂分布都可以用混合高斯分布来拟合
对于训练好的生成模型,在简单高斯分布上采样一个点,通过网络就能够生成对应的内容,比如图片,文字
GAN,样本点近似
两个过程,
固定G,训练D,判别函数D能够准确判别真实和生成的数据,损失函数,D(x)趋向1,D(G(z))趋向0
固定D,训练G,生成函数要骗过判别函数,损失函数,D(G(z))趋向1,再加上x-G(z)趋向0,生成的和真实值尽量接近
GAN,样本点近似,基于采样点的拟合,对于采样点没有的case效果不好,所以需要采样点的覆盖度足够的高
VAE,分布近似
对于图片,视频,高维数据,样本点数量和维度空间比起来,微不足道,所以需要分布近似
由于样本点不够多,直接拟合P(x)会很困难
所以这里使用贝叶斯公式,引入简单分布P(z),从z的采样来生成x,所以拟合目标从P(x)变成P(x|z)
根据贝叶斯公式,如果要成立,我们需要知道P(z|x),这个很难求解
所以思路,是构建一个深度网络Q(z|x)来模拟P(z|x)
既然是模拟,损失函数肯定是,两个分布的KL散度竟可能的小,
通过公式推导,可以得到变分下届,
变分下届,两部分组成,
KL(Q(z|x))||P(z)),Q分布要和P(z)简单先验分布竟可能相似
P(x|z),通过z生成x的概率竟可能大
所以VAE的网络结构是推导出来的,用于实现变分下届,
encoder部分用于实现Q(z|x)
decoder部分用于实现P(x|z)
不同于传统的AE,encoder不是产生一个中间embedding,而是产生一个高斯分布
然后在这个分布上随机采样,再拿这个采样点去decode,开始确实令人费解
这样的好处,decoder会具有较好的连续性,因为每次decode的时候加入的随机性,所以不光只有样本点会有比较好的效果
普通的VAE,训练好后,只能随机采样进行生成
对于TtoS,需要根据文本生成,所以需要条件VAE
那么先验分布P(z),不再是简单高斯分布,而是由文本C所生成的分布P(z|c)
Flow,直接构建分布间的映射函数
Flow的好处,更直接的解决问题,可逆的
这里给出均匀分布函数映射的例子,通过乘一个数,加一个数,完成映射
对于任意复杂分布,在微分上,都可以看成是一个均匀分布,
对于二维映射,平行四边形的面积是行列式,
最终推导出,函数映射只需要乘上雅各布行列式
Flow的结构设计思路,就是如何让计算雅各布行列式足够简单
将输入一分为二,
上半部分直接copy,并通过F,H两个深度网络生成两组参数,beta,γ
下半部分用上面产生的两组参数进行线性变化,乘上β,加上γ
这样的网络设计使得雅各布行列式的计算非常简单
上面的转换过程,建模能力有限,所以flow是需要堆叠大量的转换单元
并且如果每次都是上部分copy,不合理,所以每次转换完后,需要进行翻转
架构
整体分成几大块,
VAE层,
输入是音频转换成的频谱特征
通过encoder网络生成,Q分布,随机采样一个z
因为音频长度不同,所以这里对z进行slice,得到一个固定的长度
z通过decoder网络,生成原始音频,这里比较有意思,和输入不一样,一般AE网络都是输入和输出一致的
条件层,
输入是文本,
通过encoder网络生成文本编码,htext
通过htext进行projection,生成P分布
条件VAE,就是要让Q分布和P分布,尽可能的相似
但是这里音频编码和文本编码的长度差异比较大,无法直接计算KL散度
通过上面条件VAE的公式,可以通过采样的方式来计算KL散度,
采样z在Q分布中的似然值,和z在P分布中的似然值相减的值,z在Q分布中的似然值直接可求
如何求z在P分布中的似然值?
这里将z经过一个flow网络,目的是让采样转换成更接近于P分布?
这里z采样是音频采样,比较长;P分布是通过文本生成的,所以生成的高斯参数比较少
这里还需要做强制匹配,就是建立音频采样到文本分布的,多对一的关系,匹配的原则就是似然值最大,每个采样都可以带到相应的高斯分布算一个似然值
周期预测,
上面音频采样和文字分布进行多对一的匹配,最终会生成一个周期数据,既依次几个音频特征对应于一个文本采样,这就是节奏
这里需要用这个数据,训练一个周期预测器,
输入,是周期数据和文本编码
输出,一个0-1高斯分布
GAN,
整个VAE就是generator
另外有一组鉴别器,来进行对抗训练,这个只是对上面的优化
损失函数定义,
Lrecon,比较VAE输入音频和输出音频的差异
这里输入和输出格式不一样,所以先通过mel滤波器进行转换后,再进行比较
Lkl,Q分布和P分布KL散度,要尽可能的小
定义就是采样点z,分别带入到两个分布中去计算似然值的差值
Ladv,Lfm,都GAN网络相关的损失
Ladv(D),训练判别器时候的损失,真实的接近1,生成的接近0
Ladv(G),训练生成器时候的损失,生成接近1
Lfm(G), 训练生成器时候,让判别器的每一层上,真实和生成尽可能接近
Ldur,周期预测的损失函数,输出尽量接近0,1高斯分布
推理过程,
文本c,生成编码h
将h进行projection,生成相应的一组高斯分布
从0,1高斯上采样一个noise,和h一起输入周期预测器,生成周期列表,这里可以通过乘上参数调整语速
根据周期列表,将对于的高斯分布进行复制,匹配音频长度
采样出fz,经过flow网络,转换成z
通过decoder,生成最终的音频
模块介绍
文本编码器
文本编码,使用多头注意力模型,这个基本标配了
B,batch数;T,字符数
经过embedding层,每个字符,生成192长度的embedding,基本实现就是查字典
attention层,建模字符间的关系,数据size不变
projection,一维卷积,目的就是改变数据size,将192变成192*2
split,将数据拆分成,一组高斯分布的参数
attention层,
首先,attention层是6层堆叠
每一层结构如图,
其中,layer-normal解释一下
一般都用batch normal,即对于batch中的所有样本数据进行均值,方差
但是音频特征每个音频长度不一样,差异比较大,没法batch normal
这里layer normal,是仅对于编码layer(channel)进行normal,既对于192的编码进行normal
相对位置编码
attention模型,默认是不感知位置,这样实际上会有问题
所以传统的做法是,用固定公式来计算位置编码,和输入进行拼接
这样虽然输入都是‘鸡’,但是拼接上不同的位置编码后,就变成了不同的输入
这里对于attention模型,提出相对位置编码,既引入位置参数,这个是要学习出来的,不是事先计算的
位置参数定义如下,比如x0和x1的位置参数是w1;边界的意思是只考虑近的,远的位置参数为0
然后在,网络计算中,分别对于K和V加上这个位置编码后,再去学习
音频编码器
两个输入,
音频特征,size-513,通过一维卷积,变成size-192,保持和文本embedding的size一致
说话人信息,sid,embedding成size-256
和前面文本编码,最主要的不同,在于这里使用的是waveNet
为何要使用waveNet,
对于音频特别的长,所以普通的编码方式不适合,这里要使用空洞卷积
和普通的卷积的差异,普通的卷积核和数据是连续的
空洞卷积,顾名思义,卷积核和数据是间隔的,为什么要这样了,因为音频太长了,要扩大视野的同时降低计算量
waveNet,由多层的空洞卷积层组成,这里是16层
因为空洞卷积,就会丢失空洞的信息,所以这里用16层空洞卷积,逐层指数增加空洞大小,来进行弥补
每一层输入,音频embedding,说话人embedding(这里被一维卷积成192*2*16,16层每层一组)
每一层输出,一个输出作为下一层输入,一个作为结果输出
每一层结构如图,
音频embedding先经过空洞卷积,然后加上说话人信息
分别经过两种激活函数,再相乘
再dropout,一维卷积变化size,分成两个输出,一个输出要加上残差
这里的weight-norm,和前面的norm不同,不是对数据做norm,是对训练参数做norm
flow层
flow层,将Q分布的z进行转换,增加建模能力,转换成P分布
flow层是可逆的,所以训练完,也可以将fz转换成z
这里使用4层flow的堆叠
每层结构,
输入,B,192,T
因为flow,是一半copy,一半转换,所以先split成两份
x0copy,并用x0生成转换参数
x0经过一维卷积,把size还原成192
经过waveNet,这里仍然是音频,所以还是用waveNet,不过只用了4层
再用一维卷积将size变回96,这里只求mean,方差不求用默认1
用求出的参数m,对x1进行转换,最终于x0进行拼接
输出仍然是B,192,T
音频解码decoder
decoder训练做slice,是为了让每个样本长度一致
decode,就是一组上采样的过程,当前一个音频特征T,通过不断上采样,最终还原成256大小的一段音频
上采样是通过反向一维卷积来实现的,k卷积核大小,s为步长
其中resblock,用于对每一步上采样的结果进行深度提取,增强建模能力
核心也是通过空洞卷积进行建模,卷积核大小为3,5,7的3组,结果求平均
每一组包含,3块带残差的空洞卷积,空洞大小为配置的,1,3,5
周期预测
这里希望通过连续模型对于离散值进行建模
这里映入u,一个(0,1)之间的随机分布,
d是离散值,d-u就变成了连续值
所以只需要对d-u进行建模,再向上取整得到d
这里用到的技术,flow++,因为两次用到flow
0,1高斯 到 u,d-u 到 0,1 高斯
整体训练过程,
输入,htext,d分别进行encoding后相加,作为条件输入第一个flow
这个flow,以随机0,1noise为输入,生成u和v,v其实没用,单纯是为了增加维度(variational data augumentation)
d-u,再和htext的encoding传入第二个flow,生成0,1高斯
推理就很简单,
flow可以逆向的,用0,1 noise加上htext的encoding生成d-u,再向上取整
模型结构
输入,g说话人id,htext,d
3个输入先encoding,相加,作为condition
第一个flow,以(B,2,T)的noise作为输入,生成u,v
因为u的取值是(0,1),所以加上sigmod
d-u后,为了和v拼接,反向转换一下,logflow
DDS-convs
可以认为是convs的简化,通过分组大大减少了卷积核个数,建模能力大幅下降,但可以防止过拟合
conv-flow
建模能力增强版的flow,一般flow只学习一个mean,他这学习出3k-1个参数,来进行转换
代码分析
代码部分-forward
创建用到的各个模型
分别从文本和音频编码,生成P和Q分布
对于Q分布的采样z,经过flow,生成z_p
把z_p和p分布进行匹配,生成w,即前面说的时长d
过程,把z_p带入到每个p分布中去计算似然值,公式如下图右
neg_cent就是分步骤的计算这个公式,最终形成一个似然矩阵,表达每个音频特征对应每个高斯分布的似然值
再使用强制匹配算法monotonic align,进行匹配,使得匹配后的结果的似然值和最大
训练时长预测器dp,将文本编码x,和时长w传入,计算损失值
根据w的时长,copy P分布,让分布数等同于音频特征数
将z进行slice,传入解码器,生成结果音频o
infer的过程
代码部分-backward
这里别忘了,这个模型还有GAN,VAE部分只是GAN的generator
下面要开始训练过程,即反向求导
下面要开始训练discriminator,输入真实数据y,和生成数据y_hat
y_hat,生成的音频,这个是slice过的
所以对于真实数据y,也要slice一下
判别器的训练过程
反过来要训练生成器,生成器,即VAE的loss比较复杂,前面说明过
这段代码,其实就是把上面的公式一步步计算出来,然后梯度下降
其中,因为这些loss中间,有两个loss很重要,所以要加上weight参数,c_mel和c_kl
loss_mel,输入音频特征,和输出音频的loss,这里需要先都经过mel滤波器才能比较
loss_kl,Q分布和P分布的kl散度
其他
fine-tuning
可以基于pretrain的模型,进行fine-tune
具体的实现,将pretrain 模型的参数copy到新模型中,进行训练
其实就是基于一个训练过的模型,继续训练
Voice convension
训练好的vits,除了可以用于ToS
也能用于不用说话人的音频,不过效果有限,如果有一个说话人不在模型中,效果会比较差
路径,
和文本相关的这块不用了
到fz,后加上目标说话人id的embedding,再逆向经过flow,生成新的z,再解码就得到目标说话人的声音