语音合成中声学模型在可控性上的努力

本文主要介绍3种模型,分别是前向注意力(Forward Attention,FA/FA+TA),逐步单向注意力(Stepwise Monotonic Attention,SMA)和FastSpeech2,前两者都是要求注意力权重尽量保证单调向前。具体来说,假设某一解码步上的注意力权重为:[0,0.8,0.2,0][0,0.8,0.2,0],在求下一个解码步的注意力权重时,对原始的query和key“比较”求得的注意力权重加个“系数”,这个系数是上一个注意力权重,加上上一个注意力权重右移一位,这个注意力权重的系数就是[0,0.8,0.2,0]+[0,0,0.8,0.2]=[0,0.8,1,0.2][0,0.8,0.2,0]+[0,0,0.8,0.2]=[0,0.8,1,0.2],可以看到,这个注意力系数会让上一个解码步上“关注”的编码状态和下一个编码状态在本次解码时更加受到关注,也就是本次解码要不然停留在原地,要不然向前一步;FA+TA就是为注意力系数显式加了一个向前向后的选择,也就是计算这个注意力系数时,0.1×[0,0.8,0.2,0]+0.9×[0,0,0.8,0.2]=[0,0.08,0.74,0.18]0.1×[0,0.8,0.2,0]+0.9×[0,0,0.8,0.2]=[0,0.08,0.74,0.18],这个多出来的0.1,0.90.1,0.9是通过一个额外的网络学习得来,这个网络称之为“转移代理”(Transition Agent,TA),转移代理对向前、向后建模更为明确。注意到,转移代理是对注意力权重的系数,再乘上一个系数,而SMA直接对注意力权重乘个向前或者向后的概率,就完事。FastSpeech1/2是另外一个思路,用一个单独的网络学习每个编码状态对应几个解码状态。

Forward Attention in Sequence-to-Sequence Acoustic Modeling For Speech Synthesis

摘要

在语音合成中,音素序列和对应的声学参数序列遵循着单调性原则,也就是说,在生成语音时音素和声学参数都是同步向前的。该文提出了一种适用于该情形的注意力机制,在每一个解码步上对注意力权重添加单调性条件。在每个时间步上,通过前向算法递归地计算注意力权重,于此同时,提出了一种转移代理机制,在每个解码步上帮助注意力机制决策是向前前进一个“音素”,还是停留在原地。

局部敏感注意力LSA

回顾Tacotron-2中的局部敏感注意力机制(Location Sensitive Attention,LSA)。带有注意力机制的编解码器将输入序列映射到不同长度的输出序列,编解码器通常是由循环神经网络(Recurrent Neural Networks,RNN)实现。编码器通常将输入序列t=[t1,t2,...,tN]𝑡=[𝑡1,𝑡2,...,𝑡𝑁]映射到隐状态序列x=[x1,x2,...,xN]𝑥=[𝑥1,𝑥2,...,𝑥𝑁],解码器利用编码器隐状态序列x𝑥生成输出序列o=[o1,o2,...,oN]𝑜=[𝑜1,𝑜2,...,𝑜𝑁]。

在每一个解码步上t𝑡上,注意力机制都对编码器隐状态进行“软性地”选择,将qt𝑞𝑡记作第t𝑡个解码步上的query,qt𝑞𝑡通常是解码器的隐状态;πt∈{1,2,...,N}𝜋𝑡∈{1,2,...,𝑁}可以看作一个类别隐变量,表示根据条件分布p(π|x,qt)𝑝(𝜋|𝑥,𝑞𝑡)对编码器隐状态的选择。那么上下文向量就可通过下式计算:

ct=N∑n=1yt(n)xn𝑐𝑡=∑𝑛=1𝑁𝑦𝑡(𝑛)𝑥𝑛

其中,yt(n)=p(πt=n|x,qt)𝑦𝑡(𝑛)=𝑝(𝜋𝑡=𝑛|𝑥,𝑞𝑡)。LSA引入了卷积特征以稳定注意力的对齐,具体地,k𝑘个大小为l𝑙的卷积核卷积之前解码步的注意力权重。将F∈Rk×l𝐹∈𝑅𝑘×𝑙记作卷积矩阵,则:

yt−1=exp(et−1,n)∑Nm=1exp(et−1,m)𝑦𝑡−1=𝑒𝑥𝑝(𝑒𝑡−1,𝑛)∑𝑚=1𝑁𝑒𝑥𝑝(𝑒𝑡−1,𝑚)

ft=F∗yt−1𝑓𝑡=𝐹∗𝑦𝑡−1

et,n=vTtanh(Wqt+Vxn+Uft,n+b)𝑒𝑡,𝑛=𝑣𝑇𝑡𝑎𝑛ℎ(𝑊𝑞𝑡+𝑉𝑥𝑛+𝑈𝑓𝑡,𝑛+𝑏)

在实际的语音合成中,在编码隐状态和解码隐状态之间的对齐路径{π1,π2,...,πT}{𝜋1,𝜋2,...,𝜋𝑇}表示文本特征对应着多少个声学特征。

前向注意力

将对齐路径的概率分布记作:

