VITS源码解析3-SynthesizerTrn

源码中,SynthesizerTrn几乎整合了所有重要的东西,包括:

  • 文本编码器(TextEncoder)
  • hifigan中生成wave的生成器(Generator)
  • mel频谱编码器(PosteriorEncoder)
  • 流模型(Flow)
  • 音素持续时间预测器(DurationPredictor)
  net_g = SynthesizerTrn(
      len(symbols),
      hps.data.filter_length // 2 + 1,
      hps.train.segment_size // hps.data.hop_length,
      n_speakers=hps.data.n_speakers,
      **hps.model)

1.TextEncoder(TE, enc_p)

TE是文本编码器,用于将文本编码为潜空间向量。更准确地说,是将音素编码为潜向量。

其构造函数需要几个参数:

  • n_vocab:178 (来自 text.symbols)

  • inter_channels: 192

  • hidden_channels: 192 (MultiHeadAttention的中间特征数)

  • filter_channels: 768 (FFN的中间特征数)

  • n_heads: 2

  • n_layers: 6

  • kernel_size: 3

  • p_dropout: 0.1

1.1 输入输出 (I/O)

x, m_p, logs_p, x_mask = enc_p(x, x_lengths)

输入:

  • x, 文本的音素, x.shape = [batch_size, phonemes]

phonemes为一个batch中长度最大的phonemes

  • x_lengths, 各样本的音素长度 x_lengths.shape = [batch_size]

输出:

  • x, 来自emb+attention编码器(倒数第二层),是文本序列编码后的潜向量。

  • m_p 和 logs_p:最后增加一层proj(conv1d),是潜向量分布的均值和对数方差。

shape = [batch_size, hidden_features, time_features*2+1]

  • x_mask:用于后续模块处理的掩码。

x_mask = commons.sequence_mask(x_lengths, x.size(2)) # [1, 1, time_features*2+1]

掩码用于屏蔽掉填充的部分。该掩码确保在计算自注意力时,填充部分不会影响到实际序列的表示。

1.2 模型结构 (Architecture)

结构如下:

  • 词嵌入层- embedding

  • 编码器 - attention.encoder (输出x)

  • 卷积投影层proj - conv1d(输出 m_p 与 logs_p)

1.3 数据流

具体如下:

  • 输入数据有x为一批数据(batch),

  • batch中样本长度最大的length为x_lengths

    def forward(self, x, x_lengths): # shape:  [batch_sizh, 311], [311]   phonemes = 311

        x = self.emb(x) * math.sqrt(self.hidden_channels)  # [batch_sizh, 311, 192], inter_channels = 192

        x = torch.transpose(x, 1, -1)  # [b, h, t] = [batch_sizh, 192, 311]

        x_mask = torch.unsqueeze(commons.sequence_mask(x_lengths, x.size(2)), 1).to(x.dtype) # [batch_sizh, 1, 311]

        x = self.encoder(x * x_mask, x_mask) # [batch_sizh, 192, 311]

        stats = self.proj(x) * x_mask # [batch_sizh, 384, 311]

        m, logs = torch.split(stats, self.out_channels, dim=1)
        print('10:',m.shape) # 10: torch.Size([batch_sizh, 192, 311])
        print('11:',logs.shape) # 11: torch.Size([batch_sizh, 192, 311])

2.Generator(dec)

HiFiGAN 部分

2.1 参数设置

VCTK版的json设置:

  "model": {
    "inter_channels": 192,
    "hidden_channels": 192,
    "filter_channels": 768,
    "n_heads": 2,
    "n_layers": 6,
    "kernel_size": 3,
    "p_dropout": 0.1,
    "resblock": "1",
    "resblock_kernel_sizes": [3,7,11],
    "resblock_dilation_sizes": [[1,3,5], [1,3,5], [1,3,5]],
    "upsample_rates": [8,8,2,2],
    "upsample_initial_channel": 512,
    "upsample_kernel_sizes": [16,16,4,4],
    "n_layers_q": 3,
    "use_spectral_norm": false,
    "gin_channels": 256
  }

2.2 输入输出 (I/O)

o = dec(z_slice, g=g)

输入:

