今天终于把RNN的坑补上了,基本逻辑挺明白,但是一到代码部分就晕乎了。还是有一些问题没有搞懂,希望在以后真正实践的时候搞清楚吧!
RNN基础
循环神经网络引入一个隐藏变量
H
H
H,用
H
t
H_{t}
Ht表示
H
H
H在时间步
t
t
t的值。
H
t
H_{t}
Ht的计算基于
X
t
X_{t}
Xt和
H
t
−
1
H_{t-1}
Ht−1,可以认为
H
t
H_{t}
Ht记录了到当前字符为止的序列信息。
上图展示了如何基于RNN实现语言模型。目的是基于当前的输入与过去的输入序列,预测序列的下一个字符。
1. RNN的构造
假设 X t ∈ R n × d \boldsymbol{X}_t \in \mathbb{R}^{n \times d} Xt∈Rn×d是时间步 t t t的小批量输入, H t ∈ R n × h \boldsymbol{H}_t \in \mathbb{R}^{n \times h} Ht∈Rn×h是该时间步的隐藏变量,则:
H t = ϕ ( X t W x h + H t − 1 W h h + b h ) . \boldsymbol{H}_t = \phi(\boldsymbol{X}_t \boldsymbol{W}_{xh} + \boldsymbol{H}_{t-1} \boldsymbol{W}_{hh} + \boldsymbol{b}_h). Ht=ϕ(XtWxh+Ht−1Whh+bh).
其中, W x h ∈ R d × h \boldsymbol{W}_{xh} \in \mathbb{R}^{d \times h} Wxh∈Rd×h, W h h ∈ R h × h \boldsymbol{W}_{hh} \in \mathbb{R}^{h \times h} Whh∈Rh×h, b h ∈ R 1 × h \boldsymbol{b}_{h} \in \mathbb{R}^{1 \times h} bh∈R1×h, ϕ \phi ϕ函数是非线性激活函数。由于引入了 H t − 1 W h h \boldsymbol{H}_{t-1} \boldsymbol{W}_{hh} Ht−1Whh, H t H_{t} Ht能够捕捉截至当前时间步的序列的历史信息,就像是神经网络当前时间步的状态或记忆一样。由于 H t H_{t} Ht的计算基于 H t − 1 H_{t-1} Ht−1,上式的计算是循环的,使用循环计算的网络即循环神经网络(recurrent neural network)。
在时间步 t t t,输出层的输出为:
O t = H t W h q + b q . \boldsymbol{O}_t = \boldsymbol{H}_t \boldsymbol{W}_{hq} + \boldsymbol{b}_q. Ot=HtWhq+bq.
其中 W h q ∈ R h × q \boldsymbol{W}_{hq} \in \mathbb{R}^{h \times q} Whq∈Rh×q, b q ∈ R 1 × q \boldsymbol{b}_q \in \mathbb{R}^{1 \times q} bq∈R1×q。
RNN模型的参数
- X x h \boldsymbol{X}_{xh} Xxh:输入-状态权重
- X h h \boldsymbol{X}_{hh} Xhh:状态-状态权重
- X h q \boldsymbol{X}_{hq} Xhq:状态-输出权重
- b h \boldsymbol{b}_{h} bh:隐藏层的偏置
- b q \boldsymbol{b}_{q} bq:输出层的偏置
循环神经网络的参数就是上述的三个权重和两个偏置,并且在沿着时间训练(参数的更新),参数的数量没有发生变化,仅仅是上述的参数的值在更新。循环神经网络可以看作是沿着时间维度上的权值共享。
在卷积神经网络中,一个卷积核通过在特征图上滑动进行卷积,是空间维度的权值共享。在卷积神经网络中通过控制特征图的数量来控制每一层模型的复杂度,而循环神经网络是通过控制
X
x
h
\boldsymbol{X}_{xh}
Xxh和
X
h
h
\boldsymbol{X}_{hh}
Xhh中隐层
h
\boldsymbol{h}
h的维度来控制模型的复杂度。
2. 一个批量数据的表示
我们每次采样的小批量的形状是(批量大小, 时间步数)。我们将这样的小批量变换成数个形状为(批量大小, 词典大小)的矩阵,矩阵个数等于时间步数。也就是说,时间步 t t t的输入为 X t ∈ R n × d \boldsymbol{X}_t \in \mathbb{R}^{n \times d} Xt∈Rn×d,其中 n n n为批量大小, d d d为词向量大小,即one-hot向量长度(词典大小)。
3. 裁剪梯度
循环神经网络中较容易出现梯度衰减或梯度爆炸,这会导致网络几乎无法训练。裁剪梯度(clip gradient)是一种应对梯度爆炸的方法。假设我们把所有模型参数的梯度拼接成一个向量
g
\boldsymbol{g}
g,并设裁剪的阈值是
θ
\theta
θ。裁剪后的梯度的
L
2
L_2
L2范数不超过
θ
\theta
θ。
min
(
θ
∥
g
∥
,
1
)
g
\min\left(\frac{\theta}{\|\boldsymbol{g}\|}, 1\right)\boldsymbol{g}
min(∥g∥θ,1)g
4. 困惑度
我们通常使用困惑度(perplexity)来评价语言模型的好坏。困惑度是对交叉熵损失函数做指数运算后的到的值。
由公式可知,一个语言模型,句子概率越大、困惑度越小越好。
- 最佳情况下,模型总是把标签类别的概率预测为1,此时困惑度为1;
- 最坏情况下,模型总是把标签类别的概率预测为0,此时困惑度为正无穷;
- 基线情况下,模型总是预测所有类别的概率都相同,此时困惑度为类别个数。
显然,任何一个有效模型的困惑度必须小于类别个数。
5. RNN训练:
与之前的模型训练函数相比,这里的模型训练函数有以下几点不同:
- 使用困惑度评价模型。
- 在迭代模型参数前裁剪梯度。
- 对时序数据采用不同采样方法将导致隐藏状态初始化的不同。
隐藏状态的初始化:
- 随机采样时:每次迭代都需要重新初始化隐藏状态(每个epoch有很多词迭代,每次迭代都需要进行初始化,因为对于随机采样的样本中只有一个批量内的数据是连续的)
- 相邻采样时:如果是相邻采样,则说明前后两个batch的数据是连续的,所以在训练每个batch的时候只需要更新一次(也就是说模型在一个epoch中的迭代不需要重新初始化隐藏状态)
例,“我想拥有直升机,我想飞到天上去。”这句话分别采用两种采样方式进行采样,batch_size是2,时间步是4,效果如下。