1 textRNN vs textCNN
1.尽管TextCNN能够在很多任务里面能有不错的表现,但CNN有个最大问题是固定filter的视野,一方面无法建模更长的序列信息,另一方面filter的超参调节也很繁琐。CNN本质是做文本的特征表达工作,而自然语言处理中更常用的是RNN,能够更好的表达上下文信息,尤其是RNN的一些变种,如LSTM(更常用),GRU。
2.文本分类任务中,CNN可以用来提取句子中类似N-gram的关键信息,适合短句子文本。TextRNN擅长捕获更长的序列信息。具体到文本分类任务中,常用的Bi-directional RNN(实际使用的是双向LSTM)从某种意义上可以理解为可以捕获变长且双向的的N-gram信息。
3.RNN模型在运行速度上丝毫不占优势,后一个时间步的输出依赖于前一个时间步的输出,无法进行并行处理,导致模型训练的速度慢,比CNN模型要慢几倍到十几倍,这是一个致命的弱点。而RNN模型引以为傲的能够捕获序列中的长距离依赖关系,只需要通过构建更深层的卷积层也可实现。更深层的卷积层可以捕获更长的序列信息。
2 RNN介绍
RNN的重要特性是可以处理不定长的输入,得到一定的输出。当你的输入可长可短,比如训练翻译模型的时句子长度都不固定,无法像一个训练固定像素的图像那样用CNN搞定的。而利用RNN的循环特性可以轻松搞定。
2.1 单向RNN
缺点:随着时间步骤长度的增大,它无法从差得很远的时间步骤中获得上下文环境。不步骤t+1无法了解步骤0和1中的表示。
1)
x
t
x^t
xt代表在序列索引号t时训练样本的输入。同样的,
x
t
−
1
x^{t-1}
xt−1和
x
t
+
1
x^{t+1}
xt+1代表在序列索引号t−1和t+1时训练样本的输入。
2)
h
t
h^t
ht代表在序列索引号t时模型的隐藏状态。
h
t
h^t
ht由
x
t
x^t
xt和
h
t
−
1
h^{t-1}
ht−1共同决定。
3)
o
t
o^t
ot代表在序列索引号t时模型的输出。
o
t
o^t
ot只由模型当前的隐藏状态
h
t
h^t
ht决定。
4)
L
t
L^t
Lt代表在序列索引号t时模型的损失函数。
5)
y
t
y^t
yt代表在序列索引号t时训练样本序列的真实输出。
6)U,W,V这三个矩阵是我们的模型的线性关系参数,它在整个RNN网络中是共享的,这点和DNN很不相同。 也正因为是共享了,它体现了RNN的模型的“循环反馈”的思想。
2.1.1 RNN前向传播算法
对于任意一个序列索引号t,我们隐藏状态
h
t
h^t
ht 由
x
t
x^t
xt 和
h
t
−
1
h^{t−1}
ht−1 得到:
h
t
=
σ
(
z
t
)
=
σ
(
U
x
t
+
W
h
t
−
1
+
b
)
h^t=σ(z^t )=σ(Ux^t+Wh^{t−1}+b)
ht=σ(zt)=σ(Uxt+Wht−1+b)
其中
σ
σ
σ 为RNN的激活函数,一般为tanh, b为线性关系的偏置。
序列索引号
t
t
t 时模型的输出
o
t
o^t
ot 的表达式比较简单:
o
t
=
V
h
t
+
c
o^t=Vh^t+c
ot=Vht+c
在最终在序列索引号
t
t
t 时我们的预测输出为:
y
^
t
=
σ
(
o
t
)
\hat{y}^t =σ(o^t)
y^t=σ(ot)
通常由于RNN是识别类的分类模型,所以上面这个激活函数一般是softmax。
通过损失函数
L
t
L^t
Lt,比如对数似然损失函数,我们可以量化模型在当前位置的损失,即
y
^
t
\hat{y}^t
y^t 和
y
^
t
\hat{y}^{t}
y^t 的差距。
2.1.2 RNN反向传播算法
RNN反向传播算法的思路和DNN是一样的,即通过梯度下降法进行的迭代,得到合适的RNN模型参数
U
,
W
,
V
,
b
,
c
U,W,V,b,c
U,W,V,b,c 。由于我们是基于时间反向传播,所以RNN的反向传播有时也叫做BPTT(back-propagation through time)。当然这里的BPTT和DNN也有很大的不同点,即这里所有的
U
,
W
,
V
,
b
,
c
U,W,V,b,c
U,W,V,b,c 在序列的各个位置是共享的,反向传播时我们更新的是相同的参数。
为了简化描述,这里的损失函数我们为交叉熵损失函数,输出的激活函数为softmax函数,隐藏层的激活函数为tanh函数。
对于RNN,由于我们在序列的每个位置都有损失函数,因此最终的损失L为:
L
=
∑
t
=
1
τ
L
(
t
)
L=\sum_{t=1}^τ{L^{(t)}}
L=∑t=1τL(t),
其中
V
,
c
,
V,c,
V,c, 的梯度计算是比较简单的:
∂
L
∂
c
=
∑
t
=
1
τ
∂
L
(
t
)
∂
c
=
∑
t
=
1
τ
y
^
(
t
)
−
y
(
t
)
\frac{∂L}{∂c}=\sum_{t=1}^τ \frac{∂L^{(t)}}{∂c}=\sum_{t=1}^τ \hat{y}^{(t)}−y^{(t)}
∂c∂L=t=1∑τ∂c∂L(t)=t=1∑τy^(t)−y(t)
∂
L
∂
V
=
∑
t
=
1
τ
∂
L
(
t
)
∂
V
=
∑
t
=
1
τ
(
y
^
(
t
)
−
y
(
t
)
)
(
h
t
)
T
\frac{∂L}{∂V}=\sum_{t=1}^τ \frac {∂L^{(t)}}{∂V}=\sum_ {t=1}^τ (\hat y^{(t)}−y^{(t)})(h^t)^T
∂V∂L=t=1∑τ∂V∂L(t)=t=1∑τ(y^(t)−y(t))(ht)T
W
,
U
,
b
W,U,b
W,U,b 的梯度计算就比较的复杂了。从RNN的模型可以看出,在反向传播时,在某一序列位置t的梯度损失由当前位置的输出对应的梯度损失和序列索引位置
t
+
1
t+1
t+1 时的梯度损失两部分共同决定。对于
W
W
W 在某一序列位置t的梯度损失需要反向传播一步步的计算。我们定义序列索引t位置的隐藏状态的梯度为:
∂
L
∂
h
(
t
)
\frac{∂L}{∂h^{(t)}}
∂h(t)∂L
这样我们可以像 DNN 一样从
δ
(
t
+
1
)
δ^{(t+1)}
δ(t+1) 递推
δ
(
t
)
δ^{(t)}
δ(t) 。
δ
(
t
)
=
(
∂
o
(
t
)
∂
h
(
t
)
)
T
∂
L
∂
o
(
t
)
+
(
∂
h
(
t
+
1
)
∂
h
(
t
)
)
T
∂
L
∂
h
(
t
+
1
)
=
V
T
(
y
^
(
t
)
−
y
(
t
)
)
+
W
T
d
i
a
g
(
1
−
(
h
(
t
+
1
)
)
2
)
δ
(
t
+
1
)
δ^{(t)}=(\frac{∂o^{(t)}}{∂h^{(t)}})^T\frac{∂L}{∂o^{(t)}}+(\frac{∂h^{(t+1)}}{∂h^(t)})^T\frac{∂L}{∂h^{(t+1)}}=V^T(\hat y^{(t)}−y^{(t)})+W^Tdiag(1−(h^{(t+1)})^2)δ^{(t+1)}
δ(t)=(∂h(t)∂o(t))T∂o(t)∂L+(∂h(t)∂h(t+1))T∂h(t+1)∂L=VT(y^(t)−y(t))+WTdiag(1−(h(t+1))2)δ(t+1)
对于
δ
(
τ
)
δ^{(τ)}
δ(τ) ,由于它的后面没有其他的序列索引了,因此有:
δ
(
τ
)
=
(
∂
o
(
τ
)
∂
h
(
τ
)
)
T
∂
L
∂
o
(
τ
)
=
V
T
(
y
^
(
τ
)
−
y
(
τ
)
)
δ^{(τ)}=(\frac{∂o^{(τ)}}{∂h^{(τ)}})^T\frac{∂L}{∂o^{(τ)}}=V^T(\hat y^{(τ)}−y^{(τ)})
δ(τ)=(∂h(τ)∂o(τ))T∂o(τ)∂L=VT(y^(τ)−y(τ))
计算
W
,
U
,
b
W,U,b
W,U,b 就容易了,这里给出W,U,b的梯度计算表达式:
∂
L
∂
W
=
∑
t
=
1
τ
d
i
a
g
(
1
−
(
h
(
t
)
)
2
)
δ
(
t
)
(
h
(
t
−
1
)
)
T
\frac{∂L}{∂W}=\sum_{t=1}^τ diag(1−(h^{(t)})^2)δ^{(t)}(h^{(t−1)})^T
∂W∂L=t=1∑τdiag(1−(h(t))2)δ(t)(h(t−1))T
∂
L
∂
b
=
∑
t
=
1
τ
d
i
a
g
(
1
−
(
h
(
t
)
)
2
)
δ
(
t
)
\frac{∂L}{∂b}=\sum_{t=1}^τ diag(1−(h^{(t)})^2)δ^{(t)}
∂b∂L=t=1∑τdiag(1−(h(t))2)δ(t)
∂
L
∂
U
=
∑
t
=
1
τ
d
i
a
g
(
1
−
(
h
(
t
)
)
2
)
δ
(
t
)
(
x
(
t
)
)
T
\frac{∂L}{∂U}=\sum _{t=1}^τdiag(1−(h^{(t)})^2)δ^{(t)}(x^{(t)})^T
∂U∂L=t=1∑τdiag(1−(h(t))2)δ(t)(x(t))T
除了梯度表达式不同,RNN的反向传播算法和DNN区别不大,因此这里就不再重复总结了。
2.1.3 RNN弊端
前向传播得到误差,反向传递误差时,每次都会乘一个系数
w
w
w,当这个
w
w
w 小 于 1 时,每次反向传递都会让 RNN 的误差缩小,经过若干次误差反向传递, 到
k
e
y
key
key 状态时,误差很近似等于 0 的情况,这就叫梯度消失或者梯度弥散,反 之,如果
w
w
w 大于 1,每次反向传递都会让 RNN 的误差变大,经过若干次误差的反向传递,到
k
e
y
key
key 的状态时,误差将会是一个非常大的数(无穷大),这种情况 叫做梯度爆炸。这样导致很难确定一个初始值让 RNN 收敛。
RNN(循环/递归神经网络)详解.
2.3 BiRNN
2.3 LSTM
这张图以MLP的形式展示LSTM的传播方式,方便理解hidden_size这个参数。
这张图非常便于理解参数num_layers,实际上就是个depth堆叠。
2.3.1 遗忘门
LSTM 的第一步是决定要从 cell 状态中丢弃什么信息,这个决定是由一个叫做 forget gate layer 的 sigmoid 神经层来实现的。它的输入是
h
t
−
1
h_{t-1}
ht−1 和
x
t
x_t
xt,输出是一个数值都在 0~1 之间的向量(向量长度和
C
t
−
1
C_{t-1}
Ct−1 一样),表示让
C
t
−
1
C_{t-1}
Ct−1 的各部分信息通过的比重,0 表示不让任何信息通过,1 表示让所有信息通过。
思考一个具体的例子,假设一个语言模型试图基于前面所有的词预测下一个单词,在这种情况下,每个 cell 状态都应该包含了当前主语的性别(保留信息),这样接下来我们才能正确使用代词。但是当我们又开始描述一个新的主语时,就应该把旧主语的性别给忘了才对(忘记信息).
2.3.2 输入门
下一步是决定要让多少新的信息加入到 cell 状态中。实现这个需要包括两个步骤:首先,一个叫做 input gate layer 的 sigmoid 层决定哪些信息需要更新。另一个
t
a
n
h
tanh
tanh 层创建一个新的 candidate 向量
C
~
t
−
1
\tilde{C}_{t-1}
C~t−1。最后,我们把这两个部分联合起来对 cell 状态进行更新。
在我们的语言模型的例子中,我们想把新的主语性别信息添加到 cell 状态中,替换掉老的状态信息。有了上述的结构,我们就能够更新 cell 状态了,即把
C
t
−
1
C_{t-1}
Ct−1 更新为
C
t
C_{t}
Ct。从结构图中应该能一目了然,首先我们把旧的状态
C
t
−
1
C_{t-1}
Ct−1 和
f
t
f_{t}
ft 相乘,把一些不想保留的信息忘掉,然后加上
i
t
∗
C
~
t
i_{t}*\tilde{C}_{t}
it∗C~t 。这部分信息就是我们要添加的新内容。
2.3.3 输出门
最后,我们需要决定输出什么值了。这个输出主要是依赖于 cell 状态
C
t
C_{t}
Ct,但是是经过筛选的版本。首先,经过一个 sigmoid 层,它决定
C
t
C_{t}
Ct 中的哪些部分将会被输出。接着,我们把
C
t
C_{t}
Ct 通过一个
t
a
n
h
tanh
tanh 层(把数值归一化到 - 1 和 1 之间),然后把 层的输出和 simoid 层计算出来的权重相乘,这样就得到了最后的输出结果。
在语言模型例子中,假设我们的模型刚刚接触了一个代词,接下来可能要输出一个动词,这个输出可能就和代词的信息有关了。比如说,这个动词应该采用单数形式还是复数形式,那么我们就得把刚学到的和代词相关的信息都加入到 cell 状态中来,才能够进行正确的预测。
2.3.4 LSTM 比较重要的问题
1.为什么要使用激活函数?
如果不用激励函数(其实相当于激励函数是f(x) = x),在这种情况下你每一层输出都是上层输入的线性函数,很容易验证,无论你神经网络有多少层,输出都是输入的线性组合,与没有隐藏层效果相当,这种情况就是最原始的感知机(Perceptron)了。
正因为上面的原因,我们决定引入非线性函数作为激励函数,这样深层神经网络就有意义了(不再是输入的线性组合,可以逼近任意函数)。最早的想法是sigmoid函数或者tanh函数,输出有界,很容易充当下一层输入(以及一些人的生物解释balabala)。激活函数的作用是为了增加神经网络模型的非线性。否则你想想,没有激活函数的每层都相当于矩阵相乘。就算你叠加了若干层之后,无非还是个矩阵相乘罢了。所以你没有非线性结构的话,根本就算不上什么神经网络。
(激活函数的作用是什么?)
2.为什么用sigmoid,为什么不用relu?为什么不用tanh?
Sigmoid会饱和,确实,可能会带来问题。注意看公式,0-1才可以起到筛选的作用,如果用relu信息就会全过去了,信息它可能会爆炸,每层都会变大,下一层更大,就做不到把每层的信息都压缩到一个共同的范围了。
Relu是做不到的,它大于0的就全过去了,信息会越来越多。
用tanh的话,双曲正切,它可能会出现一个负值,如-0.2,或者-0.5之类的,就会否定掉之前的全部的。
总结来说:激活函数求导,大于1或小于1,累乘的过程数值会不断变大或变小,出现梯度爆炸或梯度消失的问题。而relu求导梯度永远等于1,所以不会给信息造成影响。
3.为什么LSTM比RNN更能解决长时依赖?
RNN怎么修改 S t S_t St 的? S t = t a n h ( W X t + U S t − 1 ) S_t=tanh(WX_t+US_{t-1}) St=tanh(WXt+USt−1) 这是个复合函数,复合函数求偏导是连乘的形式,我用的是双曲正切,双曲正切在x偏大和偏小的时候斜率是接近于0的,所以接近于0会让反向传播的时候, S t S_t St 约等于0,RNN什么都学不到了,因为没有梯度传回来了,也就是没有梯度来校正参数W WW了。
而什么时候会出现 ≈ 0 \approx{0} ≈0呢,就是链路非常长的时候,就可能带来梯度消失,也就是梯度弥散。这个时候求导的链式法则是让无数东西的梯度相乘,当有一个 ≈ 0 \approx{0} ≈0 时,整个式子的结果就会 ≈ 0 \approx{0} ≈0,当我的链路越长时,越有可能出现 ≈ 0 \approx{0} ≈0 的情况。
看LSTM,LSTM不是复合函数,是两个函数求和,f(x)+g(x)求偏导的话,得到的是两个偏导的和,即使有一个
≈
0
\approx{0}
≈0,它不会导致整体约等于0。所以我的梯度往回传的时候是沿着两条轴往回传的。梯度不是顺着一条轴往回传的,LSTM最大的变化就是把RNN的连乘变成了求和,因此,不再会严重地出现梯度消失问题,这意味着即使时间再远,我应该也是学得到的。
LSTM、BiLSTM讲解及实践+GRU讲解.
2.4 BiLSTM
2.5 GRU
GRU对LSTM做了两大改动:
- 将输入门、遗忘门、输出门变为两个门:更新门(Update Gate)zt和重置门(Reset Gate)rt。
- 将单元状态与输出合并为一个状态:h。
GRU使用同一个门控 z t z_t zt 就同时进行遗忘和选择记忆
3 textRNN的结构
3.1 第一种结构
x–>embedding–>BiLSTM–>concat layer—>softmax layer–>y
注:
1.单词的embedding可以是随机初始化,也可以使用预训练模型的embedding(如Word2vec、GloVe),后者效果较好。
2.最后拼接初始时间步和最终时间步的隐藏状态作为全连接层输入。
3.2 第二种结构
x–>embedding–>BiLSTM–>concat layer–>LSTM–>softmax layer–>y
4 TextRCNN(TextRNN + CNN)
先利用双向RNN得到每个词的前向和后向上下文的表示,这样词的表示就变成词向量和前向后向上下文向量拼接起来的形式了,最后再接跟TextCNN相同卷积层,池化层即可。
5 textRNN-Attention文本分类
论文:提出了基于注意力机制的双向LSTM网络(AttBLSTM)来捕获句子中的最重要语义信息用于关系分类
Att-BLSTM包含5部分:
输入层:输入句子到模型。
嵌入层:将每个词映射到低维向量。
LSTM层:使用BLSTM得到高层特征。
注意力层:通过与权重向量加权求和,将词级别的特征合并到句子级别的特征。
输出层:将句子层级的特征用于关系分类。
1. 词嵌入
给定句子
S
=
(
x
1
,
x
2
,
.
.
.
,
x
T
)
S=(x_1,x_2,...,x_T)
S=(x1,x2,...,xT) ,使用词向量矩阵
W
w
r
d
W^{wrd}
Wwrd 将词
x
i
x_i
xi 转为实数向量
e
i
e_i
ei ,
e
i
=
W
w
r
d
v
i
e_i=W^{wrd}v^i
ei=Wwrdvi ,
v
i
v^i
vi 是维度为
∣
V
∣
|V|
∣V∣ 的one-hot向量。句子输入到下一层为实数向量
e
m
b
s
=
e
1
,
e
2
,
.
.
.
,
e
T
emb_s={e_1,e_2,...,e_T}
embs=e1,e2,...,eT
2. 双向网络
使用LSTM的变种,计算的公式如下:
使用BiLSTM,句子中第
i
i
i 个词的输出为,即对前向和后向的Hidden State进行拼接:
整个网络结构如下图:
3. 注意力
H
=
[
h
1
,
h
2
,
.
.
.
,
h
T
]
H=[h_1,h_2,...,h_T]
H=[h1,h2,...,hT]为LSTM层的输出向量,T是句子长度。
H
∈
R
d
W
∗
T
H\isin{\R^{d^W*T}}
H∈RdW∗T,
d
w
d^w
dw 是LSTM层的输出维度。
w
w
w 的维度是
d
w
d^w
dw ,
α
\alpha
α 的维度是T,
τ
\tau
τ 的维度是
d
w
d^w
dw 。
然后用于最后分类的特征为
h
∗
=
t
a
n
h
(
τ
)
h*=tanh(\tau)
h∗=tanh(τ)
4. 分类
最后将句子的特征
h
∗
h*
h∗ 接入softmax进行分类。
然后损失函数是使用交叉熵损失,并且加入了L2正则化。
5. 正则化
在嵌入层,LSTM层和倒数第二层使用了dropout算法。然后还使用了L2正则化。