Commit 09d4e040 by xiaotong

updates of section 6

parent ed301575
...@@ -559,16 +559,16 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof ...@@ -559,16 +559,16 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof
\label{eqC6.11} \label{eqC6.11}
\end{eqnarray} \end{eqnarray}
\noindent 其中$\mathbf{x}_t$是当前时刻的输入,$\mathbf{h}_{t-1}$是上一时刻循环单元的输出,$f(\cdot)$是激活函数,$\mathbf{U}$\\ $\mathbf{W}$是参数矩阵,$\mathbf{b}$是偏置。 \noindent 其中$\mathbf{x}_t$是当前时刻的输入,$\mathbf{h}_{t-1}$是上一时刻循环单元的输出,$f(\cdot)$是激活函数,$\mathbf{U}$$\mathbf{W}$是参数矩阵,$\mathbf{b}$是偏置。
\parinterval 虽然RNN的结构很简单,但是已经具有了对序列信息进行记忆的能力。实际上,基于RNN结构的神经语言模型已经能够取得比传统$n$-gram语言模型更优异的性能。在机器翻译中,RNN也可以做作为入门或者快速原型所使用的网络结构。 \parinterval 虽然RNN的结构很简单,但是已经具有了对序列信息进行记忆的能力。实际上,基于RNN结构的神经语言模型已经能够取得比传统$n$-gram语言模型更优异的性能。在机器翻译中,RNN也可以做作为入门或者快速原型所使用的神经网络结构。
%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%
\subsubsection{长短时记忆网络(LSTM)}\index{Chapter6.3.3.2} \subsubsection{长短时记忆网络(LSTM)}\index{Chapter6.3.3.2}
\label{sec:6.3.3.2} \label{sec:6.3.3.2}
\parinterval RNN结构使得当前时刻循环单元的状态包含了之前时间步的状态信息。但是这种对历史信息的记忆并不是无损的,随着序列变长,RNN的记忆信息的损失越来越严重。在很多长序列处理任务中(如长文本生成)都观测到了类似现象。对于这个问题,Hochreiter S和Schmidhuber J提出了长短时记忆模型(long short-term memory),也就是常说的LSTM模型\cite{HochreiterLong} \parinterval RNN结构使得当前时刻循环单元的状态包含了之前时间步的状态信息。但是这种对历史信息的记忆并不是无损的,随着序列变长,RNN的记忆信息的损失越来越严重。在很多长序列处理任务中(如长文本生成)都观测到了类似现象。对于这个问题,Hochreiter和Schmidhuber提出了{\small\bfnew{长短时记忆}}(Long Short-Term Memory)模型,也就是常说的LSTM模型\cite{HochreiterLong}
%Jürgen Schmidhuber %Jürgen Schmidhuber
\parinterval LSTM模型是RNN模型的一种改进。相比RNN仅传递前一时刻的状态$\mathbf{h}_{t-1}$,LST\\M会同时传递两部分信息:状态信息$\mathbf{h}_{t-1}$和记忆信息$\mathbf{c}_{t-1}$。这里,$\mathbf{c}_{t-1}$是新引入的变量,它也是循环单元的一部分,用于显性的记录需要记录的历史内容,$\mathbf{h}_{t-1}$$\mathbf{c}_{t-1}$在循环单元中会相互作用。LSTM通过``门''单元来动态地选择遗忘多少以前的信息和记忆多少当前的信息。LSTM中所使用的门结构如图\ref{fig:6-14}所示,包括遗忘门,输入门和输出门。图中$\sigma$代表Sigmoid函数,它将函数输入映射为0-1范围内的实数,用来充当门控信号。 \parinterval LSTM模型是RNN模型的一种改进。相比RNN仅传递前一时刻的状态$\mathbf{h}_{t-1}$,LSTM会同时传递两部分信息:状态信息$\mathbf{h}_{t-1}$和记忆信息$\mathbf{c}_{t-1}$。这里,$\mathbf{c}_{t-1}$是新引入的变量,它也是循环单元的一部分,用于显性的记录需要记录的历史内容,$\mathbf{h}_{t-1}$$\mathbf{c}_{t-1}$在循环单元中会相互作用。LSTM通过``门''单元来动态地选择遗忘多少以前的信息和记忆多少当前的信息。LSTM中所使用的门结构如图\ref{fig:6-14}所示,包括遗忘门,输入门和输出门。图中$\sigma$代表Sigmoid函数,它将函数输入映射为0-1范围内的实数,用来充当门控信号。
%---------------------------------------------- %----------------------------------------------
% 图3.10 % 图3.10
...@@ -586,7 +586,7 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof ...@@ -586,7 +586,7 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof
\parinterval LSTM的结构主要分为三个部分: \parinterval LSTM的结构主要分为三个部分:
\begin{itemize} \begin{itemize}
\item {\small\sffamily\bfseries{遗忘}}。顾名思义,遗忘的目的是忘记一些历史,在LSTM中通过遗忘门实现,其结构如图\ref{fig:6-14}(a)所示。$\mathbf{x}_{t}$表示时刻$t$的输入向量,$\mathbf{h}_{t-1}$是时刻$t-1$的循环单元的输出,$\mathbf{x}_{t}$$\mathbf{h}_{t-1}$都作为$t$时刻循环单元的输入。$\sigma$将对$\mathbf{x}_{t}$$\mathbf{h}_{t-1}$进行筛选,以决定遗忘的信息,其计算公式如下: \item {\small\sffamily\bfseries{遗忘}}。顾名思义,遗忘的目的是忘记一些历史,在LSTM中通过遗忘门实现,其结构如图\ref{fig:6-14}(a)所示。$\mathbf{x}_{t}$表示时刻$t$的输入向量,$\mathbf{h}_{t-1}$是时刻$t-1$的循环单元的输出,$\mathbf{x}_{t}$$\mathbf{h}_{t-1}$都作为$t$时刻循环单元的输入。$\sigma$将对$\mathbf{x}_{t}$$\mathbf{h}_{t-1}$进行筛选,以决定遗忘的信息,其计算公式如下:
\begin{eqnarray} \begin{eqnarray}
\mathbf{f}_t=\sigma(\mathbf{W}_f [\mathbf{h}_{t-1},\mathbf{x}_{t}] + \mathbf{b}_f ) \mathbf{f}_t=\sigma(\mathbf{W}_f [\mathbf{h}_{t-1},\mathbf{x}_{t}] + \mathbf{b}_f )
\label{eqC6.12} \label{eqC6.12}
...@@ -594,7 +594,7 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof ...@@ -594,7 +594,7 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof
这里,$\mathbf{W}_f$是权值,$\mathbf{b}_f$是偏置,$[\mathbf{h}_{t-1},\mathbf{x}_{t}]$表示两个向量的拼接。该公式可以解释为,对$[\mathbf{h}_{t-1},\mathbf{x}_{t}]$进行变换,并得到一个 的实数向量$\mathbf{f}_t$$\mathbf{f}_t$的每一维都可以被理解为一个``门'',它决定可以有多少信息被留下(或遗忘)。 这里,$\mathbf{W}_f$是权值,$\mathbf{b}_f$是偏置,$[\mathbf{h}_{t-1},\mathbf{x}_{t}]$表示两个向量的拼接。该公式可以解释为,对$[\mathbf{h}_{t-1},\mathbf{x}_{t}]$进行变换,并得到一个 的实数向量$\mathbf{f}_t$$\mathbf{f}_t$的每一维都可以被理解为一个``门'',它决定可以有多少信息被留下(或遗忘)。
\item {\small\sffamily\bfseries{记忆更新}}。首先,要生成当前时刻需要新增加的信息,该部分由输入门完成,其结构如图\ref{fig:6-14}(b)红色线部分,图中``$\bigotimes$''表示进行点乘操作。输入门的计算分为两部分,首先利用$\sigma$决定门控参数$\mathbf{i}_t$,然后通过Tanh函数得到新的信息$\hat{\mathbf{c}}_t$,具体公式如下: \item {\small\sffamily\bfseries{记忆更新}}。首先,要生成当前时刻需要新增加的信息,该部分由输入门完成,其结构如图\ref{fig:6-14}(b)红色线部分,图中``$\bigotimes$''表示进行点乘操作。输入门的计算分为两部分,首先利用$\sigma$决定门控参数$\mathbf{i}_t$,然后通过Tanh函数得到新的信息$\hat{\mathbf{c}}_t$,具体公式如下:
\begin{eqnarray} \begin{eqnarray}
\mathbf{i}_t = \sigma (\mathbf{W}_i [\mathbf{h}_{t-1},\mathbf{x}_{t}] + \mathbf{b}_i ) \mathbf{i}_t = \sigma (\mathbf{W}_i [\mathbf{h}_{t-1},\mathbf{x}_{t}] + \mathbf{b}_i )
\label{eqC6.13} \label{eqC6.13}
...@@ -605,21 +605,17 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof ...@@ -605,21 +605,17 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof
\label{eqC6.14} \label{eqC6.14}
\end{eqnarray} \end{eqnarray}
之后,用$\mathbf{i}_t$点乘$\hat{\mathbf{c}}_t$,得到当前需要记忆的信息,记为$\mathbf{i}_t \cdot \hat{\mathbf{c}}_t$。接下来需要更新旧的信息$\mathbf{c}_{t-1}$,得到新的记忆信息$\mathbf{c}_t$,更新的操作如图\ref{fig:6-14}(c)红色线部分所示,``$\bigoplus$''表示相加。具体规则是通过遗忘门选择忘记一部分上文信息$\mathbf{f}_t$,通过输入门计算新增的信息$\mathbf{i}_t \cdot \hat{\mathbf{c}}_t$,然后根据``$\bigotimes$''门与``$\bigoplus$''门进行相应的乘法和加法计算: 之后,用$\mathbf{i}_t$点乘$\hat{\mathbf{c}}_t$,得到当前需要记忆的信息,记为$\mathbf{i}_t \cdot \hat{\mathbf{c}}_t$。接下来需要更新旧的信息$\mathbf{c}_{t-1}$,得到新的记忆信息$\mathbf{c}_t$,更新的操作如图\ref{fig:6-14}(c)红色线部分所示,``$\bigoplus$''表示相加。具体规则是通过遗忘门选择忘记一部分上文信息$\mathbf{f}_t$,通过输入门计算新增的信息$\mathbf{i}_t \cdot \hat{\mathbf{c}}_t$,然后根据``$\bigotimes$''门与``$\bigoplus$''门进行相应的乘法和加法计算:
\begin{eqnarray} \begin{eqnarray}
\mathbf{c}_t = \mathbf{f}_t \cdot \mathbf{c}_{t-1} + \mathbf{i}_t \cdot \hat{\mathbf{c}_t} \mathbf{c}_t = \mathbf{f}_t \cdot \mathbf{c}_{t-1} + \mathbf{i}_t \cdot \hat{\mathbf{c}_t}
\label{eqC6.15} \label{eqC6.15}
\end{eqnarray} \end{eqnarray}
\item {\small\sffamily\bfseries{输出}}。该部分使用输出门计算最终的输出信息$\mathbf{h}_t$,其结构如图\ref{fig:6-14}(d)红色线部分所示。在输出门中,首先将$\mathbf{x}_t$$\mathbf{h}_{t-1}$通过$\sigma$函数变换得到$\mathbf{o}_t$。其次,将上一步得到的新记忆信息$\mathbf{c}_t$通过Tanh函数进行变换,得到值范围在[-1,1]的向量。最后将这两部分进行点乘,具体公式如下: \item {\small\sffamily\bfseries{输出}}。该部分使用输出门计算最终的输出信息$\mathbf{h}_t$,其结构如图\ref{fig:6-14}(d)红色线部分所示。在输出门中,首先将$\mathbf{x}_t$$\mathbf{h}_{t-1}$通过$\sigma$函数变换得到$\mathbf{o}_t$。其次,将上一步得到的新记忆信息$\mathbf{c}_t$通过Tanh函数进行变换,得到值范围在[-1,1]的向量。最后将这两部分进行点乘,具体公式如下:
\begin{eqnarray} \begin{eqnarray}
\mathbf{o}_t = \sigma (\mathbf{W}_o [\mathbf{h}_{t-1},\mathbf{x}_{t}] + \mathbf{b}_o ) \mathbf{o}_t & = & \sigma (\mathbf{W}_o [\mathbf{h}_{t-1},\mathbf{x}_{t}] + \mathbf{b}_o ) \label{eqC6.16} \\
\label{eqC6.16} \mathbf{h}_t & = & \mathbf{o}_t \cdot \textrm{Tanh} (\mathbf{c}_t) \label{eqC6.17}
\end{eqnarray}
%----------------------------------------------------
\begin{eqnarray}
\mathbf{h}_t = \mathbf{o}_t \cdot \textrm{Tanh} (\mathbf{c}_t)
\label{eqC6.17}
\end{eqnarray} \end{eqnarray}
\end{itemize} \end{itemize}
...@@ -633,23 +629,18 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof ...@@ -633,23 +629,18 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof
\end{figure} \end{figure}
%---------------------------------------------- %----------------------------------------------
\parinterval LSTM的完整结构如图\ref{fig:6-15}所示,模型的参数包括:参数矩阵$\mathbf{W}_f$$\mathbf{W}_i$$\mathbf{W}_c$\\$\mathbf{W}_o$和偏置$\mathbf{b}_f$$\mathbf{b}_i$$\mathbf{b}_c$$\mathbf{b}_o$。可以看出,$\mathbf{h}_t$是由$\mathbf{c}_{t-1}$$\mathbf{h}_{t-1}$$\mathbf{x}_t$共同决定的,此外上述公式中激活函数的选择是根据函数各自的特点决定的。 \parinterval LSTM的完整结构如图\ref{fig:6-15}所示,模型的参数包括:参数矩阵$\mathbf{W}_f$$\mathbf{W}_i$$\mathbf{W}_c$\\$\mathbf{W}_o$和偏置$\mathbf{b}_f$$\mathbf{b}_i$$\mathbf{b}_c$$\mathbf{b}_o$。可以看出,$\mathbf{h}_t$是由$\mathbf{c}_{t-1}$$\mathbf{h}_{t-1}$$\mathbf{x}_t$共同决定的。此外,上述公式中激活函数的选择是根据函数各自的特点决定的。
%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%
\subsubsection{门控循环单元(GRU)}\index{Chapter6.3.3.3} \subsubsection{门控循环单元(GRU)}\index{Chapter6.3.3.3}
\parinterval LSTM 通过门控单元控制传递状态,忘记不重要的信息,记住必要的历史信息,在长序列上取得了很好的效果,但是其进行了许多门信号的计算,较为繁琐。门循环单元(Gated Recurrent Unit,GRU)作为一个LSTM的变种,它继承了LSTM中利用门控单元控制信息传递的思想,并对LSTM进行了简化\cite{Cho2014Learning}。它把循环单元状态$\mathbf{h}_t$和记忆$\mathbf{c}_t$合并成一个状态$\mathbf{h}_t$,同时使用了更少的门控单元,大大提升了计算效率。 \parinterval LSTM 通过门控单元控制传递状态,忘记不重要的信息,记住必要的历史信息,在长序列上取得了很好的效果,但是其进行了许多门信号的计算,较为繁琐。{\small\bfnew{门循环单元}}(Gated Recurrent Unit,GRU)作为一个LSTM的变种,它继承了LSTM中利用门控单元控制信息传递的思想,并对LSTM进行了简化\cite{Cho2014Learning}。它把循环单元状态$\mathbf{h}_t$和记忆$\mathbf{c}_t$合并成一个状态$\mathbf{h}_t$,同时使用了更少的门控单元,大大提升了计算效率。
\parinterval GRU的输入和RNN是一样的,由输入$\mathbf{x}_t$$t-1$时刻的状态$\mathbf{h}_{t-1}$组成。GRU只有两个门信号,分别是重置门和更新门。重置门$\mathbf{r}_t$用来控制前一时刻隐藏状态的记忆程度,其结构如图\ref{fig:6-16}(a)。更新门用来更新记忆,使用一个门同时完成遗忘和记忆两种操作,其结构如图\ref{fig:6-16}(b)。重置门和更新门的计算公式如下: \parinterval GRU的输入和RNN是一样的,由输入$\mathbf{x}_t$$t-1$时刻的状态$\mathbf{h}_{t-1}$组成。GRU只有两个门信号,分别是重置门和更新门。重置门$\mathbf{r}_t$用来控制前一时刻隐藏状态的记忆程度,其结构如图\ref{fig:6-16}(a)。更新门用来更新记忆,使用一个门同时完成遗忘和记忆两种操作,其结构如图\ref{fig:6-16}(b)。重置门和更新门的计算公式如下:
\begin{eqnarray}
\mathbf{r}_t = \sigma (\mathbf{W}_r [\mathbf{h}_{t-1},\mathbf{x}_{t}] )
\label{eqC6.18}
\end{eqnarray}
%----------------------------------------------------
\begin{eqnarray} \begin{eqnarray}
\mathbf{u}_t = \sigma (\mathbf{W}_u [\mathbf{h}_{t-1},\mathbf{x}_{t}]) \mathbf{r}_t & = &\sigma (\mathbf{W}_r [\mathbf{h}_{t-1},\mathbf{x}_{t}] ) \label{eqC6.18} \\
\label{eqC6.19} \mathbf{u}_t & = & \sigma (\mathbf{W}_u [\mathbf{h}_{t-1},\mathbf{x}_{t}]) \label{eqC6.19}
\end{eqnarray} \end{eqnarray}
\parinterval 当完成了重置门和更新门计算后,就需要更新当前隐藏状态,如图\ref{fig:6-16}(c)所示。在计算得到了重置门的权重$\mathbf{r}_t$后,使用其对前一时刻的状态$\mathbf{h}_{t-1}$进行重置($\mathbf{r}_t \cdot \mathbf{h}_{t-1}$),将重置后的结果与$\mathbf{x}_t$拼接,通过Tanh激活函数将数据变换到[-1,1]范围内: \parinterval 当完成了重置门和更新门计算后,就需要更新当前隐藏状态,如图\ref{fig:6-16}(c)所示。在计算得到了重置门的权重$\mathbf{r}_t$后,使用其对前一时刻的状态$\mathbf{h}_{t-1}$进行重置($\mathbf{r}_t \cdot \mathbf{h}_{t-1}$),将重置后的结果与$\mathbf{x}_t$拼接,通过Tanh激活函数将数据变换到[-1,1]范围内:
\begin{eqnarray} \begin{eqnarray}
\hat{\mathbf{h}}_t = \textrm{Tanh} (\mathbf{W}_h [\mathbf{r}_t \cdot \mathbf{h}_{t-1},\mathbf{x}_{t}]) \hat{\mathbf{h}}_t = \textrm{Tanh} (\mathbf{W}_h [\mathbf{r}_t \cdot \mathbf{h}_{t-1},\mathbf{x}_{t}])
\label{eqC6.20} \label{eqC6.20}
...@@ -676,6 +667,7 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof ...@@ -676,6 +667,7 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof
%---------------------------------------------- %----------------------------------------------
\parinterval GRU的输入输出和RNN类似,其采用与LSTM类似的门控思想,达到捕获长距离依赖信息的目的。此外,GRU比LSTM少了一个门结构,而且参数只有$\mathbf{W}_r$$\mathbf{W}_u$$\mathbf{W}_h$。因此,GRU具有比LSTM高的运算效率,在系统研发中也经常被使用。 \parinterval GRU的输入输出和RNN类似,其采用与LSTM类似的门控思想,达到捕获长距离依赖信息的目的。此外,GRU比LSTM少了一个门结构,而且参数只有$\mathbf{W}_r$$\mathbf{W}_u$$\mathbf{W}_h$。因此,GRU具有比LSTM高的运算效率,在系统研发中也经常被使用。
%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%
\subsubsection{双向模型}\index{Chapter6.3.3.4} \subsubsection{双向模型}\index{Chapter6.3.3.4}
\parinterval 前面提到的循环神经网络都是自左向右运行的,也就是说在处理一个单词的时候只能访问它前面的序列信息。但是,只根据句子的前文来生成一个序列的表示是不全面的,因为从最后一个词来看,第一个词的信息可能已经很微弱了。为了同时考虑前文和后文的信息,一种解决办法是使用双向循环网络,其结构如图\ref{fig:6-17}所示。这里,编码器可以看作有两个循环神经网络,第一个网络,即红色虚线框里的网络,从句子的右边进行处理,第二个网络从句子左边开始处理,最终将正向和反向得到的结果都融合后传递给码器。 \parinterval 前面提到的循环神经网络都是自左向右运行的,也就是说在处理一个单词的时候只能访问它前面的序列信息。但是,只根据句子的前文来生成一个序列的表示是不全面的,因为从最后一个词来看,第一个词的信息可能已经很微弱了。为了同时考虑前文和后文的信息,一种解决办法是使用双向循环网络,其结构如图\ref{fig:6-17}所示。这里,编码器可以看作有两个循环神经网络,第一个网络,即红色虚线框里的网络,从句子的右边进行处理,第二个网络从句子左边开始处理,最终将正向和反向得到的结果都融合后传递给码器。
...@@ -690,11 +682,11 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof ...@@ -690,11 +682,11 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof
\end{figure} \end{figure}
%---------------------------------------------- %----------------------------------------------
\parinterval 双向模型是自然语言处理领域的常用模型,包括前面提到的词对齐对称化、语言模型等中都大量的使用了类似的思路。实际上,这里也体现了建模时的非对称思想。也就是,建模时如果设计一个对称模型可能会导致问题复杂度增加,因此往往先对问题进行化简,从某一个角度解决问题。之后再使用多个模型从不同角度得到相对合理的最终方案。 \parinterval 双向模型是自然语言处理领域的常用模型,包括前面提到的词对齐对称化、语言模型等中都大量的使用了类似的思路。实际上,这里也体现了建模时的非对称思想。也就是,建模时如果设计一个对称模型可能会导致问题复杂度增加,因此往往先对问题进行化简,从某一个角度解决问题。之后再融合多个模型,从不同角度得到相对合理的最终方案。
%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%
\subsubsection{多层循环神经网络}\index{Chapter6.3.3.5} \subsubsection{多层循环神经网络}\index{Chapter6.3.3.5}
\parinterval 实际上,对于单词序列所使用的循环神经网络是一种很``深''的网络,因为从第一个单词到最后一个单词需要经过至少句子长度相当层数的神经元。比如,一个包含几十个词的句子也会对应几十个神经元层。但是,在很多深度学习应用中,更习惯把对输入序列的同一种处理作为``一层''。比如,对于输入序列,构建一个RNN,那么这些循环单元就构成了网络的``一层''。当然,这里并不是要混淆概念。这里只是要明确,在随后的讨论中,``层''并不是指一组神经元的全连接,它更多的代表的是对网络的拓扑结构的说法。 \parinterval 实际上,对于单词序列所使用的循环神经网络是一种很``深''的网络,因为从第一个单词到最后一个单词需要经过至少句子长度相当层数的神经元。比如,一个包含几十个词的句子也会对应几十个神经元层。但是,在很多深度学习应用中,更习惯把对输入序列的同一种处理作为``一层''。比如,对于输入序列,构建一个RNN,那么这些循环单元就构成了网络的``一层''。当然,这里并不是要混淆概念。只是要明确,在随后的讨论中,``层''并不是指一组神经元的全连接,它更多的代表的是对网络的拓扑结构的说法。
\parinterval 单层循环神经网络对输入序列进行了抽象,为了等到更深入的抽象能力,可以把多个循环神经网络叠在一起,构成多层循环神经网络。比如,图\ref{fig:6-18}就展示基于两层循环神经网络的解码器和编码器结构。通常来说,层数越多模型的表示能力越强,因此在很多基于循环神经网络的机器翻译系统中一般会使用4$\sim$8层的网络。但是,过多的层也会增加模型训练的难度,甚至导致模型无法进行训练。第七章还会对这个问题进行深入讨论。 \parinterval 单层循环神经网络对输入序列进行了抽象,为了等到更深入的抽象能力,可以把多个循环神经网络叠在一起,构成多层循环神经网络。比如,图\ref{fig:6-18}就展示基于两层循环神经网络的解码器和编码器结构。通常来说,层数越多模型的表示能力越强,因此在很多基于循环神经网络的机器翻译系统中一般会使用4$\sim$8层的网络。但是,过多的层也会增加模型训练的难度,甚至导致模型无法进行训练。第七章还会对这个问题进行深入讨论。
...@@ -714,20 +706,15 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof ...@@ -714,20 +706,15 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof
\label{sec:6.3.4} \label{sec:6.3.4}
\parinterval 第二章提到过``上帝是不公平的'',这个观点主要是表达了:世界上事物之间的联系不是均匀的,有些事物之间的联系会很强,而其他的联系可能很弱。自然语言也完美的契合了这个观点。比如,再重新看一下前面提到的根据上下文补缺失单词的例子, \parinterval 第二章提到过``上帝是不公平的'',这个观点主要是表达了:世界上事物之间的联系不是均匀的,有些事物之间的联系会很强,而其他的联系可能很弱。自然语言也完美的契合了这个观点。比如,再重新看一下前面提到的根据上下文补缺失单词的例子,
\begin{example} \vspace{0.8em}
\quad \centerline{中午\ \ 吃饭\ \ \ \ \ \ \ 下午\ 篮球\ \ \ 现在\ \ 饿\ \ \ \underline{\quad \quad \quad}}
\vspace{0.8em}
中午没吃饭,又刚打了一下午篮球,我现在很饿,我想\underline{\quad \quad \quad}
\end{example}
%\vspace{0.5em}
%\centerline{中午没吃饭,又刚打了一下午篮球,我现在很饿,我想\underline{\quad \quad \quad} 。 }
%\vspace{0.5em}
\noindent 之所以能想到在横线处填``吃饭''、``吃东西''很有可能是因为看到了``没吃饭''、 ``很饿''等关键信息。也就是这些关键的片段对我们预测缺失的单词起着关键性作用。而预测``吃饭''与前文中的``中午''、``又''之间的联系似乎不那么紧密。也就是说,在形成 ``吃饭''的逻辑时,在潜意识里会更注意``没吃饭''、``很饿''等关键信息。也就是我们的关注度并不是均匀的分布在整个句子上的。 \noindent 之所以能想到在横线处填``吃饭''、``吃东西''很有可能是因为看到了``没\ 吃饭''、 ``很\ 饿''等关键信息。也就是这些关键的片段对预测缺失的单词起着关键性作用。而预测``吃饭''与前文中的`` 中午''、``又''之间的联系似乎不那么紧密。也就是说,在形成 ``吃饭''的逻辑时,在潜意识里会更注意``没吃饭''、``很饿''等关键信息。也就是我们的关注度并不是均匀的分布在整个句子上的。
\parinterval 这个现象可以用注意力机制进行解释。注意力机制的概念来源于生物学的一些现象:当待接收的信息过多时,人类会选择性地关注部分信息而忽略其他信息。它在人类的视觉、听觉、嗅觉等方面均有体现,当我们在感受事物时,大脑会自动过滤或衰减部分信息,仅关注其中少数几个部分。例如,当我们在看到图\ref{fig:6-19}时,往往不是``均匀地''看图像中的所有区域,我们可能最先注意到的是大狗头上带的的帽子,然后才会关注图片中其他部分。 \parinterval 这个现象可以用注意力机制进行解释。注意力机制的概念来源于生物学的一些现象:当待接收的信息过多时,人类会选择性地关注部分信息而忽略其他信息。它在人类的视觉、听觉、嗅觉等方面均有体现,当我们在感受事物时,大脑会自动过滤或衰减部分信息,仅关注其中少数几个部分。例如,当我们看到图\ref{fig:6-19}时,往往不是``均匀地''看图像中的所有区域,可能最先注意到的是大狗头上带的的帽子,然后才会关注图片中其他部分。
\parinterval 那么注意力机制和神经机器翻译又有什么关系呢?它可以帮助我们解决哪些神经机器翻译的缺陷呢?下面就一起来看一看。 \parinterval 那么注意力机制和神经机器翻译又有什么关系呢?它如何解决神经机器翻译的问题呢?下面就一起来看一看。
%---------------------------------------------- %----------------------------------------------
% 图3.10 % 图3.10
...@@ -762,14 +749,14 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof ...@@ -762,14 +749,14 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof
\parinterval 显然,以上的问题的根本原因在于所使用的表示模型还比较``弱''。因此需要一个更强大的表示模型,在生成目标语单词时能够有选择的获取源语言句子中的更有用的部分。更准确的说,对于要生成的目标语单词,相关性更高的源语言片段应该在源语言句子的表示中体现出来,而不是将所有的源语言单词一视同仁。在神经机器翻译中引入注意力机制正是为了达到这个目的\cite{bahdanau2014neural}\cite{DBLP:journals/corr/LuongPM15}。实际上,除了机器翻译,注意力机制也被成功的应用于图像处理、语音识别、自然语言处理的其他任务。而正是注意力机制的引入,使得包括机器翻译在内很多自然语言处理系统得到了新的飞跃。 \parinterval 显然,以上的问题的根本原因在于所使用的表示模型还比较``弱''。因此需要一个更强大的表示模型,在生成目标语单词时能够有选择的获取源语言句子中的更有用的部分。更准确的说,对于要生成的目标语单词,相关性更高的源语言片段应该在源语言句子的表示中体现出来,而不是将所有的源语言单词一视同仁。在神经机器翻译中引入注意力机制正是为了达到这个目的\cite{bahdanau2014neural}\cite{DBLP:journals/corr/LuongPM15}。实际上,除了机器翻译,注意力机制也被成功的应用于图像处理、语音识别、自然语言处理的其他任务。而正是注意力机制的引入,使得包括机器翻译在内很多自然语言处理系统得到了新的飞跃。
\parinterval 神经机器翻译中的注意力机制并不复杂。对于每个目标语单词$y_j$,系统生成一个源语言表示向量$\mathbf{C}_j$与之对应,$\mathbf{C}_j$会包含生成$y_j$所需的源语言的信息,或者说$\mathbf{C}_j$是一种包含目标语言单词与源语言单词对应关系的源语言表示。相比用一个静态的表示$\mathbf{C}$,注意机制使用的是动态的表示$\mathbf{C}_j$$\mathbf{C}_j$也被称作对于目标语位置$i$的上下文向量。图\ref{fig:6-21}对比了未引入注意力机制和引入了注意力机制的编码器-解码器结构。可以看出,在注意力模型中,对于每一个目标单词的生成,都会额外引入一个单独的上下文向量参与运算。 \parinterval 神经机器翻译中的注意力机制并不复杂。对于每个目标语单词$y_j$,系统生成一个源语言表示向量$\mathbf{C}_j$与之对应,$\mathbf{C}_j$会包含生成$y_j$所需的源语言的信息,或者说$\mathbf{C}_j$是一种包含目标语言单词与源语言单词对应关系的源语言表示。相比用一个静态的表示$\mathbf{C}$,注意机制使用的是动态的表示$\mathbf{C}_j$$\mathbf{C}_j$也被称作对于目标语位置$j$的上下文向量。图\ref{fig:6-21}对比了未引入注意力机制和引入了注意力机制的编码器-解码器结构。可以看出,在注意力模型中,对于每一个目标单词的生成,都会额外引入一个单独的上下文向量参与运算。
%---------------------------------------------- %----------------------------------------------
% 图3.10 % 图3.10
\begin{figure}[htp] \begin{figure}[htp]
\centering \centering
\input{./Chapter6/Figures/figure-encoder-decoder-with-Attention} \input{./Chapter6/Figures/figure-encoder-decoder-with-Attention}
\caption{不使用(a)和使用(b)注意力机制的翻译模型对比} \caption{不使用(a)和使用(b)注意力机制的翻译模型对比}
\label{fig:6-21} \label{fig:6-21}
\end{figure} \end{figure}
%---------------------------------------------- %----------------------------------------------
...@@ -784,7 +771,7 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof ...@@ -784,7 +771,7 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof
\label{eqC6.22} \label{eqC6.22}
\end{eqnarray} \end{eqnarray}
\noindent 其中,$\alpha_{i,j}${\small\sffamily\bfseries{注意力权重}},它表示目标语第$j$个位置与源语第$i$个位置之间的相关性大小。这里,将每个时间步编码器的输出$\mathbf{h}_i$看作源语言位置$i$的表示结果。进行翻译时,解码端可以根据当前的位置$j$,通过控制不同$\mathbf{h}_i$的权重得到$\mathbf{C}_j$,使得对目标语位置$j$贡献大的$\mathbf{h}_i$$\mathbf{C}_j$的影响增大。也就是说,$\mathbf{C}_j$实际上就是\{${\mathbf{h}_1, \mathbf{h}_2,...,\mathbf{h}_m}$\}的一种组合,只不过不同的$\mathbf{h}_i$会根据对目标端的贡献给予不同的权重。图\ref{fig:6-22}展示了上下文向量$\mathbf{C}_j$的计算过程。 \noindent 其中,$\alpha_{i,j}${\small\sffamily\bfseries{注意力权重}}(Attention Weight),它表示目标语第$j$个位置与源语第$i$个位置之间的相关性大小。这里,将每个时间步编码器的输出$\mathbf{h}_i$ 看作源语言位置$i$的表示结果。进行翻译时,解码端可以根据当前的位置$j$,通过控制不同$\mathbf{h}_i$的权重得到$\mathbf{C}_j$,使得对目标语位置$j$贡献大的$\mathbf{h}_i$$\mathbf{C}_j$的影响增大。也就是说,$\mathbf{C}_j$实际上就是\{${\mathbf{h}_1, \mathbf{h}_2,...,\mathbf{h}_m}$\}的一种组合,只不过不同的$\mathbf{h}_i$会根据对目标端的贡献给予不同的权重。图\ref{fig:6-22}展示了上下文向量$\mathbf{C}_j$的计算过程。
%---------------------------------------------- %----------------------------------------------
% 图3.10 % 图3.10
...@@ -799,15 +786,17 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof ...@@ -799,15 +786,17 @@ $\textrm{P}({y_j | \mathbf{s}_{j-1} ,y_{j-1},\mathbf{C}})$由Softmax实现,Sof
\parinterval 如图5所示,注意力权重$\alpha_{i,j}$的计算分为两步: \parinterval 如图5所示,注意力权重$\alpha_{i,j}$的计算分为两步:
\begin{itemize} \begin{itemize}
\item 使用目标语言上一时刻循环单元的输出$\mathbf{s}_{j-1}$与源语言第$i$个位置的表示$\mathbf{h}_i$之间的相关性,来表示目标语言位置$j$对源语言位置$i$的关注程度,记为$\beta_{i,j}$,由函数$\textrm{a}(\cdot)$实现 \item 使用目标语言上一时刻循环单元的输出$\mathbf{s}_{j-1}$与源语言第$i$个位置的表示$\mathbf{h}_i$之间的相关性,其用来表示目标语言位置$j$对源语言位置$i$的关注程度,记为$\beta_{i,j}$,由函数$\textrm{a}(\cdot)$实现
\begin{eqnarray} \begin{eqnarray}
\beta_{i,j} = \textrm{a}(\mathbf{s}_{j-1},\mathbf{h}_i) \beta_{i,j} = a(\mathbf{s}_{j-1},\mathbf{h}_i)
\label{eqC6.23} \label{eqC6.23}
\end{eqnarray} \end{eqnarray}
$\textrm{a}(\cdot)$可以被看作是目标语表示和源语言表示的一种``统一化'',即把源语和目标语表示映射在同一个语义空间,进而语义相近的内容有更大的相似性。该函数有多种计算方式,比如,向量乘、向量夹角、单词神经网络等,如下: $a(\cdot)$可以被看作是目标语言表示和源语言表示的一种``统一化'',即把源语言和目标语言表示映射在同一个语义空间,进而语义相近的内容有更大的相似性。该函数有多种计算方式,比如,向量乘、向量夹角、单词神经网络等,如下:
\begin{eqnarray} \begin{eqnarray}
\textrm{a} (\mathbf{s},\mathbf{h}) = \left\{ \begin{array}{ll} a (\mathbf{s},\mathbf{h}) = \left\{ \begin{array}{ll}
\mathbf{s} \mathbf{h}^{\textrm{T}} & \textrm{向量乘} \\ \mathbf{s} \mathbf{h}^{\textrm{T}} & \textrm{向量乘} \\
\textrm{cos}(\mathbf{s}, \mathbf{h}) & \textrm{向量夹角} \\ \textrm{cos}(\mathbf{s}, \mathbf{h}) & \textrm{向量夹角} \\
\mathbf{s} \mathbf{W} \mathbf{h}^{\textrm{T}} & \textrm{线性模型} \\ \mathbf{s} \mathbf{W} \mathbf{h}^{\textrm{T}} & \textrm{线性模型} \\
...@@ -825,7 +814,7 @@ $\textrm{a}(\cdot)$可以被看作是目标语表示和源语言表示的一种` ...@@ -825,7 +814,7 @@ $\textrm{a}(\cdot)$可以被看作是目标语表示和源语言表示的一种`
\label{eqC6.25} \label{eqC6.25}
\end{eqnarray} \end{eqnarray}
最终,\{$\alpha_{i,j}$\}可以被看作是一个矩阵,它的长为目标语言句子长度,宽为源语言句子长度,矩阵中的每一项对应一个$\alpha_{i,j}$。图\ref{fig:6-23}给出了\{$\alpha_{i,j}$\}的一个矩阵表示。图中蓝色方框的大小表示不同的注意力权重$\alpha_{i,j}$的大小,方框越大,位置$i$位置$j$的相关性越高。能够看到,对于互译的中英文句子,\{$\alpha_{i,j}$\}可以较好的反应两种语言之间不同位置之间的对应关系。 最终,\{$\alpha_{i,j}$\}可以被看作是一个矩阵,它的长为目标语言句子长度,宽为源语言句子长度,矩阵中的每一项对应一个$\alpha_{i,j}$。图\ref{fig:6-23}给出了\{$\alpha_{i,j}$\}的一个矩阵表示。图中蓝色方框的大小表示不同的注意力权重$\alpha_{i,j}$的大小,方框越大,源语言位置$i$和目标语言位置$j$的相关性越高。能够看到,对于互译的中英文句子,\{$\alpha_{i,j}$\}可以较好的反应两种语言之间不同位置之间的对应关系。
%---------------------------------------------- %----------------------------------------------
...@@ -878,9 +867,9 @@ $\textrm{a}(\cdot)$可以被看作是目标语表示和源语言表示的一种` ...@@ -878,9 +867,9 @@ $\textrm{a}(\cdot)$可以被看作是目标语表示和源语言表示的一种`
\label{sec:6.3.4.3} \label{sec:6.3.4.3}
\parinterval 从前面的描述可以看出,注意力机制在机器翻译中就是要回答一个问题:给定一个目标语位置$j$和一系列源语言的不同位置上的表示\{${\mathbf{h}_i}$\},如何得到一个新的表示$\hat{\mathbf{h}}$,使得它与目标语位置$j$对应的最好? \parinterval 从前面的描述可以看出,注意力机制在机器翻译中就是要回答一个问题:给定一个目标语位置$j$和一系列源语言的不同位置上的表示\{${\mathbf{h}_i}$\},如何得到一个新的表示$\hat{\mathbf{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 那么,如何理解这个过程?注意力机制的本质又是什么呢?换一个角度来看,实际上,目标语位置$j$本质上是一个查询,我们希望从源语言端找到与之最匹配的源语言位置,并返回相应的表示结果。为了描述这个问题,可以建立一个查询系统。假设有一个库,里面包含若干个$\mathrm{key}$-$\mathrm{value}$单元,其中$\mathrm{key}$代表这个单元的索引关键字,$\mathrm{value}$代表这个单元的值。比如,对于学生信息系统,$\mathrm{key}$可以是学号,$\mathrm{value}$可以是学生的身高。当输入一个查询$\mathrm{query}$,我们希望这个系统返回与之最匹配的结果。也就是,希望找到匹配的$\mathrm{key}$,并输出其对应的$\mathrm{value}$。比如,当查询某个学生的身高信息时,可以输入学生的学号,之后在库中查询与这个学号相匹配的记录,并把这个记录中的$\mathrm{value}$(即身高)作为结果返回。
\parinterval\ref{fig:6-25}(a)展示了一个这样的查询系统。里面包含四个$\mathrm{key}$-$\mathrm{value}$单元,当输入查询$\mathrm{query}$,就把$\mathrm{query}$与这四个$\mathrm{key}$逐个进行匹配,如果完全匹配就返回相应的$\mathrm{value}$。在图中的例子中,$\mathrm{query}$$\mathrm{key}_3$是完全匹配的(因为都是横纹),因此系统返回第三个单元的值,即$\mathrm{value}_3$。当然,如果库中没有与$\mathrm{query}$匹配的$\mathrm{key}$,则返回一个空结果。 \parinterval\ref{fig:6-25}(a)展示了一个这样的查询系统。里面包含四个$\mathrm{key}$-$\mathrm{value}$单元,当输入查询$\mathrm{query}$,就把$\mathrm{query}$与这四个$\mathrm{key}$逐个进行匹配,如果完全匹配就返回相应的$\mathrm{value}$。在图中的例子中,$\mathrm{query}$$\mathrm{key}_3$是完全匹配的(因为都是横纹),因此系统返回第三个单元的值,即$\mathrm{value}_3$。当然,如果库中没有与$\mathrm{query}$匹配的$\mathrm{key}$,则返回一个空结果。
%---------------------------------------------- %----------------------------------------------
% 图3.10 % 图3.10
...@@ -918,7 +907,13 @@ $\textrm{a}(\cdot)$可以被看作是目标语表示和源语言表示的一种` ...@@ -918,7 +907,13 @@ $\textrm{a}(\cdot)$可以被看作是目标语表示和源语言表示的一种`
\label{eqC6.28} \label{eqC6.28}
\end{eqnarray} \end{eqnarray}
\noindent 显然, $\overline{\mathrm{value}}$就是$\mathrm{value}_i$在分布P($\mathrm{value}_i$)下的期望,即$\mathbb{E}_{\sim \\ \textrm{P}( {\mathrm{\mathrm{value}}}_i )} ({\mathrm{value}}_i) = \sum_i \textrm{P}({\mathrm{value}}_i) \cdot {\mathrm{value}}_i $。从这个观点看,注意力机制实际上是得到了一个变量($\mathrm{value}$)的期望。当然,严格意义上说,$\alpha_i$并不是从概率角度定义的,这里也并不是要追求严格的统计学意义。不过这确实说明了,往往看似简单的模型背后的数学原理可能会很深刻。 \noindent 显然, $\overline{\mathrm{value}}$就是$\mathrm{value}_i$在分布P($\mathrm{value}_i$)下的期望,即
\begin{equation}
\mathbb{E}_{\sim \\ \textrm{P}( {\mathrm{\mathrm{value}}}_i )} ({\mathrm{value}}_i) = \sum_i \textrm{P}({\mathrm{value}}_i) \cdot {\mathrm{value}}_i
\end{equation}
从这个观点看,注意力机制实际上是得到了一个变量($\mathrm{value}$)的期望。当然,严格意义上说,$\alpha_i$并不是从概率角度定义的,这里也并不是要追求严格的统计学意义。不过这确实说明了,往往看似简单的模型背后的数学原理可能会很深刻。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
...@@ -929,15 +924,15 @@ $\textrm{a}(\cdot)$可以被看作是目标语表示和源语言表示的一种` ...@@ -929,15 +924,15 @@ $\textrm{a}(\cdot)$可以被看作是目标语表示和源语言表示的一种`
\label{eqC6.29} \label{eqC6.29}
\end{eqnarray} \end{eqnarray}
\noindent 其中,$\mathbf{w}_{step}$表示更新前的模型参数,$\mathbf{w}_{step+1}$表示更新后的模型参数,$L(\mathbf{w}_{step})$表示模型相对于$\mathbf{w}_{step}$的损失,$\frac{\partial L(\mathbf{w}_{step})} {\partial \mathbf{w}_{step} }$表示损失函数的梯度,$\alpha$是更新的步进值。也就是说,给定一定量的训练数据,不断运行公式\ref{eqC6.29}的过程。而训练数据也可以反复使用,直至模型参数达到收敛或者损失函数不再变化。通常,把公式的一次执行称为``一步''更新/训练,把访问完所有样本的训练上完称为``一轮''训练。 \noindent 其中,$\mathbf{w}_{step}$表示更新前的模型参数,$\mathbf{w}_{step+1}$表示更新后的模型参数,$L(\mathbf{w}_{step})$表示模型相对于$\mathbf{w}_{step}$的损失,$\frac{\partial L(\mathbf{w}_{step})} {\partial \mathbf{w}_{step} }$表示损失函数的梯度,$\alpha$是更新的步进值。也就是说,给定一定量的训练数据,不断运行公式\ref{eqC6.29}的过程。而训练数据也可以反复使用,直至模型参数达到收敛或者损失函数不再变化。通常,把公式的一次执行称为``一步''更新/训练,把访问完所有样本的训练称为``一轮''训练。
\parinterval 将公式\ref{eqC6.29}应用于神经机器翻译有几个基本问题需要考虑:1)损失函数的选择;2)参数初始化的策略,也就是如何设置$\mathbf{w}_0$;3)优化策略和学习率调整策略;4)训练加速。下面对这些问题进行讨论。 \parinterval 将公式\ref{eqC6.29}应用于神经机器翻译有几个基本问题需要考虑:1)损失函数的选择;2)参数初始化的策略,也就是如何设置$\mathbf{w}_0$;3)优化策略和学习率调整策略;4)训练加速。下面对这些问题进行讨论。
%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%
\subsubsection{损失函数}\index{Chapter6.3.5.1} \subsubsection{损失函数}\index{Chapter6.3.5.1}
\parinterval 因为神经机器翻译在每个目标语位置都会输出一个概率分布,表示这个位置上不同单词出现的可能性,因此需要知道当前位置输出的分布相比于标准答案的``损失''。对于这个问题,常用的是交叉熵损失函数。令$\mathbf{y}$表示机器翻译模型输出的分布,$\hat{\mathbf{y}}$ 表示标准答案,则交叉熵损失可以被定义为$L_{\textrm{ce}}(\mathbf{y},\hat{\mathbf{y}}) = - \sum_{k=1}^{|V|} \mathbf{y}[k] \textrm{log} (\hat{\mathbf{y}}[k])$,其中$\mathbf{y}[k]$$\hat{\mathbf{y}}[k]$分别表示向量$\mathbf{y}$$\hat{\mathbf{y}}$的第$k$维,$|V|$表示输出向量得维度(等于词表大小)。对于一个模型输出的概率分布$\mathbf{Y} = \{ \mathbf{y}_1,\mathbf{y}_2,..., \mathbf{y}_n \}$和标准答案分布$\hat{\mathbf{Y}}=\{ \hat{\mathbf{y}}_1, \hat{\mathbf{y}}_2,...,\hat{\mathbf{y}}_n \}$,损失函数可以被定义为 \parinterval 因为神经机器翻译在每个目标语位置都会输出一个概率分布,表示这个位置上不同单词出现的可能性,因此需要知道当前位置输出的分布相比于标准答案的``损失''。对于这个问题,常用的是交叉熵损失函数。令$\mathbf{y}$表示机器翻译模型输出的分布,$\hat{\mathbf{y}}$ 表示标准答案,则交叉熵损失可以被定义为$L_{\textrm{ce}}(\mathbf{y},\hat{\mathbf{y}}) = - \sum_{k=1}^{|V|} \mathbf{y}[k] \textrm{log} (\hat{\mathbf{y}}[k])$,其中$\mathbf{y}[k]$$\hat{\mathbf{y}}[k]$分别表示向量$\mathbf{y}$$\hat{\mathbf{y}}$的第$k$维,$|V|$表示输出向量得维度(等于词表大小)。对于一个模型输出的概率分布$\mathbf{Y} = \{ \mathbf{y}_1,\mathbf{y}_2,..., \mathbf{y}_n \}$和标准答案分布$\widehat{\mathbf{Y}}=\{ \hat{\mathbf{y}}_1, \hat{\mathbf{y}}_2,...,\hat{\mathbf{y}}_n \}$,损失函数可以被定义为
%------------- %-------------
\begin{eqnarray} \begin{eqnarray}
L(\mathbf{Y},\hat{\mathbf{Y}}) = \sum_{j=1}^n L_{\textrm{ce}}(\mathbf{y}_j,\hat{\mathbf{y}}_j) L(\mathbf{Y},\widehat{\mathbf{Y}}) = \sum_{j=1}^n L_{\textrm{ce}}(\mathbf{y}_j,\hat{\mathbf{y}}_j)
\label{eqC6.30} \label{eqC6.30}
\end{eqnarray} \end{eqnarray}
...@@ -948,14 +943,14 @@ L(\mathbf{Y},\hat{\mathbf{Y}}) = \sum_{j=1}^n L_{\textrm{ce}}(\mathbf{y}_j,\hat{ ...@@ -948,14 +943,14 @@ L(\mathbf{Y},\hat{\mathbf{Y}}) = \sum_{j=1}^n L_{\textrm{ce}}(\mathbf{y}_j,\hat{
\subsubsection{长参数初始化}\index{Chapter6.3.5.2} \subsubsection{长参数初始化}\index{Chapter6.3.5.2}
\parinterval 神经网络的参数主要是各层中的线性变换矩阵和偏置。在训练开始时,需要对参数进行初始化。但是,由于神经机器翻译的网络结构复杂,因此损失函数往往不是凸函数,不同初始化会导致不同的优化结果。而且在大量实践中已经发现,神经机器翻译模型对初始化方式非常敏感,性能优异的系统往往需要特定的初始化方式。 \parinterval 神经网络的参数主要是各层中的线性变换矩阵和偏置。在训练开始时,需要对参数进行初始化。但是,由于神经机器翻译的网络结构复杂,因此损失函数往往不是凸函数,不同初始化会导致不同的优化结果。而且在大量实践中已经发现,神经机器翻译模型对初始化方式非常敏感,性能优异的系统往往需要特定的初始化方式。
\parinterval 下面以LSTM循环神经网络为例\ref{sec:6.3.3.2},介绍机器翻译模型的初始化方法。这些方法也可以推广到GRU等结构。具体内容如下: \parinterval 下面以LSTM循环神经网络为例(见\ref{sec:6.3.3.2}节),介绍机器翻译模型的初始化方法。这些方法也可以推广到GRU等结构。具体内容如下:
\begin{itemize} \begin{itemize}
\item LSTM遗忘门偏置初始化为1,也就是始终选择遗忘记忆$\mathbf{c}$,这样可以有效防止初始化时$\mathbf{c}$里包含的错误信号传播到后面的所有时刻。 \item LSTM遗忘门偏置初始化为1,也就是始终选择遗忘记忆$\mathbf{c}$,这样可以有效防止初始化时$\mathbf{c}$里包含的错误信号传播到后面的所有时刻。
\item 网络中的其他偏置一般都初始化为0,可以有效防止加入过大或过小的偏置后使得激活函数的输出跑到``饱和区'',也就是梯度接近0的区域,防止训练一开始就无法跳出局部极小的区域。 \item 网络中的其他偏置一般都初始化为0,可以有效防止加入过大或过小的偏置后使得激活函数的输出跑到``饱和区'',也就是梯度接近0的区域,防止训练一开始就无法跳出局部极小的区域。
\item 网络的权重矩阵$\mathbf{w}$一般使用Xavier参数初始化方法\cite{pmlr-v9-glorot10a},可以有效稳定训练过程,特别是对于比较``深''的网络。如果用$d_{in}$$d_{out}$分别表示$\mathbf{w}$的输入和输出的维度大小,则该方法的具体实现如下: \item 网络的权重矩阵$\mathbf{w}$一般使用Xavier参数初始化方法\cite{pmlr-v9-glorot10a},可以有效稳定训练过程,特别是对于比较``深''的网络。$d_{in}$$d_{out}$分别表示$\mathbf{w}$的输入和输出的维度大小,则该方法的具体实现如下:
\begin{eqnarray} \begin{eqnarray}
\mathbf{w} \sim U(-\sqrt{ \frac{6} { d_{in} + d_{out} } } , \sqrt{ \frac{6} { d_{in} + d_{out} } }) \mathbf{w} \sim U(-\sqrt{ \frac{6} { d_{in} + d_{out} } } , \sqrt{ \frac{6} { d_{in} + d_{out} } })
\label{eqC6.31} \label{eqC6.31}
...@@ -965,7 +960,7 @@ L(\mathbf{Y},\hat{\mathbf{Y}}) = \sum_{j=1}^n L_{\textrm{ce}}(\mathbf{y}_j,\hat{ ...@@ -965,7 +960,7 @@ L(\mathbf{Y},\hat{\mathbf{Y}}) = \sum_{j=1}^n L_{\textrm{ce}}(\mathbf{y}_j,\hat{
\end{itemize} \end{itemize}
%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%
\subsubsection{优化策略}\index{Chapter6.3.5.3} \subsubsection{优化策略}\index{Chapter6.3.5.3}
\parinterval 公式\ref{eqC6.29}展示了最基本的优化策略,也被称为标准的SGD优化器。实际上,训练神经机器翻译模型时,还有非常多的优化器可以选择,在第五章也有详细介绍,这里考虑Adam优化器。 Adam 通过对梯度的一阶矩估计(First Moment Estimation)和二阶矩估计(Second Moment Estimation)进行综合考虑,计算出更新步长。 \parinterval 公式\ref{eqC6.29}展示了最基本的优化策略,也被称为标准的SGD优化器。实际上,训练神经机器翻译模型时,还有非常多的优化器可以选择,在第五章也有详细介绍,这里考虑Adam优化器。 Adam 通过对梯度的{\small\bfnew{一阶矩估计}}(First Moment Estimation)和{\small\bfnew{二阶矩估计}}(Second Moment Estimation)进行综合考虑,计算出更新步长。
\parinterval\ref{tab:Adam vs SGD}从效果上对比了Adam和SGD的区别。通常,Adam收敛的比较快,不同任务基本上可以使用一套配置进行优化,虽性能不算差,但很难达到最优效果。相反,SGD虽能通过在不同的数据集上进行调整,来达到最优的结果,但是收敛速度慢。因此需要根据不同的需求来选择合适的优化器。若需要快速得到模型的初步结果,选择Adam较为合适,若是需要在一个任务上得到最优的结果,选择SGD更为合适。 \parinterval\ref{tab:Adam vs SGD}从效果上对比了Adam和SGD的区别。通常,Adam收敛的比较快,不同任务基本上可以使用一套配置进行优化,虽性能不算差,但很难达到最优效果。相反,SGD虽能通过在不同的数据集上进行调整,来达到最优的结果,但是收敛速度慢。因此需要根据不同的需求来选择合适的优化器。若需要快速得到模型的初步结果,选择Adam较为合适,若是需要在一个任务上得到最优的结果,选择SGD更为合适。
...@@ -978,7 +973,7 @@ L(\mathbf{Y},\hat{\mathbf{Y}}) = \sum_{j=1}^n L_{\textrm{ce}}(\mathbf{y}_j,\hat{ ...@@ -978,7 +973,7 @@ L(\mathbf{Y},\hat{\mathbf{Y}}) = \sum_{j=1}^n L_{\textrm{ce}}(\mathbf{y}_j,\hat{
\begin{tabular}{l | l l } \begin{tabular}{l | l l }
&使用 &性能 \\ \hline &使用 &性能 \\ \hline
\rule{0pt}{13pt} Adam &一套配置包打天下 &不算差,但没到极限 \\ \rule{0pt}{13pt} Adam &一套配置包打天下 &不算差,但没到极限 \\
\rule{0pt}{13pt} SGD &换一个任务就得调 &效果杠杠的 \\ \rule{0pt}{13pt} SGD &换一个任务就得调 &效果 \\
\end{tabular} \end{tabular}
\end{table} \end{table}
...@@ -986,16 +981,16 @@ L(\mathbf{Y},\hat{\mathbf{Y}}) = \sum_{j=1}^n L_{\textrm{ce}}(\mathbf{y}_j,\hat{ ...@@ -986,16 +981,16 @@ L(\mathbf{Y},\hat{\mathbf{Y}}) = \sum_{j=1}^n L_{\textrm{ce}}(\mathbf{y}_j,\hat{
%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%
\subsubsection{梯度裁剪}\index{Chapter6.3.5.4} \subsubsection{梯度裁剪}\index{Chapter6.3.5.4}
\parinterval 需要注意的是,训练循环神经网络时,反向传播使得网络层之间的梯度重复相乘,在网络层数过深时,如果连乘因子小于1可能造成梯度指数级的减少,甚至趋近于0,导致网络无法优化,也就是梯度消失问题。当连乘因子大于1时,可能会导致梯度的乘积变得异常大,造成梯度爆炸的问题。在这种情况下需要使用``梯度裁剪''来防止梯度$\pi$超过阈值。具体公式如下: \parinterval 需要注意的是,训练循环神经网络时,反向传播使得网络层之间的梯度重复相乘,在网络层数过深时,如果连乘因子小于1可能造成梯度指数级的减少,甚至趋近于0,导致网络无法优化,也就是梯度消失问题。当连乘因子大于1时,可能会导致梯度的乘积变得异常大,造成梯度爆炸的问题。在这种情况下需要使用``梯度裁剪''来防止梯度超过阈值。梯度裁剪在第五章已经介绍过,这里简单回顾一下。梯度裁剪的具体公式如下:
\begin{eqnarray} \begin{eqnarray}
\mathbf{w}' = \mathbf{w} \cdot \frac{\gamma} {\textrm{max}(\gamma,\| \mathbf{w} \|_2)} \mathbf{w}' = \mathbf{w} \cdot \frac{\gamma} {\textrm{max}(\gamma,\| \mathbf{w} \|_2)}
\label{eqC6.32} \label{eqC6.32}
\end{eqnarray} \end{eqnarray}
\noindent 其中$threshold$是手工设定的梯度大小阈值, $\| \cdot \|_2$是L2范数,$\mathbf{w}'$表示梯度裁剪后的参数。这个公式的含义在于只要梯度大小超过阈值,就按照阈值与当前梯度大小的比例进行放缩。 \noindent 其中$\gamma$是手工设定的梯度大小阈值, $\| \cdot \|_2$是L2范数,$\mathbf{w}'$表示梯度裁剪后的参数。这个公式的含义在于只要梯度大小超过阈值,就按照阈值与当前梯度大小的比例进行放缩。
%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%
\subsubsection{学习率策略}\index{Chapter6.3.5.5} \subsubsection{学习率策略}\index{Chapter6.3.5.5}
\parinterval 在公式\ref{eqC6.29}中, $\alpha$决定了每次参数更新时更新的步幅大小,称之为学习率。学习率作为基于梯度方法中的重要超参数,它决定目标函数能否收敛到较好的局部最优点以及收敛的速度。合理的学习率能够使模型快速、稳定的达到较好的状态。但是,如果学习率太小,收敛过程会很慢;而学习率太大,则模型的状态可能会出现震荡,很难达到稳定,甚至使模型无法收敛。图\ref{fig:6-27}对比了不同学习率对损失函数的影响。 \parinterval 在公式\ref{eqC6.29}中, $\alpha$决定了每次参数更新时更新的步幅大小,称之为{\small\bfnew{学习率}}(Learning Rate)。学习率作为基于梯度方法中的重要超参数,它决定目标函数能否收敛到较好的局部最优点以及收敛的速度。合理的学习率能够使模型快速、稳定的达到较好的状态。但是,如果学习率太小,收敛过程会很慢;而学习率太大,则模型的状态可能会出现震荡,很难达到稳定,甚至使模型无法收敛。图\ref{fig:6-27} 对比了不同学习率对损失函数的影响。
%---------------------------------------------- %----------------------------------------------
% 图3.10 % 图3.10
...@@ -1008,7 +1003,7 @@ L(\mathbf{Y},\hat{\mathbf{Y}}) = \sum_{j=1}^n L_{\textrm{ce}}(\mathbf{y}_j,\hat{ ...@@ -1008,7 +1003,7 @@ L(\mathbf{Y},\hat{\mathbf{Y}}) = \sum_{j=1}^n L_{\textrm{ce}}(\mathbf{y}_j,\hat{
\end{figure} \end{figure}
%---------------------------------------------- %----------------------------------------------
\parinterval 不同优化器需要的学习率不同,比如Adam一般使用0.001或0.0001,而SGD则在0.1~1之间进行挑选。在梯度下降法中,都是给定的统一的学习率,整个优化过程中都以确定的步长进行更新因此无论使用哪个优化器,为了保证训练又快又好,通常都需要根据当前的更新次数来动态调整学习率的大小。 \parinterval 不同优化器需要的学习率不同,比如Adam一般使用0.001或0.0001,而SGD则在0.1$\sim$1之间进行挑选。在梯度下降法中,都是给定的统一的学习率,整个优化过程中都以确定的步长进行更新因此无论使用哪个优化器,为了保证训练又快又好,通常都需要根据当前的更新次数来动态调整学习率的大小。
%---------------------------------------------- %----------------------------------------------
% 图3.10 % 图3.10
...@@ -1020,7 +1015,7 @@ L(\mathbf{Y},\hat{\mathbf{Y}}) = \sum_{j=1}^n L_{\textrm{ce}}(\mathbf{y}_j,\hat{ ...@@ -1020,7 +1015,7 @@ L(\mathbf{Y},\hat{\mathbf{Y}}) = \sum_{j=1}^n L_{\textrm{ce}}(\mathbf{y}_j,\hat{
\end{figure} \end{figure}
%---------------------------------------------- %----------------------------------------------
\parinterval\ref{fig:6-28}展示了一种常用的学习率调整策略。它分为两个阶段:预热阶段和衰减阶段。模型训练初期梯度通常很大,如果直接使用较大的学习率很容易让模型陷入局部最优。学习率的预热阶段便是通过在训练初期使学习率从小到大逐渐增加来减缓在初始阶段模型``跑偏''的现象。一般来说,初始学习率太高会使得模型进入一种损失函数曲面非常不平滑的区域,进而使得模型进入一种混乱状态,后续的优化过程很难取得很好的效果。一个常用的学习率预热方法是{\small\bfnew{逐渐预热}}(Gradual Warmup),如果令预热的更新次数为$T'$,初始学习率为$\alpha_0$,则预热阶段第$t$次更新的学习率为: \parinterval\ref{fig:6-28}展示了一种常用的学习率调整策略。它分为两个阶段:预热阶段和衰减阶段。模型训练初期梯度通常很大,如果直接使用较大的学习率很容易让模型陷入局部最优。学习率的预热阶段便是通过在训练初期使学习率从小到大逐渐增加来减缓在初始阶段模型``跑偏''的现象。一般来说,初始学习率太高会使得模型进入一种损失函数曲面非常不平滑的区域,进而使得模型进入一种混乱状态,后续的优化过程很难取得很好的效果。一个常用的学习率预热方法是{\small\bfnew{逐渐预热}}(Gradual Warmup)。假设预热的更新次数为$T'$,初始学习率为$\alpha_0$,则预热阶段第$t$次更新的学习率为:
%------------------------------- %-------------------------------
\begin{eqnarray} \begin{eqnarray}
\alpha_t = \frac{t}{T'} \alpha_0 \quad,\quad 1 \leq t \leq T' \alpha_t = \frac{t}{T'} \alpha_0 \quad,\quad 1 \leq t \leq T'
...@@ -1028,10 +1023,10 @@ L(\mathbf{Y},\hat{\mathbf{Y}}) = \sum_{j=1}^n L_{\textrm{ce}}(\mathbf{y}_j,\hat{ ...@@ -1028,10 +1023,10 @@ L(\mathbf{Y},\hat{\mathbf{Y}}) = \sum_{j=1}^n L_{\textrm{ce}}(\mathbf{y}_j,\hat{
\end{eqnarray} \end{eqnarray}
%------- %-------
\noindent 另一方面,当模型训练逐渐接近收敛的时候,使用太大学习率会很容易让模型在局部最优解附近震荡,从而错过局部极小,因此需要通过减小学习率来调整更新的步长,以此来不断的逼近局部最优,这一阶段也称为学习率的衰减阶段。学习率衰减的方法有很多,比如指数衰减,余弦衰减等,图\ref{fig:6-28}展示的是分段常数衰减(Piecewise Constant Decay),即每经过$m$次更新,学习率衰减为原来的$\beta_m$$\beta_m<1$)倍,其中$m$$\beta_m$为经验设置的超参。 \noindent 另一方面,当模型训练逐渐接近收敛的时候,使用太大学习率会很容易让模型在局部最优解附近震荡,从而错过局部极小,因此需要通过减小学习率来调整更新的步长,以此来不断的逼近局部最优,这一阶段也称为学习率的衰减阶段。学习率衰减的方法有很多,比如指数衰减,余弦衰减等,图\ref{fig:6-28}展示的是{\small\bfnew{分段常数衰减}}(Piecewise Constant Decay),即每经过$m$次更新,学习率衰减为原来的$\beta_m$$\beta_m<1$)倍,其中$m$$\beta_m$为经验设置的超参。
%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%
\subsubsection{并行训练}\index{Chapter6.3.5.5} \subsubsection{并行训练}\index{Chapter6.3.5.5}
\parinterval 机器翻译是自然语言处理中很``重''的任务。因为数据量巨大而且模型较为复杂,模型训练的时间往往很长。比如,使用一千万句的训练数据,性能优异的系统往往需要几天甚至一周的时间。更大规模的数据会导致训练时间更长。特别是使用多层网络同时增加模型容量时(比如增加隐层宽度)时,神经机器翻译的训练会更加缓慢。对于这个问题,一个思路是从算法上进行改进。比如前面提到的Adam就是一种高效的训练策略。另一种思路是利用多设备进行加速,也称作分布式训练。 \parinterval 机器翻译是自然语言处理中很``重''的任务。因为数据量巨大而且模型较为复杂,模型训练的时间往往很长。比如,使用一千万句的训练数据,性能优异的系统往往需要几天甚至一周的时间。更大规模的数据会导致训练时间更长。特别是使用多层网络同时增加模型容量时(比如增加隐层宽度)时,神经机器翻译的训练会更加缓慢。对于这个问题,一个思路是从模型训练算法上进行改进。比如前面提到的Adam就是一种高效的训练策略。另一种思路是利用多设备进行加速,也称作分布式训练。
%---------------------------------------------- %----------------------------------------------
% 表 % 表
...@@ -1039,9 +1034,9 @@ L(\mathbf{Y},\hat{\mathbf{Y}}) = \sum_{j=1}^n L_{\textrm{ce}}(\mathbf{y}_j,\hat{ ...@@ -1039,9 +1034,9 @@ L(\mathbf{Y},\hat{\mathbf{Y}}) = \sum_{j=1}^n L_{\textrm{ce}}(\mathbf{y}_j,\hat{
\centering \centering
\caption{ 数据并行与模型并行优缺点对比} \caption{ 数据并行与模型并行优缺点对比}
\label{tab:adv and disadv between Data parallel and model parallel } \label{tab:adv and disadv between Data parallel and model parallel }
\begin{tabular}{l | p{12em} p{10em} } \begin{tabular}{l | p{12em} p{12em} }
&优点 &缺点 \\ \hline &优点 &缺点 \\ \hline
\rule{0pt}{13pt} 数据并行 &并行度高,理论上有多大的batch就可以有多少个设备并行计算 &模型不能大于当个设备的极限 \\ \rule{0pt}{13pt} 数据并行 &并行度高,理论上有多大的batch(批次)就可以有多少个设备并行计算 &模型不能大于单个设备的极限 \\
\rule{0pt}{13pt} 模型并行 &可以对很大的模型进行运算 &只能有限并行,比如多少层就多少个设备 \\ \rule{0pt}{13pt} 模型并行 &可以对很大的模型进行运算 &只能有限并行,比如多少层就多少个设备 \\
\end{tabular} \end{tabular}
......
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
\indexentry{Chapter6.3.3|hyperpage}{27} \indexentry{Chapter6.3.3|hyperpage}{27}
\indexentry{Chapter6.3.3.1|hyperpage}{27} \indexentry{Chapter6.3.3.1|hyperpage}{27}
\indexentry{Chapter6.3.3.2|hyperpage}{28} \indexentry{Chapter6.3.3.2|hyperpage}{28}
\indexentry{Chapter6.3.3.3|hyperpage}{30} \indexentry{Chapter6.3.3.3|hyperpage}{29}
\indexentry{Chapter6.3.3.4|hyperpage}{31} \indexentry{Chapter6.3.3.4|hyperpage}{32}
\indexentry{Chapter6.3.3.5|hyperpage}{32} \indexentry{Chapter6.3.3.5|hyperpage}{32}
\indexentry{Chapter6.3.4|hyperpage}{33} \indexentry{Chapter6.3.4|hyperpage}{33}
\indexentry{Chapter6.3.4.1|hyperpage}{34} \indexentry{Chapter6.3.4.1|hyperpage}{34}
...@@ -23,30 +23,30 @@ ...@@ -23,30 +23,30 @@
\indexentry{Chapter6.3.5|hyperpage}{40} \indexentry{Chapter6.3.5|hyperpage}{40}
\indexentry{Chapter6.3.5.1|hyperpage}{40} \indexentry{Chapter6.3.5.1|hyperpage}{40}
\indexentry{Chapter6.3.5.2|hyperpage}{41} \indexentry{Chapter6.3.5.2|hyperpage}{41}
\indexentry{Chapter6.3.5.3|hyperpage}{41} \indexentry{Chapter6.3.5.3|hyperpage}{42}
\indexentry{Chapter6.3.5.4|hyperpage}{42} \indexentry{Chapter6.3.5.4|hyperpage}{42}
\indexentry{Chapter6.3.5.5|hyperpage}{42} \indexentry{Chapter6.3.5.5|hyperpage}{42}
\indexentry{Chapter6.3.5.5|hyperpage}{43} \indexentry{Chapter6.3.5.5|hyperpage}{44}
\indexentry{Chapter6.3.6|hyperpage}{46} \indexentry{Chapter6.3.6|hyperpage}{45}
\indexentry{Chapter6.3.6.1|hyperpage}{47} \indexentry{Chapter6.3.6.1|hyperpage}{47}
\indexentry{Chapter6.3.6.2|hyperpage}{48} \indexentry{Chapter6.3.6.2|hyperpage}{48}
\indexentry{Chapter6.3.6.3|hyperpage}{48} \indexentry{Chapter6.3.6.3|hyperpage}{49}
\indexentry{Chapter6.3.7|hyperpage}{50} \indexentry{Chapter6.3.7|hyperpage}{50}
\indexentry{Chapter6.4|hyperpage}{51} \indexentry{Chapter6.4|hyperpage}{51}
\indexentry{Chapter6.4.1|hyperpage}{52} \indexentry{Chapter6.4.1|hyperpage}{53}
\indexentry{Chapter6.4.2|hyperpage}{54} \indexentry{Chapter6.4.2|hyperpage}{54}
\indexentry{Chapter6.4.3|hyperpage}{56} \indexentry{Chapter6.4.3|hyperpage}{56}
\indexentry{Chapter6.4.4|hyperpage}{58} \indexentry{Chapter6.4.4|hyperpage}{59}
\indexentry{Chapter6.4.5|hyperpage}{60} \indexentry{Chapter6.4.5|hyperpage}{60}
\indexentry{Chapter6.4.6|hyperpage}{61} \indexentry{Chapter6.4.6|hyperpage}{61}
\indexentry{Chapter6.4.7|hyperpage}{62} \indexentry{Chapter6.4.7|hyperpage}{63}
\indexentry{Chapter6.4.8|hyperpage}{64} \indexentry{Chapter6.4.8|hyperpage}{64}
\indexentry{Chapter6.4.9|hyperpage}{65} \indexentry{Chapter6.4.9|hyperpage}{65}
\indexentry{Chapter6.4.10|hyperpage}{67} \indexentry{Chapter6.4.10|hyperpage}{68}
\indexentry{Chapter6.5|hyperpage}{68} \indexentry{Chapter6.5|hyperpage}{69}
\indexentry{Chapter6.5.1|hyperpage}{68} \indexentry{Chapter6.5.1|hyperpage}{69}
\indexentry{Chapter6.5.2|hyperpage}{69} \indexentry{Chapter6.5.2|hyperpage}{69}
\indexentry{Chapter6.5.3|hyperpage}{69} \indexentry{Chapter6.5.3|hyperpage}{69}
\indexentry{Chapter6.5.4|hyperpage}{70} \indexentry{Chapter6.5.4|hyperpage}{71}
\indexentry{Chapter6.5.5|hyperpage}{70} \indexentry{Chapter6.5.5|hyperpage}{71}
\indexentry{Chapter6.6|hyperpage}{71} \indexentry{Chapter6.6|hyperpage}{71}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论