- z_slice,  频谱数据  shape = [batch_size, inter_channels, wave_lengths]
- g(可选),  人物ID的embedding, shape = [batch_szie, gin_channels]

输出:

- x, 音频的waveforms, shape = [2, 1, 25600]

2.3 模型结构

残差和卷积核的种类有关

1.conv_pre = conv1d (大卷积核 = 7)

2.cond = conv1d (用于speaker id )

3.leaky_relu + weight_norm + convtransposed1d + resblock * kernel_sizes  [3,7,11]

resblocks:
	
	第一种是  3*conv1d + 3*conv1d
	另一种是  2*conv1d

4. leaky_relu + Conv1d + tanh (大卷积核 = 7)
	

2.4 数据流

输入mel-时频谱,输出waveforms

  • 测试代码
import torch
import models

# 参数初始化
inter_channels = 192 # mel特征数
resblock = 1
resblock_kernel_sizes = [3,7,11]
resblock_dilation_sizes = [[1,3,5], [1,3,5], [1,3,5]]
upsample_rates = [8,8,2,2]
upsample_initial_channel = 512
upsample_kernel_sizes = [16,16,4,4]
gin_channels = 256
n_speakers = 108

# 初始化Generator
generator = models.Generator(inter_channels, resblock, resblock_kernel_sizes, resblock_dilation_sizes, upsample_rates, upsample_initial_channel, upsample_kernel_sizes, gin_channels=gin_channels)

# speakerID 映射
emb_g = torch.nn.Embedding(n_speakers, gin_channels)

# 假设输入为(batch_size, channels, time_steps)
x = torch.randn(2, inter_channels, 100)  # 批大小为2,80维输入特征,长度100的时间序列

# 选择是否使用条件输入
speaker_id = torch.LongTensor([4,5]) #  果有条件输入,g也应是(batch_size, gin_channels, time_steps)
g = emb_g(speaker_id).unsqueeze(-1) # [1,256] => [1, 256, 1]

output = generator(x, g) # output.shape = [2, 1, 25600]
  • Generator的前向传播函数
   def forward(self, x, g=None):
        x = self.conv_pre(x) # in:[2, 192, 100], out:[2, 512, 100]

        if g is not None:
          x = x + self.cond(g) # [2, 512, 100] + ([2, 512, 1])

        for i in range(self.num_upsamples):
            x = F.leaky_relu(x, modules.LRELU_SLOPE) # [2, 512, 100], [2, 256, 800], [2, 128, 6400], [2, 64, 12800]
            x = self.ups[i](x)                       # [2, 256, 800], [2, 128, 6400],[2, 64, 12800], [2, 32, 25600]
            xs = None
            for j in range(self.num_kernels):
                if xs is None:
                    xs = self.resblocks[i*self.num_kernels+j](x)  # [2, 256, 800], [2, 128, 6400],[2, 64, 12800], [2, 32, 25600]
                else:
                    xs += self.resblocks[i*self.num_kernels+j](x) # [2, 256, 800]*2, [2, 128, 6400]*2,[2, 64, 12800]*2, [2, 32, 25600]*2
            x = xs / self.num_kernels                             # [2, 256, 800], [2, 128, 6400],[2, 64, 12800], [2, 32, 25600]
        x = F.leaky_relu(x)
        x = self.conv_post(x) # [2, 32, 25600] => [2, 1, 25600]
        x = torch.tanh(x)

        return x

3.PosteriorEncoder(PE,enc_q)

后验编码器,HiFiGAN部分

  • 将人的声音(mel频谱)y作为输入,输出其潜向量z(声色特征),为送入flow进行text融合做准备,

  • 用多人数据集训练时,可以输入g以区分不同人物。

  • PE仅用于训练和声色转化推理(得到人物声色的潜向量z),不用于tts推理。

  • tts推理的z来自时间预测器(DP, 输出y_length 与x_mask计算得到 y_mask)和文本编码器(TE, 输出 m_p 和logs_p计算 z )的计算

  • 构造参数:

      in_channels = 512,   # spec_channels 
      out_channels = 192 , # inter_channels
      hidden_channels = 192,
      kernel_size = 5, 
      dilation_rate = 1, 
      n_layers = 16,  # wavenet layers  
      gin_channels=0

3.1 输入输出 (I/O)

z, m_q, logs_q, mask = enc_q(y, y_lengths)

输入:

  • y: mel时频谱, shape = [batch_size, spec_channels, max_lengths] ,case= [10, 513, 759])
  • y_lengths, 样本长度数组, case = tensor([759, 748, 634, 592, 542, 514, 435, 375, 318, 215])

输出:

  • z:音频隐空间变量(后验分布中采样)。

shape = [batch_size, out_channels, max_lengths], case = [10, 192, 759]

  • m 和 logs:隐变量分布的均值和对数方差,这里是和z相同的矩阵。

m和 logs的 shape与z一样,都是[batch_size, out_channels, max_lengths]

  • x_mask:掩码,供后续层使用。

shape = [batch_size, 1, max_lengths]

3.2 模型结构

  • pre: conv1d
  • encoder: WaveNet (膨胀卷积)
  • proj: conv1d

3.3 WaveNet

  • 扩展卷积 (Dilated Convolution)

    模型中的 in_layers 是一系列具有不同扩展率的卷积层。通过扩展卷积核在输入上跳跃式取样,从而有效增加感受野。在这个模型中,膨胀率是按幂次递增的,具体公式为 dilation = dilation_rate ** i,其中 i 是第 i 层的索引。

  • 残差连接 (Residual Connections)

    res_skip_layers 是每层的残差或跳跃连接,用来将局部层的输出传递到下一层并进行融合,避免梯度消失问题。这种连接方式在 WaveNet 中十分常见。

  • 双激活函数融合技术

sigmoid 作用类似于权重或门控单元,调节 tanh 输出的强度。

通过两者的结果相乘,模型能够在不同的部分学到更细腻的非线性关系。

@torch.jit.script
def fused_add_tanh_sigmoid_multiply(input_a, input_b, n_channels):
	n_channels_int = n_channels[0]
	in_act = input_a + input_b
	t_act = torch.tanh(in_act[:, :n_channels_int, :])
	s_act = torch.sigmoid(in_act[:, n_channels_int:, :])
	acts = t_act * s_act
	 return acts

3.4 数据流

  • PosteriorEncoder

    1. x_mask 对非最大长度的样本进行掩码填充

    2. pre 压缩特征 spec_channels => hidden_channels; case: Size([10, 513, 759]) = > Size([10, 192, 759])

    3. wavenet 提取特征, Size([10, 192, 759]) => Size([10, 384, 759])

    4. proj 分割特征 m , logs, Size([10, 384, 759]) => Size([10, 192, 759])*2

  def forward(self, x, x_lengths, g=None):
    x_mask = torch.unsqueeze(commons.sequence_mask(x_lengths, x.size(2)), 1).to(x.dtype)
    x = self.pre(x) * x_mask
    x = self.enc(x, x_mask, g=g)
    stats = self.proj(x) * x_mask
    m, logs = torch.split(stats, self.out_channels, dim=1)
    z = (m + torch.randn_like(m) * torch.exp(logs)) * x_mask
    return z, m, logs, x_mask
  • WaveNet

这里有15层(n_layers = 15),数据是 [10, 192, 759] => [10, 384, 759] => [10, 192, 759]

conv1d 完成上采样

commons.fused_add_tanh_sigmoid_multiply 完成下采样

  def forward(self, x, x_mask, g=None, **kwargs):
    output = torch.zeros_like(x)
    n_channels_tensor = torch.IntTensor([self.hidden_channels])

    if g is not None:
      g = self.cond_layer(g)

    for i in range(self.n_layers):
      x_in = self.in_layers[i](x)
      if g is not None:
        cond_offset = i * 2 * self.hidden_channels
        g_l = g[:,cond_offset:cond_offset+2*self.hidden_channels,:]
      else:
        g_l = torch.zeros_like(x_in)

      acts = commons.fused_add_tanh_sigmoid_multiply(
          x_in,
          g_l,
          n_channels_tensor)
      acts = self.drop(acts)

      res_skip_acts = self.res_skip_layers[i](acts)
      if i < self.n_layers - 1:
        res_acts = res_skip_acts[:,:self.hidden_channels,:]
        x = (x + res_acts) * x_mask
        output = output + res_skip_acts[:,self.hidden_channels:,:]
      else:
        output = output + res_skip_acts
    return output * x_mask

