本文主要介绍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
带有对齐代理的前向注意力
所谓的对齐代理,其实就是引入了一个指示器,指示在第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
实验
由上图可以看到,生成失败的情形显著减少。
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:
- 伯努利分布Bernoulli
f(x)={p(x=1)=pp(x=0)=1−p𝑓(𝑥)={𝑝(𝑥=1)=𝑝𝑝(𝑥=0)=1−𝑝
cumprod
和cumsum
运算符
以向量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,𝑗𝛼𝑖,𝑗=𝑝𝑖,𝑗𝑞𝑖,𝑗
逐步单调注意力(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的语音合成模型,分别是:
-
基线Tacotron,采用原始的位置敏感注意力机制(Location Sensitive Attention),记作
Baseline
; -
混合高斯注意力机制(GMM Attention),20个高斯成分,记作
GMM
; -
单调注意力(Monotonic Attention),在推断时采用硬性或软性注意力,记作
MA hard
和MA soft
; -
前向注意力(Forward Attention),使用或者不使用转移代理,记作
FA+TA
和FA w/o TA
; -
逐步单调注意力(Stepwise Monotonic Attention),推断时使用硬性或软性注意力,记作
SMA hard
和SMA 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,
-
通过真实目标训练模型,而不是教师模型生成的简化版目标;
-
显式建模语音中的时长、音调和能量,在训练时直接将真实语音中提取的这些特征作为条件输入,在推断时将预测特征作为条件输入;
-
提出了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都挺快,超过了实时。