Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
M
mtbookv2
概览
Overview
Details
Activity
Cycle Analytics
版本库
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
问题
0
Issues
0
列表
Board
标记
里程碑
合并请求
0
Merge Requests
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
Snippets
成员
Collapse sidebar
Close sidebar
活动
图像
聊天
创建新问题
作业
提交
Issue Boards
Open sidebar
NiuTrans
mtbookv2
Commits
324f2259
Commit
324f2259
authored
Sep 20, 2020
by
曹润柘
Browse files
Options
Browse Files
Download
Plain Diff
合并分支 'caorunzhe' 到 'master'
Caorunzhe 查看合并请求
!232
parents
db70bd68
a2bcc2f6
显示空白字符变更
内嵌
并排
正在显示
6 个修改的文件
包含
36 行增加
和
563 行删除
+36
-563
Chapter10/Figures/mt-history.png
+0
-0
Chapter10/chapter10.tex
+6
-6
Chapter11/chapter11.tex
+0
-528
Chapter12/Figures/figure-example-of-context-vector-calculation-process.tex
+5
-0
Chapter12/Figures/figure-matrix-representation-of-attention-weights-between-chinese-english-sentence-pairs.tex
+16
-20
Chapter12/Figures/figure-query-model-corresponding-to-attention-mechanism.tex
+9
-9
没有找到文件。
Chapter10/Figures/mt-history.png
查看文件 @
324f2259
113 KB
|
W:
|
H:
170 KB
|
W:
|
H:
2-up
Swipe
Onion skin
Chapter10/chapter10.tex
查看文件 @
324f2259
...
...
@@ -42,8 +42,8 @@
%----------------------------------------------
\begin{figure}
[htp]
\centering
\includegraphics
[scale=0.
36
]
{
./Chapter10/Figures/mt-history.png
}
\caption
{
机器翻译发展简史
(
{
\color
{
red
}
图需要到2020
}
)
}
\includegraphics
[scale=0.
4
]
{
./Chapter10/Figures/mt-history.png
}
\caption
{
机器翻译发展简史
}
\label
{
fig:10-1
}
\end{figure}
%----------------------------------------------
...
...
@@ -201,7 +201,7 @@ NMT & 21.7 & 18.7 & -13.7 \\
\label
{
tab:10-3
}
\begin{tabular}
{
l | l l l
}
模型
&
作者
&
年份
&
BLEU[
\%
]
\\
\hline
ConvS2S
\upcite
{
DBLP:journals/corr/GehringAGYD17
}
&
Gehring等
&
2017
&
25.2
\\
\rule
{
0pt
}{
13pt
}
ConvS2S
\upcite
{
DBLP:journals/corr/GehringAGYD17
}
&
Gehring等
&
2017
&
25.2
\\
Transformer-Base
\upcite
{
vaswani2017attention
}
&
Vaswani等
&
2017
&
27.3
\\
Transformer-Big
\upcite
{
vaswani2017attention
}
&
Vaswani等
&
2017
&
28.4
\\
RNMT+
\upcite
{
Chen2018TheBO
}
&
Chen等
&
2018
&
28.5
\\
...
...
@@ -662,7 +662,7 @@ $\funp{P}({y_j | \vectorn{\emph{s}}_{j-1} ,y_{j-1},\vectorn{\emph{C}}})$由Softm
\noindent
之所以能想到在横线处填“吃饭”、“吃东西”很有可能是因为看到了“没/吃饭”、 “很/饿”等关键信息。也就是这些关键的片段对预测缺失的单词起着关键性作用。而预测“吃饭”与前文中的“ 中午”、“又”之间的联系似乎不那么紧密。也就是说,在形成 “吃饭”的逻辑时,在潜意识里会更注意“没/吃饭”、“很饿”等关键信息。也就是我们的关注度并不是均匀地分布在整个句子上的。
\parinterval
这个现象可以用注意力机制进行解释。注意力机制的概念来源于生物学的一些现象:当待接收的信息过多时,人类会选择性地关注部分信息而忽略其他信息。它在人类的视觉、听觉、嗅觉等方面均有体现,当我们在感受事物时,大脑会自动过滤或衰减部分信息,仅关注其中少数几个部分。例如,当看到图
\ref
{
fig:12-20
}
时,往往不是“均匀地”看图像中的所有区域,可能最先注意到的是小狗
头上戴的帽子
,然后才会关注图片中其他的部分。那注意力机制是如何解决神经机器翻译的问题呢?下面就一起来看一看。
\parinterval
这个现象可以用注意力机制进行解释。注意力机制的概念来源于生物学的一些现象:当待接收的信息过多时,人类会选择性地关注部分信息而忽略其他信息。它在人类的视觉、听觉、嗅觉等方面均有体现,当我们在感受事物时,大脑会自动过滤或衰减部分信息,仅关注其中少数几个部分。例如,当看到图
\ref
{
fig:12-20
}
时,往往不是“均匀地”看图像中的所有区域,可能最先注意到的是小狗
的嘴
,然后才会关注图片中其他的部分。那注意力机制是如何解决神经机器翻译的问题呢?下面就一起来看一看。
%----------------------------------------------
\begin{figure}
[htp]
...
...
@@ -718,7 +718,7 @@ $\funp{P}({y_j | \vectorn{\emph{s}}_{j-1} ,y_{j-1},\vectorn{\emph{C}}})$由Softm
\subsection
{
上下文向量的计算
}
\label
{
sec:10.1.3
}
\parinterval
神经机器翻译中,注意力机制的核心是:针对不同目标语言单词生成不同的上下文向量
呢?
这里,可以将注意力机制看做是一种对接收到的信息的加权处理。对于更重要的信息赋予更高的权重即更高的关注度,对于贡献度较低的信息分配较低的权重,弱化其对结果的影响。这样,
$
\vectorn
{
\emph
{
C
}}_
j
$
可以包含更多对当前目标语言位置有贡献的源语言片段的信息。
\parinterval
神经机器翻译中,注意力机制的核心是:针对不同目标语言单词生成不同的上下文向量
。
这里,可以将注意力机制看做是一种对接收到的信息的加权处理。对于更重要的信息赋予更高的权重即更高的关注度,对于贡献度较低的信息分配较低的权重,弱化其对结果的影响。这样,
$
\vectorn
{
\emph
{
C
}}_
j
$
可以包含更多对当前目标语言位置有贡献的源语言片段的信息。
\parinterval
根据这种思想,上下文向量
$
\vectorn
{
\emph
{
C
}}_
j
$
被定义为对不同时间步编码器输出的状态序列
$
\{
\vectorn
{
\emph
{
h
}}_
1
,
\vectorn
{
\emph
{
h
}}_
2
,...,
\vectorn
{
\emph
{
h
}}_
m
\}
$
进行加权求和,如下:
\begin{eqnarray}
...
...
@@ -956,7 +956,7 @@ L(\vectorn{\emph{Y}},\widehat{\vectorn{\emph{Y}}}) = \sum_{j=1}^n L_{\textrm{ce}
\parinterval
公式
\ref
{
eq:10-31
}
是一种非常通用的损失函数形式,除了交叉熵,也可以使用其他的损失函数,这时只需要替换
$
L
_{
ce
}
(
\cdot
)
$
即可。这里使用交叉熵损失函数的好处在于,它非常容易优化,特别是与Softmax组合,其反向传播的实现非常高效。此外,交叉熵损失(在一定条件下)也对应了极大似然的思想,这种方法在自然语言处理中已经被证明是非常有效的。
\parinterval
除了交叉熵,很多系统也使用了面向评价的损失函数,比如,直接利用评价指标BLEU定义损失函数。不过这类损失函数往往不可微分,因此无法直接获取梯度。这时可以引入强化学习技术,通过策略梯度等方法进行优化。不过这类方法需要采样等手段,这里不做重点讨论,相关内容会在后面技术部分进行介绍。
\parinterval
除了交叉熵,很多系统也使用了面向评价的损失函数,比如,直接利用评价指标BLEU定义损失函数
\upcite
{
DBLP:conf/acl/ShenCHHWSL16
}
。不过这类损失函数往往不可微分,因此无法直接获取梯度。这时可以引入强化学习技术,通过策略梯度等方法进行优化。不过这类方法需要采样等手段,这里不做重点讨论,相关内容会在后面技术部分进行介绍。
%----------------------------------------------------------------------------------------
% NEW SUBSUB-SECTION
...
...
Chapter11/chapter11.tex
查看文件 @
324f2259
...
...
@@ -21,531 +21,3 @@
% CHAPTER 11
%----------------------------------------------------------------------------------------
\chapter
{
基于自注意力或卷积的模型
}
%----------------------------------------------------------------------------------------
% NEW SECTION 11.1
%----------------------------------------------------------------------------------------
\section
{
自注意力机制
}
\vspace
{
0.5em
}
\label
{
sec:11.4.1
}
\parinterval
首先,再回顾一下循环神经网络处理文字序列的过程。如图
\ref
{
fig:11-36
}
所示,对于单词序列
$
\{
w
_
1
,...,w
_
m
\}
$
,处理第
$
m
$
个单词
$
w
_
m
$
时(绿色方框部分),需要输入前一时刻的信息(即处理单词
$
w
_{
m
-
1
}$
),而
$
w
_{
m
-
1
}$
又依赖于
$
w
_{
m
-
2
}$
,以此类推。也就是说,如果想建立
$
w
_
m
$
和
$
w
_
1
$
之间的关系,需要
$
m
-
1
$
次信息传递。对于长序列来说,词汇之间信息传递距离过长会导致信息在传递过程中丢失,同时这种按顺序建模的方式也使得系统对序列的处理十分缓慢。
%----------------------------------------------
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-dependencies-between-words-in-a-recurrent-neural-network
}
\caption
{
循环神经网络中单词之间的依赖关系
}
\label
{
fig:11-36
}
\end{figure}
%----------------------------------------------
\parinterval
那么能否摆脱这种顺序传递信息的方式,直接对不同位置单词之间的关系进行建模,即将信息传递的距离拉近为1?
{
\small\sffamily\bfseries
{
自注意力机制
}}
\index
{
自注意力机制
}
(Self-Attention)
\index
{
Self-Attention
}
的提出便有效解决了这个问题
\cite
{
DBLP:journals/corr/LinFSYXZB17
}
。图
\ref
{
fig:11-37
}
给出了自注意力机制对序列进行建模的示例。对于单词
$
w
_
m
$
,自注意力机制直接建立它与前
$
m
-
1
$
个单词之间的关系。也就是说,
$
w
_
m
$
与序列中所有其他单词的距离都是1。这种方式很好地解决了长距离依赖问题,同时由于单词之间的联系都是相互独立的,因此也大大提高了模型的并行度。
%----------------------------------------------
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-dependencies-between-words-of-attention
}
\caption
{
自注意力机制中单词之间的依赖关系
}
\label
{
fig:11-37
}
\end{figure}
%----------------------------------------------
\parinterval
自注意力机制也可以被看做是一个序列表示模型。比如,对于每个目标位置
$
j
$
,都生成一个与之对应的源语言句子表示,它的形式为:
$
\mathbf
{
C
}_
j
=
\sum
_
i
\alpha
_{
i,j
}
\mathbf
{
h
}_
i
$
,其中
$
\mathbf
{
h
}_
i
$
为源语言句子每个位置的表示结果,
$
\alpha
_{
i,j
}$
是目标位置
$
j
$
对
$
\mathbf
{
h
}_
i
$
的注意力权重。而自注意力机制不仅可以处理两种语言句子之间的对应,它也可以对单语句子进行表示。以源语言句子为例,同时参考
\ref
{
sec:11.3.4.3
}
节的内容,自注意力机制将序列中每个位置的表示
$
\mathbf
{
h
}_
i
$
看作
$
\mathrm
{
query
}$
(查询),并且将所有位置的表示看作
$
\mathrm
{
key
}$
(键)和
$
\mathrm
{
value
}$
(值)。自注意力模型通过计算当前位置与所有位置的匹配程度,也就是在注意力机制中提到的注意力权重,来对各个位置的
$
\mathrm
{
value
}$
进行加权求和。得到的结果可以被看作是在这个句子中当前位置的抽象表示。这个过程,可以叠加多次,形成多层注意力模型,对输入序列中各个位置进行更深层的表示。
%----------------------------------------------
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-example-of-self-attention-mechanism-calculation
}
\caption
{
自注意力计算实例
}
\label
{
fig:11-38
}
\end{figure}
%----------------------------------------------
\parinterval
举个例子,如图
\ref
{
fig:11-38
}
所示,一个汉语句子包含5个词。这里,用
$
\mathbf
{
h
}$
(``你'')表示``你''当前的表示结果。如果把``你''看作目标,这时
$
\mathrm
{
query
}$
就是
$
\mathbf
{
h
}$
(``你''),
$
\mathrm
{
key
}$
和
$
\mathrm
{
value
}$
是图中所有位置的表示,即:
{$
\mathbf
{
h
}$
(``你'')、
$
\mathbf
{
h
}$
(``什么'')、
$
\mathbf
{
h
}$
(``也'')、
$
\mathbf
{
h
}$
(``没'')、
$
\mathbf
{
h
}$
(`` 学'')
}
。在自注意力模型中,首先计算
$
\mathrm
{
query
}$
和
$
\mathrm
{
key
}$
的相关度,这里用
$
\alpha
_
i
$
表示
$
\mathbf
{
h
}$
(``你'')和位置
$
i
$
的表示之间的相关性。然后,把
$
\alpha
_
i
$
作为权重,对不同位置上的
$
\mathrm
{
value
}$
进行加权求和。最终,得到新的表示结果
$
\tilde
{
\mathbf
{
h
}}$
(``你'' ):
\begin{eqnarray}
\tilde
{
\mathbf
{
h
}}
(
\textrm
{
``你''
}
) =
\alpha
_
1
{
\mathbf
{
h
}}
(
\textrm
{
``你''
}
)
+
\alpha
_
2
{
\mathbf
{
h
}}
(
\textrm
{
``什么 ''
}
)
+
\alpha
_
3
{
\mathbf
{
h
}}
(
\textrm
{
``也''
}
)
+
\alpha
_
4
{
\mathbf
{
h
}}
(
\textrm
{
``没''
}
)
+
\alpha
_
5
{
\mathbf
{
h
}}
(
\textrm
{
``学''
}
)
\label
{
eq:11-42
}
\end{eqnarray}
\parinterval
同理,也可以用同样的方法处理这个句子中的其他单词。可以看出,在注意力机制中,并不是使用类似于循环神经网络的记忆能力去访问历史信息。序列中所有单词之间的信息都是通过同一种操作(
$
\mathrm
{
query
}$
和
$
\mathrm
{
key
}$
的相关度)进行处理。这样,表示结果
$
\tilde
{
\mathbf
{
h
}}
(
\textrm
{
``你''
}
)
$
在包含``你''这个单词的信息的同时,也包含了序列中其他词的信息。也就是,序列中每一个位置的表示结果中,都包含了其他位置的信息。从这个角度说,
$
\tilde
{
\mathbf
{
h
}}
(
\textrm
{
``你''
}
)
$
已经不再是单词''你''自身的表示结果,而是一种在单词``你''的位置上的全局信息的表示。
\parinterval
通常,也把生成
\{
$
\tilde
{
\mathbf
{
h
}}
(
\mathbf
{
w
}_
i
)
$
\}
的过程称为
{
\small\sffamily\bfseries
{
特征提取
}}
\index
{
特征提取
}
,而实现这个过程的模型被称为特征提取器。循环神经网络、自注意力模型都是典型的特征提取器。特征提取是神经机器翻译系统的关键步骤,在随后的内容中可以看到自注意力模型是一个非常适合机器翻译任务的特征提取器。
%----------------------------------------------------------------------------------------
% NEW SECTION 11.2
%----------------------------------------------------------------------------------------
\sectionnewpage
\section
{
Transformer
}
\parinterval
编码器-解码器框架提供了一个非常灵活的机制,因为开发者只需要设计编码器和解码器的结构就能完成机器翻译。但是,架构的设计是深度学习中最具挑战的工作,优秀的架构往往需要长时间的探索和大量的实验验证,而且还需要一点点``灵感''。
\parinterval
前面介绍的基于循环神经网络的翻译模型和注意力机制就是研究人员通过长期的实践发现的神经网络架构。除了神经机器翻译,它们也被广泛地应用于语音处理、图像处理等领域。虽然循环神经网络很强大,但是人们也发现了一些弊端。一个突出的问题是,循环神经网络每个循环单元都有向前依赖性,也就是当前时间步的处理依赖前一时间步处理的结果。这个性质可以使序列的``历史''信息不断被传递,但是也造成模型运行效率的下降。特别是对于自然语言处理任务,序列往往较长,无论是传统的RNN结构,还是更为复杂的LSTM结构,都需要很多次循环单元的处理才能够捕捉到单词之间的长距离依赖。由于需要多个循环单元的处理,距离较远的两个单词之间的信息传递变得很复杂。
\parinterval
针对这些问题,谷歌的研究人员提出了一种全新的模型
$
\ \dash\
$
Transformer
\cite
{
NIPS2017
_
7181
}
。与循环神经网络等传统模型不同,Transformer模型仅仅使用一种被称作自注意力机制的方法和标准的前馈神经网络,完全不依赖任何循环单元或者卷积操作。自注意力机制的优点在于可以直接对序列中任意两个单元之间的关系进行建模,这使得长距离依赖等问题可以更好地被求解。此外,自注意力机制非常适合在GPU 上进行并行化,因此模型训练的速度更快。表
\ref
{
tab:11-11
}
对比了RNN、CNN、Transformer三种模型的时间复杂度。
%----------------------------------------------
\begin{table}
[htp]
\centering
\caption
{
RNN、CNN、Transformer的对比
\cite
{
NIPS2017
_
7181
}
(
$
n
$
表示序列长度,
$
d
$
表示隐层大小,
$
k
$
表示卷积核大小)
}
\label
{
tab:11-11
}
\begin{tabular}
{
l | l l l
}
\rule
{
0pt
}{
20pt
}
Layer Type
&
\begin{tabular}
[l]
{
@
{}
l@
{}}
Complexity
\\
per Layer
\end{tabular}
&
\begin{tabular}
[l]
{
@
{}
l@
{}}
Sequential
\\
Operations
\end{tabular}
&
\begin{tabular}
[l]
{
@
{}
l@
{}}
Maximum
\\
Path Length
\end{tabular}
\\
\hline
\rule
{
0pt
}{
13pt
}
Self-Attention
&$
O
(
n
^
2
\cdot
d
)
$
&$
O
(
1
)
$
&$
O
(
1
)
$
\\
\rule
{
0pt
}{
13pt
}
Recurrent
&$
O
(
n
\cdot
d
^
2
)
$
&$
O
(
n
)
$
&$
O
(
n
)
$
\\
\rule
{
0pt
}{
13pt
}
Convolutional
&$
O
(
k
\cdot
n
\cdot
d
^
2
)
$
&$
O
(
1
)
$
&$
O
(
\mathrm
{
log
}_
k
(
n
))
$
\end{tabular}
\end{table}
%----------------------------------------------
\parinterval
Transformer在被提出之后,很快就席卷了整个自然语言处理领域。实际上,Transformer也可以当作一种表示模型,因此也被大量地使用在自然语言处理的其他领域,甚至图像处理和语音处理中也能看到它的影子。比如,目前非常流行的BERT等预训练模型就是基于Transformer。表
\ref
{
tab:11-12
}
展示了Transformer在WMT英德和英法机器翻译任务上的性能。它能用更少的计算量(FLOPS)达到比其他模型更好的翻译品质
\footnote
{
FLOPS = floating-point operations per second,即每秒浮点运算次数。它是度量计算机运算规模的常用单位
}
。
%----------------------------------------------
\begin{table}
[htp]
\centering
\caption
{
不同翻译模型性能对比
\cite
{
NIPS2017
_
7181
}}
\label
{
tab:11-12
}
\begin{tabular}
{
l l l l
}
\multicolumn
{
1
}{
l|
}{
\multirow
{
2
}{
*
}{
\#
}}
&
\multicolumn
{
2
}{
c
}{
BLEU
}
&
\multirow
{
2
}{
*
}{
\parbox
{
6em
}{
Training Cost (FLOPs)
}}
\\
\multicolumn
{
1
}{
l|
}{}
&
EN-DE
&
EN-FR
&
\\
\hline
\multicolumn
{
1
}{
l|
}{
GNMT+RL
}
&
24.6
&
39.92
&
1.4
$
\times
10
^{
20
}$
\\
\multicolumn
{
1
}{
l|
}{
ConvS2S
}
&
25.16
&
40.46
&
1.5
$
\times
10
^{
20
}$
\\
\multicolumn
{
1
}{
l|
}{
MoE
}
&
26.03
&
40.56
&
1.2
$
\times
10
^{
20
}$
\\
\multicolumn
{
1
}{
l|
}{
Transformer (Big)
}
&
{
\small\sffamily\bfseries
{
28.4
}}
&
{
\small\sffamily\bfseries
{
41.8
}}
&
2.3
$
\times
10
^{
19
}$
\\
\end{tabular}
\end{table}
%----------------------------------------------
\parinterval
注意,Transformer并不简单等同于自注意力机制。Transformer模型还包含了很多优秀的技术,比如:多头注意力、新的训练学习率调整策略等等。这些因素一起组成了真正的Transformer。下面就一起看一看自注意力机制和Transformer是如何工作的。
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 11.2.1
%----------------------------------------------------------------------------------------
\subsection
{
Transformer架构
}
%----------------------------------------------
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-transformer
}
\caption
{
Transformer结构
}
\label
{
fig:11-39
}
\end{figure}
%----------------------------------------------
\parinterval
图
\ref
{
fig:11-39
}
展示了经典的Transformer结构。解码器由若干层组成(绿色虚线框就代表一层)。每一层(layer)的输入都是一个向量序列,输出是同样大小的向量序列,而Transformer层的作用是对输入进行进一步的抽象,得到新的表示结果。不过这里的层并不是指单一的神经网络结构,它里面由若干不同的模块组成,包括:
\begin{itemize}
\vspace
{
0.5em
}
\item
{
\small\sffamily\bfseries
{
自注意力子层
}}
\index
{
自注意力子层
}
(Self-attention Sub-layer)
\index
{
Self-attention Sub-layer
}
:使用自注意力机制对输入的序列进行新的表示;
\vspace
{
0.5em
}
\item
{
\small\sffamily\bfseries
{
前馈神经网络子层
}}
\index
{
前馈神经网络子层
}
(Feed-forward Sub-layer)
\index
{
Feed-forward Sub-layer
}
:使用全连接的前馈神经网络对输入向量序列进行进一步变换;
\vspace
{
0.5em
}
\item
{
\small\sffamily\bfseries
{
残差连接
}}
\index
{
残差连接
}
(Residual Connection,标记为``Add'')
\index
{
Residual Connection
}
:对于自注意力子层和前馈神经网络子层,都有一个从输入直接到输出的额外连接,也就是一个跨子层的直连。残差连接可以使深层网络的信息传递更为有效;
\vspace
{
0.5em
}
\item
{
\small\sffamily\bfseries
{
层正则化
}}
\index
{
层正则化
}
(Layer Normalization)
\index
{
Layer Normalization
}
:自注意力子层和前馈神经网络子层进行最终输出之前,会对输出的向量进行层正则化,规范结果向量取值范围,这样易于后面进一步的处理。
\vspace
{
0.5em
}
\end{itemize}
\parinterval
以上操作就构成了Transformer的一层,各个模块执行的顺序可以简单描述为:Self-Attention
$
\to
$
Residual Connection
$
\to
$
Layer Normalization
$
\to
$
Feed Forward Network
$
\to
$
Residual Connection
$
\to
$
Layer Normalization。编码器可以包含多个这样的层,比如,可以构建一个六层编码器,每层都执行上面的操作。最上层的结果作为整个编码的结果,会被传入解码器。
\parinterval
解码器的结构与编码器十分类似。它也是由若干层组成,每一层包含编码器中的所有结构,即:自注意力子层、前馈神经网络子层、残差连接和层正则化模块。此外,为了捕捉源语言的信息,解码器又引入了一个额外的
{
\small\sffamily\bfseries
{
编码-解码注意力子层
}}
\index
{
编码-解码注意力子层
}
(Encoder-decoder Attention Sub-layer)
\index
{
Encoder-decoder Attention Sub-layer
}
。这个新的子层,可以帮助模型使用源语言句子的表示信息生成目标语不同位置的表示。编码-解码注意力子层仍然基于自注意力机制,因此它和自注意力子层的结构是相同的,只是
$
\mathrm
{
query
}$
、
$
\mathrm
{
key
}$
、
$
\mathrm
{
value
}$
的定义不同。比如,在解码端,自注意力子层的
$
\mathrm
{
query
}$
、
$
\mathrm
{
key
}$
、
$
\mathrm
{
value
}$
是相同的,它们都等于解码端每个位置的表示。而在编码-解码注意力子层中,
$
\mathrm
{
query
}$
是解码端每个位置的表示,此时
$
\mathrm
{
key
}$
和
$
\mathrm
{
value
}$
是相同的,等于编码端每个位置的表示。图
\ref
{
fig:11-40
}
给出了这两种不同注意力子层输入的区别。
%----------------------------------------------
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-self-att-vs-enco-deco-att
}
\caption
{
注意力模型的输入(自注意力子层 vs 编码-解码注意力子层)
}
\label
{
fig:11-40
}
\end{figure}
%----------------------------------------------
\parinterval
此外,编码端和解码端都有输入的词序列。编码端的词序列输入是为了对其进行表示,进而解码端能从编码端访问到源语言句子的全部信息。解码端的词序列输入是为了进行目标语的生成,本质上它和语言模型是一样的,在得到前
$
n
-
1
$
个单词的情况下输出第
$
n
$
个单词。除了输入的词序列的词嵌入,Transformer中也引入了位置嵌入,以表示每个位置信息。原因是,自注意力机制没有显性地对位置进行表示,因此也无法考虑词序。在输入中引入位置信息可以让自注意力机制间接地感受到每个词的位置,进而保证对序列表示的合理性。最终,整个模型的输出由一个Softmax层完成,它和循环神经网络中的输出层是完全一样的(
\ref
{
sec:11.3.2
}
节)。
\parinterval
在进行更详细的介绍前,先利用图
\ref
{
fig:11-39
}
简单了解一下Transformer模型是如何进行翻译的。首先,Transformer将源语``我
\
很
\
好''的
{
\small\bfnew
{
词嵌入
}}
\index
{
词嵌入
}
(Word Embedding)
\index
{
Word Embedding
}
融合
{
\small\bfnew
{
位置编码
}}
\index
{
位置编码
}
(Position Embedding)
\index
{
Position Embedding
}
后作为输入。然后,编码器对输入的源语言句子进行逐层抽象,得到包含丰富的上下文信息的源语表示并传递给解码器。解码器的每一层,使用自注意力子层对输入解码端的表示进行加工,之后再使用编码-解码注意力子层融合源语言句子的表示信息。就这样逐词生成目标语译文单词序列。解码器的每个位置的输入是当前单词(比如,``I''),而这个位置输出是下一个单词(比如,``am''),这个设计和标准的神经语言模型是完全一样的。
\parinterval
了解到这里,可能大家还有很多疑惑,比如,什么是位置编码?Transformer的自注意力机制具体是怎么进行计算的,其结构是怎样的?Add
\&
LayerNorm又是什么?等等。下面就一一展开介绍。
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 11.2.2
%----------------------------------------------------------------------------------------
\subsection
{
位置编码
}
\parinterval
在使用循环神经网络进行序列的信息提取时,每个时刻的运算都要依赖前一个时刻的输出,具有一定的时序性,这也与语言具有顺序的特点相契合。而采用自注意力机制对源语言和目标语言序列进行处理时,直接对当前位置和序列中的任意位置进行建模,忽略了词之间的顺序关系,例如图
\ref
{
fig:11-41
}
中两个语义不同的句子,通过自注意力得到的表示
$
\tilde
{
\mathbf
{
h
}}$
(``机票'')却是相同的。
%----------------------------------------------
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-calculation-of-context-vector-c
}
\caption
{
``机票''的更进一步抽象表示
$
\tilde
{
\mathbf
{
h
}}$
的计算
}
\label
{
fig:11-41
}
\end{figure}
%----------------------------------------------
\parinterval
为了解决这个问题,Transformer在原有的词向量输入基础上引入了位置编码,来表示单词之间的顺序关系。位置编码在Transformer结构中的位置如图
\ref
{
fig:11-42
}
,它是Transformer成功的一个重要因素。
%----------------------------------------------
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-transformer-input-and-position-encoding
}
\caption
{
Transformer输入与位置编码
}
\label
{
fig:11-42
}
\end{figure}
%----------------------------------------------
\parinterval
位置编码的计算方式有很多种,Transformer使用不同频率的正余弦函数:
\begin{eqnarray}
\textrm
{
PE
}
(pos,2i) =
\textrm
{
sin
}
(
\frac
{
pos
}{
10000
^{
2i/d
_{
model
}}}
)
\label
{
eq:11-43
}
\end{eqnarray}
\begin{eqnarray}
\textrm
{
PE
}
(pos,2i+1) =
\textrm
{
cos
}
(
\frac
{
pos
}{
10000
^{
2i/d
_{
model
}}}
)
\label
{
eq:11-44
}
\end{eqnarray}
\noindent
式中PE(
$
\cdot
$
)表示位置编码的函数,
$
pos
$
表示单词的位置,
$
i
$
代表位置编码向量中的第几维,
$
d
_{
model
}$
是Transformer的一个基础参数,表示每个位置的隐层大小。因为,正余弦函数的编码各占一半,因此当位置编码的维度为512 时,
$
i
$
的范围是0-255。 在Transformer中,位置编码的维度和词嵌入向量的维度相同(均为
$
d
_{
model
}$
),模型通过将二者相加作为模型输入,如图
\ref
{
fig:11-43
}
所示。
%----------------------------------------------
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-a-combination-of-position-encoding-and-word-encoding
}
\caption
{
位置编码与词编码的组合
}
\label
{
fig:11-43
}
\end{figure}
%----------------------------------------------
\parinterval
那么为什么通过这种计算方式可以很好的表示位置信息?有几方面原因。首先,正余弦函数是具有上下界的周期函数,用正余弦函数可将长度不同的序列的位置编码的范围都固定到[-1,1],这样在与词的编码进行相加时,不至于产生太大差距。另外位置编码的不同维度对应不同的正余弦曲线,这为多维的表示空间赋予一定意义。最后,根据三角函数的性质:
\begin{eqnarray}
\textrm
{
sin
}
(
\alpha
+
\beta
)
&
=
&
\textrm
{
sin
}
\alpha
\cdot
\textrm
{
cos
}
\beta
+
\textrm
{
cos
}
\alpha
\cdot
\textrm
{
sin
}
\beta
\nonumber
\\
\textrm
{
cos
}
(
\alpha
+
\beta
)
&
=
&
\textrm
{
cos
}
\alpha
\cdot
\textrm
{
cos
}
\beta
-
\textrm
{
sin
}
\alpha
\cdot
\textrm
{
sin
}
\beta
\label
{
eq:11-45
}
\end{eqnarray}
\parinterval
可以得到``
$
pos
+
k
$
''的位置编码为:
\begin{eqnarray}
\textrm
{
PE
}
(pos+k,2i)
&
=
&
\textrm
{
PE
}
(pos,2i)
\times
\textrm
{
PE
}
(k,2i+1) +
\nonumber
\\
&
&
\textrm
{
PE
}
(pos,2i+1)
\times
\textrm
{
PE
}
(k,2i)
\\
\textrm
{
PE
}
(pos+k ,2i+1)
&
=
&
\textrm
{
PE
}
(pos,2i+1)
\times
\textrm
{
PE
}
(k,2i+1) -
\nonumber
\\
&
&
\textrm
{
PE
}
(pos,2i)
\times
\textrm
{
PE
}
(k,2i)
\label
{
eq:11-46
}
\end{eqnarray}
\noindent
即对于任意固定的偏移量
$
k
$
,
$
\textrm
{
PE
}
(
pos
+
k
)
$
能被表示成
$
\textrm
{
PE
}
(
pos
)
$
的线性函数,换句话说,位置编码可以表示词之间的距离。在实践中发现,位置编码对Transformer系统的性能有很大影响。对其进行改进也会带来进一步的性能提升
\cite
{
Shaw2018SelfAttentionWR
}
。
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 11.2.3
%----------------------------------------------------------------------------------------
\subsection
{
基于点乘的注意力机制
}
%----------------------------------------------
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-position-of-self-attention-mechanism-in-the-model
}
\caption
{
自注意力机制在模型中的位置
}
\label
{
fig:11-44
}
\end{figure}
%----------------------------------------------
\parinterval
Transformer模型摒弃了循环单元和卷积等结构,完全基于注意力机制来构造模型,其中包含着大量的注意力计算。比如,可以通过自注意力机制对源语言和目标语言序列进行信息提取,并通过编码-解码注意力对双语句对之间的关系进行提取。图
\ref
{
fig:11-44
}
中红色方框部分是Transformer中使用自注意力机制的模块。
\parinterval
在
\ref
{
sec:11.4.1
}
节中已经介绍,自注意力机制中,至关重要的是获取相关性系数,也就是在融合不同位置的表示向量时各位置的权重。在
\ref
{
sec:11.3
}
节基于循环神经网络的机器翻译模型中,注意力机制的相关性系数有很多种计算方式,如余弦相似度等。而在Transformer模型中,则采用了一种基于点乘的方法来计算相关性系数。这种方法也称为
{
\small\bfnew
{
点乘注意力
}}
\index
{
点乘注意力
}
(Scaled Dot-Product Attention)
\index
{
Scaled Dot-Product Attention
}
机制。它的运算并行度高,同时并不消耗太多的存储空间。
\parinterval
具体来看,在注意力机制的计算过程中,包含三个重要的参数,分别是Query,
\\
Key和Value。在下面的描述中,分别用
$
\mathbf
{
Q
}$
,
$
\mathbf
{
K
}$
,
$
\mathbf
{
V
}$
对它们进行表示,其中
$
\mathbf
{
Q
}$
和
$
\mathbf
{
K
}$
的维度为
$
L
\times
d
_
k
$
,
$
\mathbf
{
V
}$
的维度为
$
L
\times
d
_
v
$
。这里,
$
L
$
为序列的长度,
$
d
_
k
$
和
$
d
_
v
$
分别表示每个Key和Value的大小,通常设置为
$
d
_
k
=
d
_
v
=
d
_{
model
}$
。
\parinterval
在自注意力机制中,
$
\mathbf
{
Q
}$
、
$
\mathbf
{
K
}$
、
$
\mathbf
{
V
}$
都是相同的,对应着源语言或目标语言的表示。而在编码-解码注意力机制中,由于要对双语之间的信息进行建模,因此,将目标语每个位置的表示视为编码-解码注意力机制的
$
\mathbf
{
Q
}$
,源语言句子的表示视为
$
\mathbf
{
K
}$
和
$
\mathbf
{
V
}$
。
\parinterval
在得到
$
\mathbf
{
Q
}$
,
$
\mathbf
{
K
}$
和
$
\mathbf
{
V
}$
后,便可以进行注意力机制的运算,这个过程可以被形式化为:
\begin{eqnarray}
\textrm
{
Attention
}
(
\mathbf
{
Q
}
,
\mathbf
{
K
}
,
\mathbf
{
V
}
) =
\textrm
{
Softmax
}
(
\frac
{
\mathbf
{
Q
}
\mathbf
{
K
}^{
T
}}
{
\sqrt
{
d
_
k
}}
+
\mathbf
{
Mask
}
)
\mathbf
{
V
}
\label
{
eq:11-47
}
\end{eqnarray}
\noindent
首先,通过对
$
\mathbf
{
Q
}$
和
$
\mathbf
{
K
}$
的转置进行点乘操作,计算得到一个维度大小为
$
L
\times
L
$
的相关性矩阵,即
$
\mathbf
{
Q
}
\mathbf
{
K
}^{
T
}$
,它表示一个序列上任意两个位置的相关性。再通过系数1/
$
\sqrt
{
d
_
k
}$
进行放缩操作,放缩可以尽量减少相关性矩阵的方差,具体体现在运算过程中实数矩阵中的数值不会过大,有利于模型训练。
\parinterval
在此基础上,通过对相关性矩阵累加一个掩码矩阵,来屏蔽掉矩阵中的无用信息。比如,在编码端对句子的补齐,在解码端则屏蔽掉未来信息,这一部分内容将在下一小节进行详细介绍。随后,使用Softmax函数对相关性矩阵在行的维度上进行归一化操作,这可以理解为对第
$
i
$
行进行归一化,结果对应了
$
\mathbf
{
V
}$
中的不同位置上向量的注意力权重。对于
$
\mathrm
{
value
}$
的加权求和,可以直接用相关性系数和
$
\mathbf
{
V
}$
进行矩阵乘法得到,即
$
\textrm
{
Softmax
}
(
\frac
{
\mathbf
{
Q
}
\mathbf
{
K
}^{
T
}}
{
\sqrt
{
d
_
k
}}
+
\mathbf
{
Mask
}
)
$
和
$
\mathbf
{
V
}$
进行矩阵乘。最终得到自注意力的输出,它和输入的
$
\mathbf
{
V
}$
的大小是一模一样的。图
\ref
{
fig:11-45
}
展示了点乘注意力计算的全过程。
%----------------------------------------------
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-point-product-attention-model
}
\caption
{
点乘注意力力模型
}
\label
{
fig:11-45
}
\end{figure}
%----------------------------------------------
\parinterval
下面举个简单的例子介绍点乘注意力的具体计算过程。如图
\ref
{
fig:11-46
}
所示,用黄色、蓝色和橙色的矩阵分别表示
$
\mathbf
{
Q
}$
、
$
\mathbf
{
K
}$
和
$
\mathbf
{
V
}$
。
$
\mathbf
{
Q
}$
、
$
\mathbf
{
K
}$
和
$
\mathbf
{
V
}$
中的每一个小格都对应一个单词在模型中的表示(即一个向量)。首先,通过点乘、放缩、掩码等操作得到相关性矩阵,即粉色部分。其次,将得到的中间结果矩阵(粉色)的每一行使用Softmax激活函数进行归一化操作,得到最终的权重矩阵,也就是图中的红色矩阵。红色矩阵中的每一行都对应一个注意力分布。最后,按行对
$
\mathbf
{
V
}$
进行加权求和,便得到了每个单词通过点乘注意力机制计算得到的表示。这里面,主要的计算消耗是两次矩阵乘法,即
$
\mathbf
{
Q
}$
与
$
\mathbf
{
K
}^{
T
}$
的乘法、相关性矩阵和
$
\mathbf
{
V
}$
的乘法。这两个操作都可以在GPU上高效地完成,因此可以一次性计算出序列中所有单词之间的注意力权重,并完成所有位置表示的加权求和过程,这样大大提高了模型的计算速度。
%----------------------------------------------
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-process-of-5
}
\caption
{
式
\ref
{
eq:11-47
}
的执行过程示例
}
\label
{
fig:11-46
}
\end{figure}
%----------------------------------------------
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 11.2.4
%----------------------------------------------------------------------------------------
\subsection
{
掩码操作
}
\parinterval
在公式
\ref
{
eq:11-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:11-47
}
给出了一个具体的实例。
%----------------------------------------------
% 图3.10
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-mask-instance-for-future-positions-in-transformer
}
\caption
{
Transformer中对于未来位置进行的屏蔽的Mask实例
}
\label
{
fig:11-47
}
\end{figure}
%----------------------------------------------
\vspace
{
0.5em
}
\end{itemize}
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 11.2.5
%----------------------------------------------------------------------------------------
\subsection
{
多头注意力
}
\parinterval
Transformer中使用的另一项重要技术是
{
\small\sffamily\bfseries
{
多头注意力
}}
\index
{
多头注意力
}
(Multi-head Attention)
\index
{
Multi-head Attention
}
。``多头''可以理解成将原来的
$
\mathbf
{
Q
}$
、
$
\mathbf
{
K
}$
、
$
\mathbf
{
V
}$
按照隐层维度平均切分成多份。假设切分
$
h
$
份,那么最终会得到
$
\mathbf
{
Q
}
=
\{
\mathbf
{
q
}_
1
,
\mathbf
{
q
}_
2
,...,
\mathbf
{
q
}_
h
\}
$
,
$
\mathbf
{
K
}
=
\{
\mathbf
{
k
}_
1
,
\mathbf
{
k
}_
2
,...,
\mathbf
{
k
}_
h
\}
$
,
$
\mathbf
{
V
}
=
\{
\mathbf
{
v
}_
1
,
\mathbf
{
v
}_
2
,...,
\mathbf
{
v
}_
h
\}
$
。多头注意力机制就是用每一个切分得到的
$
\mathbf
{
q
}$
,
$
\mathbf
{
k
}$
,
$
\mathbf
{
v
}$
独立的进行注意力计算。即第
$
i
$
个头的注意力计算结果
$
\mathbf
{
head
}_
i
=
\textrm
{
Attention
}
(
\mathbf
{
q
}_
i,
\mathbf
{
k
}_
i,
\mathbf
{
v
}_
i
)
$
。
\parinterval
下面根据图
\ref
{
fig:11-48
}
详细介绍多头注意力的计算过程:
\begin{itemize}
\vspace
{
0.5em
}
\item
首先将
$
\mathbf
{
Q
}$
、
$
\mathbf
{
K
}$
、
$
\mathbf
{
V
}$
分别通过线性变换的方式映射为
$
h
$
个子集(机器翻译任务中,
$
h
$
一般为8)。即
$
\mathbf
{
q
}_
i
=
\mathbf
{
Q
}
\mathbf
{
W
}_
i
^
Q
$
、
$
\mathbf
{
k
}_
i
=
\mathbf
{
K
}
\mathbf
{
W
}_
i
^
K
$
、
$
\mathbf
{
v
}_
i
=
\mathbf
{
V
}
\mathbf
{
W
}_
i
^
V
$
,其中
$
i
$
表示第
$
i
$
个头,
$
\mathbf
{
W
}_
i
^
Q
\in
\mathbb
{
R
}^{
d
_{
model
}
\times
d
_
k
}$
,
$
\mathbf
{
W
}_
i
^
K
\in
\mathbb
{
R
}^{
d
_{
model
}
\times
d
_
k
}$
,
$
\mathbf
{
W
}_
i
^
V
\in
\mathbb
{
R
}^{
d
_{
model
}
\times
d
_
v
}$
是参数矩阵;
$
d
_
k
=
d
_
v
=
d
_{
model
}
/
h
$
,对于不同的头采用不同的变换矩阵,这里
$
d
_{
model
}$
是Transformer的一个参数,表示每个隐层向量的维度;
\vspace
{
0.5em
}
\item
其次对每个头分别执行点乘注意力操作,并得到每个头的注意力操作的输出
$
\mathbf
{
head
}_
i
$
;
\vspace
{
0.5em
}
\item
最后将
$
h
$
个头的注意力输出在最后一维
$
d
_
v
$
进行拼接(Concat)重新得到维度为
$
h
\times
d
_
v
$
的输出,并通过对其左乘一个权重矩阵
$
\mathbf
{
W
}^
o
$
进行线性变换,从而对多头计算得到的信息进行融合,且将多头注意力输出的维度映射为模型的隐层大小(即
$
d
_{
model
}$
),这里参数矩阵
$
\mathbf
{
W
}^
o
\in
\mathbb
{
R
}^{
h
\times
d
_
v
\times
d
_{
model
}}$
。
\vspace
{
0.5em
}
\end{itemize}
%----------------------------------------------
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-multi-head-attention-model
}
\caption
{
多头注意力模型
}
\label
{
fig:11-48
}
\end{figure}
%----------------------------------------------
\parinterval
多头机制具体的计算公式如下:
\begin{eqnarray}
\textrm
{
MultiHead
}
(
\mathbf
{
Q
}
,
\mathbf
{
K
}
,
\mathbf
{
V
}
)
&
=
&
\textrm
{
Concat
}
(
\mathbf
{
head
}_
1, ... ,
\mathbf
{
head
}_
h )
\mathbf
{
W
}^
o
\label
{
eq:11-48
}
\\
\mathbf
{
head
}_
i
&
=
&
\textrm
{
Attention
}
(
\mathbf
{
Q
}
\mathbf
{
W
}_
i
^
Q ,
\mathbf
{
K
}
\mathbf
{
W
}_
i
^
K ,
\mathbf
{
V
}
\mathbf
{
W
}_
i
^
V )
\label
{
eq:11-49
}
\end{eqnarray}
\parinterval
多头机制的好处是允许模型在不同的表示子空间里学习。在很多实验中发现,不同表示空间的头捕获的信息是不同的,比如,在使用Transformer处理自然语言时,有的头可以捕捉句法信息,有头可以捕捉词法信息。
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 11.2.6
%----------------------------------------------------------------------------------------
\subsection
{
残差网络和层正则化
}
\parinterval
Transformer编码器、解码器分别由多层网络组成(通常为6层),每层网络又包含多个子层(自注意力网络、前馈神经网络)。因此Transformer实际上是一个很深的网络结构。再加上前面介绍的点乘注意力机制,包含很多线性和非线性变换;另外,注意力函数Attention(
$
\cdot
$
)的计算也涉及多层网络,整个网络的信息传递非常复杂。从反向传播的角度来看,每次回传的梯度都会经过若干步骤,容易产生梯度爆炸或者消失。
\parinterval
解决这个问题的一种办法就是使用
{
\small\sffamily\bfseries
{
残差连接
}}
\index
{
残差连接
}
\cite
{
DBLP:journals/corr/HeZRS15
}
。残差连接是一种用来训练深层网络的技术,其结构如图
\ref
{
fig:11-49
}
,即在子层之前通过增加直接连接的方式,将底层信息直接传递给上层。
%----------------------------------------------
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-residual-network-structure
}
\caption
{
残差网络结构
}
\label
{
fig:11-49
}
\end{figure}
%----------------------------------------------
\parinterval
残差连接从广义上讲也叫
{
\small\bfnew
{
短连接
}}
\index
{
短连接
}
(Short-cut Connection)
\index
{
Short-cut Connection
}
,指的是这种短距离的连接。它的思想很简单,就是把层和层之间的距离拉近。如图
\ref
{
fig:11-49
}
所示,子层1通过残差连接跳过了子层2,直接和子层3进行信息传递。使信息传递变得更高效,有效解决了深层网络训练过程中容易出现的梯度消失/爆炸问题,使得深层网络的训练更加容易。其计算公式为:
\begin{eqnarray}
x
_{
l+1
}
= x
_
l +
\mathcal
{
F
}
(x
_
l)
\label
{
eq:11-50
}
\end{eqnarray}
\noindent
其中
$
\mathcal
{
F
}
(
x
_
l
)
$
是子层运算。如果
$
l
=
2
$
,那么公式
\ref
{
eq:11-50
}
可以解释为,第3层的输出等于第2层的输出加上第二层的输入。图
\ref
{
fig:11-50
}
中的红色方框展示了Transformer中残差连接的位置。
%----------------------------------------------
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-position-of-difference-and-layer-regularization-in-the-model
}
\caption
{
残差和层正则化在模型中的位置
}
\label
{
fig:11-50
}
\end{figure}
%----------------------------------------------
\parinterval
在Transformer的训练过程中,由于引入了残差操作,将前面所有层的输出加到一起。这样会导致不同层(或子层)的结果之间的差异性很大,造成训练过程不稳定、训练时间较长。为了避免这种情况,在每层中加入了层正则化操作
\cite
{
Ba2016LayerN
}
。层正则化的计算公式如下:
\begin{eqnarray}
\textrm
{
LN
}
(x) = g
\cdot
\frac
{
x-
\mu
}
{
\sigma
}
+ b
\label
{
eq:11-51
}
\end{eqnarray}
\noindent
该公式使用均值
$
\mu
$
和方差
$
\sigma
$
对样本进行平移缩放,将数据规范化为均值为0,方差为1的标准分布。
$
g
$
和
$
b
$
是可学习的参数。
\parinterval
在Transformer中经常使用的层正则化操作有两种结构,分别是
{
\small\bfnew
{
后正则化
}}
\index
{
后正则化
}
(Post-norm)
\index
{
Post-norm
}
和
{
\small\bfnew
{
前正则化
}}
\index
{
前正则化
}
(Pre-norm)
\index
{
Pre-norm
}
,结构如图
\ref
{
fig:11-51
}
所示。后正则化中先进行残差连接再进行层正则化,而前正则化则是在子层输入之前进行层正则化操作。在很多实践中已经发现,前正则化的方式更有利于信息传递,因此适合训练深层的Transformer模型
\cite
{
WangLearning
}
。
%----------------------------------------------
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-different-regularization-methods
}
\caption
{
不同正则化方式
}
\label
{
fig:11-51
}
\end{figure}
%----------------------------------------------
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 11.2.7
%----------------------------------------------------------------------------------------
\subsection
{
前馈全连接网络子层
}
\parinterval
在Transformer的结构中,每一个编码层或者解码层中都包含一个前馈神经网络,它在模型中的位置如图
\ref
{
fig:11-52
}
中红色方框所示。
%----------------------------------------------
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-position-of-feedforward-neural-network-in-the-model
}
\caption
{
前馈神经网络在模型中的位置
}
\label
{
fig:11-52
}
\end{figure}
%----------------------------------------------
\parinterval
Transformer使用了全连接网络。全连接网络的作用主要体现在将经过注意力操作之后的表示映射到新的空间中,新的空间会有利于接下来的非线性变换等操作。实验证明,去掉全连接网络会对模型的性能造成影响。Transformer的全连接前馈神经网络包含两次线性变换和一次非线性变换(ReLU激活函数:ReLU
$
(
x
)=
\textrm
{
max
}
(
0
,x
)
$
),每层的前馈神经网络参数不共享,计算公式如下:
\begin{eqnarray}
\textrm
{
FFN
}
(x) =
\textrm
{
max
}
(0,
\mathbf
{
x
}
\mathbf
{
W
}_
1 +
\mathbf
{
b
}_
1)
\mathbf
{
W
}_
2 +
\mathbf
{
b
}_
2
\label
{
eq:11-52
}
\end{eqnarray}
\noindent
其中,
$
\mathbf
{
W
}_
1
$
、
$
\mathbf
{
W
}_
2
$
、
$
\mathbf
{
b
}_
1
$
和
$
\mathbf
{
b
}_
2
$
为模型的参数。通常情况下,前馈神经网络的隐层维度要比注意力部分的隐层维度大,而且研究人员发现这种设置对Transformer是至关重要的。 比如,注意力部分的隐层维度为512,前馈神经网络部分的隐层维度为2048。当然,继续增大前馈神经网络的隐层大小,比如设为4096,甚至8192,还可以带来性能的增益,但是前馈部分的存储消耗较大,需要更大规模GPU 设备的支持。因此在具体实现时,往往需要在翻译准确性和存储/速度之间找到一个平衡。
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 11.2.8
%----------------------------------------------------------------------------------------
\subsection
{
训练
}
\parinterval
与前面介绍的神经机器翻译模型的训练一样,Transformer的训练流程为:首先对模型进行初始化,然后在编码器输入包含结束符的源语言单词序列。前面已经介绍过,解码端每个位置单词的预测都要依赖已经生成的序列。在解码端输入包含起始符号的目标语序列,通过起始符号预测目标语的第一个单词,用真实的目标语的第一个单词去预测第二个单词,以此类推,然后用真实的目标语序列和预测的结果比较,计算它的损失。Transformer使用了
{
\small\bfnew
{
交叉熵损失
}}
\index
{
交叉熵损失
}
(Cross Entropy Loss)
\index
{
Cross Entropy Loss
}
函数,损失越小说明模型的预测越接近真实输出。然后利用反向传播来调整模型中的参数。由于Transformer 将任意时刻输入信息之间的距离拉近为1,摒弃了RNN中每一个时刻的计算都要基于前一时刻的计算这种具有时序性的训练方式,因此Transformer中训练的不同位置可以并行化训练,大大提高了训练效率。
%----------------------------------------------
%\begin{figure}[htp]
%\centering
%\input{./Chapter11/Figures/figure-structure-of-the-network-during-transformer-training}
%\caption{Transformer训练时网络的结构}
%\label{fig:11-53}
%\end{figure}
%----------------------------------------------
\parinterval
需要注意的是,Transformer也包含很多工程方面的技巧。首先,在训练优化器方面,需要注意以下几点:
\begin{itemize}
\vspace
{
0.5em
}
\item
Transformer使用Adam优化器优化参数,并设置
$
\beta
_
1
=
0
.
9
$
,
$
\beta
_
2
=
0
.
98
$
,
$
\epsilon
=
10
^{
-
9
}$
。
\vspace
{
0.5em
}
\item
Transformer在学习率中同样应用了学习率
{
\small\bfnew
{
预热
}}
\index
{
预热
}
(Warmup)
\index
{
Warmup
}
策略,其计算公式如下:
\begin{eqnarray}
lrate = d
_{
model
}^{
-0.5
}
\cdot
\textrm
{
min
}
(step
^{
-0.5
}
, step
\cdot
warmup
\_
steps
^{
-1.5
}
)
\label
{
eq:11-53
}
\vspace
{
0.5em
}
\end{eqnarray}
其中,
$
step
$
表示更新的次数(或步数)。通常设置网络更新的前4000步为预热阶段即
$
warmup
\_
steps
=
4000
$
。Transformer的学习率曲线如图
\ref
{
fig:11-54
}
所示。在训练初期,学习率从一个较小的初始值逐渐增大(线性增长),当到达一定的步数,学习率再逐渐减小。这样做可以减缓在训练初期的不稳定现象,同时在模型达到相对稳定之后,通过逐渐减小的学习率让模型进行更细致的调整。这种学习率的调整方法是Transformer的一个很大的工程贡献。
\vspace
{
0.5em
}
\end{itemize}
%----------------------------------------------
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-lrate-of-transformer
}
\caption
{
Transformer模型的学习率曲线
}
\label
{
fig:11-54
}
\end{figure}
%----------------------------------------------
\parinterval
另外,Transformer为了提高模型训练的效率和性能,还进行了以下几方面的操作:
\begin{itemize}
\vspace
{
0.5em
}
\item
{
\small\bfnew
{
小批量训练
}}
\index
{
小批量训练
}
(Mini-batch Training)
\index
{
Mini-batch Training
}
:每次使用一定数量的样本进行训练,即每次从样本中选择一小部分数据进行训练。这种方法的收敛较快,同时易于提高设备的利用率。批次大小通常设置为2048/4096(token数即每个批次中的单词个数)。每一个批次中的句子并不是随机选择的,模型通常会根据句子长度进行排序,选取长度相近的句子组成一个批次。这样做可以减少padding数量,提高训练效率,如图
\ref
{
fig:11-55
}
。
%----------------------------------------------
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-comparison-of-the-number-of-padding-in-batch
}
\caption
{
batch中padding数量对比(白色部分为padding)
}
\label
{
fig:11-55
}
\end{figure}
%----------------------------------------------
\vspace
{
0.5em
}
\item
{
\small\bfnew
{
Dropout
}}
\index
{
Dropout
}
:由于Transformer模型网络结构较为复杂,会导致过度拟合训练数据,从而对未见数据的预测结果变差。这种现象也被称作
{
\small\sffamily\bfseries
{
过拟合
}}
\index
{
过拟合
}
(Over Fitting)
\index
{
Over fitting
}
。为了避免这种现象,Transformer加入了Dropout操作
\cite
{
JMLR:v15:srivastava14a
}
。Transformer中这四个地方用到了Dropout:词嵌入和位置编码、残差连接、注意力操作和前馈神经网络。Dropout比例通常设置为
$
0
.
1
$
。
\vspace
{
0.5em
}
\item
{
\small\bfnew
{
标签平滑
}}
\index
{
标签平滑
}
(Label Smoothing)
\index
{
Label Smoothing
}
:在计算损失的过程中,需要用预测概率去拟合真实概率。在分类任务中,往往使用One-hot向量代表真实概率,即真实答案位置那一维对应的概率为1,其余维为0,而拟合这种概率分布会造成两个问题:1)无法保证模型的泛化能力,容易造成过拟合;2) 1和0概率鼓励所属类别和其他类别之间的差距尽可能加大,会造成模型过于相信预测的类别。因此Transformer里引入标签平滑
\cite
{
Szegedy
_
2016
_
CVPR
}
来缓解这种现象,简单的说就是给正确答案以外的类别分配一定的概率,而不是采用非0即1的概率。这样,可以学习一个比较平滑的概率分布,从而提升泛化能力。
\vspace
{
0.5em
}
\end{itemize}
\parinterval
不同的Transformer可以适应不同的任务,常见的Transformer模型有Transformer Base、Transformer Big和Transformer Deep
\cite
{
NIPS2017
_
7181,WangLearning
}
,具体设置如下:
\begin{itemize}
\vspace
{
0.5em
}
\item
Transformer Base:标准的Transformer结构,解码器编码器均包含6层,隐层维度为512,前馈神经网络维度为2048,多头注意力机制为8头,Dropout设为0.1。
\vspace
{
0.5em
}
\item
Transformer Big:为了提升网络的容量,使用更宽的网络。在Base的基础上增大隐层维度至1024,前馈神经网络的维度变为4096,多头注意力机制为16头,Dropout设为0.3。
\vspace
{
0.5em
}
\item
Transformer Deep:加深编码器网络层数可以进一步提升网络的性能,它的参数设置与Transformer Base基本一致,但是层数增加到48层,同时使用Pre-Norm作为层正则化的结构。
\vspace
{
0.5em
}
\end{itemize}
\parinterval
在WMT'16数据 上的实验对比如表
\ref
{
tab:6-13
}
所示。可以看出,Transformer Base的BLE
\\
U得分虽不如另外两种模型,但其参数量是最少的。而Transformer Deep的性能整体好于Transformer Big。
%----------------------------------------------
\begin{table}
[htp]
\centering
\caption
{
三种Transformer模型的对比
}
\label
{
tab:11-13
}
\begin{tabular}
{
l | l l l
}
\multirow
{
2
}{
*
}{
系统
}
&
\multicolumn
{
2
}{
c
}{
BLEU[
\%
]
}
&
\#
of
\\
&
EN-DE
&
EN-FR
&
params
\\
\hline
Transformer Base
&
27.3
&
38.1
&
65
$
\times
10
^{
6
}$
\\
Transformer Big
&
28.4
&
41.8
&
213
$
\times
10
^{
6
}$
\\
Transformer Deep(48层)
&
30.2
&
43.1
&
194
$
\times
10
^{
6
}$
\\
\end{tabular}
\end{table}
%----------------------------------------------
%----------------------------------------------------------------------------------------
% NEW SUB-SECTION 11.2.9
%----------------------------------------------------------------------------------------
\subsection
{
推断
}
\parinterval
Transformer解码器生成目标语的过程和前面介绍的循环网络翻译模型类似,都是从左往右生成,且下一个单词的预测依赖已经生成的上一个单词。其具体推断过程如图
\ref
{
fig:11-56
}
所示,其中
$
\mathbf
{
C
}_
i
$
是编码-解码注意力的结果,解码器首先根据``<eos>''和
$
\mathbf
{
C
}_
1
$
生成第一个单词``how'',然后根据``how''和
$
\mathbf
{
C
}_
2
$
生成第二个单词``are'',以此类推,当解码器生成``<eos>''时结束推断。
\parinterval
但是,Transformer在推断阶段无法对所有位置进行并行化操作,因为对于每一个目标语单词都需要对前面所有单词进行注意力操作,因此它推断速度非常慢。可以采用的加速手段有:低精度
\cite
{
DBLP:journals/corr/CourbariauxB16
}
、Cache(缓存需要重复计算的变量)
\cite
{
DBLP:journals/corr/abs-1805-00631
}
、共享注意力网络等
\cite
{
Xiao2019SharingAW
}
。关于Transformer模型的推断技术将会在第七章进一步深入介绍。
%----------------------------------------------
\begin{figure}
[htp]
\centering
\input
{
./Chapter11/Figures/figure-decode-of-transformer
}
\caption
{
Transformer推断过程示例
}
\label
{
fig:11-56
}
\end{figure}
%----------------------------------------------
Chapter12/Figures/figure-example-of-context-vector-calculation-process.tex
查看文件 @
324f2259
...
...
@@ -105,8 +105,13 @@
{
% coverage score formula node
\node
[anchor=north west] (formula) at ([xshift=-0.3
\hnode
,yshift=-1.5
\hnode
]attn11.south)
{
\small
{
不同
$
\vectorn
{
\emph
{
C
}}_
j
$
所对应的源语言词的权重是不同的
}}
;
<<<<<<< Chapter12/Figures/figure-example-of-context-vector-calculation-process.tex
\node
[anchor=north west] (example) at (formula.south west)
{
\footnotesize
{$
\vectorn
{
\emph
{
C
}}_
2
=
0
.
4
\times
{{
h
}}
(
\textrm
{
你
}
)
+
0
.
4
\times
{{
h
}}
(
\textrm
{
什么
}
)
+
0
\times
{{
h
}}
(
\textrm
{
都
}
)
$}}
;
\node
[anchor=north west] (example2) at ([yshift=0.4em]example.south west)
{
\footnotesize
{$
\ \ \ \ \ \ \ \
+
0
.
1
\times
{{
h
}}
(
\textrm
{
没
}
)
+
..
$}}
;
=======
\node
[anchor=north west] (example) at (formula.south west)
{
\footnotesize
{$
\vectorn
{
\emph
{
C
}}_
2
=
0
.
4
\times
\vectorn
{
\emph
{
h
}}
(
\textrm
{
他
}
)
+
0
.
4
\times
\vectorn
{
\emph
{
h
}}
(
\textrm
{
什么
}
)
+
$}}
;
\node
[anchor=north west] (example2) at ([yshift=0.4em]example.south west)
{
\footnotesize
{$
\ \ \ \ \ \ \ \
0
\times
\vectorn
{
\emph
{
h
}}
(
\textrm
{
都
}
)
+
0
.
1
\times
\vectorn
{
\emph
{
h
}}
(
\textrm
{
没
}
)
+
..
$}}
;
>>>>>>> Chapter12/Figures/figure-example-of-context-vector-calculation-process.tex
}
%\visible<3->
...
...
Chapter12/Figures/figure-matrix-representation-of-attention-weights-between-chinese-english-sentence-pairs.tex
查看文件 @
324f2259
%-------------------------------------------
\begin{tikzpicture}
...
...
@@ -17,7 +13,7 @@
\tikzstyle
{
labelnode
}
= [above]
% alignment matrix
\begin{scope}
[scale=
0.9
,yshift=0.12in]
\begin{scope}
[scale=
1.2
,yshift=0.12in]
\foreach
\i
/
\j
/
\c
in
{
0/7/0.2, 1/7/0.45, 2/7/0.15, 3/7/0.15, 4/7/0.15, 5/7/0.15,
0/6/0.35, 1/6/0.45, 2/6/0.15, 3/6/0.15, 4/6/0.15, 5/6/0.15,
...
...
@@ -27,7 +23,7 @@
0/2/0.15, 1/2/0.15, 2/2/0.15, 3/2/0.15, 4/2/0.25, 5/2/0.3,
0/1/0.15, 1/1/0.15, 2/1/0.15, 3/1/0.15, 4/1/0.8, 5/1/0.15,
0/0/0.15, 1/0/0.15, 2/0/0.15, 3/0/0.15, 4/0/0.25, 5/0/0.60
}
\node
[elementnode,minimum size=0.
6
*1.2cm*\c,inner sep=0.1pt,fill=blue]
(a
\i\j
) at (0.5*1.2cm*
\i
-5.4*0.5*1.2cm,0.5*1.2cm*
\j
-1.05*1.2cm)
{}
;
\node
[elementnode,minimum size=0.
8
*1.2cm*\c,inner sep=0.1pt,fill=blue]
(a
\i\j
) at (0.5*1.2cm*
\i
-5.4*0.5*1.2cm,0.5*1.2cm*
\j
-1.05*1.2cm)
{}
;
%attention score labels
\node
[align=center]
(l17) at (a17)
{
\scriptsize
{{
\color
{
white
}
.4
}}}
;
...
...
@@ -40,22 +36,22 @@
\node
[align=center]
(l17) at (a50)
{
\small
{{
\color
{
white
}
.7
}}}
;
% source
\node
[srcnode]
(src1) at (-5.4*0.5*1.2cm,-1.05*1.2cm+7.5*0.5*1.2cm)
{
\s
criptsize
{
Have
}}
;
\node
[srcnode]
(src2) at ([xshift=0.6cm]src1.south west)
{
\s
criptsize
{
you
}}
;
\node
[srcnode]
(src3) at ([xshift=0.6cm]src2.south west)
{
\s
criptsize
{
learned
}}
;
\node
[srcnode]
(src4) at ([xshift=0.6cm]src3.south west)
{
\s
criptsize
{
nothing
}}
;
\node
[srcnode]
(src5) at ([xshift=0.6cm]src4.south west)
{
\s
criptsize
{
?
}}
;
\node
[srcnode]
(src6) at ([xshift=0.6cm]src5.south west)
{
\s
criptsize
{$
\langle
$
eos
$
\rangle
$}}
;
\node
[srcnode]
(src1) at (-5.4*0.5*1.2cm,-1.05*1.2cm+7.5*0.5*1.2cm)
{
\s
mall
{
Have
}}
;
\node
[srcnode]
(src2) at ([xshift=0.6cm]src1.south west)
{
\s
mall
{
you
}}
;
\node
[srcnode]
(src3) at ([xshift=0.6cm]src2.south west)
{
\s
mall
{
learned
}}
;
\node
[srcnode]
(src4) at ([xshift=0.6cm]src3.south west)
{
\s
mall
{
nothing
}}
;
\node
[srcnode]
(src5) at ([xshift=0.6cm]src4.south west)
{
\s
mall
{
?
}}
;
\node
[srcnode]
(src6) at ([xshift=0.6cm]src5.south west)
{
\s
mall
{$
\langle
$
eos
$
\rangle
$}}
;
% target
\node
[tgtnode]
(tgt1) at (-6.0*0.5*1.2cm,-1.05*1.2cm+7.5*0.5*1.2cm)
{
\s
criptsize
{
你
}}
;
\node
[tgtnode]
(tgt2) at ([yshift=-0.6cm]tgt1.north east)
{
\s
criptsize
{
什么
}}
;
\node
[tgtnode]
(tgt3) at ([yshift=-0.6cm]tgt2.north east)
{
\s
criptsize
{
都
}}
;
\node
[tgtnode]
(tgt4) at ([yshift=-0.6cm]tgt3.north east)
{
\s
criptsize
{
没
}}
;
\node
[tgtnode]
(tgt5) at ([yshift=-0.6cm]tgt4.north east)
{
\s
criptsize
{
学
}}
;
\node
[tgtnode]
(tgt6) at ([yshift=-0.6cm]tgt5.north east)
{
\s
criptsize
{
到
}}
;
\node
[tgtnode]
(tgt7) at ([yshift=-0.6cm]tgt6.north east)
{
\s
criptsize
{
?
}}
;
\node
[tgtnode]
(tgt8) at ([yshift=-0.6cm]tgt7.north east)
{
\s
criptsize
{$
\langle
$
eos
$
\rangle
$}}
;
\node
[tgtnode]
(tgt1) at (-6.0*0.5*1.2cm,-1.05*1.2cm+7.5*0.5*1.2cm)
{
\s
mall
{
你
}}
;
\node
[tgtnode]
(tgt2) at ([yshift=-0.6cm]tgt1.north east)
{
\s
mall
{
什么
}}
;
\node
[tgtnode]
(tgt3) at ([yshift=-0.6cm]tgt2.north east)
{
\s
mall
{
都
}}
;
\node
[tgtnode]
(tgt4) at ([yshift=-0.6cm]tgt3.north east)
{
\s
mall
{
没
}}
;
\node
[tgtnode]
(tgt5) at ([yshift=-0.6cm]tgt4.north east)
{
\s
mall
{
学
}}
;
\node
[tgtnode]
(tgt6) at ([yshift=-0.6cm]tgt5.north east)
{
\s
mall
{
到
}}
;
\node
[tgtnode]
(tgt7) at ([yshift=-0.6cm]tgt6.north east)
{
\s
mall
{
?
}}
;
\node
[tgtnode]
(tgt8) at ([yshift=-0.6cm]tgt7.north east)
{
\s
mall
{$
\langle
$
eos
$
\rangle
$}}
;
\end{scope}
...
...
Chapter12/Figures/figure-query-model-corresponding-to-attention-mechanism.tex
查看文件 @
324f2259
...
...
@@ -12,17 +12,17 @@
\tikzstyle
{
rnode
}
= [draw,minimum width=3.5em,minimum height=1.2em]
\node
[rnode,anchor=south west,fill=red!20!white] (value1) at (0,0)
{
\scriptsize
{$
\vectorn
{
\emph
{
h
}}
(
\textrm
{
“你”
}
)
$}}
;
\node
[rnode,anchor=south west,fill=red!20!white] (value2) at ([xshift=1em]value1.south east)
{
\scriptsize
{$
\vectorn
{
\emph
{
h
}}
(
\textrm
{
“什么”
}
)
$}}
;
\node
[rnode,anchor=south west,fill=red!20!white] (value3) at ([xshift=1em]value2.south east)
{
\scriptsize
{$
\vectorn
{
\emph
{
h
}}
(
\textrm
{
“也”
}
)
$}}
;
\node
[rnode,anchor=south west,fill=red!20!white] (value4) at ([xshift=1em]value3.south east)
{
\scriptsize
{$
\vectorn
{
\emph
{
h
}}
(
\textrm
{
“没”
}
)
$}}
;
\node
[rnode,anchor=south west,fill=red!20!white] (value1) at (0,0)
{
\scriptsize
{$
{{
h
}}
(
\textrm
{
你
}
)
$}}
;
\node
[rnode,anchor=south west,fill=red!20!white] (value2) at ([xshift=1em]value1.south east)
{
\scriptsize
{$
{{
h
}}
(
\textrm
{
什么
}
)
$}}
;
\node
[rnode,anchor=south west,fill=red!20!white] (value3) at ([xshift=1em]value2.south east)
{
\scriptsize
{$
{{
h
}}
(
\textrm
{
也
}
)
$}}
;
\node
[rnode,anchor=south west,fill=red!20!white] (value4) at ([xshift=1em]value3.south east)
{
\scriptsize
{$
{{
h
}}
(
\textrm
{
没
}
)
$}}
;
\node
[rnode,anchor=south west,fill=green!20!white] (key1) at ([yshift=0.2em]value1.north west)
{
\scriptsize
{$
\vectorn
{
\emph
{
h
}}
(
\textrm
{
“你”
}
)
$}}
;
\node
[rnode,anchor=south west,fill=green!20!white] (key2) at ([yshift=0.2em]value2.north west)
{
\scriptsize
{$
\vectorn
{
\emph
{
h
}}
(
\textrm
{
“什么”
}
)
$}}
;
\node
[rnode,anchor=south west,fill=green!20!white] (key3) at ([yshift=0.2em]value3.north west)
{
\scriptsize
{$
\vectorn
{
\emph
{
h
}}
(
\textrm
{
“也”
}
)
$}}
;
\node
[rnode,anchor=south west,fill=green!20!white] (key4) at ([yshift=0.2em]value4.north west)
{
\scriptsize
{$
\vectorn
{
\emph
{
h
}}
(
\textrm
{
“没”
}
)
$}}
;
\node
[rnode,anchor=south west,fill=green!20!white] (key1) at ([yshift=0.2em]value1.north west)
{
\scriptsize
{$
{{
h
}}
(
\textrm
{
你
}
)
$}}
;
\node
[rnode,anchor=south west,fill=green!20!white] (key2) at ([yshift=0.2em]value2.north west)
{
\scriptsize
{$
{{
h
}}
(
\textrm
{
什么
}
)
$}}
;
\node
[rnode,anchor=south west,fill=green!20!white] (key3) at ([yshift=0.2em]value3.north west)
{
\scriptsize
{$
{{
h
}}
(
\textrm
{
也
}
)
$}}
;
\node
[rnode,anchor=south west,fill=green!20!white] (key4) at ([yshift=0.2em]value4.north west)
{
\scriptsize
{$
{{
h
}}
(
\textrm
{
没
}
)
$}}
;
\node
[rnode,anchor=east] (query) at ([xshift=-2em]key1.west)
{
\scriptsize
{$
\vectorn
{
\emph
{
s
}}
(
\textrm
{
“you”
}
)
$}}
;
\node
[rnode,anchor=east] (query) at ([xshift=-2em]key1.west)
{
\scriptsize
{$
{{
s
}}
(
\textrm
{
you
}
)
$}}
;
\node
[anchor=east] (querylabel) at ([xshift=-0.2em]query.west)
{
\scriptsize
{
query
}}
;
\draw
[->] ([yshift=1pt,xshift=6pt]query.north) .. controls +(90:1em) and +(90:1em) .. ([yshift=1pt]key1.north);
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论