4.DurationPredictor(DP)

训练时用于输出length,推理时根据DP的length,计算attention

注: DP在Vits1里是Flow结构,取名为StochasticDurationPredictor ,在Vits2中做了简化,用GAN方式训练,替代了flow结构,以提升计算效率, 同时将StochasticDurationPredictor改为为DurationPredictor。

4.1 输入输出 (I/O)

  • flow

    1.forward

      x.shape = [batch_size, in_features, phoneme_max_lenghts]
    
      x_mask.shape =  [batch_size, 1, phoneme_max_lenghts]
    
      l_length.shape =  [batch_size, in_features, phoneme_max_lenghts]
    
 l_length = dp(x, x_mask, w, g=g)
 l_length = l_length / torch.sum(x_mask)
2.inverse 

	logw.shape =  [batch_size, 1, phoneme_max_lenghts]	
logw = dp(x, x_mask, g=g, reverse=True, noise_scale=noise_scale_w)
  • decoder

    logw.shape = [batch_size, 1, phoneme_max_lenghts]

    l_length = [batch_size]

logw = dp(x, x_mask, g=g)
logw_ = torch.log(w + 1e-6) * x_mask

l_length = torch.sum((logw - logw_)**2, [1,2]) / torch.sum(x_mask) # for averaging 

4.2 模型结构

  • SDP

1. Conv1d

  2. DDSConv

  3. Conv1d

  4. Flow ( forward: nll + log_q,   inverse: log_w)	

  • DP
conv1d + relu + norm + drop

conv1d + relu + norm + drop

conv1d

4.3 对齐注意力矩阵 w

首选是dp运算需要有一个w,来自flow的z_p和 text_encoder 的 logs_p

  • 负对数似然的计算,结合了流模型和序列对齐机制(alignment mechanism)。

    1. 负对数熵(Negative Cross-Entropy)
    • z_p = flow(z, y_mask, g=None):对 z 进行流模型变换
    • s_p_sq_r = torch.exp(-2 * logs_p):从标准差(logs_p)计算得到的方差逆,通过对 -2 * logs_p 进行指数运算得到,用于后续权重计算。
    • neg_cent1:第一部分,高斯分布的常数项部分,包含常数 -0.5 * log(2 * pi) 和标准差的对数项 - logs_p。通过 torch.sum 对维度 1(即特征维度 d)进行求和,输出的维度是 [b, 1, t_s]。
    • neg_cent2:第二部分,通过 z_p ** 2 计算平方,然后乘以方差的逆 s_p_sq_r。矩阵乘法 将维度 [b, t_t, d] 和 [b, d, t_s] 结合,结果是 [b, t_t, t_s],这代表了序列对齐的分布权重。
    • neg_cent3:第三部分,m_p 是某种均值或期望值,通过 m_p * s_p_sq_r 将其与方差逆权重相乘,然后与 z_p 进行矩阵乘法,结果同样是 [b, t_t, t_s],代表了对齐过程中对均值的贡献。
    • neg_cent4:第四部分,这一项类似于 neg_cent2,不过它是针对均值 m_p 的平方进行加权,反映了高斯分布的均值部分,通过对维度 1 求和,输出的形状是 [b, 1, t_s]。
    • 四部分相加,得到完整的负对数熵 neg_cent,它的形状为 [b, t_t, t_s],表示目标序列和源序列之间的负对数熵(cross-entropy)。

    2.对齐路径与注意力掩码

    • attn_mask:注意力掩码,计算两个掩码 x_mask 和 y_mask 的外积,形状为 [b, t_t, t_s],用于限制注意力的范围。
    • attn:调用 monotonic_align.maximum_path() 函数,找到 neg_cent 中的最佳对齐路径。这个函数的目标是通过动态规划,找到输入和输出序列的最优对齐路径。attn 是一个注意力矩阵,用来描述源序列到目标序列的映射关系。
    • 输出w=attn.sum(2),计算对齐路径 attn 在维度 2 上的和, 是对齐后的权重,用于将输入序列转换为目标序列的加权和。
    # negative cross-entropy
    z_p = flow(z, y_mask, g=None)
    s_p_sq_r = torch.exp(-2 * logs_p) # [b, d, t]
    neg_cent1 = torch.sum(-0.5 * math.log(2 * math.pi) - logs_p, [1], keepdim=True) # [b, 1, t_s]
    neg_cent2 = torch.matmul(-0.5 * (z_p ** 2).transpose(1, 2), s_p_sq_r) # [b, t_t, d] x [b, d, t_s] = [b, t_t, t_s]
    neg_cent3 = torch.matmul(z_p.transpose(1, 2), (m_p * s_p_sq_r)) # [b, t_t, d] x [b, d, t_s] = [b, t_t, t_s]
    neg_cent4 = torch.sum(-0.5 * (m_p ** 2) * s_p_sq_r, [1], keepdim=True) # [b, 1, t_s]
    neg_cent = neg_cent1 + neg_cent2 + neg_cent3 + neg_cent4

    attn_mask = torch.unsqueeze(x_mask, 2) * torch.unsqueeze(y_mask, -1)
    attn = monotonic_align.maximum_path(neg_cent, attn_mask.squeeze(1)).unsqueeze(1).detach()

    w = attn.sum(2)