p(π1:t|x,q1:t)=t∏t′=1p(πt′|x,qt′)=t∏t′=1yt′(πt′)𝑝(𝜋1:𝑡|𝑥,𝑞1:𝑡)=∏𝑡′=1𝑡𝑝(𝜋𝑡′|𝑥,𝑞𝑡′)=∏𝑡′=1𝑡𝑦𝑡′(𝜋𝑡′)

将P𝑃记作对齐路径,其保持着单调性和连续性,并且不跳过任何编码器状态。

前向变量αt(n)𝛼𝑡(𝑛)定义为对齐路径{π0,π1,...,πt}∈P{𝜋0,𝜋1,...,𝜋𝑡}∈𝑃上的总概率:

αt(n)=∑π0:t∈Pt∏t′=1yt′(πt′)𝛼𝑡(𝑛)=∑𝜋0:𝑡∈𝑃∏𝑡′=1𝑡𝑦𝑡′(𝜋𝑡′)

其中,π0=1,πt=n𝜋0=1,𝜋𝑡=𝑛。也就是说,假设第0时间步必选中第1个编码器隐状态,第t𝑡时间步必选中第n𝑛个编码器隐状态。

注意,αt(n)𝛼𝑡(𝑛)由αt−1(n)𝛼𝑡−1(𝑛)和αt−1(n−1)𝛼𝑡−1(𝑛−1)计算而来:

αt(n)=(αt−1(n)+αt−1(n−1))yt(n)(1)(1)𝛼𝑡(𝑛)=(𝛼𝑡−1(𝑛)+𝛼𝑡−1(𝑛−1))𝑦𝑡(𝑛)

并且,^αt(n)=αt(n)∑nαt(n)𝛼^𝑡(𝑛)=𝛼𝑡(𝑛)∑𝑛𝛼𝑡(𝑛)

保证^αt(n)𝛼^𝑡(𝑛)归一化,^αt(n)𝛼^𝑡(𝑛)表示第n𝑛个编码步上第t𝑡个解码步的对齐权重,张量大小为[batch_size,encoder_times]。并且利用^αt(n)𝛼^𝑡(𝑛)代替注意力权重yt(n)𝑦𝑡(𝑛)求取上下文向量:

ct=N∑n=1^αt(n)xn𝑐𝑡=∑𝑛=1𝑁𝛼^𝑡(𝑛)𝑥𝑛

前向注意力的算法流程如下:

对应的PyTorch实现:

class AttentionBase(torch.nn.Module):
    """Abstract attention class.
    
    Arguments:
        representation_dim -- size of the hidden representation
        query_dim -- size of the attention query input (probably decoder hidden state)
        memory_dim -- size of the attention memory input (probably encoder outputs)
    """

    def __init__(self, representation_dim, query_dim, memory_dim):
        super(AttentionBase, self).__init__()
        self._bias = Parameter(torch.zeros(1, representation_dim))
        self._energy = Linear(representation_dim, 1, bias=False)     
        self._query = Linear(query_dim, representation_dim, bias=False)          
        self._memory = Linear(memory_dim, representation_dim, bias=False)
        self._memory_dim = memory_dim
    
    def reset(self, encoded_input, batch_size, max_len, device):
        """Initialize previous attention weights & prepare attention memory."""
        self._memory_transform = self._memory(encoded_input)   
        self._prev_weights = torch.zeros(batch_size, max_len, device=device)
        self._prev_context = torch.zeros(batch_size, self._memory_dim, device=device)
        return self._prev_context

    def _attent(self, query, memory_transform, weights):      
        raise NotImplementedError

    def _combine_weights(self, previsous_weights, weights):      
        raise NotImplementedError

    def _normalize(self, energies, mask):
        raise NotImplementedError

    def forward(self, query, memory, mask, prev_decoder_output):
        energies = self._attent(query, self._memory_transform, self._prev_weights)
        attention_weights = self._normalize(energies, mask)
        self._prev_weights = self._combine_weights(self._prev_weights, attention_weights)
        attention_weights = attention_weights.unsqueeze(1)
        self._prev_context = torch.bmm(attention_weights, memory).squeeze(1)
        return self._prev_context, attention_weights.squeeze(1)


class ForwardAttention(AttentionBase):
    """
    Forward Attention:
        Forward Attention in Sequence-to-sequence Acoustic Modelling for Speech Synthesis
        without the transition agent: https://arxiv.org/abs/1807.06736.
        However, the attention with convolutional features should have a negative effect 
        on the naturalness of synthetic speech.
    """

    def __init__(self, *args, **kwargs):
        super(ForwardAttention, self).__init__(*args, **kwargs)
        
    def reset(self, encoded_input, batch_size, max_len, device):
        super(ForwardAttention, self).reset(encoded_input, batch_size, max_len, device)
        self._prev_weights[:,0] = 1
        return self._prev_context

    def _prepare_transition(self, query, memory_transform, weights):
        query = self._query(query.unsqueeze(1))
        # W*q_t+V*x_n
        energy = query + memory_transform
        # energy: [batch_size,encoder_times]
        energy = self._energy(torch.tanh(energy + self._bias)).squeeze(-1)
        energy = F.softmax(energy, dim=1)
        # shifted_weights: [batch_size,encoder_times]
        # shifted_weights: [0;encoder_times[:-1]]
        shifted_weights = F.pad(weights, (1, 0))[:, :-1]
        return energy, shifted_weights

    def _attent(self, query, memory_transform, cum_weights):
        energy, shifted_weights = self._prepare_transition(query, memory_transform, self._prev_weights)
        # \alpha_t(n)=(\alpha_{t-1}(n)+\alpha_{t-1}(n-1))y_t(n)
        self._prev_weights = (self._prev_weights + shifted_weights) * energy
        return self._prev_weights

    def _normalize(self, energies, mask):
        energies[~mask] = float(0)
        return F.normalize(torch.clamp(energies, 1e-6), p=1)
        
    def _combine_weights(self, previous_weights, weights):      
        return weights

