前言
cs224n第八讲,介绍传统的RNN。
传统语言模型
语言模型是计算一个单词序列,也就是一个句子的概率的模型。
传统的语言模型就是利用待预测单词前窗口内的单词序列进行预测,假设窗口大小为n,则根据马尔科夫假设,可以用以下公式进行表示:
p
(
w
1
,
w
2
.
.
.
w
m
)
=
∏
i
=
1
i
=
m
p
(
w
i
∣
w
1
,
w
2
.
.
.
w
i
−
1
)
=
∏
i
=
1
i
=
m
p
(
w
i
∣
w
i
−
1
,
w
i
−
2
.
.
.
w
i
−
n
)
p(w_1,w_2...w_m)=\prod_{i=1}^{i=m}p(w_i|w_1,w_2...w_{i-1})=\prod_{i=1}^{i=m}p(w_i|w_{i-1},w_{i-2}...w_{i-n})
p(w1,w2...wm)=i=1∏i=mp(wi∣w1,w2...wi−1)=i=1∏i=mp(wi∣wi−1,wi−2...wi−n)
式中概率的计算方法:
p
(
w
2
∣
w
1
)
=
C
o
u
n
t
(
w
1
,
w
2
)
C
o
u
n
t
(
w
1
)
p(w_2|w_1)=\frac{Count(w_1,w_2)}{Count(w_1)}
p(w2∣w1)=Count(w1)Count(w1,w2)
p
(
w
3
∣
w
1
,
w
2
)
=
C
o
u
n
t
(
w
1
,
w
2
,
w
3
)
C
o
u
n
t
(
w
1
,
w
2
)
p(w_3|w_1,w_2)=\frac{Count(w_1,w_2,w_3)}{Count(w_1,w_2)}
p(w3∣w1,w2)=Count(w1,w2)Count(w1,w2,w3)
这种方法会消耗大量的内存和时间。
Bengio et al提出了第一个大规模深度学习自然语言处理模型,只不过是用前n个单词的词向量来做同样的事情(上文建模)而已,其网络结构如下:
这种方法就是简单是利用窗口内单词的词向量进行预测,公式如下:
y
^
=
s
o
f
t
m
a
x
(
W
(
2
)
t
a
n
h
(
W
(
1
)
x
+
b
(
1
)
)
+
W
(
3
)
x
+
b
(
3
)
)
\hat{y}=softmax(W^{(2)}tanh(W^{(1)}x+b^{(1)})+W^{(3)}x+b^{(3)})
y^=softmax(W(2)tanh(W(1)x+b(1))+W(3)x+b(3))
这里
W
(
3
)
x
+
b
(
3
)
W^{(3)}x+b^{(3)}
W(3)x+b(3)就是前n个单词词向量的线性运算,虽然这种模型名字里有“Neural”,但依然是传统模型。
Recurrent Neural Networks
这里开始讲到了常见的RNN模型,如下图所示
这种方法所需内存只和词表大小有关,而且每个词的预测都和之前所有词有关。
每个时刻的隐层表示:
h
t
=
σ
(
W
(
h
h
)
h
t
−
1
+
W
(
h
x
)
x
t
)
h_t=\sigma(W^{(hh)}h_{t-1}+W^{(hx)}x_t)
ht=σ(W(hh)ht−1+W(hx)xt)
其中,
x
t
∈
R
d
:
x_t∈R^d:
xt∈Rd:是时间t时输入的单词的词向量。
W
(
h
x
)
∈
R
D
h
×
d
:
W^{(hx)}∈R^{{D_h}×d}:
W(hx)∈RDh×d:用来condition输入词向量
x
t
x_t
xt的的权值矩阵。
W
(
h
h
)
∈
R
D
h
×
D
h
:
W^{(hh)}∈R^{{D_h×D_h}}:
W(hh)∈RDh×Dh:用来condition前一个时间节点隐藏层特征表示
h
t
−
1
h_{t−1}
ht−1的权值矩阵。
h
t
−
1
∈
R
D
h
:
h_{t−1}∈R^{D_h}:
ht−1∈RDh: 前一个时间点 t−1 的非线性激活函数的输出,
h
0
∈
R
D
h
h_0∈R^{D_h}
h0∈RDh 是时间点 t=0 时的隐藏层初始状态。
σ
(
)
:
σ():
σ(): 非线性激活函数(sigmoid)
y
t
^
=
s
o
f
t
m
a
x
(
W
(
S
)
h
t
)
:
\hat{y_t}=softmax(W^{(S)}h_t) :
yt^=softmax(W(S)ht):在时刻t时输出的整个词表 |V| 上的概率分布,
y
t
^
\hat{y_t}
yt^是给定上文
h
t
−
1
h_{t−1}
ht−1和最近的单词
x
(
t
)
x^{(t)}
x(t)预测的下一个单词。其中
W
(
S
)
∈
R
∣
V
∣
×
D
h
,
y
^
∈
R
∣
V
∣
W^{(S)}∈R^{|V|×D_h} , \hat{y}∈R^{|V|}
W(S)∈R∣V∣×Dh,y^∈R∣V∣。
RNN的模型图也有如下画法:
损失函数
这里的损失函数就是常见的交叉熵,在t时刻的损失为:
J
(
t
)
(
θ
)
=
−
∑
j
=
1
∣
V
∣
y
t
,
j
×
l
o
g
(
y
^
t
,
j
)
J^{(t)}(θ)=−\sum_{j=1}^{|V|}y_{t,j}×log(\hat{y}_{t,j})
J(t)(θ)=−j=1∑∣V∣yt,j×log(y^t,j)
在大小为T的整个语料上的损失:
J
=
1
T
∑
t
=
1
T
J
(
t
)
(
θ
)
=
−
1
T
∑
t
=
1
T
∑
j
=
1
∣
V
∣
y
t
,
j
×
l
o
g
(
y
^
t
,
j
)
J=\frac{1}{T}\sum_{t=1}^{T}J^{(t)}(θ)=-\frac{1}{T}\sum_{t=1}^{T}\sum_{j=1}^{|V|}y_{t,j}×log(\hat{y}_{t,j})
J=T1t=1∑TJ(t)(θ)=−T1t=1∑Tj=1∑∣V∣yt,j×log(y^t,j)
如果以2为底数会得到“perplexity困惑度”,代表模型下结论时的困惑程度,越小越好:
P e r p l e x i t y = 2 J Perplexity=2^J Perplexity=2J
训练RNN困难
这里主要说了两个问题:前向传播的长距离依赖问题和反向传播的梯度消失(爆炸)问题。
长距离依赖
对比以下两个句子
“Jane walked into the room. John walked in too. Jane said hi to ___”
“Jane walked into the room. John walked in too. It was late in the day, and everyone was walking home after a long day at work. Jane said hi to ___”
对于第二个句子,RNN很难学会在空白处填上john,因为在前向传播过程中,输入x乘以很多次权重矩阵W,这样使得前面的信息被大量丢失,导致对后面影响较小,无法准确预测。
梯度消失(爆炸)
另一个问题就是反向传播的梯度消失和梯度爆炸。
整个序列的误差是每个时刻的误差之和:
∂
E
∂
W
=
∑
t
=
1
T
∂
E
t
∂
W
\frac{∂E}{∂W}=\sum_{t=1}^{T}\frac{∂E_t}{∂W}
∂W∂E=t=1∑T∂W∂Et
同时,根据每个时刻的隐层向量
h
t
h_t
ht的计算公式可以知道其与前一时刻的隐层向量有关,所以求梯度:
∂
E
t
∂
W
=
∑
k
=
1
t
∂
E
t
∂
y
t
∂
y
t
∂
h
t
∂
h
t
∂
h
k
∂
h
k
∂
W
\frac{∂E_t}{∂W}=\sum_{k=1}^{t}\frac{∂E_t}{∂y_t}\frac{∂y_t}{∂h_t}\frac{∂h_t}{∂h_k}\frac{∂h_k}{∂W}
∂W∂Et=k=1∑t∂yt∂Et∂ht∂yt∂hk∂ht∂W∂hk
熟悉RNN的都知道造成梯度消失和梯度爆炸的原因主要是公式中间的
∂
h
t
∂
h
k
\frac{∂h_t}{∂h_k}
∂hk∂ht,这是一个连乘的过程,累乘跨度是时间跨度,而且一般
h
t
h_t
ht的激活函数的导数是小于1的,累乘之后就会造成梯度消失问题,当大于1时就会造成梯度爆炸问题。
一般来说,梯度消失问题比较常见,而梯度爆炸问题不常见而且容易解决。
课程中提到了一个梯度消失的直观例子,数据如下:
学习到的决策边界:
以上决策边界是用经典的三层网络结构训练得到的,课程中给出了不同激活函数下,不同层的梯度大小,其中蓝色是第一层梯度,绿色是第二层梯度:
sigmod激活函数下:
ReLU激活函数下:
从以上图中可以看出来,仅仅一层的差别,就会出现近乎减半的梯度的减小。
防止梯度爆炸
直观来讲,防止梯度爆炸的做法就是在梯度增长到达一定阈值时,暴力性的将其缩放到某个值,也就是梯度剪切。
防止梯度消失
这里讲到的是将参数矩阵初始化为单位矩阵,另外改变激活函数,使用ReLU这样的激活函数。当然,下图中还有LSTM这种改变RNN结构来缓解梯度消失的比较。
最后,课程中提到应该记录每个时刻t的误差,反向传播的时候将误差累加起来。
序列模型的应用
可以把每个词分类到NER、实体级别的情感分析(饭菜味道不错,但环境不太卫生)、意见表达。
其中,意见挖掘任务就是将每个词语归类为:
DSE:直接主观描述(明确表达观点等)
ESE:间接主观描述(间接地表达情感等)
本质上也还是分类任务
这种任务可以用简单的RNN实现,但是传统的RNN不能利用词语的下文进行预测,所以引出了双向RNN。
在双向RNN的基础上多加几个层。构成深度双向RNN
这里对不同层数下的结果进行了实验分析,结果表明并不是层数越多越好。
应用:RNN机器翻译模型
这部分讲解了机器翻译,也就是用RNN构成的常见的encoder-decoder模型。
这里:
h
t
=
f
(
W
(
h
h
)
h
t
−
1
+
W
(
h
x
)
x
t
)
h_t=f(W^{(hh)}h_{t-1}+W^{(hx)}x_t)
ht=f(W(hh)ht−1+W(hx)xt)
h
t
=
f
(
W
(
h
h
)
h
t
−
1
)
h_t=f(W^{(hh)}h_{t-1})
ht=f(W(hh)ht−1)
y
t
=
s
o
f
t
m
a
x
(
W
(
S
)
h
t
)
y_t=softmax(W^{(S)}h_t)
yt=softmax(W(S)ht)
其中
W
(
S
)
W^{(S)}
W(S)是RNN(encoder部分)最后一个单元的输出,作为后面decoder的输入。
以上方法训练效果不佳,考虑一下改进:
1.encoder和decoder使用不同的权值矩阵。也就是上述两个
W
(
h
h
)
W^{(hh)}
W(hh)不再相同。
2.改变decoder的输入,变成三个输入(前一时刻隐层、encoder最后一个隐层、前一个预测结果)
3.使用深度RNN
4.使用bi-directional encoder
5.不再用A B C→X Y作为训练实例,而是逆转原文词序:C B A→X Y。因为A更可能翻译为X,而梯度消失导致A无法影响输出,倒过来A离输出近一些。