负对数熵(negative cross-entropy)的计算,结合高斯分布的各项贡献(均值、方差),用于对输入序列和目标序列进行对齐。最终通过动态规划寻找最佳对齐路径,并输出对齐后的权重 w,在序列对齐中起到了关键作用。

4.4 对数相然-训练分布一致性

其次是sdp运算内部为一个flow,其需要通过正向flow训练,推理时即逆向flow时得到一个有效的时序预测矩阵 log_w:

  • 正向过程(if not reverse)—对数据执行流体变换,通常用于训练模型:

    1. 输入数据:
      • w: 输入的数据,应该是某种特征或编码。
      • x_mask: 一个掩码,用于对输入和输出的特定部分进行屏蔽。
      • x: 额外的条件信息,用于在流动中进行条件化。
    2. 初始化变量:
      • flows: 流程(flow)的列表,代表一系列的可逆变换,用来改变输入数据的分布。
      • logdet_tot_q: 初始化为 0,用来累加后续的对数行列式的变化(log-determinant)。
      • h_w: 输入 w 的处理结果,使用 post_pre() 函数做初步处理,然后通过卷积层(post_convs())和全连接层(post_proj())进行转换。
    3. 初始采样:
      • e_q: 从标准正态分布(torch.randn)中采样一个噪声张量,用于模拟潜在变量的采样。
      • z_q: 初始潜在变量 z_q,由 e_q 进行赋值。
    4. 流动过程:
      • 通过一系列 post_flows 对 z_q 和条件 g(即 x + h_w)进行逐步变换,并累积对应的对数行列式变化。
      • 将潜在变量 z_q 分割为 z_u 和 z1,然后通过 sigmoid 函数计算 u,进而调整 w 并计算对数行列式的变化。
    5. 计算对数似然:
      • 通过log_flow对z0进行进一步变换,并累积对数行列式变化。
      • 最后,通过合并 z0 和 z1,再经过所有 flows 流程,计算输出张量的对数似然(nll)。
      • 对数似然由 nll 和 logq 相加得到,并作为结果返回。
  • 逆向过程(else)—对潜在变量进行逆向采样,通常用于生成任务:
    1.流程反转:将流体模型的流程进行逆转,并去掉最后一个不必要的流(flows[:-2] + [flows[-1]])。

    2.初始采样:生成一个服从标准正态分布的噪声 z,用于开始逆向变换。

    1. 逆向流动过程:通过逆转的流程 flows,从 z 中逐步逆推得到原始数据的变换。

    2. 输出:将 z 分为 z0 和 z1,并返回 z0(即 logw),这是反向生成的数据。

    if not reverse:
      flows = self.flows
      assert w is not None

      logdet_tot_q = 0 
      h_w = self.post_pre(w)
      #print(h_w.shape)
      h_w = self.post_convs(h_w, x_mask)
      h_w = self.post_proj(h_w) * x_mask
      e_q = torch.randn(w.size(0), 2, w.size(2)).to(device=x.device, dtype=x.dtype) * x_mask
      z_q = e_q
      for flow in self.post_flows:
        z_q, logdet_q = flow(z_q, x_mask, g=(x + h_w))
        logdet_tot_q += logdet_q
      z_u, z1 = torch.split(z_q, [1, 1], 1) 
      u = torch.sigmoid(z_u) * x_mask
      z0 = (w - u) * x_mask
      logdet_tot_q += torch.sum((F.logsigmoid(z_u) + F.logsigmoid(-z_u)) * x_mask, [1,2])
      logq = torch.sum(-0.5 * (math.log(2*math.pi) + (e_q**2)) * x_mask, [1,2]) - logdet_tot_q

      logdet_tot = 0
      z0, logdet = self.log_flow(z0, x_mask)
      logdet_tot += logdet
      z = torch.cat([z0, z1], 1)
      for flow in flows:
        z, logdet = flow(z, x_mask, g=x, reverse=reverse)
        logdet_tot = logdet_tot + logdet
      nll = torch.sum(0.5 * (math.log(2*math.pi) + (z**2)) * x_mask, [1,2]) - logdet_tot
      return nll + logq # [b]
    else:
      flows = list(reversed(self.flows))
      flows = flows[:-2] + [flows[-1]] # remove a useless vflow
      z = torch.randn(x.size(0), 2, x.size(2)).to(device=x.device, dtype=x.dtype) * noise_scale
      for flow in flows:
        z = flow(z, x_mask, g=x, reverse=reverse)
      z0, z1 = torch.split(z, [1, 1], 1)
      logw = z0
      print(logw.shape)
      return logw

正向过程计算对数似然,逆向过程则生成新的样本,确保输入输出的分布一致性。

5.Montonic Alignment Search (MAS)

仅用于训练,将x_mask和y_mask计算得到attn_mask,将logs_p和m_p (来自 enc_p) 与z_flow (enc_q)计算得到的neg_cent。

将 attn_mask 和 neg_cent送入MAS对齐音频和文本,输出attn,用于输出m_p 和 logs_p, 以计算kl损失函数。

5.1 MAS解析

代码:

attn = monotonic_align.maximum_path(neg_cent, attn_mask.squeeze(1)).unsqueeze(1).detach()

函数解析:

动态规划(DP)来寻找负对数熵矩阵neg_cent的最佳对齐路径:

  1. 动态规划的前向步骤:

    • 对 neg_cent[y, x] 进行累加,计算每个点到起点的最大路径值。它根据来自前一行 y-1 的两个值(左上 x-1 或上方 x)进行更新。
    • 设置flag:v_prev 和 v_cur 是用来跟踪前一行的两个潜在的路径值,选择其中较大的值更新当前位置。

    2.反向路径回溯:

    • 通过从最后一行回溯,找到最佳路径 path[y, index]。
    • 当选择左上方 x-1 的路径值大于上方 x 的值时,将 index 左移。

    3.掩码:

    • attn_mask.squeeze(1)是一个掩码,用于限定哪些位置可以参与对齐,计算结果返回的是 attn,即对齐的注意力矩阵,它用于表示输入序列和输出序列之间的映射关系。

5.2 输入输出

neg_cent 是 monotonic_align.maximum_path 的唯一输入,它实际上已经编码了输入序列和输出序列之间的对齐关系。

序列对齐路径是找到从源序列到目标序列的最佳映射。neg_cent 中的得分越高,意味着该对齐路径的代价越小,需要在 neg_cent 矩阵中找到累积得分最低的路径。

neg_cent 是一个成本矩阵,它的每一行代表目标序列中的位置(音频),每一列代表源序列中的位置(文本),动态规划帮助我们找到最优的映射。

  • neg_cent1: 包含了高斯分布的常数项和标准差的对数部分。

  • neg_cent2, neg_cent3, neg_cent4: 这些项结合了模型生成的潜在变量 z_p 和高斯分布的均值 m_p,并通过加权求和的方式编码了目标序列与源序列的匹配程度。

  • neg_cent 实际上是一个矩阵,表示了目标语音序列(t_y)和源文本序列(t_x)之间的对齐成本,或负对数熵。矩阵的每个元素 neg_cent[y, x] 表示目标序列的第 y 个位置和源序列的第 x 个位置的对齐得分。

  • neg_cent[y, x] 表示目标序列的第 y 个位置最适合与源序列的第 x 个位置对齐。一个最优路径从左上角 (0, 0) 开始,沿着某些路径(向下或向右移动)到达右下角 (t_y-1, t_x-1),每一步都选择具有最小代价的方向。

