本章主要记录卷积神经网络(及其常见的附加操作,例如池化操作)的原理,及其在机器翻译中的使用方法。此外,在附录中,会填补前面在写RNN相关文章时的埋的坑QuasiRNN和SRU。卷积神经网络(CNN)在机器翻译上的应用比较尴尬,其提出时模型效果与RNN相比没有太过明显的优势(经典测试集WMT2014英德比SOTA高0.5个点),然后不到半年时间Transformer横空出世,带走了所有研究学者的目光,因此其在机器翻译领域地位稍显尴尬。此外笔者加速了计划的写作进度,所以对CNN这种在整个深度学习领域比较重要的网络结构本笔记不会写得特别深入,相比前面各章也省去了一些内容(例如TF的实现)。有需要的读者还需要参考其它资料
本文以CS224n 2019Winter的讲义、2020Winter的幻灯片和CS231讲义为主,涉及具体工作的内容来源于原始论文
本小节额外参考如何通俗易懂地解释卷积? - 马同学的回答 - 知乎
卷积本来是一个在泛函中的一个抽象概念。对函数 f f f和 g g g,两者卷积的连续定义为
( f ∗ g ) ( n ) = ∫ − ∞ ∞ f ( τ ) g ( n − τ ) d τ (f \ast g)(n) = \int_{-\infty}^{\infty}f(\tau)g(n-\tau)d\tau (f∗g)(n)=∫−∞∞f(τ)g(n−τ)dτ
离散卷积定义为
( f ∗ g ) ( n ) = ∑ τ = − ∞ ∞ f ( τ ) g ( n − τ ) (f \ast g)(n) = \sum_{\tau = -\infty}^\infty f(\tau)g(n-\tau) (f∗g)(n)=τ=−∞∑∞f(τ)g(n−τ)
在比较简单的离散卷积定义下,可以看一个比较直观的例子:假设有两枚骰子,掷出以后,两者点数加起来为4的概率是多少?记 f ( x ) f(x) f(x)为第一枚骰子投出 x x x的概率, g ( x ) g(x) g(x)为第二枚骰子掷出 x x x的概率,则两枚骰子点数加起来为4的概率为
f ( 1 ) g ( 3 ) + f ( 2 ) g ( 2 ) + f ( 3 ) g ( 1 ) f(1)g(3) + f(2)g(2) + f(3)g(1) f(1)g(3)+f(2)g(2)+f(3)g(1)
这就符合了离散卷积的定义,写成标准的形式就是
( f ∗ g ) ( 4 ) = ∑ m = 1 3 f ( 4 − m ) g ( m ) (f \ast g)(4) = \sum_{m=1}^3 f(4-m)g(m) (f∗g)(4)=m=1∑3f(4−m)g(m)
从前述的例子中可以看出,卷积操作蕴含了一些滑动窗口的思想在里面,而具体应用于CNN时,这种原理体现得更加明显。在CNN中,最简单的情况是使用一个小的二维矩阵(称之为卷积核kernel,通常大小为 3 × 3 3 \times 3 3×3或 5 × 5 5 \times 5 5×5)作用在一个输入上。在经典的CV领域中,输入一般也是一个二维矩阵。在这种情况下,记卷积核矩阵为 K ∈ R m × n \boldsymbol{K} \in \mathbb{R}^{m \times n} K∈Rm×n(通常 m = n m = n m=n),输入矩阵为 X ∈ R M × N \boldsymbol{X} \in \mathbb{R}^{M \times N} X∈RM×N,结果矩阵为 Y \boldsymbol{Y} Y,则计算二维卷积的过程可以描述为
不难得到,当 s = 1 s=1 s=1时,结果矩阵 Y ∈ R ( M − m + 1 ) × ( N − n + 1 ) \boldsymbol{Y} \in \mathbb{R}^{(M - m + 1) \times (N - n + 1)} Y∈R(M−m+1)×(N−n+1)
前述二维卷积操作是最基础的卷积操作。实际应用时,通常会在以下方面做扩展
本部分完全来自于CS231n讲义
通常来讲,二维卷积一般都是
每个卷积核共有 F × F × D 1 F \times F \times D_1 F×F×D1个参数,该滤波器共有 F × F × D 1 × K F \times F \times D_1 \times K F×F×D1×K个参数和 K K K个偏置
下面这个动图展示了输入维度为 5 × 5 × 3 5 \times 5 \times 3 5×5×3, K = 2 , F = 3 , S = 2 , P = 1 K=2, F=3, S=2, P=1 K=2,F=3,S=2,P=1的二维卷积计算过程
在NLP问题中,如果不考虑batch的大小(即认为batch为1),输入可以看做是一个 N × d N \times d N×d的矩阵,其中每一行都是一个词向量。此时一般是认为输入有 d d d个信道,使用长度为 n n n( n n n一般为2到4)的一维卷积核进行卷积操作。其效果等同于对矩阵使用 n × d n \times d n×d的二维卷积核进行操作(此时只有一个信道)
卷积的最大作用是通过参数共享减少了参数数量。在最早的ImageNet 2012优胜方案中,其输入图像大小为 227 × 227 × 3 227 \times 227 \times 3 227×227×3,使用的卷积核大小 F = 11 F = 11 F=11,步长 S = 4 S = 4 S=4,滤波器大小为 K = 96 K = 96 K=96。由于 ( 227 − 11 ) / 4 + 1 = 55 (227 - 11) / 4 + 1 = 55 (227−11)/4+1=55,因此输出大小为 55 × 55 × 96 55 \times 55 \times 96 55×55×96。如果没有参数共享,每个感受野(即卷积核每次能“看到”的部分)使用不同参数,则该网络需要的参数为 11 × 11 × 3 × 55 × 55 × 96 = 105 , 705 , 600 11 \times 11 \times 3 \times 55 \times 55 \times 96 = 105,705,600 11×11×3×55×55×96=105,705,600。但是,一种符合直觉的假设是,如果一个特征在某个空间位置 ( x , y ) (x, y) (x,y)起作用,那么它对另一个位置 ( x 2 , y 2 ) (x_2, y_2) (x2,y2)也可以起作用,即对于同一个卷积核,可以用相同的权重生成该卷积核对应的所有像素点,不同的卷积核才使用不同权重。也就是说,同一个卷积核输出的像素点共享相同权重。这样,该层的参数数量就会降到 11 × 11 × 3 × 96 = 34 , 848 11 \times 11 \times 3 \times 96 = 34,848 11×11×3×96=34,848
上述解释是从模型复杂度角度对卷积意义进行的介绍。从应用原理来看,对于NLP问题,卷积操作的实际意义是它提取了句子中所有n元组信息。例如,当卷积核大小为2时,提取的就是所有二元组信息
在卷积神经网络中,卷积的输出通常会接一个池化操作(pooling),以减少特征数量,降低网络计算复杂度,防止过拟合。不正式地讲,常见的池化就是将输入特征划分为若干个不相交的部分,对每个部分的所有元素做一个聚合操作
按照CS231讲义对池化的介绍,其操作原理与卷积类似,对 W 1 × H 1 × D 1 W_1 \times H_1 \times D_1 W1×H1×D1的输入,池化操作的尺寸为 F F F,步长为 S S S,池化后的结果大小为 W 2 × H 2 × D 1 W_2 \times H_2 \times D_1 W2×H2×D1,其中
不过有三个不同
常见的池化操作就是求所有元素的最大值(即最大池化)。之前也有工作尝试其他聚合方法,例如求均值,但是效果不如最大值好
更多的池化操作,可见卷积神经网络中的各种池化操作 - pprp的文章 - 知乎
(个人不太喜欢池化这个翻译。pool在英语里本就有汇集的意思,翻译成池化,感觉容易让人不明就里)
比较经典的CNN结构大致遵循如下模式
INPUT -> [[CONV -> RELU] * N -> POOL?] * M -> [FC -> RELU] * K -> FC
其中CONV
是卷积操作,RELU
是ReLU激活函数,POOL
是池化操作(不必须),FC
是全连接操作。不过,近几年的新CNN架构通常更加复杂
ByteNet[bytenet]体系结构于2016年提出,是一种字符级翻译模型,尝试使用一维卷积神经网络解决机器翻译问题,其中解码器部分使用了掩码来防止网络在训练时看到目标序列中未解码出的部分。ByteNet的运行时间与源句和目标句的长度呈线性关系,效率相对较高
与传统编码器-解码器结构不同,ByteNet使用了一种独特的结构,将解码器堆叠在编码器之上,其目的按照文章的说法是为了“增加表示带宽”。在实际实现上,解码器的每个时间步的输入都对应编码器该时间步的输出。然而,机器翻译不能保证解码器的输出长度一定小于等于编码器的输出长度,按照前述设计,如果解码的句子长度长于编码器的输入,有一些标识符应该是没有对应输入的。文章使用动态展开(dynamic unfolding)来解决这个问题。首先,模型需要根据源句长度预估一个宽松的目标句句长上限,文章中使用线性函数估计,即给定源句 s \boldsymbol{s} s,记其长度为 ∣ s ∣ |\boldsymbol{s}| ∣s∣,目标句预估长度 ∣ t ^ ∣ = a ∣ s ∣ + b |\hat{\boldsymbol{t}}| = a|\boldsymbol{s}| + b ∣t^∣=a∣s∣+b。对英德翻译,一般设置 a = 1.2 , b = 0 a = 1.2, b = 0 a=1.2,b=0。解码时,如果出现EOS符,则解码停止。
(这里有两个问题:首先,原文图示举例了三种情况,其中一种解码长度甚至超出了 ∣ t ^ ∣ |\hat{\boldsymbol{t}}| ∣t^∣,这说明解码器在没有输入的时候也可以根据前一步的状态解码,那 ∣ t ^ ∣ |\hat{\boldsymbol{t}}| ∣t^∣预估有何用?其次,解码器从哪儿接收时间步在 ∣ s ∣ + 1 |{\boldsymbol{s}}| + 1 ∣s∣+1和 ∣ t ^ ∣ |\hat{\boldsymbol{t}}| ∣t^∣之间的表示?补0?)
ByteNet没有使用池化层进行降采样,而是使用了空洞卷积(dilated convolution,又称膨胀卷积、扩张卷积)来增大感受野的范围。所谓空洞卷积,可以理解为卷积核在覆盖的感受野内每隔一个间隔选取一个像素作为输入。例如,空洞率为1时, 3 × 3 3 \times 3 3×3的卷积核的感受野范围是 5 × 5 5 \times 5 5×5的,只不过只接受 X 0 , 0 , X 0 , 2 , X 0 , 4 , X 2 , 0 , X 2 , 2 , X 2 , 4 , X 4 , 0 , X 4 , 2 , X 4 , 4 X_{0, 0}, X_{0, 2}, X_{0, 4}, X_{2, 0}, X_{2, 2}, X_{2, 4}, X_{4, 0}, X_{4, 2}, X_{4, 4} X0,0,X0,2,X0,4,X2,0,X2,2,X2,4,X4,0,X4,2,X4,4作为输入。在ByteNet的设计中,最底层的空洞率为1,每高一层空洞率翻倍,封顶16
下图左给出了ByteNet的结构示意图。ByteNet的解码器每层封装成一个残差块,如下图右所示
每个残差块包含三个卷积操作,可以分为两种
每个卷积操作接收的输入都先经过一个层归一化和一个ReLU激活
ByteNet可以在 ∣ S ∣ + ∣ T ∣ |\boldsymbol{S}| + |\boldsymbol{T}| ∣S∣+∣T∣的时间内完成解码。此外,由于空洞卷积可以将解码器任意时间步的输入标识符和输出标识符相连,ByteNet中解码器任意时刻输入和任意时刻输出的最短路径的最大值也是一个常数,可以有效避免路径过长导致网络不能捕捉长距离依赖关系的现象
ConvS2S[convs2s]同样也是设计了一种基于卷积神经网络的编码器-解码器结构,用来解决机器翻译任务。文章认为分层的卷积神经网络中,底层可以捕捉句子的局部信息,上层可以捕捉句子中的长距离信息。对长度为 n n n的窗口,使用大小为 k k k的卷积核,只需要 O ( n k ) O(\frac{n}{k}) O(kn)次卷积操作就可以获得该窗口的特征表示,而RNN需要 O ( n ) O(n) O(n)次操作,因此CNN的效率要高一些
ConvS2S的模型结构有两个重要的组成部分,分别是卷积层和多步注意力机制。为方便起见,先约定:对模型的第 l l l层,解码器的输出为 H l = ( h 1 l , … , h n l ) \boldsymbol{H}^l = (\boldsymbol{h}_1^l, \ldots, \boldsymbol{h}_n^l) Hl=(h1l,…,hnl),编码器的输出为 Z l = ( z 1 l , … , z m l ) \boldsymbol{Z}^l = (\boldsymbol{z}_1^l, \ldots, \boldsymbol{z}_m^l) Zl=(z1l,…,zml)
总体而言,ConvS2S也是分层架构,每一层(也称为块)内部结构都相等,包括一个大小为 k k k的一维卷积,接一个非线性变换。一维卷积接收的输入为 X ∈ R k × d \boldsymbol{X} \in \mathbb{R}^{k \times d} X∈Rk×d,文章所说卷积核大小为 W ∈ R 2 d × k d \boldsymbol{W} \in \mathbb{R}^{2d \times kd} W∈R2d×kd,但是这里个人感觉容易引起误会。按照笔者理解,这里应该是使用了一个大小为 k k k的一维卷积核, 2 d 2d 2d个卷积核一起组成了一个滤波器,最后将输入投射到一个 2 d × 1 2d \times 1 2d×1的向量(按照文章的说法,投射后应该还要再加一个偏置)
然而,网络输入每个位置的维度都是 d d d维,这样卷积以后变成了 2 d 2d 2d维,如果不做处理,每一层都会将维数翻倍。因此ConvS2S使用了一个相对ReLU等常见激活函数来讲比较复杂的非线性变换,称之为门控线性单元(gated linear unit)。对卷积操作的输出 c = [ a b ] ∈ R 2 d \boldsymbol{c} = [\boldsymbol{a}\ \boldsymbol{b}] \in \mathbb{R}^{2d} c=[a b]∈R2d,有
G L U ( [ a b ] ) = a ⊙ σ ( b ) {\rm GLU}([\boldsymbol{a}\ \boldsymbol{b}]) = \boldsymbol{a} \odot \sigma(\boldsymbol{b}) GLU([a b])=a⊙σ(b)
其中 a , b ∈ R d \boldsymbol{a}, \boldsymbol{b} \in \mathbb{R}^d a,b∈Rd(即将输出对半分), ⊙ \odot ⊙是Hadamard乘积操作(逐元素相乘)
在卷积块的基础上,根据之前训练深度卷积神经网络的经验,ConvS2S也使用了残差连接。此外,对解码器,在输入的左端和右端各补了 k − 1 k-1 k−1个0向量,并移除了卷积输出的最后 k − 1 k-1 k−1个元素,来防止解码器偷看到未来时间步的信息。(注意:原始论文写的是删除最后 k k k个元素,但是理论分析感觉这里有错,会导致解码输出随着层数升高而变少。检查fairseq实现代码发现的确应该移除最后 k − 1 k-1 k−1个元素)。例如,假设卷积核大小 T1 T2 T3 T4 T5 T1k=3
,解码器对应的输入一共有5个元素,那么经过pad,就会变成
,第一次卷积看到的元素只有
,看不到后面的信息,因此不会被后面的元素干扰。该层输入传递到下一层后,在前面又会继续补padding,后面的信息还是会被屏蔽,直到最后输出
分层卷积的引入使得上层能接受更广的输入信息。对于一个6层的编码器,假设卷积核大小为5,那么第一层做第一次卷积操作时能看到第1到第5个标识符,做第二次卷积操作能看到第2到第6个标识符,以此类推,第一层做第五次卷积操作能看到第5到第9个标识符。由于第二层第一次卷积看到的是第一层前5个输出,因此实际上第二层第一次卷积接受的信息就来自于第1到第9个标识符,以此类推,第六层第一次卷积接收的信息就是前25个标识符,这是一个相当大的感受范围
在ConvS2S中,解码器的每一层都有一个独立的注意力计算。计算注意力时,把当前的解码器状态 h i l \boldsymbol{h}_i^l hil和前一个目标单元的词嵌入 g i \boldsymbol{g}_i gi相加。由于训练时解码器的输出和输入有错一位的关系,因此其实 g i \boldsymbol{g}_i gi就是在第 i i i步解码器的输入。最后的综合表示如下
d i l = W d l h i l + b d l + g i \boldsymbol{d}^l_i = \boldsymbol{W}_d^l\boldsymbol{h}_i^l + \boldsymbol{b}_d^l + \boldsymbol{g}_i dil=Wdlhil+bdl+gi
该综合表示与编码器最后一层 u u u的所有输出一起计算注意力。对编码器的第 j j j个位置,注意力得分为
a i j l = exp ( d i l ⋅ z j u ) ∑ t = 1 m exp ( d i l ⋅ z t u ) a_{ij}^l = \frac{\exp(\boldsymbol{d}_i^l \cdot \boldsymbol{z}_j^u)}{\sum_{t=1}^m \exp(\boldsymbol{d}_i^l \cdot \boldsymbol{z}_t^u)} aijl=∑t=1mexp(dil⋅ztu)exp(dil⋅zju)
编码器各位置的输出先和对应位置的所有输入嵌入相加,然后按照注意力得分加权求和
c i l = ∑ j = 1 m a i j l ( z j u + e j ) \boldsymbol{c}_i^l = \sum_{j=1}^m a_{ij}^l(\boldsymbol{z}^u_j + \boldsymbol{e}_j) cil=j=1∑maijl(zju+ej)
这里和RNN不同的是输入嵌入也参与了计算。实践表明 e j \boldsymbol{e}_j ej的引入是有效的,起到了类似键值对记忆网络的作用,此时键为 z j u \boldsymbol{z}_j^u zju,值为 z j u + e j \boldsymbol{z}_j^u + \boldsymbol{e}_j zju+ej。其中, z j u \boldsymbol{z}_j^u zju编码了输入上下文, e j \boldsymbol{e}_j ej提供了单点信息,蕴含一个指定的输入元素
c i l \boldsymbol{c}_i^l cil计算好以后,和解码器的原始输出 h i l \boldsymbol{h}_i^l hil相加,作为下一层的输入
由于卷积操作的存在,每高一层都会额外考虑前面 k − 1 k-1 k−1步的注意力历史,因此网络有更强的记忆力
除去卷积层和多步注意力,ConvS2S还有一些其它特殊的设计细节
位置嵌入。卷积操作的引入不可避免地淡化了词之间的顺序关系。为了应对这一点,ConvS2S不仅对所有输入标识符学习一个嵌入矩阵,还对所有位置学习一个嵌入矩阵。即编码器的第一层输入中,每个输入向量都是对应位置标识符嵌入和位置嵌入的加和,即
e i = w i + p i \boldsymbol{e}_i = \boldsymbol{w}_i + \boldsymbol{p}_i ei=wi+pi
解码器第一层的输入向量 g i \boldsymbol{g}_i gi也以类似方式计算
初始化策略。好的初始化策略可以使得前后向传播时激活函数的输出稳定,即方差比较小。具体策略如下
归一化策略。除去精心设计的初始化策略,归一化是使模型稳定训练的另一种方法。ConvS2S主要是对残差块的输出和注意力的输出做缩放来保持激活结果的不变性
下图给出了ConvS2S的模型架构。在实践中,使用深层,小卷积核(宽度为3)的模型架构效果较好
FAIR在ICLR 2019提出的工作[wu2019]意在取代Transformer(在后面一章详细介绍)中编码器和解码器使用的自注意力模块。文章提出了两种体系结构,分别是轻量卷积(lightweight convolution)和动态卷积(dynamic convolution)。
轻量卷积的核心是深度卷积(depthwise convolution)。前面提到过,卷积操作一般都是对所有信道同时计算,即对于 k × k k \times k k×k,信道数为 D 1 D_1 D1的卷积核,这个卷积核的参数数量为 D 1 k 2 D_1k^2 D1k2,同时一般使用 D 2 D_2 D2个卷积核,即对于这样的滤波器,其总参数量为 D 1 D 2 k 2 D_1D_2k^2 D1D2k2。深度卷积的不同点在于,首先,一个卷积核只看一个信道,其次,输入输出信道数通常保持一致。这样,滤波器的参数数量降低到了 D 1 k 2 D_1k^2 D1k2个(对于一维卷积,就是 D 1 k D_1k D1k个)。轻量卷积的输入 X ∈ R n × d \boldsymbol{X} \in \mathbb{R}^{n \times d} X∈Rn×d( n n n为时间步数, d d d为输入输出维度),轻量卷积权重矩阵 W ∈ R d × k \boldsymbol{W} \in \mathbb{R}^{d \times k} W∈Rd×k,则对输出 O ∈ R n × d \boldsymbol{O}\in \mathbb{R}^{n \times d} O∈Rn×d第 c c c维的第 i i i个元素有
O i , c = D e p t h w i s e C o n v ( X , W c , : , i , c ) = ∑ j = 1 k W c , j ⋅ X ( i + j − ⌈ k + 1 2 ⌉ ) , c O_{i,c} = {\rm DepthwiseConv}(\boldsymbol{X}, \boldsymbol{W}_{c,:}, i, c) = \sum_{j=1}^k W_{c,j}\cdot X_{(i+j-\lceil \frac{k+1}{2} \rceil), c} Oi,c=DepthwiseConv(X,Wc,:,i,c)=j=1∑kWc,j⋅X(i+j−⌈2k+1⌉),c
(这里输入两端应该各补了一个pad)
轻量卷积在深度卷积基础上做了两个改进
s o f t m a x ( W ) h , j = exp { W h , j } ∑ j ′ = 1 k exp { W h , j ′ } {\rm softmax}({W})_{h,j} = \frac{\exp\{W_{h,j}\}}{\sum_{j'=1}^k \exp\{W_{h, j'}\}} softmax(W)h,j=∑j′=1kexp{ Wh,j′}exp{ Wh,j}
即
L i g h t C o n v ( X , W ⌈ c H d ⌉ , : , i , c ) = D e p t h w i s e C o n v ( X , s o f t m a x ( W ⌈ c H d ⌉ , : ) , i , c ) {\rm LightConv}(\boldsymbol{X}, \boldsymbol{W}_{\lceil \frac{cH}{d} \rceil, :}, i, c) = {\rm DepthwiseConv}(\boldsymbol{X}, {\rm softmax}(\boldsymbol{W}_{\lceil \frac{cH}{d}\rceil,:}), i, c) LightConv(X,W⌈dcH⌉,:,i,c)=DepthwiseConv(X,softmax(W⌈dcH⌉,:),i,c)
下图左给出了使用轻量卷积进行自编码的模型结构示意图。对给定的 d d d维输入,先使用一个线性变换将其映射到 2 d 2d 2d维空间,然后使用前述GLU做非线性变换再做轻量卷积操作。卷积的结果再进行一个 W O ∈ R d × d \boldsymbol{W}^O \in \mathbb{R}^{d \times d} WO∈Rd×d的线性映射。此外,进行轻量卷积时,还使用了DropConnect进行正则化,即在训练阶段对正则化后的权重矩阵,对其每个元素以概率 p p p丢弃,然后将矩阵除以 1 − p 1-p 1−p
由于轻量卷积大幅降低了卷积操作的参数数量,因此可以每一步使用单独的卷积核,即
D y n a m i c C o n v ( X , i , c ) = L i g h t C o n v ( X , f ( X i ) h , : , i , c ) f ( X i ) = ∑ c = 1 d W h , j , c Q X i , c , W Q ∈ R H × k × d \begin{aligned} {\rm DynamicConv}(\boldsymbol{X}, i, c) &= {\rm LightConv}(\boldsymbol{X}, f(\boldsymbol{X}_i)_{h,:}, i, c) \\ f({\boldsymbol{X}_i}) &= \sum_{c=1}^d W_{h,j,c}^QX_{i,c}, \boldsymbol{W}^Q \in \mathbb{R}^{H\times k\times d} \end{aligned} DynamicConv(X,i,c)f(Xi)=LightConv(X,f(Xi)h,:,i,c)=c=1∑dWh,j,cQXi,c,WQ∈RH×k×d
示意图如上图右所示
CNN的设计思想也影响了一些RNN的设计思路,这里主要介绍QuasiRNN[quasirnn]和SRU[sru]
QuasiRNN (QRNN) 设计的目标是同时解决RNN并行能力弱和CNN丢失位置信息的问题,其主要由两部分组成,分别为卷积层和池化层。卷积层的作用是提取n-gram特征,设置门控,而池化层的作用则是对门控加以利用
假设输入 X ∈ R T × n \boldsymbol{X} \in \mathbb{R}^{T \times n} X∈RT×n,其中 T T T是序列长度, n n n是输入向量维度,QNN在卷积层使用 m m m个大小为 k k k的卷积核,沿时间维度做一维卷积,产生 T × m {T \times m} T×m的输出序列。QRNN也使用了掩码操作,在序列的左边补了 k − 1 k-1 k−1个向量,防止诸如语言模型这样的任务偷看到未来时间步的内容。QRNN使用了三组卷积计算
Z = tanh ( W z ∗ X ) F = σ ( W f ∗ X ) O = σ ( W o ∗ X ) \begin{aligned} \boldsymbol{Z} &= \tanh (\boldsymbol{W}_z \ast \boldsymbol{X}) \\ \boldsymbol{F} &= \sigma (\boldsymbol{W}_f \ast \boldsymbol{X}) \\ \boldsymbol{O} &= \sigma (\boldsymbol{W}_o \ast \boldsymbol{X}) \end{aligned} ZFO=tanh(Wz∗X)=σ(Wf∗X)=σ(Wo∗X)
其中 W z \boldsymbol{W}_z Wz、 W f \boldsymbol{W}_f Wf和 W o \boldsymbol{W}_o Wo均为含 m m m个大小为 k k k的卷积核的滤波器, ∗ \ast ∗为卷积操作。当 k = 2 k=2 k=2时,上述三式与LSTM的计算方式比较像,但原文实验大部分是在字符级任务上进行,因此通常要设较大的 k k k
QRNN没有使用传统的最大池或者平均池,而是设计了三种“动态平均池”,与LSTM也有异曲同工之妙,分别为
其中第一个 h \boldsymbol{h} h或 c \boldsymbol{c} c被初始化为全零向量
基于NLP的研究进展,可以为QRNN引入诸多扩展。
正则化方法 RNN常见的正则化方法有基于变分推断的dropout和zoneout。由于QRNN没有循环连接所用的共享权重,因此不适合使用基于变分推断的方法。作者扩展了zoneout,对池化函数作了修改,对前一个池化状态,随机选择一个信道子集,保留它们不去改变。这也等价于修改QRNN的遗忘门
F = 1 − d r o p o u t ( 1 − σ ( W f ∗ X ) ) ) \boldsymbol{F} = 1-{\rm dropout}(1-\sigma (\boldsymbol{W}_f \ast \boldsymbol{X}))) F=1−dropout(1−σ(Wf∗X)))
如此实现,就可以不修改池化函数了。此外,在一些实验中,还尝试了在层与层之间加入传统的dropout
稠密连接层 对于句子分类任务,QRNN使用“稠密卷积”效果更佳,即在输入嵌入和所有层,以及各层之间建立连接。这相当于把每一层的输入和输出先沿信道维连接再送往下一层
编码器-解码器模型 QRNN可以做机器翻译任务,可以既用在编码器上也用在解码器上,但是需要对解码器做一些修改,因为直接将编码器最后的隐藏状态(即池化层的输出)送入解码器池化层不会影响该池化层接收的门控结果,进而限制解码器的表示能力。为此,将编码器的最后一个隐藏状态送入解码器各层卷积函数的输出,即先将隐藏状态做一个线性映射,然后将其广播,与卷积操作每一步的结果相加。记 h ~ T l \tilde{\boldsymbol{h}}_T^l h~Tl为第 l l l层编码器的最后状态,对解码器,有
Z l = tanh ( W z l ∗ X l + V z l h ~ T l ) F l = σ ( W f l ∗ X l + V f l h ~ T l ) O l = σ ( W o l ∗ X l + V o l h ~ T l ) \begin{aligned} \boldsymbol{Z}^l &= \tanh \left(\boldsymbol{W}_z^l \ast \boldsymbol{X}^l + \boldsymbol{V}_z^l\tilde{\boldsymbol{h}}_T^l\right) \\ \boldsymbol{F}^l &= \sigma \left(\boldsymbol{W}_f^l \ast \boldsymbol{X}^l + \boldsymbol{V}_f^l\tilde{\boldsymbol{h}}_T^l\right) \\ \boldsymbol{O}^l &= \sigma \left(\boldsymbol{W}_o^l \ast \boldsymbol{X}^l + \boldsymbol{V}_o^l\tilde{\boldsymbol{h}}_T^l\right) \end{aligned} ZlFlOl=tanh(Wzl∗Xl+Vzlh~Tl)=σ(Wfl∗Xl+Vflh~Tl)=σ(Wol∗Xl+Volh~Tl)
使用编码器最后一层和未经输出门处理的解码器最后一层输出计算注意力得分,然后通过输出门处理。若网络有 L L L层,则
α s t = s o f t m a x a l l s ( c t L ⋅ h ~ s L ) k t = ∑ s α s t h ~ s L h t L = o t ⊙ ( W k k t + W c c t L ) \begin{aligned} \alpha_{st} &= \mathop{\rm softmax}_{ {\rm all\ }s}\left(\boldsymbol{c}_t^L \cdot \tilde{\boldsymbol{h}}_s^L\right) \\ \boldsymbol{k}_t &= \sum_s \alpha_{st}\tilde{\boldsymbol{h}}_s^L \\ {\boldsymbol{h}}_t^L &= \boldsymbol{o}_t \odot \left(\boldsymbol{W}_k\boldsymbol{k}_t + \boldsymbol{W}_c\boldsymbol{c}_t^L\right) \end{aligned} αstkthtL=softmaxall s(ctL⋅h~sL)=s∑αsth~sL=ot⊙(Wkkt+WcctL)
SRU 另一种结构Simple Recurrent Unit (SRU) [sru]于2018年被独立提出,但是其原理可以看做是卷积核为1且加入high-way连接的QRNN