Commit fa92b495 by xiaotong

re-organization (secs 10 and 12)

parent 63d7ae61
......@@ -472,8 +472,6 @@ $\funp{P}({y_j | \vectorn{\emph{s}}_{j-1} ,y_{j-1},\vectorn{\emph{C}}})$由Softm
\vspace{0.5em}
\end{itemize}
\parinterval 针对以上问题,输入层(词嵌入)和输出层(Softmax)的内容已在第九章进行了介绍,下面将详细介绍常用的循环神经网络结构(表示学习模型结构)。
%----------------------------------------------
\begin{figure}[htp]
\centering
......@@ -483,21 +481,8 @@ $\funp{P}({y_j | \vectorn{\emph{s}}_{j-1} ,y_{j-1},\vectorn{\emph{C}}})$由Softm
\end{figure}
%----------------------------------------------
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 10.3.2
%----------------------------------------------------------------------------------------
\subsection{循环神经网络结构}
\label{sec:10.3.2}
\parinterval 循环神经网络的核心是设计循环单元的结构。至今,研究人员已经提出了很多优秀的循环单元结构,这里将介绍其中三种基本结构:RNN,LSTM和GRU。LSTM\\和GRU是RNN的变体,在自然语言处理任务中得到了广泛的应用。
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
%----------------------------------------------------------------------------------------
\subsubsection{1. 循环神经单元(RNN)}
\parinterval 输入层(词嵌入)和输出层(Softmax)的内容已在{\chapternine}进行了介绍,因此这里的核心内容是设计循环神经网络结构,即设计循环单元的结构。至今,研究人员已经提出了很多优秀的循环单元结构。其中RNN(Recurrent Neural Network)是最原始的循环神经网络结构。在RNN中,对于序列$\vectorn{\emph{x}}=\{ \vectorn{\emph{x}}_1, \vectorn{\emph{x}}_2,...,\vectorn{\emph{x}}_m \}$,每个时刻$t$都对应一个循环单元,它的输出是一个向量$\vectorn{\emph{h}}_t$,可以被描述为:
\parinterval RNN(Recurrent Neural Network)是最原始的循环神经网络结构。在RNN中,对于序列$\vectorn{\emph{x}}=\{ \vectorn{\emph{x}}_1, \vectorn{\emph{x}}_2,...,\vectorn{\emph{x}}_m \}$,每个时刻$t$都对应一个循环单元,它的输出是一个向量$\vectorn{\emph{h}}_t$,可以被描述为:
\begin{eqnarray}
\vectorn{\emph{h}}_t=f(\vectorn{\emph{x}}_t \vectorn{\emph{U}}+\vectorn{\emph{h}}_{t-1} \vectorn{\emph{W}}+\vectorn{\emph{b}})
\label{eq:10-11}
......@@ -505,14 +490,14 @@ $\funp{P}({y_j | \vectorn{\emph{s}}_{j-1} ,y_{j-1},\vectorn{\emph{C}}})$由Softm
\noindent 其中$\vectorn{\emph{x}}_t$是当前时刻的输入,$\vectorn{\emph{h}}_{t-1}$是上一时刻循环单元的输出,$f(\cdot)$是激活函数,$\vectorn{\emph{U}}$$\vectorn{\emph{W}}$是参数矩阵,$\vectorn{\emph{b}}$是偏置。
\parinterval 虽然RNN的结构很简单,但是已经具有了对序列信息进行记忆的能力。实际上,基于RNN结构的神经语言模型已经能够取得比传统$n$-gram语言模型更优异的性能。在机器翻译中,RNN也可以做为入门或者快速原型所使用的神经网络结构。
\parinterval 虽然RNN的结构很简单,但是已经具有了对序列信息进行记忆的能力。实际上,基于RNN结构的神经语言模型已经能够取得比传统$n$-gram语言模型更优异的性能。在机器翻译中,RNN也可以做为入门或者快速原型所使用的神经网络结构。后面会进一步介绍更加先进的循环单元结构,以及搭建循环神经网络中的常用技术。
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
% NEW SUB-SECTION
%----------------------------------------------------------------------------------------
\subsubsection{2. 长短时记忆网络(LSTM)}
\label{sec:10.3.3.2}
\subsection{长短时记忆网络(LSTM)}
\label{sec:lstm-cell}
\parinterval RNN结构使得当前时刻循环单元的状态包含了之前时间步的状态信息。但是这种对历史信息的记忆并不是无损的,随着序列变长,RNN的记忆信息的损失越来越严重。在很多长序列处理任务中(如长文本生成)都观测到了类似现象。对于这个问题,Hochreiter和Schmidhuber提出了{\small\bfnew{长短时记忆}}\index{长短时记忆}(Long Short-term Memory)\index{Long Short-Term Memory}模型,也就是常说的LSTM模型\upcite{HochreiterLong}
......@@ -579,10 +564,10 @@ $\funp{P}({y_j | \vectorn{\emph{s}}_{j-1} ,y_{j-1},\vectorn{\emph{C}}})$由Softm
\parinterval LSTM的完整结构如图\ref{fig:10-16}所示,模型的参数包括:参数矩阵$\vectorn{\emph{W}}_f$$\vectorn{\emph{W}}_i$$\vectorn{\emph{W}}_c$\\$\vectorn{\emph{W}}_o$和偏置$\vectorn{\emph{b}}_f$$\vectorn{\emph{b}}_i$$\vectorn{\emph{b}}_c$$\vectorn{\emph{b}}_o$。可以看出,$\vectorn{\emph{h}}_t$是由$\vectorn{\emph{c}}_{t-1}$$\vectorn{\emph{h}}_{t-1}$$\vectorn{\emph{x}}_t$共同决定的。此外,上述公式中激活函数的选择是根据函数各自的特点决定的。
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
% NEW SUB-SECTION
%----------------------------------------------------------------------------------------
\subsubsection{3. 门控循环单元(GRU)}
\subsection{门控循环单元(GRU)}
\parinterval LSTM 通过门控单元控制传递状态,忘记不重要的信息,记住必要的历史信息,在长序列上取得了很好的效果,但是其进行了许多门信号的计算,较为繁琐。{\small\bfnew{门循环单元}}\index{门循环单元}(Gated Recurrent Unit,GRU)\index{Gated Recurrent Unit,GRU}作为一个LSTM的变种,它继承了LSTM中利用门控单元控制信息传递的思想,并对LSTM进行了简化\upcite{Cho2014Learning}。它把循环单元状态$\vectorn{\emph{h}}_t$和记忆$\vectorn{\emph{c}}_t$合并成一个状态$\vectorn{\emph{h}}_t$,同时使用了更少的门控单元,大大提升了计算效率。
......@@ -620,10 +605,10 @@ $\funp{P}({y_j | \vectorn{\emph{s}}_{j-1} ,y_{j-1},\vectorn{\emph{C}}})$由Softm
\parinterval GRU的输入输出和RNN类似,其采用与LSTM类似的门控思想,达到捕获长距离依赖信息的目的。此外,GRU比LSTM少了一个门结构,而且参数只有$\vectorn{\emph{W}}_r$$\vectorn{\emph{W}}_u$$\vectorn{\emph{W}}_h$。因此,GRU具有比LSTM高的运算效率,在系统研发中也经常被使用。
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
% NEW SUB-SECTION
%----------------------------------------------------------------------------------------
\subsubsection{4. 双向模型}
\subsection{双向模型}
\parinterval 前面提到的循环神经网络都是自左向右运行的,也就是说在处理一个单词的时候只能访问它前面的序列信息。但是,只根据句子的前文来生成一个序列的表示是不全面的,因为从最后一个词来看,第一个词的信息可能已经很微弱了。为了同时考虑前文和后文的信息,一种解决办法是使用双向循环网络,其结构如图\ref{fig:10-18}所示。这里,编码器可以看作有两个循环神经网络,第一个网络,即红色虚线框里的网络,从句子的右边进行处理,第二个网络从句子左边开始处理,最终将正向和反向得到的结果都融合后传递给解码器。
......@@ -639,10 +624,10 @@ $\funp{P}({y_j | \vectorn{\emph{s}}_{j-1} ,y_{j-1},\vectorn{\emph{C}}})$由Softm
\parinterval 双向模型是自然语言处理领域的常用模型,包括前面提到的词对齐对称化、语言模型等中都大量地使用了类似的思路。实际上,这里也体现了建模时的非对称思想。也就是,建模时如果设计一个对称模型可能会导致问题复杂度增加,因此往往先对问题进行化简,从某一个角度解决问题。之后再融合多个模型,从不同角度得到相对合理的最终方案。
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
% NEW SUB-SECTION
%----------------------------------------------------------------------------------------
\subsubsection{5. 多层循环神经网络}
\subsection{多层网络}
\parinterval 实际上,对于单词序列所使用的循环神经网络是一种很“深”的网络,因为从第一个单词到最后一个单词需要经过至少句子长度相当层数的神经元。比如,一个包含几十个词的句子也会对应几十个神经元层。但是,在很多深度学习应用中,更习惯把对输入序列的同一种处理作为“一层”。比如,对于输入序列,构建一个RNN,那么这些循环单元就构成了网络的“一层”。当然,这里并不是要混淆概念。只是要明确,在随后的讨论中,“层”并不是指一组神经元的全连接,它一般指的是网络的拓扑结构。
......@@ -658,196 +643,468 @@ $\funp{P}({y_j | \vectorn{\emph{s}}_{j-1} ,y_{j-1},\vectorn{\emph{C}}})$由Softm
%----------------------------------------------
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 10.3.3
% NEW SECTION
%----------------------------------------------------------------------------------------
\vspace{-1.5em}
\subsection{训练}
\parinterval 第九章已经介绍了神经网络的训练方法。其中最常用的是基于梯度的方法,即:使用一定量样本进行神经网络的前向计算,之后进行反向计算,并得到所有参数的梯度信息,再使用下面的规则进行参数更新:
\begin{eqnarray}
\vectorn{\emph{w}}_{step+1} = \vectorn{\emph{w}}_{step} - \alpha \cdot \frac{ \partial L(\vectorn{\emph{w}}_{step})} {\partial \vectorn{\emph{w}}_{step} }
\label{eq:10-30}
\end{eqnarray}
\noindent 其中,$\vectorn{\emph{w}}_{step}$表示更新前的模型参数,$\vectorn{\emph{w}}_{step+1}$表示更新后的模型参数,$L(\vectorn{\emph{w}}_{step})$表示模型相对于$\vectorn{\emph{w}}_{step}$的损失,$\frac{\partial L(\vectorn{\emph{w}}_{step})} {\partial \vectorn{\emph{w}}_{step} }$表示损失函数的梯度,$\alpha$是更新的步进值。也就是说,给定一定量的训练数据,不断执行公式\ref{eq:10-30}的过程。反复使用训练数据,直至模型参数达到收敛或者损失函数不再变化。通常,把公式的一次执行称为“一步”更新/训练,把访问完所有样本的训练称为“一轮”训练。
\parinterval 将公式\ref{eq:10-30}应用于神经机器翻译有几个基本问题需要考虑:1)损失函数的选择;2)参数初始化的策略,也就是如何设置$\vectorn{\emph{w}}_0$;3)优化策略和学习率调整策略;4)训练加速。下面对这些问题进行讨论。
\sectionnewpage
\section{注意力机制}
\label{sec:10.4}
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
%----------------------------------------------------------------------------------------
\parinterval 前面提到GNMT使用了注意力机制,那么注意力机制究竟是什么?回顾一下第二章提到过的“上帝是不公平的”这个观点,它主要是表达了:世界上事物之间的联系不是均匀的,有些事物之间的联系会很强,而其他的联系可能很弱。自然语言也完美地契合了这个观点。比如,再重新看一下前面提到的根据上下文补全缺失单词的例子,
\subsubsection{1. 损失函数}
\vspace{0.8em}
\centerline{中午\ \ 吃饭\ \ \ \ \ \ \ 下午\ 篮球\ \ \ 现在\ \ 饿\ \ \ \underline{\quad \quad \quad}}
\vspace{0.8em}
\parinterval 因为神经机器翻译在每个目标语位置都会输出一个概率分布,表示这个位置上不同单词出现的可能性,因此需要知道当前位置输出的分布相比于标准答案的“损失”。对于这个问题,常用的是交叉熵损失函数。令$\vectorn{\emph{y}}$表示机器翻译模型输出的分布,$\hat{\vectorn{\emph{y}}}$ 表示标准答案,则交叉熵损失可以被定义为:
\begin{eqnarray}
L_{\textrm{ce}}(\vectorn{\emph{y}},\hat{\vectorn{\emph{y}}}) = - \sum_{k=1}^{|V|} \vectorn{\emph{y}}[k] \textrm{log} (\hat{\vectorn{\emph{y}}}[k])
\label{eq:10-3222}
\end{eqnarray}
\noindent 之所以能想到在横线处填“吃饭”、“吃东西”很有可能是因为看到了“没\ 吃饭”、 “很\ 饿”等关键信息。也就是这些关键的片段对预测缺失的单词起着关键性作用。而预测“吃饭”与前文中的“ 中午”、“又”之间的联系似乎不那么紧密。也就是说,在形成 “吃饭”的逻辑时,在潜意识里会更注意“没吃饭”、“很饿”等关键信息。也就是我们的关注度并不是均匀地分布在整个句子上的。
\noindent 其中$\vectorn{\emph{y}}[k]$$\hat{\vectorn{\emph{y}}}[k]$分别表示向量$\vectorn{\emph{y}}$$\hat{\vectorn{\emph{y}}}$的第$k$维,$|V|$表示输出向量的维度(等于词表大小)。对于一个模型输出的概率分布$\vectorn{\emph{Y}} = \{ \vectorn{\emph{y}}_1,\vectorn{\emph{y}}_2,..., \vectorn{\emph{y}}_n \}$和标准答案分布$\widehat{\vectorn{\emph{Y}}}=\{ \hat{\vectorn{\emph{y}}}_1, \hat{\vectorn{\emph{y}}}_2,...,\hat{\vectorn{\emph{y}}}_n \}$,损失函数可以被定义为:
\begin{eqnarray}
L(\vectorn{\emph{Y}},\widehat{\vectorn{\emph{Y}}}) = \sum_{j=1}^n L_{\textrm{ce}}(\vectorn{\emph{y}}_j,\hat{\vectorn{\emph{y}}}_j)
\label{eq:10-31}
\end{eqnarray}
\parinterval 这个现象可以用注意力机制进行解释。注意力机制的概念来源于生物学的一些现象:当待接收的信息过多时,人类会选择性地关注部分信息而忽略其他信息。它在人类的视觉、听觉、嗅觉等方面均有体现,当我们在感受事物时,大脑会自动过滤或衰减部分信息,仅关注其中少数几个部分。例如,当看到图\ref{fig:12-20}时,往往不是“均匀地”看图像中的所有区域,可能最先注意到的是大狗头上戴的帽子,然后才会关注图片中其他的部分。
\parinterval 公式\ref{eq:10-31}是一种非常通用的损失函数形式,除了交叉熵,也可以使用其他的损失函数,这时只需要替换$L_{ce} (\cdot)$即可。这里使用交叉熵损失函数的好处在于,它非常容易优化,特别是与Softmax组合,其反向传播的实现非常高效。此外,交叉熵损失(在一定条件下)也对应了极大似然的思想,这种方法在自然语言处理中已经被证明是非常有效的
\parinterval 那么注意力机制和神经机器翻译又有什么关系呢?它如何解决神经机器翻译的问题呢?下面就一起来看一看
\parinterval 除了交叉熵,很多系统也使用了面向评价的损失函数,比如,直接利用评价指标BLEU定义损失函数。不过这类损失函数往往不可微分,因此无法直接获取梯度。这时可以引入强化学习技术,通过策略梯度等方法进行优化。不过这类方法需要采样等手段,这里不做重点讨论,相关内容会在后面技术部分进行介绍。
%----------------------------------------------
\begin{figure}[htp]
\centering
\includegraphics[scale=0.2]{./Chapter12/Figures/dog-hat.jpg}
\caption{戴帽子的狗}
\label{fig:12-20}
\end{figure}
%----------------------------------------------
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
% NEW SUB-SECTION
%----------------------------------------------------------------------------------------
\subsection{翻译中的注意力机制}
\subsubsection{2. 参数初始化}
\parinterval 神经网络的参数主要是各层中的线性变换矩阵和偏置。在训练开始时,需要对参数进行初始化。但是,由于神经机器翻译的网络结构复杂,因此损失函数往往不是凸函数,不同初始化会导致不同的优化结果。而且在大量实践中已经发现,神经机器翻译模型对初始化方式非常敏感,性能优异的系统往往需要特定的初始化方式。
\parinterval 下面以LSTM循环神经网络为例(见\ref{sec:10.3.3.2}节),介绍机器翻译模型的初始化方法。这些方法也可以推广到GRU等结构。具体内容如下:
\parinterval 早期的神经机器翻译只使用循环神经网络最后一个单元的输出作为整个序列的表示,这种方式有两个明显的缺陷:
\begin{itemize}
\vspace{0.5em}
\item LSTM遗忘门偏置初始化为1,也就是始终选择遗忘记忆$\vectorn{\emph{c}}$,这样可以有效防止初始化时$\vectorn{\emph{c}}$里包含的错误信号传播到后面的所有时刻。
\vspace{0.5em}
\item 网络中的其他偏置一般都初始化为0,可以有效防止加入过大或过小的偏置后使得激活函数的输出跑到“饱和区”,也就是梯度接近0的区域,防止训练一开始就无法跳出局部极小的区域。
\item 首先,虽然编码器把一个源语句子的表示传递给解码器,但是一个维度固定的向量所能包含的信息是有限的,随着源语序列的增长,将整个句子的信息编码到一个固定维度的向量中可能会造成源语句子信息的丢失。显然,在翻译较长的句子时,解码端可能无法获取完整的源语信息,降低翻译性能;
\vspace{0.5em}
\item 网络的权重矩阵$\vectorn{\emph{w}}$一般使用Xavier参数初始化方法\upcite{pmlr-v9-glorot10a},可以有效稳定训练过程,特别是对于比较“深”的网络。令$d_{in}$$d_{out}$分别表示$\vectorn{\emph{w}}$的输入和输出的维度大小,则该方法的具体实现如下:
\begin{eqnarray}
\vectorn{\emph{w}} \sim U(-\sqrt{ \frac{6} { d_{in} + d_{out} } } , \sqrt{ \frac{6} { d_{in} + d_{out} } })
\label{eq:10-32}
\item 此外,当生成某一个目标语单词时,并不是均匀的使用源语句子中的单词信息。更普遍的情况是,系统会参考与这个目标语单词相对应的源语单词进行翻译。这有些类似于词对齐的作用,即翻译是基于单词之间的某种对应关系。但是,使用单一的源语表示根本无法区分源语句子的不同部分,更不用说对源语单词和目标语单词之间的联系进行建模了。
\vspace{0.5em}
\end{eqnarray}
其中$U(a,b)$表示以$[a,b]$为范围的均匀分布,$6$是固定值。\\
\end{itemize}
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
%----------------------------------------------------------------------------------------
\vspace{-2.5em}
\subsubsection{3. 优化策略}
\parinterval 更直观的,如图\ref{fig:12-21},目标语中的“very long”仅依赖于源文中的“很长”。这时如果将所有源语编码成一个固定的实数向量,“很长”的信息就很可能被其他词的信息淹没掉。
%\vspace{0.5em}
\parinterval 公式\ref{eq:10-30}展示了最基本的优化策略,也被称为标准的SGD优化器。实际上,训练神经机器翻译模型时,还有非常多的优化器可以选择,在第九章也有详细介绍,这里考虑Adam优化器。 Adam 通过对梯度的{\small\bfnew{一阶矩估计}}\index{一阶矩估计}(First Moment Estimation)\index{First Moment Estimation}{\small\bfnew{二阶矩估计}}\index{二阶矩估计}(Second Moment Estimation)\index{Second Moment Estimation}进行综合考虑,计算出更新步长。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter12/Figures/figure-attention-of-source-and-target-words}
\caption{源语词和目标语词的关注度}
\label{fig:12-21}
\end{figure}
%----------------------------------------------
\parinterval\ref{tab:10-8}从效果上对比了Adam和SGD的区别。通常,Adam收敛的比较快,不同任务基本上可以使用一套配置进行优化,虽性能不算差,但很难达到最优效果。相反,SGD虽能通过在不同的数据集上进行调整,来达到最优的结果,但是收敛速度慢。因此需要根据不同的需求来选择合适的优化器。若需要快得到模型的初步结果,选择Adam较为合适,若是需要在一个任务上得到最优的结果,选择SGD更为合适。
\parinterval 显然,以上问题的根本原因在于所使用的表示模型还比较“弱”。因此需要一个更强大的表示模型,在生成目标语单词时能够有选择地获取源语句子中更有用的部分。更准确的说,对于要生成的目标语单词,相关性更高的源语片段应该在源语句子的表示中体现出来,而不是将所有的源语单词一视同仁。在神经机器翻译中引入注意力机制正是为了达到这个目的\upcite{bahdanau2014neural,DBLP:journals/corr/LuongPM15}。实际上,除了机器翻译,注意力机制也被成功地应用于图像处理、语音识别、自然语言处理等其他任务。而正是注意力机制的引入,使得包括机器翻译在内很多自然语言处理系统得到了飞跃发展。
\parinterval 神经机器翻译中的注意力机制并不复杂。对于每个目标语单词$y_j$,系统生成一个源语表示向量$\vectorn{\emph{{C}}}_j$与之对应,$\vectorn{\emph{C}}_j$会包含生成$y_j$所需的源语的信息,或者说$\vectorn{\emph{C}}_j$是一种包含目标语单词与源语单词对应关系的源语表示。相比用一个静态的表示$\vectorn{\emph{C}}$,注意机制使用的是动态的表示$\vectorn{\emph{C}}_j$$\vectorn{\emph{C}}_j$也被称作对于目标语位置$j$的上下文向量。图\ref{fig:12-22}对比了未引入注意力机制和引入了注意力机制的编码器-解码器结构。可以看出,在注意力模型中,对于每一个目标单词的生成,都会额外引入一个单独的上下文向量参与运算。
\vspace{-0.5em}
%----------------------------------------------
\begin{table}[htp]
\begin{figure}[htp]
\centering
\caption{ Adam / SGD对比}
\label{tab:10-8}
\begin{tabular}{l | l l }
&使用 &性能 \\ \hline
\rule{0pt}{15pt} Adam &一套配置包打天下 &不算差,但没到极限 \\
\rule{0pt}{15pt} SGD &换一个任务就得调 &效果好 \\
\end{tabular}
\end{table}
\input{./Chapter12/Figures/figure-encoder-decoder-with-attention}
\caption{(a)不使用和(b)使用注意力机制的翻译模型对比}
\label{fig:12-22}
\end{figure}
%----------------------------------------------
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
% NEW SUB-SECTION
%----------------------------------------------------------------------------------------
\vspace{-2.5em}
\subsubsection{4. 梯度裁剪}
%\vspace{0.5em}
\subsection{上下文向量的计算}
\label{sec:12.1.3}
\parinterval 需要注意的是,训练循环神经网络时,反向传播使得网络层之间的梯度重复相乘,在网络层数过深时,如果连乘因子小于1可能造成梯度指数级的减少,甚至趋近于0,导致网络无法优化,也就是梯度消失问题。当连乘因子大于1时,可能会导致梯度的乘积变得异常大,造成梯度爆炸的问题。在这种情况下需要使用“梯度裁剪”来防止梯度超过阈值。梯度裁剪在第九章已经介绍过,这里简单回顾一下。梯度裁剪的具体公式如下:
\vspace{-0.5em}
\parinterval 那么注意力机制是如何针对不同单词生成不同的上下文向量呢?这里,可以将注意力机制看做是一种对接收到的信息的加权处理。对于更重要的信息赋予更高的权重即更高的关注度,对于贡献度较低的信息分配较低的权重,弱化其对结果的影响。这样,$\vectorn{\emph{C}}_j$可以包含更多对当前目标语位置有贡献的源语片段的信息。
\parinterval 根据这种思想,上下文向量$\vectorn{\emph{C}}_j$被定义为对不同时间步编码器输出的状态序列$\{ \vectorn{\emph{h}}_1, \vectorn{\emph{h}}_2,...,\vectorn{\emph{h}}_m \}$进行加权求和,如下:
\begin{eqnarray}
\vectorn{\emph{w}}' = \vectorn{\emph{w}} \cdot \frac{\gamma} {\textrm{max}(\gamma,\| \vectorn{\emph{w}} \|_2)}
\label{eq:10-33}
\vectorn{\emph{C}}_j=\sum_{i} \alpha_{i,j} \vectorn{\emph{h}}_i
\label{eq:12-22}
\end{eqnarray}
%\vspace{0.5em}
\noindent 其中$\gamma$是手工设定的梯度大小阈值, $\| \cdot \|_2$是L2范数,$\vectorn{\emph{w}}'$表示梯度裁剪后的参数。这个公式的含义在于只要梯度大小超过阈值,就按照阈值与当前梯度大小的比例进行放缩
\noindent 其中$\alpha_{i,j}${\small\sffamily\bfseries{注意力权重}}\index{注意力权重}(Attention Weight)\index{Attention Weight},它表示目标语第$j$个位置与源语第$i$个位置之间的相关性大小。这里,将每个时间步编码器的输出$\vectorn{\emph{h}}_i$ 看作源语位置$i$的表示结果。进行翻译时,解码端可以根据当前的位置$j$,通过控制不同$\vectorn{\emph{h}}_i$的权重得到$\vectorn{\emph{C}}_j$,使得对目标语位置$j$贡献大的$\vectorn{\emph{h}}_i$$\vectorn{\emph{C}}_j$的影响增大。也就是说,$\vectorn{\emph{C}}_j$实际上就是\{${\vectorn{\emph{h}}_1, \vectorn{\emph{h}}_2,...,\vectorn{\emph{h}}_m}$\}的一种组合,只不过不同的$\vectorn{\emph{h}}_i$会根据对目标端的贡献给予不同的权重。图\ref{fig:12-23}展示了上下文向量$\vectorn{\emph{C}}_j$的计算过程
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
%----------------------------------------------------------------------------------------
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter12/Figures/figure-calculation-process-of-context-vector-c}
\caption{上下文向量$\vectorn{\emph{C}}_j$的计算过程}
\label{fig:12-23}
\end{figure}
%----------------------------------------------
\subsubsection{5. 学习率策略}
\parinterval 如图\ref{fig:12-23}所示,注意力权重$\alpha_{i,j}$的计算分为两步:
\begin{itemize}
\vspace{0.5em}
\item 使用目标语上一时刻循环单元的输出$\vectorn{\emph{s}}_{j-1}$与源语第$i$个位置的表示$\vectorn{\emph{h}}_i$之间的相关性,其用来表示目标语位置$j$对源语位置$i$的关注程度,记为$\beta_{i,j}$,由函数$\textrm{a}(\cdot)$实现:
\begin{eqnarray}
\beta_{i,j} = a(\vectorn{\emph{s}}_{j-1},\vectorn{\emph{h}}_i)
\label{eq:12-23}
\end{eqnarray}
\parinterval 在公式\ref{eq:10-30}中, $\alpha$决定了每次参数更新时更新的步幅大小,称之为{\small\bfnew{学习率}}\index{学习率}(Learning Rate)\index{Learning Rate}。学习率作为基于梯度方法中的重要超参数,它决定目标函数能否收敛到较好的局部最优点以及收敛的速度。合理的学习率能够使模型快速、稳定地达到较好的状态。但是,如果学习率太小,收敛过程会很慢;而学习率太大,则模型的状态可能会出现震荡,很难达到稳定,甚至使模型无法收敛。图\ref{fig:10-28} 对比了不同学习率对优化过程的影响。
$a(\cdot)$可以被看作是目标语表示和源语表示的一种“统一化”,即把源语和目标语表示映射在同一个语义空间,进而语义相近的内容有更大的相似性。该函数有多种计算方式,比如,向量乘、向量夹角、单词神经网络等,数学表达如下:
\begin{eqnarray}
a (\vectorn{\emph{s}},\vectorn{\emph{h}}) = \left\{ \begin{array}{ll}
\vectorn{\emph{s}} \vectorn{\emph{h}}^{\textrm{T}} & \textrm{向量乘} \\
\textrm{cos}(\vectorn{\emph{s}}, \vectorn{\emph{h}}) & \textrm{向量夹角} \\
\vectorn{\emph{s}} \vectorn{\emph{W}} \vectorn{\emph{h}}^{\textrm{T}} & \textrm{线性模型} \\
\textrm{TanH}(\vectorn{\emph{W}}[\vectorn{\emph{s}},\vectorn{\emph{h}}])\vectorn{\emph{v}}^{\textrm{T}} & \textrm{拼接}[\vectorn{\emph{s}},\vectorn{\emph{h}}]+\textrm{单层网络}
\end{array}
\right.
\label{eq:12-24}
\end{eqnarray}
其中$\vectorn{\emph{W}}$$\vectorn{\emph{v}}$是可学习的参数。
\vspace{0.5em}
\item 进一步,利用Softmax函数,将相关性系数$\beta_{i,j}$进行指数归一化处理,得到注意力权重$\alpha_{i,j}$
\vspace{0.5em}
\begin{eqnarray}
\alpha_{i,j}=\frac{\textrm{exp}(\beta_{i,j})} {\sum_{i'} \textrm{exp}(\beta_{i',j})}
\label{eq:12-25}
\end{eqnarray}
\vspace{0.5em}
最终,\{$\alpha_{i,j}$\}可以被看作是一个矩阵,它的长为目标语言句子长度,宽为源语言句子长度,矩阵中的每一项对应一个$\alpha_{i,j}$。图\ref{fig:12-24}给出了\{$\alpha_{i,j}$\}的一个矩阵表示。图中蓝色方框的大小表示不同的注意力权重$\alpha_{i,j}$的大小,方框越大,源语言位置$i$和目标语言位置$j$的相关性越高。能够看到,对于互译的中英文句子,\{$\alpha_{i,j}$\}可以较好的反应两种语言之间不同位置的对应关系。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter10/Figures/figure-convergence&lr}
\caption{学习率过小(左) vs 学习率过大(右) }
\label{fig:10-28}
\input{./Chapter12/Figures/figure-matrix-representation-of-attention-weights-between-chinese-english-sentence-pairs}
\caption{一个汉英句对之间的注意力权重{$\alpha_{i,j}$}的矩阵表示}
\label{fig:12-24}
\end{figure}
%----------------------------------------------
\parinterval 不同优化器需要的学习率不同,比如Adam一般使用0.001或0.0001,而SGD则在0.1$\sim$1之间进行挑选。在梯度下降法中,都是给定的统一的学习率,整个优化过程中都以确定的步长进行更新因此无论使用哪个优化器,为了保证训练又快又好,通常都需要根据当前的更新次数来动态调整学习率的大小。
\vspace{0.5em}
\end{itemize}
\parinterval\ref{fig:12-25}展示了一个上下文向量的计算过程实例。首先,计算目标语第一个单词“Have”与源语中的所有单词的相关性,即注意力权重,对应图中第一列$\alpha_{i,1}$,则当前时刻所使用的上下文向量$\vectorn{\emph{C}}_1 = \sum_{i=1}^8 \alpha_{i,1} \vectorn{\emph{h}}_i$;然后,计算第二个单词“you”的注意力权重对应第二列$\alpha_{i,2}$,其上下文向量$\vectorn{\emph{C}}_2 = \sum_{i=1}^8 \alpha_{i,2} \vectorn{\emph{h}}_i$,以此类推,可以得到任意目标语位置$j$的上下文向量$\vectorn{\emph{C}}_j$。很容易看出,不同目标语单词的上下文向量对应的源语言词的权重$\alpha_{i,j}$是不同的,不同的注意力权重为不同位置赋予了不同重要性,对应了注意力机制的思想。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter10/Figures/figure-relationship-between-learning-rate-and-number-of-updates}
\caption{学习率与更新次数的变化关系}
\label{fig:10-29}
\input{./Chapter12/Figures/figure-example-of-context-vector-calculation-process}
\caption{上下文向量计算过程实例}
\label{fig:12-25}
\end{figure}
%----------------------------------------------
\parinterval\ref{fig:10-29}展示了一种常用的学习率调整策略。它分为两个阶段:预热阶段和衰减阶段。模型训练初期梯度通常很大,如果直接使用较大的学习率很容易让模型陷入局部最优。学习率的预热阶段便是通过在训练初期使学习率从小到大逐渐增加来减缓在初始阶段模型“跑偏”的现象。一般来说,初始学习率太高会使得模型进入一种损失函数曲面非常不平滑的区域,进而使得模型进入一种混乱状态,后续的优化过程很难取得很好的效果。一个常用的学习率预热方法是{\small\bfnew{逐渐预热}}\index{逐渐预热}(Gradual Warmup)\index{Gradual Warmup}。假设预热的更新次数为$T'$,初始学习率为$\alpha_0$,则预热阶段第$t$次更新的学习率为:
%\vspace{0.5em}
\parinterval\ref{sec:10.3.1}节中,使用公式\ref{eq:10-5}描述了目标语单词生成概率$ \funp{P} (y_j | \vectorn{\emph{y}}_{<j},\vectorn{\emph{x}})$。在引入注意力机制后,不同时刻的上下文向量$\vectorn{\emph{C}}_j$替换了传统模型中固定的句子表示$\vectorn{\emph{C}}$。描述如下:
\begin{eqnarray}
\alpha_t = \frac{t}{T'} \alpha_0 \quad,\quad 1 \leq t \leq T'
\label{eq:10-34}
\funp{P} (y_j | \vectorn{\emph{y}}_{<j},\vectorn{\emph{x}}) \equiv \funp{P} (y_j | \vectorn{\emph{s}}_{j-1},y_{j-1},\vectorn{\emph{C}}_j )
\label{eq:12-26}
\end{eqnarray}
%-------
\noindent 另一方面,当模型训练逐渐接近收敛的时候,使用太大学习率会很容易让模型在局部最优解附近震荡,从而错过局部极小,因此需要通过减小学习率来调整更新的步长,以此来不断的逼近局部最优,这一阶段也称为学习率的衰减阶段。学习率衰减的方法有很多,比如指数衰减,余弦衰减等,图\ref{fig:10-29}右侧展示的是{\small\bfnew{分段常数衰减}}\index{分段常数衰减}(Piecewise Constant Decay)\index{Piecewise Constant Decay},即每经过$m$次更新,学习率衰减为原来的$\beta_m$$\beta_m<1$)倍,其中$m$$\beta_m$为经验设置的超参。
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
%----------------------------------------------------------------------------------------
%\vspace{0.5em}
\subsubsection{6. 并行训练}
%\vspace{0.5em}
\parinterval 机器翻译是自然语言处理中很“重”的任务。因为数据量巨大而且模型较为复杂,模型训练的时间往往很长。比如,使用一千万句的训练数据,性能优异的系统往往需要几天甚至一周的时间。更大规模的数据会导致训练时间更长。特别是使用多层网络同时增加模型容量时(比如增加隐层宽度时),神经机器翻译的训练会更加缓慢。对于这个问题,一个思路是从模型训练算法上进行改进。比如前面提到的Adam就是一种高效的训练策略。另一种思路是利用多设备进行加速,也称作分布式训练。
\parinterval 这样,可以在生成每个$y_j$时动态的使用不同的源语言表示$\vectorn{\emph{C}}_j$,并更准确地捕捉源语和目标语不同位置之间的相关性。表\ref{tab:12-7}展示了引入注意力机制前后译文单词生成公式的对比。
\vspace{0.5em}
%----------------------------------------------
\begin{table}[htp]
\centering
\caption{ 数据并行与模型并行优缺点对比}
\label{tab:10-9}
\begin{tabular}{l | p{12em} p{12em} }
&优点 &缺点 \\ \hline
\rule{0pt}{15pt} 数据并行 &并行度高,理论上有多大的batch(批次)就可以有多少个设备并行计算 &模型不能大于单个设备的极限 \\
\rule{0pt}{15pt} 模型并行 &可以对很大的模型进行运算 &只能有限并行,比如多少层就多少个设备 \\
\caption{引入注意力机制前后译文单词生成公式}
\label{tab:12-7}
\begin{tabular}{ l | l }
\rule{0pt}{13pt} 引入注意力之前 &引入注意力之后 \\ \hline
\rule{0pt}{16pt} $\textrm{“have”} = \argmax_{y_1} \funp{P} (y_1 | \vectorn{\emph{C}} , y_0)$ &$\textrm{“have”} = \argmax_{y_1} \funp{P} (y_1 | \vectorn{\emph{C}}_1 , y_0)$ \\
\rule{0pt}{16pt} $\textrm{“you”} = \argmax_{y_2} \funp{P} (y_2 | \vectorn{\emph{s}}_1 , y_1)$ &$\textrm{“you”} = \argmax_{y_2} \funp{P} (y_2 | \vectorn{\emph{s}}_1, \vectorn{\emph{C}}_2 , y_1)$ \\
\end{tabular}
\end{table}
%----------------------------------------------
\parinterval 常用的多设备并行化加速方法有数据并行和模型并行,其优缺点的简单对比如表\ref{tab:10-9}所示。数据并行是指把同一个批次的不同样本分到不同设备上进行并行计算。其优点是并行度高,理论上有多大的批次就可以有多少个设备并行计算,但模型体积不能大于单个设备容量的极限。而模型并行是指把“模型”切分成若干模块后分配到不同设备上并行计算。其优点是可以对很大的模型进行运算,但只能有限并行,比如,如果按层对模型进行分割,那么有多少层就需要多少个设备,同时这两种方法可以一起使用进一步提高神经网络的训练速度。具体来说:
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION
%----------------------------------------------------------------------------------------
\subsection{注意力机制的解读}
\label{sec:12.1.4}
\begin{itemize}
\vspace{0.5em}
\parinterval 从前面的描述可以看出,注意力机制在机器翻译中就是要回答一个问题:给定一个目标语位置$j$和一系列源语的不同位置上的表示\{${\vectorn{\emph{h}}_i}$\},如何得到一个新的表示$\hat{\vectorn{\emph{h}}}$,使得它与目标语位置$j$对应得最好?
\item {\small\bfnew{数据并行}}\index{数据并行}。如果一台设备能完整放下一个神经机器翻译模型,那么数据并行可以把一个大批次均匀切分成$n$个小批次,然后分发到$n$个设备上并行计算,最后把结果汇总,相当于把运算时间变为原来的${1}/{n}$,数据并行的过程如图\ref{fig:10-30}所示。不过,需要注意的是,多设备并行需要对数据在不同设备间传输,特别是多个GPU的情况,设备间传输的带宽十分有限,设备间传输数据往往会造成额外的时间消耗\upcite{xiao2017fast}。通常,数据并行的训练速度无法随着设备数量增加呈线性增长。不过这个问题也有很多优秀的解决方案,比如采用多个设备的异步训练,但是这些内容已经超出本章的内容,因此这里不做过多讨论。
\parinterval 那么,如何理解这个过程?注意力机制的本质又是什么呢?换一个角度来看,实际上,目标语位置$j$本质上是一个查询,我们希望从源语端找到与之最匹配的源语位置,并返回相应的表示结果。为了描述这个问题,可以建立一个查询系统。假设有一个库,里面包含若干个$\mathrm{key}$-$\mathrm{value}$单元,其中$\mathrm{key}$代表这个单元的索引关键字,$\mathrm{value}$代表这个单元的值。比如,对于学生信息系统,$\mathrm{key}$可以是学号,$\mathrm{value}$可以是学生的身高。当输入一个查询$\mathrm{query}$,我们希望这个系统返回与之最匹配的结果。也就是,希望找到匹配的$\mathrm{key}$,并输出其对应的$\mathrm{value}$。比如,当查询某个学生的身高信息时,可以输入学生的学号,之后在库中查询与这个学号相匹配的记录,并把这个记录中的$\mathrm{value}$(即身高)作为结果返回。
\parinterval\ref{fig:12-26}展示了一个这样的查询系统。里面包含四个$\mathrm{key}$-$\mathrm{value}$单元,当输入查询$\mathrm{query}$,就把$\mathrm{query}$与这四个$\mathrm{key}$逐个进行匹配,如果完全匹配就返回相应的$\mathrm{value}$。在图中的例子中,$\mathrm{query}$$\mathrm{key}_3$是完全匹配的(因为都是横纹),因此系统返回第三个单元的值,即$\mathrm{value}_3$。当然,如果库中没有与$\mathrm{query}$匹配的$\mathrm{key}$,则返回一个空结果。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter10/Figures/figure-data-parallel-process}
\caption{数据并行过程}
\label{fig:10-30}
\input{./Chapter12/Figures/figure-query-model-corresponding-to-traditional-query-model-vs-attention-mechanism}
\caption{传统查询模型}
\label{fig:12-26}
\end{figure}
%----------------------------------------------
\vspace{0.5em}
\item {\small\bfnew{模型并行}}\index{模型并行}。另一种思路是,把较大的模型分成若干小模型,之后在不同设备上训练小模型。对于循环神经网络,不同层的网络天然就是一个相对独立的模型,因此非常适合使用这种方法。比如,对于$l$层的循环神经网络,把每层都看做一个小模型,然后分发到$l$个设备上并行计算。在序列较长的时候,该方法使其运算时间变为原来的${1}/{l}$。图\ref{fig:10-31}以三层循环网络为例展示了对句子“你\ \ 不错\ 。”进行模型并行的过程。其中,每一层网络都被放到了一个设备上。当模型根据已经生成的第一个词“你”预测下一个词时(图\ref{fig:10-31}(a)),同层的下一个时刻的计算和对“你”的第二层的计算就可以同时开展(图\ref{fig:10-31}(b))。以此类推,就完成了模型的并行计算。
\vspace{0.5em}
\end{itemize}
\parinterval 也可以用这个系统描述翻译中的注意力问题,其中,$\mathrm{query}$即目标语位置$j$的某种表示,$\mathrm{key}$$\mathrm{value}$即源语每个位置$i$上的${\vectorn{\emph{h}}_i}$(这里$\mathrm{key}$$\mathrm{value}$是相同的)。但是,这样的系统在机器翻译问题上并不好用,因为目标语的表示和源语的表示都在多维实数空间上,所以无法要求两个实数向量像字符串一样进行严格匹配,或者说这种严格匹配的模型可能会导致$\mathrm{query}$几乎不会命中任何的$\mathrm{key}$。既然无法严格精确匹配,注意力机制就采用了一个“模糊”匹配的方法。这里定义每个$\mathrm{key}_i$$\mathrm{query}$都有一个0~1之间的匹配度,这个匹配度描述了$\mathrm{key}_i$$\mathrm{query}$之间的相关程度,记为$\alpha_i$。而查询的结果(记为$\overline{\mathrm{value}}$)也不再是某一个单元的$\mathrm{value}$,而是所有单元$\mathrm{value}$$\alpha_i$的加权和:
\begin{eqnarray}
\overline{\mathrm{value}} = \sum_i \alpha_i \cdot {\mathrm{value}}_i
\label{eq:12-27}
\end{eqnarray}
%-------------------------------------------
\noindent 也就是说所有的$\mathrm{value}_i$都会对查询结果有贡献,只是贡献度不同罢了。可以通过设计$\alpha_i$来捕捉$\mathrm{key}$$\mathrm{query}$之间的相关性,以达到相关度越大的$\mathrm{key}$所对应的$\mathrm{value}$对结果的贡献越大。
\parinterval 重新回到神经机器翻译问题上来。这种基于模糊匹配的查询模型可以很好的满足对注意力建模的要求。实际上,公式\ref{eq:12-27}中的$\alpha_i$就是前面提到的注意力权重,它可以由注意力函数a($\cdot$)计算得到。这样,$\overline{\mathrm{value}}$就是得到的上下文向量,它包含了所有\{$\vectorn{\emph{h}}_i$\}的信息,只是不同$\vectorn{\emph{h}}_i$的贡献度不同罢了。图\ref{fig:12-27}展示了将基于模糊匹配的查询模型应用于注意力机制的实例。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter12/Figures/figure-query-model-corresponding-to-attention-mechanism}
\caption{注意力机制所对应的查询模型}
\label{fig:12-27}
\end{figure}
%----------------------------------------------
\parinterval 最后,从统计学的角度,如果把$\alpha_i$作为每个$\mathrm{value}_i$出现的概率的某种估计,即:$ \funp{P} (\mathrm{value}_i$) $= \alpha_i$,于是可以把公式\ref{eq:12-27}重写为:
\begin{eqnarray}
\overline{\mathrm{value}} = \sum_i \funp{P} ( {\mathrm{value}}_i) \cdot {\mathrm{value}}_i
\label{eq:12-28}
\end{eqnarray}
\noindent 显然, $\overline{\mathrm{value}}$就是$\mathrm{value}_i$在分布$ \funp{P}( \mathrm{value}_i$)下的期望,即
\begin{equation}
\mathbb{E}_{\sim \\ \funp{P} ( {\mathrm{\mathrm{value}}}_i )} ({\mathrm{value}}_i) = \sum_i \funp{P} ({\mathrm{value}}_i) \cdot {\mathrm{value}}_i
\label{eq:12-29}
\end{equation}
从这个观点看,注意力机制实际上是得到了一个变量($\mathrm{value}$)的期望。当然,严格意义上说,$\alpha_i$并不是从概率角度定义的,这里也并不是要追求严格的统计学意义。不过这确实说明了,往往看似简单的模型背后的数学原理可能会很深刻。
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 10.3.5
%----------------------------------------------------------------------------------------
\subsection{实例-GNMT}
\vspace{0.5em}
\parinterval 循环神经网络在机器翻译中有很多成功的应用,比如、RNNSearch\upcite{bahdanau2014neural}、Nematus\upcite{DBLP:journals/corr/SennrichFCBHHJL17}等系统就被很多研究者作为实验系统。在众多基于循环神经网络的系统中,Google's Neural Machine Translation System(GNMT)系统是非常成功的一个\upcite{Wu2016GooglesNM}。GNMT是谷歌2016年发布的神经机器翻译系统。当时,神经机器翻译有三个弱点:训练和推理速度较慢、在翻译稀有单词上缺乏鲁棒性和有时无法完整翻译源语句子中的所有单词。GNMT的提出有效的缓解了上述问题。
\parinterval GNMT使用了编码器-解码器结构,构建了一个8层的深度网络,每层网络均由LSTM组成,且在编码器-解码器之间使用了多层注意力连接。其结构如图\ref{fig:10-35},编码器只有最下面2层为双向LSTM。GNMT在束搜索中也加入了长度惩罚和覆盖度因子来确保输出高质量的翻译结果(公式\ref{eq:10-41})。
\vspace{0.5em}
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter10/Figures/figure-structure-of-gnmt}
\caption{GNMT结构}
\label{fig:10-35}
\end{figure}
%----------------------------------------------
\parinterval 实际上,GNMT的主要贡献在于集成了多种优秀的技术,而且在大规模数据上证明了神经机器翻译的有效性。在引入注意力机制之前,神经机器翻译在较大规模的任务上的性能弱于统计机器翻译。加入注意力机制和深层网络后,神经机器翻译性能有了很大的提升。在英德和英法的任务中,GNMT的BLEU值不仅超过了当时优秀的神经机器翻译系统RNNSearch和LSTM(6层),还超过了当时处于领导地位的基于短语的统计机器翻译系统(PBMT)(表\ref{tab:10-10})。相比谷歌的基于短语的系统,在人工评价中,该模型能将翻译错误平均减少了60\%。这一结果也充分表明了神经机器翻译带来的巨大性能提升。
%----------------------------------------------
\begin{table}[htp]
\centering
\caption{GNMT与其他翻译模型对比\upcite{Wu2016GooglesNM}}
\label{tab:10-10}
\begin{tabular}{l l l l}
\multicolumn{1}{l|}{\multirow{2}{*}{\#}} & \multicolumn{2}{c}{BLEU} & \multirow{2}{*}{CPU decoding time} \\
\multicolumn{1}{l|}{} & EN-DE & EN-FR & \\ \hline
\multicolumn{1}{l|}{PBMT} & 20.7 & 37.0 & - \\
\multicolumn{1}{l|}{RNNSearch} & 16.5 & - & - \\
\multicolumn{1}{l|}{LSTM(6 layers)} & - & 31.5 & - \\
\multicolumn{1}{l|}{Deep-Att} & 20.6 & 37.7 & - \\
\multicolumn{1}{l|}{GNMT} & 24.6 & 39.0 & 0.2s per sentence \\
\end{tabular}
\end{table}
%----------------------------------------------
%----------------------------------------------------------------------------------------
% NEW SECTION
%----------------------------------------------------------------------------------------
\sectionnewpage
\section{训练及推断}
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION
%----------------------------------------------------------------------------------------
\vspace{-1.5em}
\subsection{训练}
\parinterval 第九章已经介绍了神经网络的训练方法。其中最常用的是基于梯度的方法,即:使用一定量样本进行神经网络的前向计算,之后进行反向计算,并得到所有参数的梯度信息,再使用下面的规则进行参数更新:
\begin{eqnarray}
\vectorn{\emph{w}}_{step+1} = \vectorn{\emph{w}}_{step} - \alpha \cdot \frac{ \partial L(\vectorn{\emph{w}}_{step})} {\partial \vectorn{\emph{w}}_{step} }
\label{eq:10-30}
\end{eqnarray}
\noindent 其中,$\vectorn{\emph{w}}_{step}$表示更新前的模型参数,$\vectorn{\emph{w}}_{step+1}$表示更新后的模型参数,$L(\vectorn{\emph{w}}_{step})$表示模型相对于$\vectorn{\emph{w}}_{step}$的损失,$\frac{\partial L(\vectorn{\emph{w}}_{step})} {\partial \vectorn{\emph{w}}_{step} }$表示损失函数的梯度,$\alpha$是更新的步进值。也就是说,给定一定量的训练数据,不断执行公式\ref{eq:10-30}的过程。反复使用训练数据,直至模型参数达到收敛或者损失函数不再变化。通常,把公式的一次执行称为“一步”更新/训练,把访问完所有样本的训练称为“一轮”训练。
\parinterval 将公式\ref{eq:10-30}应用于神经机器翻译有几个基本问题需要考虑:1)损失函数的选择;2)参数初始化的策略,也就是如何设置$\vectorn{\emph{w}}_0$;3)优化策略和学习率调整策略;4)训练加速。下面对这些问题进行讨论。
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
%----------------------------------------------------------------------------------------
\subsubsection{1. 损失函数}
\parinterval 因为神经机器翻译在每个目标语位置都会输出一个概率分布,表示这个位置上不同单词出现的可能性,因此需要知道当前位置输出的分布相比于标准答案的“损失”。对于这个问题,常用的是交叉熵损失函数。令$\vectorn{\emph{y}}$表示机器翻译模型输出的分布,$\hat{\vectorn{\emph{y}}}$ 表示标准答案,则交叉熵损失可以被定义为:
\begin{eqnarray}
L_{\textrm{ce}}(\vectorn{\emph{y}},\hat{\vectorn{\emph{y}}}) = - \sum_{k=1}^{|V|} \vectorn{\emph{y}}[k] \textrm{log} (\hat{\vectorn{\emph{y}}}[k])
\label{eq:10-3222}
\end{eqnarray}
\noindent 其中$\vectorn{\emph{y}}[k]$$\hat{\vectorn{\emph{y}}}[k]$分别表示向量$\vectorn{\emph{y}}$$\hat{\vectorn{\emph{y}}}$的第$k$维,$|V|$表示输出向量的维度(等于词表大小)。对于一个模型输出的概率分布$\vectorn{\emph{Y}} = \{ \vectorn{\emph{y}}_1,\vectorn{\emph{y}}_2,..., \vectorn{\emph{y}}_n \}$和标准答案分布$\widehat{\vectorn{\emph{Y}}}=\{ \hat{\vectorn{\emph{y}}}_1, \hat{\vectorn{\emph{y}}}_2,...,\hat{\vectorn{\emph{y}}}_n \}$,损失函数可以被定义为:
\begin{eqnarray}
L(\vectorn{\emph{Y}},\widehat{\vectorn{\emph{Y}}}) = \sum_{j=1}^n L_{\textrm{ce}}(\vectorn{\emph{y}}_j,\hat{\vectorn{\emph{y}}}_j)
\label{eq:10-31}
\end{eqnarray}
\parinterval 公式\ref{eq:10-31}是一种非常通用的损失函数形式,除了交叉熵,也可以使用其他的损失函数,这时只需要替换$L_{ce} (\cdot)$即可。这里使用交叉熵损失函数的好处在于,它非常容易优化,特别是与Softmax组合,其反向传播的实现非常高效。此外,交叉熵损失(在一定条件下)也对应了极大似然的思想,这种方法在自然语言处理中已经被证明是非常有效的。
\parinterval 除了交叉熵,很多系统也使用了面向评价的损失函数,比如,直接利用评价指标BLEU定义损失函数。不过这类损失函数往往不可微分,因此无法直接获取梯度。这时可以引入强化学习技术,通过策略梯度等方法进行优化。不过这类方法需要采样等手段,这里不做重点讨论,相关内容会在后面技术部分进行介绍。
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
%----------------------------------------------------------------------------------------
\subsubsection{2. 参数初始化}
\parinterval 神经网络的参数主要是各层中的线性变换矩阵和偏置。在训练开始时,需要对参数进行初始化。但是,由于神经机器翻译的网络结构复杂,因此损失函数往往不是凸函数,不同初始化会导致不同的优化结果。而且在大量实践中已经发现,神经机器翻译模型对初始化方式非常敏感,性能优异的系统往往需要特定的初始化方式。
\parinterval 下面以LSTM循环神经网络为例(见\ref{sec:lstm-cell}节),介绍机器翻译模型的初始化方法。这些方法也可以推广到GRU等结构。具体内容如下:
\begin{itemize}
\vspace{0.5em}
\item LSTM遗忘门偏置初始化为1,也就是始终选择遗忘记忆$\vectorn{\emph{c}}$,这样可以有效防止初始化时$\vectorn{\emph{c}}$里包含的错误信号传播到后面的所有时刻。
\vspace{0.5em}
\item 网络中的其他偏置一般都初始化为0,可以有效防止加入过大或过小的偏置后使得激活函数的输出跑到“饱和区”,也就是梯度接近0的区域,防止训练一开始就无法跳出局部极小的区域。
\vspace{0.5em}
\item 网络的权重矩阵$\vectorn{\emph{w}}$一般使用Xavier参数初始化方法\upcite{pmlr-v9-glorot10a},可以有效稳定训练过程,特别是对于比较“深”的网络。令$d_{in}$$d_{out}$分别表示$\vectorn{\emph{w}}$的输入和输出的维度大小,则该方法的具体实现如下:
\begin{eqnarray}
\vectorn{\emph{w}} \sim U(-\sqrt{ \frac{6} { d_{in} + d_{out} } } , \sqrt{ \frac{6} { d_{in} + d_{out} } })
\label{eq:10-32}
\vspace{0.5em}
\end{eqnarray}
其中$U(a,b)$表示以$[a,b]$为范围的均匀分布,$6$是固定值。\\
\end{itemize}
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
%----------------------------------------------------------------------------------------
\vspace{-2.5em}
\subsubsection{3. 优化策略}
%\vspace{0.5em}
\parinterval 公式\ref{eq:10-30}展示了最基本的优化策略,也被称为标准的SGD优化器。实际上,训练神经机器翻译模型时,还有非常多的优化器可以选择,在第九章也有详细介绍,这里考虑Adam优化器。 Adam 通过对梯度的{\small\bfnew{一阶矩估计}}\index{一阶矩估计}(First Moment Estimation)\index{First Moment Estimation}{\small\bfnew{二阶矩估计}}\index{二阶矩估计}(Second Moment Estimation)\index{Second Moment Estimation}进行综合考虑,计算出更新步长。
\parinterval\ref{tab:10-8}从效果上对比了Adam和SGD的区别。通常,Adam收敛的比较快,不同任务基本上可以使用一套配置进行优化,虽性能不算差,但很难达到最优效果。相反,SGD虽能通过在不同的数据集上进行调整,来达到最优的结果,但是收敛速度慢。因此需要根据不同的需求来选择合适的优化器。若需要快得到模型的初步结果,选择Adam较为合适,若是需要在一个任务上得到最优的结果,选择SGD更为合适。
\vspace{-0.5em}
%----------------------------------------------
\begin{table}[htp]
\centering
\caption{ Adam / SGD对比}
\label{tab:10-8}
\begin{tabular}{l | l l }
&使用 &性能 \\ \hline
\rule{0pt}{15pt} Adam &一套配置包打天下 &不算差,但没到极限 \\
\rule{0pt}{15pt} SGD &换一个任务就得调 &效果好 \\
\end{tabular}
\end{table}
%----------------------------------------------
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
%----------------------------------------------------------------------------------------
\vspace{-2.5em}
\subsubsection{4. 梯度裁剪}
%\vspace{0.5em}
\parinterval 需要注意的是,训练循环神经网络时,反向传播使得网络层之间的梯度重复相乘,在网络层数过深时,如果连乘因子小于1可能造成梯度指数级的减少,甚至趋近于0,导致网络无法优化,也就是梯度消失问题。当连乘因子大于1时,可能会导致梯度的乘积变得异常大,造成梯度爆炸的问题。在这种情况下需要使用“梯度裁剪”来防止梯度超过阈值。梯度裁剪在第九章已经介绍过,这里简单回顾一下。梯度裁剪的具体公式如下:
\vspace{-0.5em}
\begin{eqnarray}
\vectorn{\emph{w}}' = \vectorn{\emph{w}} \cdot \frac{\gamma} {\textrm{max}(\gamma,\| \vectorn{\emph{w}} \|_2)}
\label{eq:10-33}
\end{eqnarray}
%\vspace{0.5em}
\noindent 其中$\gamma$是手工设定的梯度大小阈值, $\| \cdot \|_2$是L2范数,$\vectorn{\emph{w}}'$表示梯度裁剪后的参数。这个公式的含义在于只要梯度大小超过阈值,就按照阈值与当前梯度大小的比例进行放缩。
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
%----------------------------------------------------------------------------------------
\subsubsection{5. 学习率策略}
\vspace{0.5em}
\parinterval 在公式\ref{eq:10-30}中, $\alpha$决定了每次参数更新时更新的步幅大小,称之为{\small\bfnew{学习率}}\index{学习率}(Learning Rate)\index{Learning Rate}。学习率作为基于梯度方法中的重要超参数,它决定目标函数能否收敛到较好的局部最优点以及收敛的速度。合理的学习率能够使模型快速、稳定地达到较好的状态。但是,如果学习率太小,收敛过程会很慢;而学习率太大,则模型的状态可能会出现震荡,很难达到稳定,甚至使模型无法收敛。图\ref{fig:10-28} 对比了不同学习率对优化过程的影响。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter10/Figures/figure-convergence&lr}
\caption{学习率过小(左) vs 学习率过大(右) }
\label{fig:10-28}
\end{figure}
%----------------------------------------------
\parinterval 不同优化器需要的学习率不同,比如Adam一般使用0.001或0.0001,而SGD则在0.1$\sim$1之间进行挑选。在梯度下降法中,都是给定的统一的学习率,整个优化过程中都以确定的步长进行更新因此无论使用哪个优化器,为了保证训练又快又好,通常都需要根据当前的更新次数来动态调整学习率的大小。
\vspace{0.5em}
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter10/Figures/figure-relationship-between-learning-rate-and-number-of-updates}
\caption{学习率与更新次数的变化关系}
\label{fig:10-29}
\end{figure}
%----------------------------------------------
\parinterval\ref{fig:10-29}展示了一种常用的学习率调整策略。它分为两个阶段:预热阶段和衰减阶段。模型训练初期梯度通常很大,如果直接使用较大的学习率很容易让模型陷入局部最优。学习率的预热阶段便是通过在训练初期使学习率从小到大逐渐增加来减缓在初始阶段模型“跑偏”的现象。一般来说,初始学习率太高会使得模型进入一种损失函数曲面非常不平滑的区域,进而使得模型进入一种混乱状态,后续的优化过程很难取得很好的效果。一个常用的学习率预热方法是{\small\bfnew{逐渐预热}}\index{逐渐预热}(Gradual Warmup)\index{Gradual Warmup}。假设预热的更新次数为$T'$,初始学习率为$\alpha_0$,则预热阶段第$t$次更新的学习率为:
%\vspace{0.5em}
\begin{eqnarray}
\alpha_t = \frac{t}{T'} \alpha_0 \quad,\quad 1 \leq t \leq T'
\label{eq:10-34}
\end{eqnarray}
%-------
\noindent 另一方面,当模型训练逐渐接近收敛的时候,使用太大学习率会很容易让模型在局部最优解附近震荡,从而错过局部极小,因此需要通过减小学习率来调整更新的步长,以此来不断的逼近局部最优,这一阶段也称为学习率的衰减阶段。学习率衰减的方法有很多,比如指数衰减,余弦衰减等,图\ref{fig:10-29}右侧展示的是{\small\bfnew{分段常数衰减}}\index{分段常数衰减}(Piecewise Constant Decay)\index{Piecewise Constant Decay},即每经过$m$次更新,学习率衰减为原来的$\beta_m$$\beta_m<1$)倍,其中$m$$\beta_m$为经验设置的超参。
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
%----------------------------------------------------------------------------------------
%\vspace{0.5em}
\subsubsection{6. 并行训练}
%\vspace{0.5em}
\parinterval 机器翻译是自然语言处理中很“重”的任务。因为数据量巨大而且模型较为复杂,模型训练的时间往往很长。比如,使用一千万句的训练数据,性能优异的系统往往需要几天甚至一周的时间。更大规模的数据会导致训练时间更长。特别是使用多层网络同时增加模型容量时(比如增加隐层宽度时),神经机器翻译的训练会更加缓慢。对于这个问题,一个思路是从模型训练算法上进行改进。比如前面提到的Adam就是一种高效的训练策略。另一种思路是利用多设备进行加速,也称作分布式训练。
\vspace{0.5em}
%----------------------------------------------
\begin{table}[htp]
\centering
\caption{ 数据并行与模型并行优缺点对比}
\label{tab:10-9}
\begin{tabular}{l | p{12em} p{12em} }
&优点 &缺点 \\ \hline
\rule{0pt}{15pt} 数据并行 &并行度高,理论上有多大的batch(批次)就可以有多少个设备并行计算 &模型不能大于单个设备的极限 \\
\rule{0pt}{15pt} 模型并行 &可以对很大的模型进行运算 &只能有限并行,比如多少层就多少个设备 \\
\end{tabular}
\end{table}
%----------------------------------------------
\parinterval 常用的多设备并行化加速方法有数据并行和模型并行,其优缺点的简单对比如表\ref{tab:10-9}所示。数据并行是指把同一个批次的不同样本分到不同设备上进行并行计算。其优点是并行度高,理论上有多大的批次就可以有多少个设备并行计算,但模型体积不能大于单个设备容量的极限。而模型并行是指把“模型”切分成若干模块后分配到不同设备上并行计算。其优点是可以对很大的模型进行运算,但只能有限并行,比如,如果按层对模型进行分割,那么有多少层就需要多少个设备,同时这两种方法可以一起使用进一步提高神经网络的训练速度。具体来说:
\begin{itemize}
\vspace{0.5em}
\item {\small\bfnew{数据并行}}\index{数据并行}。如果一台设备能完整放下一个神经机器翻译模型,那么数据并行可以把一个大批次均匀切分成$n$个小批次,然后分发到$n$个设备上并行计算,最后把结果汇总,相当于把运算时间变为原来的${1}/{n}$,数据并行的过程如图\ref{fig:10-30}所示。不过,需要注意的是,多设备并行需要对数据在不同设备间传输,特别是多个GPU的情况,设备间传输的带宽十分有限,设备间传输数据往往会造成额外的时间消耗\upcite{xiao2017fast}。通常,数据并行的训练速度无法随着设备数量增加呈线性增长。不过这个问题也有很多优秀的解决方案,比如采用多个设备的异步训练,但是这些内容已经超出本章的内容,因此这里不做过多讨论。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter10/Figures/figure-data-parallel-process}
\caption{数据并行过程}
\label{fig:10-30}
\end{figure}
%----------------------------------------------
\vspace{0.5em}
\item {\small\bfnew{模型并行}}\index{模型并行}。另一种思路是,把较大的模型分成若干小模型,之后在不同设备上训练小模型。对于循环神经网络,不同层的网络天然就是一个相对独立的模型,因此非常适合使用这种方法。比如,对于$l$层的循环神经网络,把每层都看做一个小模型,然后分发到$l$个设备上并行计算。在序列较长的时候,该方法使其运算时间变为原来的${1}/{l}$。图\ref{fig:10-31}以三层循环网络为例展示了对句子“你\ \ 不错\ 。”进行模型并行的过程。其中,每一层网络都被放到了一个设备上。当模型根据已经生成的第一个词“你”预测下一个词时(图\ref{fig:10-31}(a)),同层的下一个时刻的计算和对“你”的第二层的计算就可以同时开展(图\ref{fig:10-31}(b))。以此类推,就完成了模型的并行计算。
\vspace{0.5em}
\end{itemize}
%-------------------------------------------
\begin{figure}[htp]
\centering
\begin{tabular}{l l}
......@@ -998,287 +1255,8 @@ L(\vectorn{\emph{Y}},\widehat{\vectorn{\emph{Y}}}) = \sum_{j=1}^n L_{\textrm{ce}
\noindent 显然,当目标语$y$过短时,$\textrm{lp}(\vectorn{\emph{y}})$的值越小,因为$\textrm{log } \funp{P}(\vectorn{\emph{y}} | \vectorn{\emph{x}})$是负数,所以句子得分$\textrm{score} ( \vectorn{\emph{y}} , \vectorn{\emph{x}})$越小。也就是说,模型会惩罚译文过短的结果。当覆盖度较高时,同样会使得分变低。通过这样的惩罚机制,使模型得分更为合理,从而帮助模型选择出质量更高的译文。
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 10.3.5
%----------------------------------------------------------------------------------------
\subsection{实例-GNMT}
\vspace{0.5em}
\parinterval 循环神经网络在机器翻译中有很多成功的应用,比如、RNNSearch\upcite{bahdanau2014neural}、Nematus\upcite{DBLP:journals/corr/SennrichFCBHHJL17}等系统就被很多研究者作为实验系统。在众多基于循环神经网络的系统中,Google's Neural Machine Translation System(GNMT)系统是非常成功的一个\upcite{Wu2016GooglesNM}。GNMT是谷歌2016年发布的神经机器翻译系统。当时,神经机器翻译有三个弱点:训练和推理速度较慢、在翻译稀有单词上缺乏鲁棒性和有时无法完整翻译源语句子中的所有单词。GNMT的提出有效的缓解了上述问题。
\parinterval GNMT使用了编码器-解码器结构,构建了一个8层的深度网络,每层网络均由LSTM组成,且在编码器-解码器之间使用了多层注意力连接。其结构如图\ref{fig:10-35},编码器只有最下面2层为双向LSTM。GNMT在束搜索中也加入了长度惩罚和覆盖度因子来确保输出高质量的翻译结果(公式\ref{eq:10-41})。
\vspace{0.5em}
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter10/Figures/figure-structure-of-gnmt}
\caption{GNMT结构}
\label{fig:10-35}
\end{figure}
%----------------------------------------------
\parinterval 实际上,GNMT的主要贡献在于集成了多种优秀的技术,而且在大规模数据上证明了神经机器翻译的有效性。在引入注意力机制之前,神经机器翻译在较大规模的任务上的性能弱于统计机器翻译。加入注意力机制和深层网络后,神经机器翻译性能有了很大的提升。在英德和英法的任务中,GNMT的BLEU值不仅超过了当时优秀的神经机器翻译系统RNNSearch和LSTM(6层),还超过了当时处于领导地位的基于短语的统计机器翻译系统(PBMT)(表\ref{tab:10-10})。相比谷歌的基于短语的系统,在人工评价中,该模型能将翻译错误平均减少了60\%。这一结果也充分表明了神经机器翻译带来的巨大性能提升。
%----------------------------------------------
\begin{table}[htp]
\centering
\caption{GNMT与其他翻译模型对比\upcite{Wu2016GooglesNM}}
\label{tab:10-10}
\begin{tabular}{l l l l}
\multicolumn{1}{l|}{\multirow{2}{*}{\#}} & \multicolumn{2}{c}{BLEU} & \multirow{2}{*}{CPU decoding time} \\
\multicolumn{1}{l|}{} & EN-DE & EN-FR & \\ \hline
\multicolumn{1}{l|}{PBMT} & 20.7 & 37.0 & - \\
\multicolumn{1}{l|}{RNNSearch} & 16.5 & - & - \\
\multicolumn{1}{l|}{LSTM(6 layers)} & - & 31.5 & - \\
\multicolumn{1}{l|}{Deep-Att} & 20.6 & 37.7 & - \\
\multicolumn{1}{l|}{GNMT} & 24.6 & 39.0 & 0.2s per sentence \\
\end{tabular}
\end{table}
%----------------------------------------------
%----------------------------------------------------------------------------------------
% NEW SECTION 10.4
%----------------------------------------------------------------------------------------
\sectionnewpage
\section{注意力机制}
\label{sec:10.4}
\parinterval 前面提到GNMT使用了注意力机制,那么注意力机制究竟是什么?回顾一下第二章提到过的“上帝是不公平的”这个观点,它主要是表达了:世界上事物之间的联系不是均匀的,有些事物之间的联系会很强,而其他的联系可能很弱。自然语言也完美地契合了这个观点。比如,再重新看一下前面提到的根据上下文补全缺失单词的例子,
\vspace{0.8em}
\centerline{中午\ \ 吃饭\ \ \ \ \ \ \ 下午\ 篮球\ \ \ 现在\ \ 饿\ \ \ \underline{\quad \quad \quad}}
\vspace{0.8em}
\noindent 之所以能想到在横线处填“吃饭”、“吃东西”很有可能是因为看到了“没\ 吃饭”、 “很\ 饿”等关键信息。也就是这些关键的片段对预测缺失的单词起着关键性作用。而预测“吃饭”与前文中的“ 中午”、“又”之间的联系似乎不那么紧密。也就是说,在形成 “吃饭”的逻辑时,在潜意识里会更注意“没吃饭”、“很饿”等关键信息。也就是我们的关注度并不是均匀地分布在整个句子上的。
\parinterval 这个现象可以用注意力机制进行解释。注意力机制的概念来源于生物学的一些现象:当待接收的信息过多时,人类会选择性地关注部分信息而忽略其他信息。它在人类的视觉、听觉、嗅觉等方面均有体现,当我们在感受事物时,大脑会自动过滤或衰减部分信息,仅关注其中少数几个部分。例如,当看到图\ref{fig:12-20}时,往往不是“均匀地”看图像中的所有区域,可能最先注意到的是大狗头上戴的帽子,然后才会关注图片中其他的部分。
\parinterval 那么注意力机制和神经机器翻译又有什么关系呢?它如何解决神经机器翻译的问题呢?下面就一起来看一看。
%----------------------------------------------
\begin{figure}[htp]
\centering
\includegraphics[scale=0.2]{./Chapter12/Figures/dog-hat.jpg}
\caption{戴帽子的狗}
\label{fig:12-20}
\end{figure}
%----------------------------------------------
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 12.1.2
%----------------------------------------------------------------------------------------
\subsection{翻译中的注意力机制}
\parinterval 早期的神经机器翻译只使用循环神经网络最后一个单元的输出作为整个序列的表示,这种方式有两个明显的缺陷:
\begin{itemize}
\vspace{0.5em}
\item 首先,虽然编码器把一个源语句子的表示传递给解码器,但是一个维度固定的向量所能包含的信息是有限的,随着源语序列的增长,将整个句子的信息编码到一个固定维度的向量中可能会造成源语句子信息的丢失。显然,在翻译较长的句子时,解码端可能无法获取完整的源语信息,降低翻译性能;
\vspace{0.5em}
\item 此外,当生成某一个目标语单词时,并不是均匀的使用源语句子中的单词信息。更普遍的情况是,系统会参考与这个目标语单词相对应的源语单词进行翻译。这有些类似于词对齐的作用,即翻译是基于单词之间的某种对应关系。但是,使用单一的源语表示根本无法区分源语句子的不同部分,更不用说对源语单词和目标语单词之间的联系进行建模了。
\vspace{0.5em}
\end{itemize}
\parinterval 更直观的,如图\ref{fig:12-21},目标语中的“very long”仅依赖于源文中的“很长”。这时如果将所有源语编码成一个固定的实数向量,“很长”的信息就很可能被其他词的信息淹没掉。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter12/Figures/figure-attention-of-source-and-target-words}
\caption{源语词和目标语词的关注度}
\label{fig:12-21}
\end{figure}
%----------------------------------------------
\parinterval 显然,以上问题的根本原因在于所使用的表示模型还比较“弱”。因此需要一个更强大的表示模型,在生成目标语单词时能够有选择地获取源语句子中更有用的部分。更准确的说,对于要生成的目标语单词,相关性更高的源语片段应该在源语句子的表示中体现出来,而不是将所有的源语单词一视同仁。在神经机器翻译中引入注意力机制正是为了达到这个目的\upcite{bahdanau2014neural,DBLP:journals/corr/LuongPM15}。实际上,除了机器翻译,注意力机制也被成功地应用于图像处理、语音识别、自然语言处理等其他任务。而正是注意力机制的引入,使得包括机器翻译在内很多自然语言处理系统得到了飞跃发展。
\parinterval 神经机器翻译中的注意力机制并不复杂。对于每个目标语单词$y_j$,系统生成一个源语表示向量$\vectorn{\emph{{C}}}_j$与之对应,$\vectorn{\emph{C}}_j$会包含生成$y_j$所需的源语的信息,或者说$\vectorn{\emph{C}}_j$是一种包含目标语单词与源语单词对应关系的源语表示。相比用一个静态的表示$\vectorn{\emph{C}}$,注意机制使用的是动态的表示$\vectorn{\emph{C}}_j$$\vectorn{\emph{C}}_j$也被称作对于目标语位置$j$的上下文向量。图\ref{fig:12-22}对比了未引入注意力机制和引入了注意力机制的编码器-解码器结构。可以看出,在注意力模型中,对于每一个目标单词的生成,都会额外引入一个单独的上下文向量参与运算。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter12/Figures/figure-encoder-decoder-with-attention}
\caption{(a)不使用和(b)使用注意力机制的翻译模型对比}
\label{fig:12-22}
\end{figure}
%----------------------------------------------
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 12.1.3
%----------------------------------------------------------------------------------------
\subsection{上下文向量的计算}
\label{sec:12.1.3}
\parinterval 那么注意力机制是如何针对不同单词生成不同的上下文向量呢?这里,可以将注意力机制看做是一种对接收到的信息的加权处理。对于更重要的信息赋予更高的权重即更高的关注度,对于贡献度较低的信息分配较低的权重,弱化其对结果的影响。这样,$\vectorn{\emph{C}}_j$可以包含更多对当前目标语位置有贡献的源语片段的信息。
\parinterval 根据这种思想,上下文向量$\vectorn{\emph{C}}_j$被定义为对不同时间步编码器输出的状态序列$\{ \vectorn{\emph{h}}_1, \vectorn{\emph{h}}_2,...,\vectorn{\emph{h}}_m \}$进行加权求和,如下:
\begin{eqnarray}
\vectorn{\emph{C}}_j=\sum_{i} \alpha_{i,j} \vectorn{\emph{h}}_i
\label{eq:12-22}
\end{eqnarray}
\noindent 其中,$\alpha_{i,j}${\small\sffamily\bfseries{注意力权重}}\index{注意力权重}(Attention Weight)\index{Attention Weight},它表示目标语第$j$个位置与源语第$i$个位置之间的相关性大小。这里,将每个时间步编码器的输出$\vectorn{\emph{h}}_i$ 看作源语位置$i$的表示结果。进行翻译时,解码端可以根据当前的位置$j$,通过控制不同$\vectorn{\emph{h}}_i$的权重得到$\vectorn{\emph{C}}_j$,使得对目标语位置$j$贡献大的$\vectorn{\emph{h}}_i$$\vectorn{\emph{C}}_j$的影响增大。也就是说,$\vectorn{\emph{C}}_j$实际上就是\{${\vectorn{\emph{h}}_1, \vectorn{\emph{h}}_2,...,\vectorn{\emph{h}}_m}$\}的一种组合,只不过不同的$\vectorn{\emph{h}}_i$会根据对目标端的贡献给予不同的权重。图\ref{fig:12-23}展示了上下文向量$\vectorn{\emph{C}}_j$的计算过程。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter12/Figures/figure-calculation-process-of-context-vector-c}
\caption{上下文向量$\vectorn{\emph{C}}_j$的计算过程}
\label{fig:12-23}
\end{figure}
%----------------------------------------------
\parinterval 如图\ref{fig:12-23}所示,注意力权重$\alpha_{i,j}$的计算分为两步:
\begin{itemize}
\vspace{0.5em}
\item 使用目标语上一时刻循环单元的输出$\vectorn{\emph{s}}_{j-1}$与源语第$i$个位置的表示$\vectorn{\emph{h}}_i$之间的相关性,其用来表示目标语位置$j$对源语位置$i$的关注程度,记为$\beta_{i,j}$,由函数$\textrm{a}(\cdot)$实现:
\begin{eqnarray}
\beta_{i,j} = a(\vectorn{\emph{s}}_{j-1},\vectorn{\emph{h}}_i)
\label{eq:12-23}
\end{eqnarray}
$a(\cdot)$可以被看作是目标语表示和源语表示的一种“统一化”,即把源语和目标语表示映射在同一个语义空间,进而语义相近的内容有更大的相似性。该函数有多种计算方式,比如,向量乘、向量夹角、单词神经网络等,数学表达如下:
\begin{eqnarray}
a (\vectorn{\emph{s}},\vectorn{\emph{h}}) = \left\{ \begin{array}{ll}
\vectorn{\emph{s}} \vectorn{\emph{h}}^{\textrm{T}} & \textrm{向量乘} \\
\textrm{cos}(\vectorn{\emph{s}}, \vectorn{\emph{h}}) & \textrm{向量夹角} \\
\vectorn{\emph{s}} \vectorn{\emph{W}} \vectorn{\emph{h}}^{\textrm{T}} & \textrm{线性模型} \\
\textrm{TanH}(\vectorn{\emph{W}}[\vectorn{\emph{s}},\vectorn{\emph{h}}])\vectorn{\emph{v}}^{\textrm{T}} & \textrm{拼接}[\vectorn{\emph{s}},\vectorn{\emph{h}}]+\textrm{单层网络}
\end{array}
\right.
\label{eq:12-24}
\end{eqnarray}
其中$\vectorn{\emph{W}}$$\vectorn{\emph{v}}$是可学习的参数。
\vspace{0.5em}
\item 进一步,利用Softmax函数,将相关性系数$\beta_{i,j}$进行指数归一化处理,得到注意力权重$\alpha_{i,j}$
\vspace{0.5em}
\begin{eqnarray}
\alpha_{i,j}=\frac{\textrm{exp}(\beta_{i,j})} {\sum_{i'} \textrm{exp}(\beta_{i',j})}
\label{eq:12-25}
\end{eqnarray}
\vspace{0.5em}
最终,\{$\alpha_{i,j}$\}可以被看作是一个矩阵,它的长为目标语言句子长度,宽为源语言句子长度,矩阵中的每一项对应一个$\alpha_{i,j}$。图\ref{fig:12-24}给出了\{$\alpha_{i,j}$\}的一个矩阵表示。图中蓝色方框的大小表示不同的注意力权重$\alpha_{i,j}$的大小,方框越大,源语言位置$i$和目标语言位置$j$的相关性越高。能够看到,对于互译的中英文句子,\{$\alpha_{i,j}$\}可以较好的反应两种语言之间不同位置的对应关系。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter12/Figures/figure-matrix-representation-of-attention-weights-between-chinese-english-sentence-pairs}
\caption{一个汉英句对之间的注意力权重{$\alpha_{i,j}$}的矩阵表示}
\label{fig:12-24}
\end{figure}
%----------------------------------------------
\vspace{0.5em}
\end{itemize}
\parinterval\ref{fig:12-25}展示了一个上下文向量的计算过程实例。首先,计算目标语第一个单词“Have”与源语中的所有单词的相关性,即注意力权重,对应图中第一列$\alpha_{i,1}$,则当前时刻所使用的上下文向量$\vectorn{\emph{C}}_1 = \sum_{i=1}^8 \alpha_{i,1} \vectorn{\emph{h}}_i$;然后,计算第二个单词“you”的注意力权重对应第二列$\alpha_{i,2}$,其上下文向量$\vectorn{\emph{C}}_2 = \sum_{i=1}^8 \alpha_{i,2} \vectorn{\emph{h}}_i$,以此类推,可以得到任意目标语位置$j$的上下文向量$\vectorn{\emph{C}}_j$。很容易看出,不同目标语单词的上下文向量对应的源语言词的权重$\alpha_{i,j}$是不同的,不同的注意力权重为不同位置赋予了不同重要性,对应了注意力机制的思想。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter12/Figures/figure-example-of-context-vector-calculation-process}
\caption{上下文向量计算过程实例}
\label{fig:12-25}
\end{figure}
%----------------------------------------------
\parinterval\ref{sec:10.3.1}节中,使用公式\ref{eq:10-5}描述了目标语单词生成概率$ \funp{P} (y_j | \vectorn{\emph{y}}_{<j},\vectorn{\emph{x}})$。在引入注意力机制后,不同时刻的上下文向量$\vectorn{\emph{C}}_j$替换了传统模型中固定的句子表示$\vectorn{\emph{C}}$。描述如下:
\begin{eqnarray}
\funp{P} (y_j | \vectorn{\emph{y}}_{<j},\vectorn{\emph{x}}) \equiv \funp{P} (y_j | \vectorn{\emph{s}}_{j-1},y_{j-1},\vectorn{\emph{C}}_j )
\label{eq:12-26}
\end{eqnarray}
\parinterval 这样,可以在生成每个$y_j$时动态的使用不同的源语言表示$\vectorn{\emph{C}}_j$,并更准确地捕捉源语和目标语不同位置之间的相关性。表\ref{tab:12-7}展示了引入注意力机制前后译文单词生成公式的对比。
\vspace{0.5em}
%----------------------------------------------
\begin{table}[htp]
\centering
\caption{引入注意力机制前后译文单词生成公式}
\label{tab:12-7}
\begin{tabular}{ l | l }
\rule{0pt}{13pt} 引入注意力之前 &引入注意力之后 \\ \hline
\rule{0pt}{16pt} $\textrm{“have”} = \argmax_{y_1} \funp{P} (y_1 | \vectorn{\emph{C}} , y_0)$ &$\textrm{“have”} = \argmax_{y_1} \funp{P} (y_1 | \vectorn{\emph{C}}_1 , y_0)$ \\
\rule{0pt}{16pt} $\textrm{“you”} = \argmax_{y_2} \funp{P} (y_2 | \vectorn{\emph{s}}_1 , y_1)$ &$\textrm{“you”} = \argmax_{y_2} \funp{P} (y_2 | \vectorn{\emph{s}}_1, \vectorn{\emph{C}}_2 , y_1)$ \\
\end{tabular}
\end{table}
%----------------------------------------------
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 12.1.4
%----------------------------------------------------------------------------------------
\subsection{注意力机制的解读}
\label{sec:12.1.4}
\vspace{0.5em}
\parinterval 从前面的描述可以看出,注意力机制在机器翻译中就是要回答一个问题:给定一个目标语位置$j$和一系列源语的不同位置上的表示\{${\vectorn{\emph{h}}_i}$\},如何得到一个新的表示$\hat{\vectorn{\emph{h}}}$,使得它与目标语位置$j$对应得最好?
\parinterval 那么,如何理解这个过程?注意力机制的本质又是什么呢?换一个角度来看,实际上,目标语位置$j$本质上是一个查询,我们希望从源语端找到与之最匹配的源语位置,并返回相应的表示结果。为了描述这个问题,可以建立一个查询系统。假设有一个库,里面包含若干个$\mathrm{key}$-$\mathrm{value}$单元,其中$\mathrm{key}$代表这个单元的索引关键字,$\mathrm{value}$代表这个单元的值。比如,对于学生信息系统,$\mathrm{key}$可以是学号,$\mathrm{value}$可以是学生的身高。当输入一个查询$\mathrm{query}$,我们希望这个系统返回与之最匹配的结果。也就是,希望找到匹配的$\mathrm{key}$,并输出其对应的$\mathrm{value}$。比如,当查询某个学生的身高信息时,可以输入学生的学号,之后在库中查询与这个学号相匹配的记录,并把这个记录中的$\mathrm{value}$(即身高)作为结果返回。
\parinterval\ref{fig:12-26}展示了一个这样的查询系统。里面包含四个$\mathrm{key}$-$\mathrm{value}$单元,当输入查询$\mathrm{query}$,就把$\mathrm{query}$与这四个$\mathrm{key}$逐个进行匹配,如果完全匹配就返回相应的$\mathrm{value}$。在图中的例子中,$\mathrm{query}$$\mathrm{key}_3$是完全匹配的(因为都是横纹),因此系统返回第三个单元的值,即$\mathrm{value}_3$。当然,如果库中没有与$\mathrm{query}$匹配的$\mathrm{key}$,则返回一个空结果。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter12/Figures/figure-query-model-corresponding-to-traditional-query-model-vs-attention-mechanism}
\caption{传统查询模型}
\label{fig:12-26}
\end{figure}
%----------------------------------------------
\parinterval 也可以用这个系统描述翻译中的注意力问题,其中,$\mathrm{query}$即目标语位置$j$的某种表示,$\mathrm{key}$$\mathrm{value}$即源语每个位置$i$上的${\vectorn{\emph{h}}_i}$(这里$\mathrm{key}$$\mathrm{value}$是相同的)。但是,这样的系统在机器翻译问题上并不好用,因为目标语的表示和源语的表示都在多维实数空间上,所以无法要求两个实数向量像字符串一样进行严格匹配,或者说这种严格匹配的模型可能会导致$\mathrm{query}$几乎不会命中任何的$\mathrm{key}$。既然无法严格精确匹配,注意力机制就采用了一个“模糊”匹配的方法。这里定义每个$\mathrm{key}_i$$\mathrm{query}$都有一个0~1之间的匹配度,这个匹配度描述了$\mathrm{key}_i$$\mathrm{query}$之间的相关程度,记为$\alpha_i$。而查询的结果(记为$\overline{\mathrm{value}}$)也不再是某一个单元的$\mathrm{value}$,而是所有单元$\mathrm{value}$$\alpha_i$的加权和:
\begin{eqnarray}
\overline{\mathrm{value}} = \sum_i \alpha_i \cdot {\mathrm{value}}_i
\label{eq:12-27}
\end{eqnarray}
\noindent 也就是说所有的$\mathrm{value}_i$都会对查询结果有贡献,只是贡献度不同罢了。可以通过设计$\alpha_i$来捕捉$\mathrm{key}$$\mathrm{query}$之间的相关性,以达到相关度越大的$\mathrm{key}$所对应的$\mathrm{value}$对结果的贡献越大。
\parinterval 重新回到神经机器翻译问题上来。这种基于模糊匹配的查询模型可以很好的满足对注意力建模的要求。实际上,公式\ref{eq:12-27}中的$\alpha_i$就是前面提到的注意力权重,它可以由注意力函数a($\cdot$)计算得到。这样,$\overline{\mathrm{value}}$就是得到的上下文向量,它包含了所有\{$\vectorn{\emph{h}}_i$\}的信息,只是不同$\vectorn{\emph{h}}_i$的贡献度不同罢了。图\ref{fig:12-27}展示了将基于模糊匹配的查询模型应用于注意力机制的实例。
%----------------------------------------------
\begin{figure}[htp]
\centering
\input{./Chapter12/Figures/figure-query-model-corresponding-to-attention-mechanism}
\caption{注意力机制所对应的查询模型}
\label{fig:12-27}
\end{figure}
%----------------------------------------------
\parinterval 最后,从统计学的角度,如果把$\alpha_i$作为每个$\mathrm{value}_i$出现的概率的某种估计,即:$ \funp{P} (\mathrm{value}_i$) $= \alpha_i$,于是可以把公式\ref{eq:12-27}重写为:
\begin{eqnarray}
\overline{\mathrm{value}} = \sum_i \funp{P} ( {\mathrm{value}}_i) \cdot {\mathrm{value}}_i
\label{eq:12-28}
\end{eqnarray}
\noindent 显然, $\overline{\mathrm{value}}$就是$\mathrm{value}_i$在分布$ \funp{P}( \mathrm{value}_i$)下的期望,即
\begin{equation}
\mathbb{E}_{\sim \\ \funp{P} ( {\mathrm{\mathrm{value}}}_i )} ({\mathrm{value}}_i) = \sum_i \funp{P} ({\mathrm{value}}_i) \cdot {\mathrm{value}}_i
\label{eq:12-29}
\end{equation}
从这个观点看,注意力机制实际上是得到了一个变量($\mathrm{value}$)的期望。当然,严格意义上说,$\alpha_i$并不是从概率角度定义的,这里也并不是要追求严格的统计学意义。不过这确实说明了,往往看似简单的模型背后的数学原理可能会很深刻。
%----------------------------------------------------------------------------------------
% NEW SECTION 10.4
% NEW SECTION
%----------------------------------------------------------------------------------------
\sectionnewpage
\section{小节及深入阅读}
......
......@@ -3,27 +3,27 @@
\begin{scope}
\tikzstyle{rnode} = [draw,minimum width=3.5em,minimum height=1.2em]
\node [rnode,anchor=south west,fill=red!20!white] (e1) at (0,0) {\scriptsize{$\vectorn{\emph{e}}(\textrm{“沈阳”})$}};
\node [rnode,anchor=south west,fill=red!20!white] (e2) at ([xshift=1em]e1.south east) {\scriptsize{$\vectorn{{\emph{e}}}(\textrm{“到”})$}};
\node [rnode,anchor=south west,fill=red!20!white] (e3) at ([xshift=1em]e2.south east) {\scriptsize{$\vectorn{{\emph{e}}}(\textrm{“广州”})$}};
\node [rnode,anchor=south west,fill=red!20!white] (e4) at ([xshift=1em]e3.south east) {\scriptsize{$\vectorn{{\emph{e}}}(\textrm{“的”})$}};
\node [rnode,anchor=south west,fill=red!20!white] (e5) at ([xshift=1em]e4.south east) {\scriptsize{$\vectorn{\emph{e}}(\textrm{“机票”})$}};
\node [rnode,anchor=south west,fill=green!20!white] (h1) at ([yshift=1.5em]e1.north west) {\scriptsize{$\vectorn{\emph{h}}(\textrm{“沈阳”})$}};
\node [rnode,anchor=south west,fill=green!20!white] (h2) at ([yshift=1.5em]e2.north west) {\scriptsize{$\vectorn{\emph{h}}(\textrm{“到”})$}};
\node [rnode,anchor=south west,fill=green!20!white] (h3) at ([yshift=1.5em]e3.north west) {\scriptsize{$\vectorn{\emph{h}}(\textrm{“广州”})$}};
\node [rnode,anchor=south west,fill=green!20!white] (h4) at ([yshift=1.5em]e4.north west) {\scriptsize{$\vectorn{\emph{h}}(\textrm{“的”})$}};
\node [rnode,anchor=south west,fill=green!20!white] (h5) at ([yshift=1.5em]e5.north west) {\scriptsize{$\vectorn{\emph{h}}(\textrm{“机票”})$}};
\node [rnode,anchor=south west,fill=red!20!white] (e1) at (0,0) {\scriptsize{$e(\textrm{沈阳})$}};
\node [rnode,anchor=south west,fill=red!20!white] (e2) at ([xshift=1em]e1.south east) {\scriptsize{$e(\textrm{})$}};
\node [rnode,anchor=south west,fill=red!20!white] (e3) at ([xshift=1em]e2.south east) {\scriptsize{$e(\textrm{广州})$}};
\node [rnode,anchor=south west,fill=red!20!white] (e4) at ([xshift=1em]e3.south east) {\scriptsize{$e(\textrm{})$}};
\node [rnode,anchor=south west,fill=red!20!white] (e5) at ([xshift=1em]e4.south east) {\scriptsize{$e(\textrm{机票})$}};
\node [rnode,anchor=south west,fill=green!20!white] (h1) at ([yshift=1.5em]e1.north west) {\scriptsize{$h(\textrm{沈阳})$}};
\node [rnode,anchor=south west,fill=green!20!white] (h2) at ([yshift=1.5em]e2.north west) {\scriptsize{$h(\textrm{})$}};
\node [rnode,anchor=south west,fill=green!20!white] (h3) at ([yshift=1.5em]e3.north west) {\scriptsize{$h(\textrm{广州})$}};
\node [rnode,anchor=south west,fill=green!20!white] (h4) at ([yshift=1.5em]e4.north west) {\scriptsize{$h(\textrm{})$}};
\node [rnode,anchor=south west,fill=green!20!white] (h5) at ([yshift=1.5em]e5.north west) {\scriptsize{$h(\textrm{机票})$}};
\foreach \x in {1,2,3,4,5}{
\node [anchor=north] (plus\x) at ([yshift=-0em]e\x.south) {\scriptsize{$\mathbf{\oplus}$}};
}
\node [rnode,anchor=north,fill=yellow!20!white] (pos1) at ([yshift=-1.1em]e1.south) {\scriptsize{$\vectorn{{\emph{PE}}}(1)$}};
\node [rnode,anchor=north,fill=yellow!20!white] (pos2) at ([yshift=-1.1em]e2.south) {\scriptsize{$ \vectorn{{\emph{PE}}}(2)$}};
\node [rnode,anchor=north,fill=yellow!20!white] (pos3) at ([yshift=-1.1em]e3.south) {\scriptsize{$ \vectorn{{\emph{PE}}}(3)$}};
\node [rnode,anchor=north,fill=yellow!20!white] (pos4) at ([yshift=-1.1em]e4.south) {\scriptsize{$ \vectorn{{\emph{PE}}}(4)$}};
\node [rnode,anchor=north,fill=yellow!20!white] (pos5) at ([yshift=-1.1em]e5.south) {\scriptsize{$ \vectorn{{\emph{PE}}}(5)$}};
\node [rnode,anchor=north,fill=yellow!20!white] (pos1) at ([yshift=-1.1em]e1.south) {\scriptsize{$\textrm{PE}(1)$}};
\node [rnode,anchor=north,fill=yellow!20!white] (pos2) at ([yshift=-1.1em]e2.south) {\scriptsize{$\textrm{PE}(2)$}};
\node [rnode,anchor=north,fill=yellow!20!white] (pos3) at ([yshift=-1.1em]e3.south) {\scriptsize{$\textrm{PE}(3)$}};
\node [rnode,anchor=north,fill=yellow!20!white] (pos4) at ([yshift=-1.1em]e4.south) {\scriptsize{$\textrm{PE}(4)$}};
\node [rnode,anchor=north,fill=yellow!20!white] (pos5) at ([yshift=-1.1em]e5.south) {\scriptsize{$\textrm{PE}(5)$}};
\foreach \x in {1,2,3,4,5}{
......
......@@ -4,11 +4,11 @@
\tikzstyle{rnode} = [draw,minimum width=3.5em,minimum height=1.2em]
\node [rnode,anchor=south west,fill=green!20!white] (key1) at (0,0) {\scriptsize{$\vectorn{\emph{h}}(\textrm{``沈阳''})$}};
\node [rnode,anchor=south west,fill=green!20!white] (key2) at ([xshift=1em]key1.south east) {\scriptsize{$\vectorn{\emph{h}}(\textrm{``到''})$}};
\node [rnode,anchor=south west,fill=green!20!white] (key3) at ([xshift=1em]key2.south east) {\scriptsize{$\vectorn{\emph{h}}(\textrm{``广州''})$}};
\node [rnode,anchor=south west,fill=green!20!white] (key4) at ([xshift=2em]key3.south east) {\scriptsize{$\vectorn{\emph{h}}(\textrm{``机票''})$}};
\node [rnode,anchor=south west] (key5) at ([xshift=1em]key4.south east) {\scriptsize{$\vectorn{\emph{h}}(\textrm{``机票''})$}};
\node [rnode,anchor=south west,fill=green!20!white] (key1) at (0,0) {\scriptsize{$h(\textrm{沈阳})$}};
\node [rnode,anchor=south west,fill=green!20!white] (key2) at ([xshift=1em]key1.south east) {\scriptsize{$h(\textrm{})$}};
\node [rnode,anchor=south west,fill=green!20!white] (key3) at ([xshift=1em]key2.south east) {\scriptsize{$h(\textrm{广州})$}};
\node [rnode,anchor=south west,fill=green!20!white] (key4) at ([xshift=2em]key3.south east) {\scriptsize{$h(\textrm{机票})$}};
\node [rnode,anchor=south west] (key5) at ([xshift=1em]key4.south east) {\scriptsize{$h(\textrm{机票})$}};
\node [anchor=west] (sep1) at ([xshift=0.3em]key3.east) {\scriptsize{$\textbf{...}$}};
......@@ -24,11 +24,11 @@
\vspace{0.5em}
\node [rnode,anchor=south west,fill=green!20!white] (key6) at ([yshift=2em]key1.north west) {\scriptsize{$\vectorn{\emph{h}}(\textrm{``广州''})$}};
\node [rnode,anchor=south west,fill=green!20!white] (key7) at ([yshift=2em]key2.north west) {\scriptsize{$\vectorn{\emph{h}}(\textrm{``到''})$}};
\node [rnode,anchor=south west,fill=green!20!white] (key8) at ([yshift=2em]key3.north west) {\scriptsize{$\vectorn{\emph{h}}(\textrm{``沈阳''})$}};
\node [rnode,anchor=south west,fill=green!20!white] (key9) at ([yshift=2em]key4.north west) {\scriptsize{$\vectorn{\emph{h}}(\textrm{``机票''})$}};
\node [rnode,anchor=south west] (key10) at ([yshift=2em]key5.north west) {\scriptsize{$\vectorn{\emph{h}}(\textrm{``机票''})$}};
\node [rnode,anchor=south west,fill=green!20!white] (key6) at ([yshift=2em]key1.north west) {\scriptsize{$h(\textrm{广州})$}};
\node [rnode,anchor=south west,fill=green!20!white] (key7) at ([yshift=2em]key2.north west) {\scriptsize{$h(\textrm{})$}};
\node [rnode,anchor=south west,fill=green!20!white] (key8) at ([yshift=2em]key3.north west) {\scriptsize{$h(\textrm{沈阳})$}};
\node [rnode,anchor=south west,fill=green!20!white] (key9) at ([yshift=2em]key4.north west) {\scriptsize{$h(\textrm{机票})$}};
\node [rnode,anchor=south west] (key10) at ([yshift=2em]key5.north west) {\scriptsize{$h(\textrm{``机票''})$}};
\node [anchor=west] (sep1) at ([xshift=0.3em]key8.east) {\scriptsize{$\textbf{...}$}};
......@@ -49,7 +49,7 @@
\vspace{-1.0em}
\footnotesize{
\begin{eqnarray}
\tilde{\mathbf{\emph{h}}} (\textrm{''机票''}) & = & 0.2 \times \vectorn{\emph{h}}(\textrm{``沈阳''}) + 0.3 \times \vectorn{\emph{h}}(\textrm{``到''}) + \nonumber \\
& & 0.1 \times \vectorn{\emph{h}}(\textrm{``广州''}) + ... + 0.3 \times \vectorn{\emph{h}}(\textrm{``机票''}) \nonumber
\tilde{\mathbf{h}} (\textrm{机票}) & = & 0.2 \times h(\textrm{沈阳}) + 0.3 \times h(\textrm{}) + \nonumber \\
& & 0.1 \times h(\textrm{广州}) + ... + 0.3 \times h(\textrm{机票}) \nonumber
\end{eqnarray}
}
\ No newline at end of file
......@@ -84,11 +84,20 @@
\parinterval 通常,也把生成\{ $\tilde{\vectorn{\emph{h}}}(\vectorn{\emph{w}}_i)$ \}的过程称为{\small\sffamily\bfseries{特征提取}}\index{特征提取},而实现这个过程的模型被称为特征提取器。循环神经网络、自注意力模型都是典型的特征提取器。特征提取是神经机器翻译系统的关键步骤,在随后的内容中可以看到自注意力模型是一个非常适合机器翻译任务的特征提取器。
%----------------------------------------------------------------------------------------
% NEW SECTION 12.2
% NEW SECTION
%----------------------------------------------------------------------------------------
\sectionnewpage
\section{Transformer}
\parinterval 本小节将以Transformer为例,详细地展示自注意力机制在神经机器翻译中的应用,以及Transformer的相关技术。首先回顾一下{\chapterten}介绍的循环神经网络,虽然它很强大,但是也存在一些弊端。其中比较突出的问题是,循环神经网络每个循环单元都有向前依赖性,也就是当前时间步的处理依赖前一时间步处理的结果。这个性质可以使序列的“历史”信息不断被传递,但是也造成模型运行效率的下降。特别是对于自然语言处理任务,序列往往较长,无论是传统的RNN结构,还是更为复杂的LSTM结构,都需要很多次循环单元的处理才能够捕捉到单词之间的长距离依赖。由于需要多个循环单元的处理,距离较远的两个单词之间的信息传递变得很复杂。
\section{Transformer架构}
本节会对Transformer模型由来以及总体架构进行介绍。
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION
%----------------------------------------------------------------------------------------
\subsection{为什么需要Transformer}
\parinterval 首先回顾一下{\chapterten}介绍的循环神经网络,虽然它很强大,但是也存在一些弊端。其中比较突出的问题是,循环神经网络每个循环单元都有向前依赖性,也就是当前时间步的处理依赖前一时间步处理的结果。这个性质可以使序列的“历史”信息不断被传递,但是也造成模型运行效率的下降。特别是对于自然语言处理任务,序列往往较长,无论是传统的RNN结构,还是更为复杂的LSTM结构,都需要很多次循环单元的处理才能够捕捉到单词之间的长距离依赖。由于需要多个循环单元的处理,距离较远的两个单词之间的信息传递变得很复杂。
\parinterval 针对这些问题,谷歌的研究人员提出了一种全新的模型$\ \dash\ $Transformer\upcite{NIPS2017_7181}。与循环神经网络等传统模型不同,Transformer模型仅仅使用一种被称作自注意力机制的方法和标准的前馈神经网络,完全不依赖任何循环单元或者卷积操作。自注意力机制的优点在于可以直接对序列中任意两个单元之间的关系进行建模,这使得长距离依赖等问题可以更好地被求解。此外,自注意力机制非常适合在GPU 上进行并行化,因此模型训练的速度更快。表\ref{tab:12-11}对比了RNN、CNN、Transformer三种模型的时间复杂度。
......@@ -128,10 +137,10 @@
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 12.2.1
% NEW SUB-SECTION
%----------------------------------------------------------------------------------------
\subsection{Transformer架}
\subsection{总体结}
%----------------------------------------------
\begin{figure}[htp]
\centering
......@@ -175,12 +184,12 @@
\parinterval 了解到这里,可能大家还有很多疑惑,比如,什么是位置编码?Transformer的自注意力机制具体是怎么进行计算的,其结构是怎样的?Add\& LayerNorm又是什么?等等。下面就一一展开介绍。
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 12.2.2
% NEW SECTION
%----------------------------------------------------------------------------------------
\subsection{位置编码}
\section{位置编码}
\parinterval 在使用循环神经网络进行序列的信息提取时,每个时刻的运算都要依赖前一个时刻的输出,具有一定的时序性,这也与语言具有顺序的特点相契合。而采用自注意力机制对源语言和目标语言序列进行处理时,直接对当前位置和序列中的任意位置进行建模,忽略了词之间的顺序关系,例如图\ref{fig:12-41}中两个语义不同的句子,通过自注意力得到的表示$\tilde{\vectorn{\emph{h}}}$(“机票”)却是相同的。
\parinterval 在使用循环神经网络进行序列的信息提取时,每个时刻的运算都要依赖前一个时刻的输出,具有一定的时序性,这也与语言具有顺序的特点相契合。而采用自注意力机制对源语言和目标语言序列进行处理时,直接对当前位置和序列中的任意位置进行建模,忽略了词之间的顺序关系,例如图\ref{fig:12-41}中两个语义不同的句子,通过自注意力得到的表示$\tilde{h}$(“机票”)却是相同的。
%----------------------------------------------
\begin{figure}[htp]
......@@ -242,10 +251,12 @@
\noindent 即对于任意固定的偏移量$k$$\textrm{PE}(pos+k)$能被表示成$\textrm{PE}(pos)$的线性函数,换句话说,位置编码可以表示词之间的距离。在实践中发现,位置编码对Transformer系统的性能有很大影响。对其进行改进也会带来进一步的性能提升\upcite{Shaw2018SelfAttentionWR}
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 12.2.3
% NEW SECTION
%----------------------------------------------------------------------------------------
\subsection{基于点乘的注意力机制}
\section{基于点乘的多头注意力机制}
\parinterval Transformer模型摒弃了循环单元和卷积等结构,完全基于注意力机制来构造模型,其中包含着大量的注意力计算。比如,可以通过自注意力机制对源语言和目标语言序列进行信息提取,并通过编码-解码注意力对双语句对之间的关系进行提取。图\ref{fig:12-44}中红色方框部分是Transformer中使用自注意力机制的模块。而这些模块都是由基于点乘的多头注意力机制实现的。
%----------------------------------------------
\begin{figure}[htp]
......@@ -256,7 +267,11 @@
\end{figure}
%----------------------------------------------
\parinterval Transformer模型摒弃了循环单元和卷积等结构,完全基于注意力机制来构造模型,其中包含着大量的注意力计算。比如,可以通过自注意力机制对源语言和目标语言序列进行信息提取,并通过编码-解码注意力对双语句对之间的关系进行提取。图\ref{fig:12-44}中红色方框部分是Transformer中使用自注意力机制的模块。
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION
%----------------------------------------------------------------------------------------
\subsection{点乘注意力}
\parinterval\ref{sec:12.1.3}节中已经介绍,自注意力机制中至关重要的是获取相关性系数,也就是在融合不同位置的表示向量时各位置的权重。不同于\ref{sec:12.1.3}节介绍的注意力机制的相关性系数计算方式,Transformer模型采用了一种基于点乘的方法来计算相关性系数。这种方法也称为{\small\bfnew{点乘注意力}}\index{点乘注意力}(Scaled Dot-Product Attention)\index{Scaled Dot-Product Attention}机制。它的运算并行度高,同时并不消耗太多的存储空间。
......@@ -297,34 +312,7 @@
%----------------------------------------------
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 12.2.4
%----------------------------------------------------------------------------------------
\subsection{掩码操作}
\parinterval 在公式\ref{eq:12-47}中提到了Mask(掩码),它的目的是对向量中某些值进行掩盖,避免无关位置的数值对运算造成影响。Transformer中的Mask主要应用在注意力机制中的相关性系数计算,具体方式是在相关性系数矩阵上累加一个Mask矩阵。该矩阵在需要Mask的位置的值为负无穷-inf(具体实现时是一个非常小的数,比如-1e-9),其余位置为0,这样在进行了Softmax归一化操作之后,被掩码掉的位置计算得到的权重便近似为0,也就是说对无用信息分配的权重为0,从而避免了其对结果产生影响。Transformer包含两种Mask:
\begin{itemize}
\vspace{0.5em}
\item Padding Mask。在批量处理多个样本时(训练或解码),由于要对源语言和目标语言的输入进行批次化处理,而每个批次内序列的长度不一样,为了方便对批次内序列进行矩阵表示,需要进行对齐操作,即在较短的序列后面填充0来占位(padding操作)。而这些填充的位置没有意义,不参与注意力机制的计算,因此,需要进行Mask操作,屏蔽其影响。
\vspace{0.5em}
\item Future Mask。对于解码器来说,由于在预测的时候是自左向右进行的,即第$t$时刻解码器的输出只能依赖于$t$时刻之前的输出。且为了保证训练解码一致,避免在训练过程中观测到目标语端每个位置未来的信息,因此需要对未来信息进行屏蔽。具体的做法是:构造一个上三角值全为-inf的Mask矩阵,也就是说,在解码端计算中,在当前位置,通过Future Mask把序列之后的信息屏蔽掉了,避免了$t$时刻之后的位置对当前的计算产生影响。图\ref{fig:12-47}给出了一个具体的实例。
%----------------------------------------------
% 图3.10
\begin{figure}[htp]
\centering
\input{./Chapter12/Figures/figure-mask-instance-for-future-positions-in-transformer}
\caption{Transformer中对于未来位置进行的屏蔽的Mask实例}
\label{fig:12-47}
\end{figure}
%----------------------------------------------
\vspace{0.5em}
\end{itemize}
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 12.2.5
% NEW SUB-SECTION
%----------------------------------------------------------------------------------------
\subsection{多头注意力}
......@@ -362,10 +350,37 @@
\parinterval 多头机制的好处是允许模型在不同的表示子空间里学习。在很多实验中发现,不同表示空间的头捕获的信息是不同的,比如,在使用Transformer处理自然语言时,有的头可以捕捉句法信息,有头可以捕捉词法信息。
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 12.2.6
% NEW SUB-SECTION
%----------------------------------------------------------------------------------------
\subsection{掩码操作}
\parinterval 在公式\ref{eq:12-47}中提到了Mask(掩码),它的目的是对向量中某些值进行掩盖,避免无关位置的数值对运算造成影响。Transformer中的Mask主要应用在注意力机制中的相关性系数计算,具体方式是在相关性系数矩阵上累加一个Mask矩阵。该矩阵在需要Mask的位置的值为负无穷-inf(具体实现时是一个非常小的数,比如-1e-9),其余位置为0,这样在进行了Softmax归一化操作之后,被掩码掉的位置计算得到的权重便近似为0,也就是说对无用信息分配的权重为0,从而避免了其对结果产生影响。Transformer包含两种Mask:
\begin{itemize}
\vspace{0.5em}
\item Padding Mask。在批量处理多个样本时(训练或解码),由于要对源语言和目标语言的输入进行批次化处理,而每个批次内序列的长度不一样,为了方便对批次内序列进行矩阵表示,需要进行对齐操作,即在较短的序列后面填充0来占位(padding操作)。而这些填充的位置没有意义,不参与注意力机制的计算,因此,需要进行Mask操作,屏蔽其影响。
\vspace{0.5em}
\item Future Mask。对于解码器来说,由于在预测的时候是自左向右进行的,即第$t$时刻解码器的输出只能依赖于$t$时刻之前的输出。且为了保证训练解码一致,避免在训练过程中观测到目标语端每个位置未来的信息,因此需要对未来信息进行屏蔽。具体的做法是:构造一个上三角值全为-inf的Mask矩阵,也就是说,在解码端计算中,在当前位置,通过Future Mask把序列之后的信息屏蔽掉了,避免了$t$时刻之后的位置对当前的计算产生影响。图\ref{fig:12-47}给出了一个具体的实例。
%----------------------------------------------
% 图3.10
\begin{figure}[htp]
\centering
\input{./Chapter12/Figures/figure-mask-instance-for-future-positions-in-transformer}
\caption{Transformer中对于未来位置进行的屏蔽的Mask实例}
\label{fig:12-47}
\end{figure}
%----------------------------------------------
\vspace{0.5em}
\end{itemize}
%----------------------------------------------------------------------------------------
% NEW SECTION
%----------------------------------------------------------------------------------------
\subsection{残差网络和层正则化}
\section{残差网络和层正则化}
\parinterval Transformer编码器、解码器分别由多层网络组成(通常为6层),每层网络又包含多个子层(自注意力网络、前馈神经网络)。因此Transformer实际上是一个很深的网络结构。再加上前面介绍的点乘注意力机制,包含很多线性和非线性变换;另外,注意力函数Attention($\cdot$)的计算也涉及多层网络,整个网络的信息传递非常复杂。从反向传播的角度来看,每次回传的梯度都会经过若干步骤,容易产生梯度爆炸或者消失。
......@@ -417,10 +432,10 @@ x_{l+1} = x_l + \mathcal{F} (x_l)
%----------------------------------------------
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 12.2.7
% NEW SECTION
%----------------------------------------------------------------------------------------
\subsection{前馈全连接网络子层}
\section{前馈全连接网络子层}
\parinterval 在Transformer的结构中,每一个编码层或者解码层中都包含一个前馈神经网络,它在模型中的位置如图\ref{fig:12-52}中红色方框所示。
......@@ -442,10 +457,10 @@ x_{l+1} = x_l + \mathcal{F} (x_l)
\noindent 其中,$\vectorn{\emph{W}}_1$$\vectorn{\emph{W}}_2$$\vectorn{\emph{b}}_1$$\vectorn{\emph{b}}_2$为模型的参数。通常情况下,前馈神经网络的隐层维度要比注意力部分的隐层维度大,而且研究人员发现这种设置对Transformer是至关重要的。 比如,注意力部分的隐层维度为512,前馈神经网络部分的隐层维度为2048。当然,继续增大前馈神经网络的隐层大小,比如设为4096,甚至8192,还可以带来性能的增益,但是前馈部分的存储消耗较大,需要更大规模GPU 设备的支持。因此在具体实现时,往往需要在翻译准确性和存储/速度之间找到一个平衡。
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 12.2.8
% NEW SECTION
%----------------------------------------------------------------------------------------
\subsection{训练}
\section{训练}
\parinterval 与前面介绍的神经机器翻译模型的训练一样,Transformer的训练流程为:首先对模型进行初始化,然后在编码器输入包含结束符的源语言单词序列。前面已经介绍过,解码端每个位置单词的预测都要依赖已经生成的序列。在解码端输入包含起始符号的目标语序列,通过起始符号预测目标语的第一个单词,用真实的目标语的第一个单词去预测第二个单词,以此类推,然后用真实的目标语序列和预测的结果比较,计算它的损失。Transformer使用了{\small\bfnew{交叉熵损失}}\index{交叉熵损失}(Cross Entropy Loss)\index{Cross Entropy Loss}函数,损失越小说明模型的预测越接近真实输出。然后利用反向传播来调整模型中的参数。由于Transformer 将任意时刻输入信息之间的距离拉近为1,摒弃了RNN中每一个时刻的计算都要基于前一时刻的计算这种具有时序性的训练方式,因此Transformer中训练的不同位置可以并行化训练,大大提高了训练效率。
......@@ -534,10 +549,10 @@ Transformer Deep(48层) & 30.2 & 43.1 & 194$\times 10^{6}$
%----------------------------------------------
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 12.2.9
% NEW SECTION
%----------------------------------------------------------------------------------------
\subsection{推断}
\section{推断}
\parinterval Transformer解码器生成目标语的过程和前面介绍的循环网络翻译模型类似,都是从左往右生成,且下一个单词的预测依赖已经生成的上一个单词。其具体推断过程如图\ref{fig:12-56}所示,其中$\vectorn{\emph{C}}_i$是编码-解码注意力的结果,解码器首先根据“<eos>”和$\vectorn{\emph{C}}_1$生成第一个单词“how”,然后根据“how”和$\vectorn{\emph{C}}_2$生成第二个单词“are”,以此类推,当解码器生成“<eos>”时结束推断。
......@@ -559,4 +574,4 @@ Transformer Deep(48层) & 30.2 & 43.1 & 194$\times 10^{6}$
%----------------------------------------------------------------------------------------
\section{小结及深入阅读}
\parinterval
\ No newline at end of file
\parinterval
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论