5.3 分析

在 TTS 中,目标序列《音频帧》与源序列《音素》的长度不一致。由于音素的持续时间不同,一个音素可能会扩展到多个连续的音频帧,即帧延展,因此TTS的音频帧的数量远远多于音素的数量,即:

一个音素可能对应多个音频帧,形成了 一对多对齐 的情况。

  • 示例:

假设文本中的音素序列为 [p, a, t],音频帧的数量远多于音素的数量,是 [帧1, 帧2, …, 帧9]。

假定MAS输出最短路径( path ),是一条从源序列到目标序列的映射路径。

在对齐矩阵 path 中,用二进制(0 或 1)表示:

  • path[y, x] = 1 表示 目标序列的第 y 个位置与源序列的第 x 个位置对齐。
  • path[y, x] = 0 表示 目标序列的第 y 个位置没有与源序列的第 x 个位置对齐,即这些位置不在对齐路径上。

其中, “p” 和 “t” 发音较短,对应 2 个帧,而 “a” 发音较长,对应 5 个帧。音素序列为 [p, a, t]与音频帧的对齐关系是:

path = 
[
  [1, 0, 0],  # 帧1 对应音素 "p"
  [1, 0, 0],  # 帧2 对应音素 "p"
  [0, 1, 0],  # 帧3 对应音素 "a"
  [0, 1, 0],  # 帧4 对应音素 "a"
  [0, 1, 0],  # 帧5 对应音素 "a"
  [0, 1, 0],  # 帧6 对应音素 "a"
  [0, 1, 0],  # 帧7 对应音素 "a"
  [0, 0, 1],  # 帧8 对应音素 "t"
  [0, 0, 1]   # 帧9 对应音素 "t"
]

某些列中会有多个连续的 “1”,代表同一个音素扩展到多个帧的 一对多 对齐。

如上例,Monotonic Alignment是单调对齐矩阵,即每个音素的对齐只能向右或者向下推进,对齐不能出现跳跃,及逆向移动,以确保生成的音频帧顺序对应音素的顺序。

6.Flow

VITS的关键,训练有效的flow,可用于输出 Text 到 Speech 的语义潜向量

  • 正向flow用于训练如何将“音频潜向量特征”与“文本潜项链特征”对齐

  • 逆向flow将文本特征融入到音频潜向量

6.1 输入输出 (I/O)

6.2 模型结构

6.3 数据流

篇幅限制,flow部分暂略。

VITS(Variational Inference for Text-to-Speech)是一种端到端的文本到语音合成方法,它可以将文本转化为自然流畅的语音。VITS-Fast Fine-Tuning是对VITS模型进行快速微调的方法。 在传统的语音合成任务中,需要大量的语音对齐标注数据来训练模型。然而,这个过程非常耗时和昂贵。VITS-Fast Fine-Tuning的目标就是通过少量的标注数据来快速微调已有的VITS模型,以在新的任务上取得更好的性能。 VITS-Fast Fine-Tuning方法的关键在于使用变分推断(variational inference)来构建先验和后验分布。通过这个方法,我们可以使用其他大型语音合成数据集训练好的模型作为先验分布,然后使用少量目标任务的标注数据来估计后验分布。这样一来,我们就能够在新任务上快速微调VITS模型。 具体而言,VITS-Fast Fine-Tuning的过程分为两步。第一步是预训练,它使用大型语音数据集来训练VITS模型,并生成一个先验分布。第二步是微调,它使用目标任务的标注数据来调整VITS模型的参数,以获得更好的性能。由于预训练的先验分布已经包含了一定的知识,微调的过程可以更快速和高效。 总之,VITS-Fast Fine-Tuning是一种用于快速微调VITS模型的方法。它利用变分推断和预训练的先验分布,通过少量目标任务的标注数据来优化模型性能。这个方法可以加快语音合成模型的训练过程,降低训练的时间和成本。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值