Multilingual_Text_to_Speech/modules/attention.py at master · Tomiinek/Multilingual_Text_to_Speech · GitHub

带有对齐代理的前向注意力

所谓的对齐代理,其实就是引入了一个指示器,指示在第t𝑡个解码步上移动到下一个“音素”上的概率,这个指示器由一个全连接层和Sigmoid层组成,输入为上下文向量ct𝑐𝑡、解码器输出ot−1𝑜𝑡−1和query。

可以看到,相比于前向注意力,算法中就是多了一个概率值u𝑢,用于控制“注意力”的移动,每一步都根据本时刻的上下文向量、上一时刻的解码器输出以及query计算下一个时刻的概率值。

这可以看作是一个专家产品(Product-of-Experts,PoE)模型,专家产品模型由若干个独立的部分组成,结果由各个组件共同决定。在该文提出的方法中,一个组件(1−μt−1)^αt−1(n)+μt−1^αt−1(n−1)(1−𝜇𝑡−1)𝛼^𝑡−1(𝑛)+𝜇𝑡−1𝛼^𝑡−1(𝑛−1)描述了单调性对齐的限制,另一个组件yt(n)𝑦𝑡(𝑛)描述了原始的对齐方式,最终的注意力权重^αt(n)𝛼^𝑡(𝑛)的计算由两者共同决定。

与此同时,该文的注意力对齐机制还可以控制音频速度。在生成时向转移代理DNN的Sigmoid输出中添加正或负的偏置值,转移概率ut𝑢𝑡就会增加或者减小,这就会导致注意力移动的速度变快或者变慢,从而导致生成语音变快或变慢。

class ForwardAttentionWithTransition(ForwardAttention):
    """
    Forward Attention:
        Forward Attention in Sequence-to-sequence Acoustic Modelling for Speech Synthesis
        with the transition agent: https://arxiv.org/abs/1807.06736.
    
    Arguments:
        decoder_output_dim -- size of the decoder output (from previous step)
    """

    def __init__(self, decoder_output_dim, representation_dim, query_dim, memory_dim):
        super(ForwardAttentionWithTransition, self).__init__(representation_dim, query_dim, memory_dim)
        self._transition_agent = Linear(memory_dim + query_dim + decoder_output_dim, 1)
        
    def reset(self, encoded_input, batch_size, max_len):
        super(ForwardAttentionWithTransition, self).reset(encoded_input, batch_size, max_len)
        self._t_prob = 0.5
        return self._prev_context

    def _attent(self, query, memory_transform, cum_weights):
        energy, shifted_weights = self._prepare_transition(query, memory_transform, self._prev_weights)
        # use last time u_{t-1}
        self._prev_weights = ((1 - self._t_prob) * self._prev_weights + self._t_prob * shifted_weights) * energy
        return self._prev_weights

    def forward(self, query, memory, mask, prev_decoder_output):
        # `self._prev_context` should be equal to `context` in `forward()` of `AttentionBase`
        context, weights = super(ForwardAttentionWithTransition, self).forward(query, memory, mask, None)
        transtition_input = torch.cat([self._prev_context, query, prev_decoder_output], dim=1)
        t_prob = self._transition_agent(transtition_input)
        self._t_prob = torch.sigmoid(t_prob)
        return context, weights

Multilingual_Text_to_Speech/modules/attention.py at master · Tomiinek/Multilingual_Text_to_Speech · GitHub

实验

由上图可以看到,生成失败的情形显著减少。

Robust Sequence-to-Sequence Acoustic Modeling with Stepwise Monotonic Attention for Neural TTS

摘要

该文同样是利用语音合成的单调特性,强制输入输出之间不仅仅单调,而且不能跳过输入的每一个时间步。软性注意力可以用来消除训练和推断之间的不一致性。

相关工作

普通注意力

首先还是回顾一下最通用的注意力机制:

ei,j=Attention(si−1,hj)𝑒𝑖,𝑗=𝐴𝑡𝑡𝑒𝑛𝑡𝑖𝑜𝑛(𝑠𝑖−1,ℎ𝑗)

αi,j=exp(ei,j)∑nk=1exp(ei,k)=softmax(ei,:)j𝛼𝑖,𝑗=𝑒𝑥𝑝(𝑒𝑖,𝑗)∑𝑘=1𝑛𝑒𝑥𝑝(𝑒𝑖,𝑘)=𝑠𝑜𝑓𝑡𝑚𝑎𝑥(𝑒𝑖,:)𝑗

ci=n∑j=1αi,jhj𝑐𝑖=∑𝑗=1𝑛𝛼𝑖,𝑗ℎ𝑗

其中,hjℎ𝑗为第j𝑗个编码步的输出,si−1𝑠𝑖−1为上一时刻i−1𝑖−1解码器隐状态,ei,j𝑒𝑖,𝑗用于衡量si−1𝑠𝑖−1和hjℎ𝑗之间的“相似度”,ci𝑐𝑖是对编码向量进行加权求和,作为第i𝑖步注意力的输出。

单调注意力(Monotonic attention)可以同时保持单调性和局部性,可以应用到包括语音合成领域中:在每个解码步i𝑖上,该机制都会检查上一个解码步选择的memory索引ti−1𝑡𝑖−1,从“音素位置”j=ti−1𝑗=𝑡𝑖−1开始,采样伯努利分布zi,j∼Bernoulli(pi,j)𝑧𝑖,𝑗∼𝐵𝑒𝑟𝑛𝑜𝑢𝑙𝑙𝑖(𝑝𝑖,𝑗)决定需要保持j𝑗不动,还是移动到下一个位置j←j+1𝑗←𝑗+1上。“音素位置”j𝑗会一直向前移动,直到到达输入末尾或者收到采样值zi,j=1𝑧𝑖,𝑗=1。当音素位置j𝑗停止时,该位置上对应的memory,即hjℎ𝑗将会被直接被作为上下文向量ci𝑐𝑖。可以用下式递归计算αi,j𝛼𝑖,𝑗:

αi,j=pi,j((1−pi,j−1)αi,j−1pi,j−1+αi−1,j)𝛼𝑖,𝑗=𝑝𝑖,𝑗((1−𝑝𝑖,𝑗−1)𝛼𝑖,𝑗−1𝑝𝑖,𝑗−1+𝛼𝑖−1,𝑗)

或者用下式并行计算:

αi=pi⋅cumprod(1−pi)⋅cumsum(αi−1cumprod(1−pi))𝛼𝑖=𝑝𝑖⋅𝑐𝑢𝑚𝑝𝑟𝑜𝑑⁡(1−𝑝𝑖)⋅𝑐𝑢𝑚𝑠𝑢𝑚⁡(𝛼𝑖−1𝑐𝑢𝑚𝑝𝑟𝑜𝑑(1−𝑝𝑖))

之后就可以像正常的注意力机制一样,利用注意力权重αi,j𝛼𝑖,𝑗或者αi𝛼𝑖求得上下文向量ci𝑐𝑖了。

注1:

  1. 伯努利分布Bernoulli

f(x)={p(x=1)=pp(x=0)=1−p𝑓(𝑥)={𝑝(𝑥=1)=𝑝𝑝(𝑥=0)=1−𝑝

  1. cumprodcumsum运算符

以向量A=[1,2,3,4]𝐴=[1,2,3,4]为例,

  • cumprod累积乘积

cumprod(A)=[1,2,6,24]𝑐𝑢𝑚𝑝𝑟𝑜𝑑⁡(𝐴)=[1,2,6,24]

向量cumprod(A)𝑐𝑢𝑚𝑝𝑟𝑜𝑑⁡(𝐴)第一位元素为1:

1=1×11=1×1

向量cumprod(A)𝑐𝑢𝑚𝑝𝑟𝑜𝑑⁡(𝐴)第二位元素为2:

2=1×22=1×2

向量cumprod(A)𝑐𝑢𝑚𝑝𝑟𝑜𝑑⁡(𝐴)第三位元素为6:

6=2×36=2×3

向量cumprod(A)𝑐𝑢𝑚𝑝𝑟𝑜𝑑⁡(𝐴)第四位元素为24:

24=6×424=6×4

  • cumprod累加求和

cumprod(A)=[1,3,6,10]𝑐𝑢𝑚𝑝𝑟𝑜𝑑⁡(𝐴)=[1,3,6,10]

硬性单调注意力

在最通用的注意力机制中,利用αi,j𝛼𝑖,𝑗计算ci𝑐𝑖的时候,没有使用采样,而是加权求期望,这样能使得训练过程可以反向传播。

在软性注意力中,对同一时刻i𝑖,即使hjℎ𝑗(j=1,2,...,T𝑗=1,2,...,𝑇)是天然有序的,但是在计算αi,j𝛼𝑖,𝑗的时候,hjℎ𝑗是无序的。即使打乱hjℎ𝑗的顺序,每个hjℎ𝑗对应的注意力权重值αi,j𝛼𝑖,𝑗保持不变。并且对于不同时刻对齐到的hjℎ𝑗并不是单调的,本时刻对齐到hjℎ𝑗,并不影响注意力下次可能就对齐到hj−1ℎ𝑗−1,或者hj−3ℎ𝑗−3 ,这并不符合语音合成的注意力特性。

硬性单调注意力能够保证在计算αi,j𝛼𝑖,𝑗时在编码步j𝑗上是有序的,且让αi,j𝛼𝑖,𝑗在解码步上是单调的。主要思路是,对任意一个时刻i−1𝑖−1,关注且仅关注一个编码隐状态(attend and only attend to one hidden state),记作hjℎ𝑗。在每一个解码步,计算概率pi,j𝑝𝑖,𝑗,决定当前解码步是继续关注原先的编码隐状态hjℎ𝑗,还是跳到下一个hj+1ℎ𝑗+1。

ei,j=a(si−1,hj)pi,j=σ(ei,j)zi,j∼Bernoulli(pi,j)(1)(1)𝑒𝑖,𝑗=𝑎(𝑠𝑖−1,ℎ𝑗)𝑝𝑖,𝑗=𝜎(𝑒𝑖,𝑗)𝑧𝑖,𝑗∼𝐵𝑒𝑟𝑛𝑜𝑢𝑙𝑙𝑖(𝑝𝑖,𝑗)

ei,j𝑒𝑖,𝑗的计算和普通的软性注意力(Soft Attention)是一样的,都是求query和每个编码隐状态之间的“相似度”,但是和普通的软性注意力不一样的是,得到ei,j𝑒𝑖,𝑗之后并没有对解码步i𝑖上的所有j𝑗进行Softmax,而是独立进行Sigmoid,得到一个概率值pi,j𝑝𝑖,𝑗。当概率pi,j𝑝𝑖,𝑗的伯努利采样值zi,j=1𝑧𝑖,𝑗=1,则关注原先的编码隐状态hjℎ𝑗,否则zi,j=0𝑧𝑖,𝑗=0则前进一个编码隐状态hj+1ℎ𝑗+1。如果前进一步(zi,j=0𝑧𝑖,𝑗=0),在前进之后再次计算zi,j+1𝑧𝑖,𝑗+1,直到zi,j′=1𝑧𝑖,𝑗′=1,停止前进,此时得到上下文向量ci=hj′𝑐𝑖=ℎ𝑗′。有了上下文向量ci𝑐𝑖之后就可以按照普通的软性注意力里的方法,计算si𝑠𝑖和yi𝑦𝑖。接着计算下一个上下文向量ci+1𝑐𝑖+1,此时需要从hj′ℎ𝑗′开始,重复上述过程。

训练

公式1介绍了硬性单调注意力的方法,但是这里有一个从伯努利分布的采样操作,采样操作是没办法反向传播的。解决方法和普通的软性注意力一样,使用ci=∑Tj=1αi,jhj𝑐𝑖=∑𝑗=1𝑇𝛼𝑖,𝑗ℎ𝑗代替原来直接的hjℎ𝑗,但是硬性单调注意力中的hjℎ𝑗时通过一步一步前进获得的,而求期望则是一个加权求和的过程。那么求每一解码步上联合概率αi,j𝛼𝑖,𝑗的方法如下:

  • 当i=1𝑖=1时,如果是关注hjℎ𝑗,此时对应的概率为pi,j𝑝𝑖,𝑗,且k=1,2,...,j−1𝑘=1,2,...,𝑗−1都被跳过了,对应的概率为∏j−1k=1(1−p1,k)∏𝑘=1𝑗−1(1−𝑝1,𝑘),则该事件的联合概率为α1,j=pi,j∏j−1k=1(1−p1,k)𝛼1,𝑗=𝑝𝑖,𝑗∏𝑘=1𝑗−1(1−𝑝1,𝑘)

  • 当i>=2𝑖>=2时,假设i−1𝑖−1解码步选中了hkℎ𝑘,那么从时刻11到时刻i−1𝑖−1选中k𝑘这总共i−1𝑖−1个事件的联合概率即为αi−1,k𝛼𝑖−1,𝑘;又假设i𝑖时刻选中了hjℎ𝑗,概率为pi,j𝑝𝑖,𝑗,那么说明hk,hk+1...,hj−1ℎ𝑘,ℎ𝑘+1...,ℎ𝑗−1都应该被跳过,概率为∏j−1l=k(1−pi,l)∏𝑙=𝑘𝑗−1(1−𝑝𝑖,𝑙)

    因此整个联合概率模型为:

    αi,j=pi,jj∑k=1(αi−1,kj−1∏l=k(1−pi,l))(2)(2)𝛼𝑖,𝑗=𝑝𝑖,𝑗∑𝑘=1𝑗(𝛼𝑖−1,𝑘∏𝑙=𝑘𝑗−1(1−𝑝𝑖,𝑙))

    上式中的αi,j𝛼𝑖,𝑗中,外层的∑∑上界中的k𝑘最多可以到j𝑗,此时内层的∏∏变为∏j−1j∏𝑗𝑗−1;定义这种起始点大于终点的累乘结果为1。在这种情形下是正确的,因为当k=j𝑘=𝑗时,说明i𝑖时刻和i−1𝑖−1时刻都选中了hjℎ𝑗,概率为pi,j(αi−1,j∏j−1l=j(1−pi,l))=pi,jαi−1,j𝑝𝑖,𝑗(𝛼𝑖−1,𝑗∏𝑙=𝑗𝑗−1(1−𝑝𝑖,𝑙))=𝑝𝑖,𝑗𝛼𝑖−1,𝑗。利用该式对公式2的αi,j𝛼𝑖,𝑗进行化简。

化简方法1

首先对公式2从外层的求和中分离出k=j𝑘=𝑗,则

αi,j=pi,j(j−1∑k=1(αi−1,kj−1∏l=k(1−pi,l))+αi−1,jj−1∏l=j(1−pi,l))𝛼𝑖,𝑗=𝑝𝑖,𝑗(∑𝑘=1𝑗−1(𝛼𝑖−1,𝑘∏𝑙=𝑘𝑗−1(1−𝑝𝑖,𝑙))+𝛼𝑖−1,𝑗∏𝑙=𝑗𝑗−1(1−𝑝𝑖,𝑙))

从连乘符号中分离出l=j−1𝑙=𝑗−1,并且利用∏j−1l=j(1−pi,l)=1∏𝑙=𝑗𝑗−1(1−𝑝𝑖,𝑙)=1,那么,

αi,j=pi,j(j−1∑k=1(αi−1,kj−2∏l=k((1−pi,l)(1−pi,j−1))+αi−1,j)𝛼𝑖,𝑗=𝑝𝑖,𝑗(∑𝑘=1𝑗−1(𝛼𝑖−1,𝑘∏𝑙=𝑘𝑗−2((1−𝑝𝑖,𝑙)(1−𝑝𝑖,𝑗−1))+𝛼𝑖−1,𝑗)

把上式中的(1−pi,j−1)(1−𝑝𝑖,𝑗−1)从内部的连乘和求和符号中拿出来,毕竟已经不和l,k𝑙,𝑘有关了。变为:

αi,j=pi,j((1−pi,j−1)j−1∑k=1(αi−1,kj−2∏l=k(1−pi,l))+αi−1,j)(3)(3)𝛼𝑖,𝑗=𝑝𝑖,𝑗((1−𝑝𝑖,𝑗−1)∑𝑘=1𝑗−1(𝛼𝑖−1,𝑘∏𝑙=𝑘𝑗−2(1−𝑝𝑖,𝑙))+𝛼𝑖−1,𝑗)

对比一下上面的那个联合概率公式,也就是公式2,把公式2等号右侧的pi,j𝑝𝑖,𝑗除到左边,那么,

αi,jpi,j=j∑k=1(αi−1,kj−1∏l=k(1−pi,l))(4)(4)𝛼𝑖,𝑗𝑝𝑖,𝑗=∑𝑘=1𝑗(𝛼𝑖−1,𝑘∏𝑙=𝑘𝑗−1(1−𝑝𝑖,𝑙))

和公式4相比,上面公式3的连乘和求和符号部分,就是上标由j𝑗变为了j−1𝑗−1。把上式3等号右侧的pi,j𝑝𝑖,𝑗除到左边,公式3就变成了:

αi,jpi,j=(1−pi,j−1)j−1∑k=1(αi−1,kj−2∏l=k(1−pi,l))+αi−1,j(5)(5)𝛼𝑖,𝑗𝑝𝑖,𝑗=(1−𝑝𝑖,𝑗−1)∑𝑘=1𝑗−1(𝛼𝑖−1,𝑘∏𝑙=𝑘𝑗−2(1−𝑝𝑖,𝑙))+𝛼𝑖−1,𝑗

结合公式4,将求和符号包围的部分替换掉,公式5变为:

αi,jpi,j=(1−pi,j−1)αi,j−1pi,j−1+αi−1,j(6)(6)𝛼𝑖,𝑗𝑝𝑖,𝑗=(1−𝑝𝑖,𝑗−1)𝛼𝑖,𝑗−1𝑝𝑖,𝑗−1+𝛼𝑖−1,𝑗

令qi,j=αi,jpi,j𝑞𝑖,𝑗=𝛼𝑖,𝑗𝑝𝑖,𝑗,则上式6可以简写为:

qi,j=(1−pi,j−1)qi,j−1+αi−1,j𝑞𝑖,𝑗=(1−𝑝𝑖,𝑗−1)𝑞𝑖,𝑗−1+𝛼𝑖−1,𝑗

到此,将αi,j𝛼𝑖,𝑗的递推公式化简完毕。

化简方法2

现在利用另一种方法化简αi,j𝛼𝑖,𝑗。将公式2内侧的连乘∏j−1l=k(1−pi,l)∏𝑙=𝑘𝑗−1(1−𝑝𝑖,𝑙)变换为∏j−1l=0(1−pi,l)∏k−1l=0(1−pi,l)∏𝑙=0𝑗−1(1−𝑝𝑖,𝑙)∏𝑙=0𝑘−1(1−𝑝𝑖,𝑙),使得分子与k𝑘无关,那么分子就可以从外层的求和中分离出来,上式2变为

pi,jj∑k=1(αi−1,k∏j−1l=0(1−pi,l)∏k−1l=0(1−pi,l))=pi,jj−1∏l=0(1−pi,l)j∑k=1(αi−1,k∏k−1l=0(1−pi,l))𝑝𝑖,𝑗∑𝑘=1𝑗(𝛼𝑖−1,𝑘∏𝑙=0𝑗−1(1−𝑝𝑖,𝑙)∏𝑙=0𝑘−1(1−𝑝𝑖,𝑙))=𝑝𝑖,𝑗∏𝑙=0𝑗−1(1−𝑝𝑖,𝑙)∑𝑘=1𝑗(𝛼𝑖−1,𝑘∏𝑙=0𝑘−1(1−𝑝𝑖,𝑙))

令qi=αi,jpi,j𝑞𝑖=𝛼𝑖,𝑗𝑝𝑖,𝑗,将pi,j𝑝𝑖,𝑗除到等号左侧,则

qi=cumprod(1−pi)cumsum(αi−1cumprod(1−pi))𝑞𝑖=𝑐𝑢𝑚𝑝𝑟𝑜𝑑⁡(1−𝑝𝑖)𝑐𝑢𝑚𝑠𝑢𝑚⁡(𝛼𝑖−1𝑐𝑢𝑚𝑝𝑟𝑜𝑑⁡(1−𝑝𝑖))

则,

αi,j=pi,jj∑k=1(αi−1,kj−1∏l=k(1−pi,l))=pi,j((1−pi,j−1)αi,j−1pi,j−1+αi−1,j)𝛼𝑖,𝑗=𝑝𝑖,𝑗∑𝑘=1𝑗(𝛼𝑖−1,𝑘∏𝑙=𝑘𝑗−1(1−𝑝𝑖,𝑙))=𝑝𝑖,𝑗((1−𝑝𝑖,𝑗−1)𝛼𝑖,𝑗−1𝑝𝑖,𝑗−1+𝛼𝑖−1,𝑗)

令qi,j=αi,jpi,j𝑞𝑖,𝑗=𝛼𝑖,𝑗𝑝𝑖,𝑗,上式中的pi,j𝑝𝑖,𝑗除到左侧,简化之后的上式:

qi,j=(1−pi,j−1)qi,j−1+αi−1,j𝑞𝑖,𝑗=(1−𝑝𝑖,𝑗−1)𝑞𝑖,𝑗−1+𝛼𝑖−1,𝑗

可以看到,和上面的化简方法1得到的结果一致。

整理一下上述结果:

ei,j=a(si−1,hj)pi,j=σ(ei,j)qi,j=(1−pi,j−1)qi,j−1+αi−1,jαi,j=pi,jqi,j𝑒𝑖,𝑗=𝑎(𝑠𝑖−1,ℎ𝑗)𝑝𝑖,𝑗=𝜎(𝑒𝑖,𝑗)𝑞𝑖,𝑗=(1−𝑝𝑖,𝑗−1)𝑞𝑖,𝑗−1+𝛼𝑖−1,𝑗𝛼𝑖,𝑗=𝑝𝑖,𝑗𝑞𝑖,𝑗

https://zhuanlan.zhihu.com/p/99389088

逐步单调注意力(Stepwise monotonic attention)

逐步单调注意力(Stepwise monotonic attention)在单调注意力(Monotonic attention)的基础上添加额外的限制:在每一个解码步,硬对齐位置应该最多移动一步。

在每一个解码步i𝑖上,该机制探测上一步使用的memory条目j=ti−1𝑗=𝑡𝑖−1,仅需要决定是向前或者停止不动。因此可以直接建立pi,j𝑝𝑖,𝑗分布的递推关系:

αij=αi−1,j−1(1−pi,j−1)+αi−1,jpij𝛼𝑖𝑗=𝛼𝑖−1,𝑗−1(1−𝑝𝑖,𝑗−1)+𝛼𝑖−1,𝑗𝑝𝑖𝑗

同样地,可以更为高效地并行计算:

αi=αi−1⋅pi+[0;αi−1,:−1⋅(1−pi,:−1)]𝛼𝑖=𝛼𝑖−1⋅𝑝𝑖+[0;𝛼𝑖−1,:−1⋅(1−𝑝𝑖,:−1)]

其中,[0;][0;]表示左侧填充0。

在训练阶段,上下文向量和普通的注意力机制类似。但是可以看到,无论是单调注意力,或是逐步单调注意力,在训练和推断阶段的上下文向量存在不匹配的现象,在训练阶段,送入解码器中的输入是一个“软性”上下文向量而非单一的memory,也就是说在训练阶段,解码器可以利用将来要注意的memory而不是基于当前的memory来预测声学特征。当然,该文建议在推断阶段仍然使用软性注意力,只不过结合上式中求αi,j𝛼𝑖,𝑗的方法,然后对memory进行加权求和。不过如果这样做的话,就没办法流式合成语音了。

实验

实验中比较了5种基于Tacotron的语音合成模型,分别是:

  1. 基线Tacotron,采用原始的位置敏感注意力机制(Location Sensitive Attention),记作Baseline

  2. 混合高斯注意力机制(GMM Attention),20个高斯成分,记作GMM

  3. 单调注意力(Monotonic Attention),在推断时采用硬性或软性注意力,记作MA hardMA soft

  4. 前向注意力(Forward Attention),使用或者不使用转移代理,记作FA+TAFA w/o TA

  5. 逐步单调注意力(Stepwise Monotonic Attention),推断时使用硬性或软性注意力,记作SMA hardSMA soft

与基线Tacotron的偏向性测试

可以看到,SMA优于Baseline,具体来说SMA soft又优于SMA hard

与软性逐步单调注意力(Soft Stepwise Monotonic Attention)推断的偏向性测试

可以看到,SMA soft显著优于GMM,但是比FA+TA还差是什么鬼。

FastSpeech 2: Fast and High-quality End-to-End Text to Speech

摘要

相比于FastSpeech,

  1. 通过真实目标训练模型,而不是教师模型生成的简化版目标;

  2. 显式建模语音中的时长、音调和能量,在训练时直接将真实语音中提取的这些特征作为条件输入,在推断时将预测特征作为条件输入;

  3. 提出了FastSpeech 2s,直接从文本映射为语音,不适用频谱作为中间媒介,提升生成速度,完全端到端并行生成。

相关工作

FastSpeech是一种非自回归的语音合成方法,利用自回归的教师模型提供:1)音素时长以训练时长预测模型;2)生成频谱进行知识蒸馏。但是FastSpeech存在一些问题,比如教师-学生知识蒸馏流程耗费过多时间,从教师模型中提取的注意力权重不够精确。此外,将教师模型的生成目标作为目标损失了训练数据中音调、能量、韵律等方面的多样性信息,生成目标要比真实录音简单且“单调”。

语音合成是一种典型的一对多问题,因为语音中比如音调、时长、音量和韵律等变化,同一段文本可以对应任意多的语音。

如何去解决这种一对多映射问题呢?一个方法就是FastSpeech那样,引入一个教师模型,利用教师模型生成目标和注意力权重,去除变动的部分,简化原始语料中存在的数据偏差;第二个就是FastSpeech 2中采用的方法,将这些易于变动的部分直接剥离开,利用一个单独的预测器生成变动,产生方差(variation)。

为了解决上述问题,FastSpeech 2直接利用原始语音进行训练,使用从原始的目标音频中抽取的时长、音调和能量作为额外输入,并且利用这些额外输入训练对应的预测器。在推断时,直接使用预测的特征作为额外输入,生成语音。考虑到音调对语音比较重要,并且过于多变难以建模,因此利用连续小波变换(Continuous Wavelet Transform,CWT)将音调包络转换为音调谱(Pitch Spectrogram)。总结下来,FastSpeech 2的优势有:

  • 移除了教师-学生知识蒸馏机制,简化训练流程;

  • 使用真实目标而非生成目标,减少信息损失;

  • 显式建模语音中易于变动的特征比如音调、时长、能量等,减轻一对多映射的难题,并且直接从原始语音中提取这些特征,训练时更为精确。

FastSpeech 2/2s

整个FastSpeech 2/2s结构如下图所示,编码器将音素序列编码到音素隐状态序列,利用方差适配器(Variance Adaptor)向隐状态序列中添加额外的方差信息,比如时长、音调、能量等,之后FastSpeech 2/2s利用解码器将多信息混合的隐状态序列转换为梅尔频谱或语音。

将语音中易于变动的信息总结如下:

  • 音素时长,影响语音长度;

  • 音调,是影响语音情感和韵律的重要特征;

  • 能量,梅尔频谱的幅度,直接影响语音的音量。

实际上可以将语音中更多易于变动的部分分离开来,单独建模,比如情感、风格和说话人等等。在该文中仅单独建模上述三个特征,模型中对应着三个预测器。在训练时,使用从录音文本中提取的真实时长,声调和能量值作为目标去训练时长、音调和能量预测器。

时长预测器

时长预测器输入音素隐状态,输出每一个音素的时长,表示有多少梅尔频谱帧对应着这个音素。使用平均方差(Mean Square Error,MSE)作为损失函数,抽取MFA(Montreal Force Alignment)抽取的时长作为目标值。

音调预测器

由于真实音调中的高方差,预测的音调值和真实音调值分布有比较大的不同,为了更好地对音调包络的变化进行建模,使用连续小波变换(Continuous Wavelet Transform,CWT)将连续的音调序列分解为音调谱(Pitch Spectrogram),并且将音调谱作为音调预测器的目标值,同样使用平均方差MSE作为损失函数。在该文中,将每一帧的音调F0𝐹0量化为对数域的256个值,之后做嵌入,将其转换为对数嵌入向量p𝑝,将其加入到扩展隐向量序列。

能量预测器

计算每一个短时傅里叶变换帧能量的L2范数作为能量,之后将每一帧的能量量化到256个值,同样做嵌入,将其转化为能量嵌入向量e𝑒,像音调一样加入到扩展隐向量序列中。注意,使用能量预测器直接预测原始的能量值,而非量化之后的值,并且使用平均方差MSE作为损失函数。而不是像音调一样,对能量包络进行连续小波变换的主要原因是能量不像音调一样,有那么大的变化,对能量进行连续小波变换之后,也观察不到任何的提升。

FastSpeech 2s

FastSpeech 2s使用中间隐状态而非频谱直接生成语音,使得整个语音合成模型更加紧凑简洁。之前的工作不直接对语音样本点进行建模的主要原因是,相比于频谱,语音波形更加富有变化;并且大量语音样本点的建模,对有限的GPU显存而言,是一个比较大的挑战。

在FastSpeech 2s中,语音解码器采用对抗训练的方式,使用类似于Parallel WaveGAN(PWG)中的判别器,而生成器输入一小段隐状态序列,然后上采样。

实验

语音质量

可以看到,合成语音的平均意见得分(Mean Opinion Score,MOS)还是相当高的,但是FastSpeech 2s直接建模语音波形,语音质量稍差。

生成速度

FastSpeech 2的训练速度都比较快,推断速度FastSpeech 2/2s都挺快,超过了实时。

  • 14
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值