RNN 与语言模型
学习笔记,原贴来源: The Unreasonable Effectiveness of Recurrent Neural Networks, May 21, 2015 BY Andrej Karpathy
总结:用 RNN 训练语言模型,并基于此算法介绍 5 个文本生成案例。
文章目录
1. RNN 的特别之处:处理序列
传统的神经网络和卷积神经网络有很大的局限:只接受固定长度的向量作为 input(比如图片),output 的也同样是一个固定长度的向量(比如每个分类的概率),并且运算的次数也是固定的(指神经网络的层数)。而 RNN 不受限于此,它可以处理序列,可以是 input 序列也可以 output 序列。
图中每个方块代表一个向量:红色方块(input),绿色方块(RNN’s state),蓝色方块(output)。从左到右 5 个模型:
1) 传统神经网络:固定长度的 input 和 output(图像分类)
2) 序列 output(图像识别:输入一个图像,输出一句话描述)
3) 序列 input(情感分析:输入一句评论,输出情感分类)
4) 序列 input 和 output(机器翻译:输入一句英语,输出一句中文)
5) 同步 (synced) 序列 input 和 output(视频逐帧分类)
注意这些模型都没有对序列的长度进行限制,因为循环转换(绿色方块)可以被无限循环使用。
即使输入的不是序列,只是一个固定向量,同样可以使用 RNN 用序列方式进行处理。
2. RNN 的隐藏层:“记忆”
RNN 接收一个 input x
,输出一个 output y
。注意 y
不只取决于这一次输入的 x
,之前历史输入的 x
对它都有影响!!!下面定义的 step
函数体现了这个“记忆”概念。
class RNN:
# ...
def step(self, x):
# update the hidden state
self.h = np.tanh(np.dot(self.W_hh, self.h) + np.dot(self.W_xh, x))
# compute the output vector
y = np.dot(self.W_hy, self.h)
return y
这个 RNN 模型有 3 组参数 W_hh, W_xh, W_hy
(代表权重的矩阵)。隐藏状态 self.h
初始值设为零向量。这里的 tanh
是一个非线性的激活函数。下面的数学公式直观解释上面两行代码:
h
t
=
tanh
(
W
h
h
h
t
−
1
+
W
x
h
x
t
)
y
=
W
h
y
h
t
\begin{aligned} h_t &= \tanh(W_{hh}h_{t-1}+W_{xh}x_t)\\ y &= W_{hy}h_t \end{aligned}
hty=tanh(Whhht−1+Wxhxt)=Whyht 在时间
t
t
t,隐藏状态
h
t
h_t
ht 更新,与前一次的隐藏状态
h
t
−
1
h_{t-1}
ht−1 和这一次的 input
x
t
x_t
xt 有关。再根据更新后的隐藏状态
h
t
h_t
ht 得到输出结果
y
y
y。
rnn = RNN()
y = rnn.step(x) # x is an input vector, y is the RNN's output vector
更进一步:2 层 RNN
层层叠加的 RNN。下面是两个 RNN 模型,前者的 output 作为后者的 input 继续拓展。
y1 = rnn1.step(x)
y = rnn2.step(y1)
更佳的 RNN:LSTM
更加常用的模型 LSTM 是一种特殊的 RNN,在 self.h = ...
这里的表达式稍微复杂一点。
3. Character-Level 语言模型
RNN 的一个应用:训练 Character-Level 语言模型。输入大量文本语料让 RNN 进行训练,建立一个概率分布模型使得我们可以根据已有的序列预判下一个出现的字母。这个有助于我们进行文本生成,每次生成一个字母。
例. 假设我们有一个词汇表只包含 4 个字母 h e l o,我们在 “hello” 这个序列上用 RNN 进行训练。这个训练序列实际包含 4 个训练样例:
- 已有 h 的情况下,e 出现的概率
- 已有 he 的情况下,l 出现的概率
- 已有 hel 的情况下,l 出现的概率
- 已有 hell 的情况下,o 出现的概率
我们把每一个字母进行编码,用向量表示(1-of-k encoding,即除该字母在词汇表所在位置,其他值均为0),然后把每个字母逐个给 RNN 训练(step
function)。我们会得到一个 4 维向量的输出(每个字母 1 维),表示下一个出现的所有字母的得分。
上图例子的隐藏层包含 3 个神经元(如第一个方块,(0.3,-0.1,0.9)),显示了输入 “hell” 将会得到的 output 是词汇表里所有字母出现的得分(词汇表:“h,e,l,o”)。训练目的是要使得输入层里绿色的值尽量高,而红色的值尽量低。
比如第一步输入 “h”,下一个为 “h” 的得分为 1.0,“e” 的为 2.2,“l” 为 -3.0,而 “o” 为 4.1。根据我们的训练数据 “hello”,下一个字母应为 “e”,我们要使得 “e” 的得分尽量高,而其他的尽量小。同样的,在每一步(共 4 步)都有一个得分是期望被最大化的。和传统神经网络的训练一样,用 BP 算法调整权重参数。调参后绿色的值会被增大(比如 2.2 变成 2.3),其他值变小。不断地重复训练,直到模型收敛和预测的结果保持稳定,这个模型就可以准确地预测下一个字母。
更深入具体分析这个模型,我们同时在每一次 output 使用 Softmax 分类器(交叉熵损失函数)。RNN 用 mini-batch SGD 进行训练。作者应用 RMSProp 或 Adam (per-parameter adaptive learning rate methods) 来更新模型。
注意第一次输入 “l”,希望输出 “l”;而第二次输入 “l” 则希望输出 “o”。所以 RNN 不能只根据单次的输入来判断,它需要循环读取之前的“记忆”来进行。
4. Python 实现
https://gist.github.com/karpathy/d4dee566867f8291f086
5. 几个有趣的应用
- Paul Graham generator
- Shakespeare
- Wikipedia
- Algebraic Geometry (Latex)
- Generating Baby Names