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,再解码就得到目标说话人的声音

 

posted on 2024-02-02 20:00  fxjwind  阅读(562)  评论(0编辑  收藏  举报