語音合成中聲學模型在可控性上的努力


本文主要介紹3種模型,分別是前向注意力(Forward Attention,FA/FA+TA),逐步單向注意力(Stepwise Monotonic Attention,SMA)和FastSpeech2,前兩者都是要求注意力權重盡量保證單調向前。具體來說,假設某一解碼步上的注意力權重為:\([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]\),可以看到,這個注意力系數會讓上一個解碼步上“關注”的編碼狀態和下一個編碼狀態在本次解碼時更加受到關注,也就是本次解碼要不然停留在原地,要不然向前一步;FA+TA就是為注意力系數顯式加了一個向前向后的選擇,也就是計算這個注意力系數時,\(0.1\times [0,0.8,0.2,0]+0.9\times [0,0,0.8,0.2]=[0,0.08,0.74,0.18]\),這個多出來的\(0.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=[t_1,t_2,...,t_N]\)映射到隱狀態序列\(x=[x_1,x_2,...,x_N]\),解碼器利用編碼器隱狀態序列\(x\)生成輸出序列\(o=[o_1,o_2,...,o_N]\)

在每一個解碼步上\(t\)上,注意力機制都對編碼器隱狀態進行“軟性地”選擇,將\(q_t\)記作第\(t\)個解碼步上的query,\(q_t\)通常是解碼器的隱狀態;\(\pi_t\in \{1,2,...,N\}\)可以看作一個類別隱變量,表示根據條件分布\(p(\pi|x,q_t)\)對編碼器隱狀態的選擇。那么上下文向量就可通過下式計算:

\[c_t=\sum_{n=1}^N y_t(n)x_n \]

其中,\(y_t(n)=p(\pi_t=n|x,q_t)\)。LSA引入了卷積特征以穩定注意力的對齊,具體地,\(k\)個大小為\(l\)的卷積核卷積之前解碼步的注意力權重。將\(F\in R^{k\times l}\)記作卷積矩陣,則:

\[y_{t-1}=\frac{exp(e_{t-1,n})}{\sum_{m=1}^Nexp(e_{t-1,m})} \]

\[f_t=F*y_{t-1} \]

\[e_{t,n}=v^T tanh(Wq_t+Vx_n+Uf_{t,n}+b) \]

在實際的語音合成中,在編碼隱狀態和解碼隱狀態之間的對齊路徑\(\{\pi_1,\pi_2,...,\pi_T\}\)表示文本特征對應着多少個聲學特征。

前向注意力

將對齊路徑的概率分布記作:

\[p(\pi_{1:t}|x,q_{1:t})=\prod_{t'=1}^tp(\pi_{t'}|x,q_{t'})=\prod_{t'=1}^ty_{t'}(\pi_{t'}) \]

\(P\)記作對齊路徑,其保持着單調性和連續性,並且不跳過任何編碼器狀態。

前向變量\(\alpha_t(n)\)定義為對齊路徑\(\{\pi_0,\pi_1,...,\pi_t\}\in P\)上的總概率:

\[\alpha_t(n)=\sum_{\pi_{0:t}\in P}\prod_{t'=1}^t y_{t'}(\pi_{t'}) \]

其中,\(\pi_0=1,\pi_t=n\)。也就是說,假設第0時間步必選中第1個編碼器隱狀態,第\(t\)時間步必選中第\(n\)個編碼器隱狀態。

注意,\(\alpha_t(n)\)\(\alpha_{t-1}(n)\)\(\alpha_{t-1}(n-1)\)計算而來:

\[\alpha_t(n)=(\alpha_{t-1}(n)+\alpha_{t-1}(n-1))y_t(n)\tag{1} \]

並且,

\[\hat{\alpha}_t(n)=\frac{\alpha_t(n)}{\sum_n\alpha_t(n)} \]

保證\(\hat{\alpha}_t(n)\)歸一化,\(\hat{\alpha}_t(n)\)表示第\(n\)個編碼步上第\(t\)個解碼步的對齊權重,張量大小為[batch_size,encoder_times]。並且利用\(\hat{\alpha}_t(n)\)代替注意力權重\(y_t(n)\)求取上下文向量:

\[c_t=\sum_{n=1}^N\hat{\alpha}_t(n)x_n \]

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

對應的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

https://github.com/Tomiinek/Multilingual_Text_to_Speech/blob/master/modules/attention.py

帶有對齊代理的前向注意力

所謂的對齊代理,其實就是引入了一個指示器,指示在第\(t\)個解碼步上移動到下一個“音素”上的概率,這個指示器由一個全連接層和Sigmoid層組成,輸入為上下文向量\(c_t\)、解碼器輸出\(o_{t-1}\)和query。

可以看到,相比於前向注意力,算法中就是多了一個概率值\(u\),用於控制“注意力”的移動,每一步都根據本時刻的上下文向量、上一時刻的解碼器輸出以及query計算下一個時刻的概率值。

這可以看作是一個專家產品(Product-of-Experts,PoE)模型,專家產品模型由若干個獨立的部分組成,結果由各個組件共同決定。在該文提出的方法中,一個組件\((1-\mu_{t-1})\hat{\alpha}_{t-1}(n)+\mu_{t-1}\hat{\alpha}_{t-1}(n-1)\)描述了單調性對齊的限制,另一個組件\(y_t(n)\)描述了原始的對齊方式,最終的注意力權重\(\hat{\alpha}_t(n)\)的計算由兩者共同決定。

與此同時,該文的注意力對齊機制還可以控制音頻速度。在生成時向轉移代理DNN的Sigmoid輸出中添加正或負的偏置值,轉移概率\(u_t\)就會增加或者減小,這就會導致注意力移動的速度變快或者變慢,從而導致生成語音變快或變慢。

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

https://github.com/Tomiinek/Multilingual_Text_to_Speech/blob/master/modules/attention.py

實驗

由上圖可以看到,生成失敗的情形顯著減少。

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

摘要

該文同樣是利用語音合成的單調特性,強制輸入輸出之間不僅僅單調,而且不能跳過輸入的每一個時間步。軟性注意力可以用來消除訓練和推斷之間的不一致性。

相關工作

普通注意力

首先還是回顧一下最通用的注意力機制:

\[e_{i,j}=Attention(s_{i-1},h_j) \]

\[\alpha_{i,j}=\frac{exp(e_{i,j})}{\sum_{k=1}^n exp(e_{i,k})}=softmax(e_{i,:})_j \]

\[c_i=\sum_{j=1}^n\alpha_{i,j}h_j \]

其中,\(h_j\)為第\(j\)個編碼步的輸出,\(s_{i-1}\)為上一時刻\(i-1\)解碼器隱狀態,\(e_{i,j}\)用於衡量\(s_{i-1}\)\(h_j\)之間的“相似度”,\(c_i\)是對編碼向量進行加權求和,作為第\(i\)步注意力的輸出。

單調注意力(Monotonic attention)可以同時保持單調性和局部性,可以應用到包括語音合成領域中:在每個解碼步\(i\)上,該機制都會檢查上一個解碼步選擇的memory索引\(t_{i-1}\),從“音素位置”\(j=t_{i-1}\)開始,采樣伯努利分布\(z_{i,j}\sim Bernoulli(p_{i,j})\)決定需要保持\(j\)不動,還是移動到下一個位置\(j\leftarrow j+1\)上。“音素位置”\(j\)會一直向前移動,直到到達輸入末尾或者收到采樣值\(z_{i,j}=1\)。當音素位置\(j\)停止時,該位置上對應的memory,即\(h_j\)將會被直接被作為上下文向量\(c_i\)。可以用下式遞歸計算\(\alpha_{i,j}\)

\[\alpha_{i,j}=p_{i,j}(\frac{(1-p_{i,j-1})\alpha_{i,j-1}}{p_{i,j-1}}+\alpha_{i-1,j}) \]

或者用下式並行計算:

\[\alpha_i=p_i\cdot \mathop{cumprod}(1-p_i)\cdot \mathop{cumsum}(\frac{\alpha_{i-1}}{\mathop{cumprod(1-p_i)}}) \]

之后就可以像正常的注意力機制一樣,利用注意力權重\(\alpha_{i,j}\)或者\(\alpha_i\)求得上下文向量\(c_i\)了。

注1:

  1. 伯努利分布Bernoulli

\[ f(x)=\left\{ \begin{matrix} p(x=1)=p \\ p(x=0)=1-p \end{matrix} \right. \]

  1. cumprodcumsum運算符

以向量\(A=[1,2,3,4]\)為例,

  • cumprod累積乘積

\[\mathop{cumprod}(A)=[1,2,6,24] \]

向量\(\mathop{cumprod}(A)\)第一位元素為1:

\[1=1\times 1 \]

向量\(\mathop{cumprod}(A)\)第二位元素為2:

\[2=1\times 2 \]

向量\(\mathop{cumprod}(A)\)第三位元素為6:

\[6=2\times 3 \]

向量\(\mathop{cumprod}(A)\)第四位元素為24:

\[24=6\times 4 \]

  • cumprod累加求和

\[\mathop{cumprod}(A)=[1,3,6,10] \]

硬性單調注意力

在最通用的注意力機制中,利用\(\alpha_{i,j}\)計算\(c_i\)的時候,沒有使用采樣,而是加權求期望,這樣能使得訓練過程可以反向傳播。

在軟性注意力中,對同一時刻\(i\),即使\(h_j\)\(j=1,2,...,T\))是天然有序的,但是在計算\(\alpha_{i,j}\)的時候,\(h_j\)是無序的。即使打亂\(h_j\)的順序,每個\(h_j\)對應的注意力權重值\(\alpha_{i,j}\)保持不變。並且對於不同時刻對齊到的\(h_j\)並不是單調的,本時刻對齊到\(h_j\),並不影響注意力下次可能就對齊到\(h_{j-1}\),或者\(h_{j-3}\) 😦,這並不符合語音合成的注意力特性。

硬性單調注意力能夠保證在計算\(\alpha_{i,j}\)時在編碼步\(j\)上是有序的,且讓\(\alpha_{i,j}\)在解碼步上是單調的。主要思路是,對任意一個時刻\(i-1\),關注且僅關注一個編碼隱狀態(attend and only attend to one hidden state),記作\(h_j\)。在每一個解碼步,計算概率\(p_{i,j}\),決定當前解碼步是繼續關注原先的編碼隱狀態\(h_j\),還是跳到下一個\(h_{j+1}\)

\[e_{i,j}=a(s_{i-1},h_j)\\ p_{i,j}=\sigma(e_{i,j})\\ z_{i,j}\sim Bernoulli(p_{i,j})\tag{1} \]

\(e_{i,j}\)的計算和普通的軟性注意力(Soft Attention)是一樣的,都是求query和每個編碼隱狀態之間的“相似度”,但是和普通的軟性注意力不一樣的是,得到\(e_{i,j}\)之后並沒有對解碼步\(i\)上的所有\(j\)進行Softmax,而是獨立進行Sigmoid,得到一個概率值\(p_{i,j}\)。當概率\(p_{i,j}\)的伯努利采樣值\(z_{i,j}=1\),則關注原先的編碼隱狀態\(h_j\),否則\(z_{i,j}=0\)則前進一個編碼隱狀態\(h_{j+1}\)。如果前進一步(\(z_{i,j}=0\)),在前進之后再次計算\(z_{i,j+1}\),直到\(z_{i,j'}=1\),停止前進,此時得到上下文向量\(c_i=h_{j'}\)。有了上下文向量\(c_i\)之后就可以按照普通的軟性注意力里的方法,計算\(s_i\)\(y_i\)。接着計算下一個上下文向量\(c_{i+1}\),此時需要從\(h_{j'}\)開始,重復上述過程。

訓練

公式1介紹了硬性單調注意力的方法,但是這里有一個從伯努利分布的采樣操作,采樣操作是沒辦法反向傳播的。解決方法和普通的軟性注意力一樣,使用\(c_i=\sum_{j=1}^T\alpha_{i,j}h_j\)代替原來直接的\(h_j\),但是硬性單調注意力中的\(h_j\)時通過一步一步前進獲得的,而求期望則是一個加權求和的過程。那么求每一解碼步上聯合概率\(\alpha_{i,j}\)的方法如下:

  • \(i=1\)時,如果是關注\(h_j\),此時對應的概率為\(p_{i,j}\),且\(k=1,2,...,j-1\)都被跳過了,對應的概率為\(\prod_{k=1}^{j-1}(1-p_{1,k})\),則該事件的聯合概率為\(\alpha_{1,j}=p_{i,j}\prod_{k=1}^{j-1}(1-p_{1,k})\)

  • \(i>=2\)時,假設\(i-1\)解碼步選中了\(h_k\),那么從時刻\(1\)到時刻\(i-1\)選中\(k\)這總共\(i-1\)個事件的聯合概率即為\(\alpha_{i-1,k}\);又假設\(i\)時刻選中了\(h_j\),概率為\(p_{i,j}\),那么說明\(h_k,h_{k+1}...,h_{j-1}\)都應該被跳過,概率為\(\prod_{l=k}^{j-1}(1-p_{i,l})\)

    因此整個聯合概率模型為:

    \[\alpha_{i,j}=p_{i,j}\sum_{k=1}^{j}(\alpha_{i-1,k}\prod_{l=k}^{j-1}(1-p_{i,l}))\tag{2} \]

    上式中的\(\alpha_{i,j}\)中,外層的\(\sum\)上界中的\(k\)最多可以到\(j\),此時內層的\(\prod\)變為\(\prod_{j}^{j-1}\);定義這種起始點大於終點的累乘結果為1。在這種情形下是正確的,因為當\(k=j\)時,說明\(i\)時刻和\(i-1\)時刻都選中了\(h_j\),概率為\(p_{i,j}(\alpha_{i-1,j}\prod_{l=j}^{j-1}(1-p_{i,l}))=p_{i,j}\alpha_{i-1,j}\)。利用該式對公式2的\(\alpha_{i,j}\)進行化簡。

化簡方法1

首先對公式2從外層的求和中分離出\(k=j\),則

\[\alpha_{i,j}=p_{i,j}(\sum_{k=1}^{j-1}(\alpha_{i-1,k}\prod_{l=k}^{j-1}(1-p_{i,l}))+\alpha_{i-1,j}\prod_{l=j}^{j-1}(1-p_{i,l})) \]

從連乘符號中分離出\(l=j-1\),並且利用\(\prod_{l=j}^{j-1}(1-p_{i,l})=1\),那么,

\[\alpha_{i,j}=p_{i,j}(\sum_{k=1}^{j-1}(\alpha_{i-1,k}\prod_{l=k}^{j-2}((1-p_{i,l})(1-p_{i,j-1}))+\alpha_{i-1,j}) \]

把上式中的\((1-p_{i,j-1})\)從內部的連乘和求和符號中拿出來,畢竟已經不和\(l,k\)有關了。變為:

\[\alpha_{i,j}=p_{i,j}((1-p_{i,j-1})\sum_{k=1}^{j-1}(\alpha_{i-1,k}\prod_{l=k}^{j-2}(1-p_{i,l}))+\alpha_{i-1,j})\tag{3} \]

對比一下上面的那個聯合概率公式,也就是公式2,把公式2等號右側的\(p_{i,j}\)除到左邊,那么,

\[\frac{\alpha_{i,j}}{p_{i,j}}=\sum_{k=1}^j(\alpha_{i-1,k}\prod_{l=k}^{j-1}(1-p_{i,l}))\tag{4} \]

和公式4相比,上面公式3的連乘和求和符號部分,就是上標由\(j\)變為了\(j-1\)。把上式3等號右側的\(p_{i,j}\)除到左邊,公式3就變成了:

\[\frac{\alpha_{i,j}}{p_{i,j}}=(1-p_{i,j-1})\sum_{k=1}^{j-1}(\alpha_{i-1,k}\prod_{l=k}^{j-2}(1-p_{i,l}))+\alpha_{i-1,j}\tag{5} \]

結合公式4,將求和符號包圍的部分替換掉,公式5變為:

\[\frac{\alpha_{i,j}}{p_{i,j}}=(1-p_{i,j-1})\frac{\alpha_{i,j-1}}{p_{i,j-1}}+\alpha_{i-1,j}\tag{6} \]

\(q_{i,j}=\frac{\alpha_{i,j}}{p_{i,j}}\),則上式6可以簡寫為:

\[q_{i,j}=(1-p_{i,j-1})q_{i,j-1}+\alpha_{i-1,j} \]

到此,將\(\alpha_{i,j}\)的遞推公式化簡完畢。

化簡方法2

現在利用另一種方法化簡\(\alpha_{i,j}\)。將公式2內側的連乘\(\prod_{l=k}^{j-1}(1-p_{i,l})\)變換為\(\frac{\prod_{l=0}^{j-1}(1-p_{i,l})}{\prod_{l=0}^{k-1}(1-p_{i,l})}\),使得分子與\(k\)無關,那么分子就可以從外層的求和中分離出來,上式2變為

\[p_{i,j}\sum_{k=1}^j(\alpha_{i-1,k}\frac{\prod_{l=0}^{j-1}(1-p_{i,l})}{\prod_{l=0}^{k-1}(1-p_{i,l})})=p_{i,j}\prod_{l=0}^{j-1}(1-p_{i,l})\sum_{k=1}^j(\frac{\alpha_{i-1,k}}{\prod_{l=0}^{k-1}(1-p_{i,l})}) \]

\(q_i=\frac{\alpha_{i,j}}{p_{i,j}}\),將\(p_{i,j}\)除到等號左側,則

\[q_i=\mathop{cumprod}(1-p_i)\mathop{cumsum}(\frac{\alpha_{i-1}}{\mathop{cumprod}(1-p_i)}) \]

則,

\[\alpha_{i,j}=p_{i,j}\sum_{k=1}^j(\alpha_{i-1,k}\prod_{l=k}^{j-1}(1-p_{i,l}))=p_{i,j}((1-p_{i,j-1})\frac{\alpha_{i,j-1}}{p_{i,j-1}}+\alpha_{i-1,j}) \]

\(q_{i,j}=\frac{\alpha_{i,j}}{p_{i,j}}\),上式中的\(p_{i,j}\)除到左側,簡化之后的上式:

\[q_{i,j}=(1-p_{i,j-1})q_{i,j-1}+\alpha_{i-1,j} \]

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

整理一下上述結果:

\[e_{i,j}=a(s_{i-1},h_{j})\\ p_{i,j}=\sigma(e_{i,j})\\ q_{i,j}=(1-p_{i,j-1})q_{i,j-1}+\alpha_{i-1,j}\\ \alpha_{i,j}=p_{i,j}q_{i,j} \]

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

逐步單調注意力(Stepwise monotonic attention)

逐步單調注意力(Stepwise monotonic attention)在單調注意力(Monotonic attention)的基礎上添加額外的限制:在每一個解碼步,硬對齊位置應該最多移動一步。

在每一個解碼步\(i\)上,該機制探測上一步使用的memory條目\(j=t_{i-1}\),僅需要決定是向前或者停止不動。因此可以直接建立\(p_{i,j}\)分布的遞推關系:

\[\alpha_{ij}=\alpha_{i-1,j-1}(1-p_{i,j-1})+\alpha_{i-1,j}p_{ij} \]

同樣地,可以更為高效地並行計算:

\[\alpha_i=\alpha_{i-1}\cdot p_i+[0;\alpha_{i-1,:-1}\cdot (1-p_{i,:-1})] \]

其中,\([0;]\)表示左側填充0。

在訓練階段,上下文向量和普通的注意力機制類似。但是可以看到,無論是單調注意力,或是逐步單調注意力,在訓練和推斷階段的上下文向量存在不匹配的現象,在訓練階段,送入解碼器中的輸入是一個“軟性”上下文向量而非單一的memory,也就是說在訓練階段,解碼器可以利用將來要注意的memory而不是基於當前的memory來預測聲學特征。當然,該文建議在推斷階段仍然使用軟性注意力,只不過結合上式中求\(\alpha_{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作為損失函數。在該文中,將每一幀的音調\(F_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都挺快,超過了實時。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM