循环神经网络模型
循环神经网络
文章目录
-
循环神经网络
-
一、序列模型
-
- 统计工具
-
二、语言模型
-
- 1.语言模型简介
- 2.使用计数来建模
- 2.N元语法
-
三、RNN模型
-
- 使用循环神经网络的语言模型
- 循环神经网络
- 困惑度(perplexity)
- 梯度裁剪
- 上面的过程如何在代码上实现?
-
四、门控循环单元(GRU)
-
五、长期记忆网络(LSTM)
提示:以下是本篇文章正文内容,下面案例可供参考
一、序列模型
统计工具
-
在时间t观察到x_t,那么得到T个不独立的随机变量(x_1,x_2,...,x_T) \backsim p(\bf{X})
-
使用条件概率展开 p(a,b)=p(a)p(b|a)=p(b)p(a|b)
推广到T个随机变量有
p(\bf{X})=p(x_1)\cdot p(x_2|x_1)\cdot p(x_3|x_1,x_2)\cdot ...p(x_T|x_1,...x_{T-1})

或者反过来
p(\bf{X})=p(x_T)\cdot p(x_{T-1}|x_T)\cdot p(x_{T-2}|x_T,x_{T-1})\cdot ...p(x_T|x_2,...x_T)

反序的意义,例如知道未来的事情退过去的事情,物理上不一定可行是指如果未来的事情依赖于过去的事情而产生,那么就没法反推(这一点我还没有理解,既然未来的事情是作为已知条件,过去有和未来有联系,怎么就无法反推出过去的事情呢?) -
对条件概率建模
p(x_T|x_1,...x_{t-1}) = p(x_t|h(x_1,...,x_{t-1}))
给定t-1个数据,求第t个数据,假设用前t-1个数据建立一个函数h,h就是一个模型,通过h_{t-1}求h_t。通过对前面的数据建模,预测后一个数据,称为自回归模型,核心是求h -
如何求解f
方案A-马尔科夫假设
假设当前数据只跟\tau个过去数据点相关
P(x_T|x_1,...x_{t-1}) =P(x_T|x_{t-\tau},...x_{t-1})= P(x_t|h(x_{t-\tau},...,x_{t-1}))
方案B-潜变量模型
引入潜变量h_t来表示过去的信息h_t=g(h_{t-1},x_{t-1})
这样\hat{x_t}=P(x_t|h_t,x_{t-1})

二、语言模型
1.语言模型简介
- 给定文本序列x_1,...,x_T, 语言模型的目标是估计联合概率p(x_1,...x_T)
- 它的作用包括:做预训练模型(egBERT,GPT-3);生成文本,给定前面几个词,不断的使用x_t \backsim p(x_t|x_1,...,x_{t-1}) ; 判断多个序列中那个更常见,e.g “to recognize speech” vs “to wreck a nice beach”
2.使用计数来建模
- 假设序列长度为2,我们预测 p(x, x^\prime) = p(x)p(x^\prime|x) = \frac{n(x)}{n}\frac{n(x,x^\prime)}{n(x)}
- 这里n是总词数,n(x),n(x,x^\prime)是单个单词和连续单词对的出现次数
- 很容易扩展到3的情况 p(x, x^\prime,x^{\prime\prime}) = p(x)p(x^\prime|x)p(^{\prime\prime}|x,x^\prime)=\frac{n(x)}{n}\frac{n(x,x^\prime)}{n(x)}\frac{n(x,x^\prime,x^{\prime\prime})}{n(x,x^\prime)}
- 注意,这里的序列(x_,x^{\prime})是一个有序的元组
2.N元语法
- 当序列很长时,因为文本量不够大,很可能n(x_1,...x_T)\le1
- 使用马尔科夫假设可以缓解这个问题
- 一元语法 p(x_1,x_2,x_3,x_4) = p(x_1)p(x_2)p(x_3)p(x_4)=\frac{n(x_1)}{n}\frac{n(x_2)}{n}\frac{n(x_3)}{n}\frac{n(x_4)}{n}
- 二元语法 p(x_1,x_2,x_3,x_4) = p(x_1)p(x_2|x_1)p(x_3|x_2)p(x_4|x_3)=\frac{n(x_1)}{n}\frac{n(x_1,x_2)}{n(x_1)}\frac{n(x_2,x_3)}{n(x_2)}\frac{n(x_3,x_4)}{n(x_3)}
- 三元语法 p(x_1,x_2,x_3,x_4) = p(x_1)p(x_2|x_1)p(x_3|x_1,x_2)p(x_4|x_2,x_3)=\frac{n(x_1)}{n}\frac{n(x_1,x_2)}{n(x_1)}\frac{n(x_1,x_2,x_3)}{n(x_1,x_2)}\frac{n(x_2,x_3,x_4)}{n(x_2,x_3}
三、RNN模型
使用循环神经网络的语言模型

训练的过程是输入‘你’ 预测 ‘好’,输入‘好’预测 ‘,’,以此类推,然后通过输出与观察值做损失函数来学习权重
循环神经网络

困惑度(perplexity)
- 衡量一个语言模型的好坏可以用平均交叉熵
\pi=\frac{1}{t}\sum_{i=1}^{t}-logp(x_t|x_{t-1})
- p是语言模型的预测概率,x_{t}是真实词
- 历史原因NLP使用困惑度exp(\pi)来衡量,1表示完美,无穷大是最差情况
梯度裁剪
- 迭代 中计算这T个时间步上的梯度,在反向传播过程中产生长度为O(T)的矩阵乘法链,导致数值不稳定
- 梯度裁剪能有效预防梯度爆炸
- 如果梯度长度超过\theta,那么把梯度长度拖回长度\theta
g \longleftarrow min(1,\frac{\theta}{\lVert g\rVert})g
上面的过程如何在代码上实现?
模型的输入是什么?
假设我们现在有一个训练的数据集“The Time Machine”这篇小说,我们取其中连续的10000个词元(这里一个词元代表一个英文字符)作为训练的数据集,我们先进行文本处理(NLP)
batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
python
其中num_steps 就是上文中的T也就是循环神经网络图中绿色圆圈的个数
得到一个词汇表vocab如下
['<unk>', ' ', 'e', 't', 'a', 'i', 'n', 'o', 's', 'h', 'r', 'd', 'l', 'm', 'u', 'c', 'f', 'w', 'g', 'y', 'p', 'b', 'v', 'k', 'x', 'z', 'j', 'q']
python
vocab是一个28维的向量,26个英文字母加上一个空格和用来表示其他符号的 ‘< unk >’ 。
很多读者可能不理解batch_size和num_steps。我们下面用一个简单的例子来理解
假设我们有一个用于训练的序列,如果我们每次只输入一个字符,那么是非常浪费时间的,如何能高效的使用训练数据呢?
我们定义num_steps = 5,那么就按照5这个长度将这个序列划分为下面的5块,然后我么将这5块装进一个盒子里。现在我们定义batch_size = 2,那么我就每次从盒子中取两块放在一起,你可以按顺序取,也可以随机取。

假设我们随机取得“the t”,“e by ”这两块,那么我们就得到了一个2X5的矩阵X
X=\left[ \begin{array}{c} ‘t’ & ‘h’ & ‘e’ & ‘\;’ & ‘t’\\ ‘e’ & ‘\;’ & ‘b’ & ‘y’ & ‘\;’\\ \end{array} \right]
如果用Y表示训练数据的标签的话,就是取与X的每一个元素在序列中的后一个元素,因为我们是用前一个字符预测下一个字符,得到的Y就如下:
Y=\left[ \begin{array}{c} ‘h’ & ‘e’ & ‘\;’ & ‘t’ & ‘i’\\ ‘\;’ & ‘b’ & ‘y’ & ‘\;’ & ‘h’\\ \end{array} \right]
回到我们刚才的代码中的train_iter,其结构如下:
(tensor([[ 3, 9, 2, ..., 2, 1, 3],
[15, 4, 14, ..., 4, 6, 11],
...,
[ 3, 1, 4, ..., 12, 2, 13],
[ 9, 2, 1, ..., 4, 5, 10]]),
tensor([[ 9, 2, 1, ..., 1, 3, 5],
[ 4, 14, 18, ..., 6, 11, 20],
...,
[ 1, 4, 1, ..., 2, 13, 1],
[ 2, 1, 13, ..., 5, 10, 1]]))
python

可以看到它是(tensor,tensor)这样的结构,你可能已经发现了,(tensor,tensor) = (X, Y),他们都是一个32X35的矩阵,矩阵中的每一个元素都是一个英文字母在vocab里面的映射。这就是我们输入RNN模型循环一次的输入数据。现在 我们来计算一下train_iter中有多少个(tensor, tensor),我们有10000个词元,num_steps=35,那么我们得到的分块为10000/35 = 285 ,batch_size = 32, 那么我们最终得到了285/32 = 8个(tensor, tensor)
但是如果使用上面的矩阵作为输入,神经网络是无法学习到好的模式的,我们通常会使用one-hot编码来实现
F.one_hot(torch.tensor([0, 2]), len(vocab))
给定一个序列[0,2] 和 vocab向量,函数会把 0 生成为一个维度大小与vocab相同的向量,
向量中的1代表的是0在vocab中的位置,其他全为0
tensor([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0]])
python
模型细节

每一个时间步包含了n个神经元
四、门控循环单元(GRU)
关注一个序列
- 不是每一个观察值都同等重要
- 只记住相关的观察需要:能关注的机制(更新门);能遗忘的机制(重置门)
门
R_t = \sigma(X_tW_{xr} + H_{t-1}W_{hr} + b_r)\\ Z_t = \sigma(X_tW_{xz} + H_{t-1}W_{hz} + b_r)

候选隐藏状态
\tilde{H_t} = tanh(X_tW_{xh} + (R_t \bigodot H_{t-1}) W_{hh} + b_h)

隐状态
H_t = Z_t \bigodot H_{t-1} + (1-Z_t) \bigodot \tilde{H_t}

总结
\begin{array}{l} R_t = \sigma(X_tW_{xr} + H_{t-1}W_{hr} + b_r)\\ Z_t = \sigma(X_tW_{xz} + H_{t-1}W_{hz} + b_r)\\ \tilde{H_t} = tanh(X_tW_{xh} + (R_t \bigodot H_{t-1}) W_{hh} + b_h)\\ H_t = Z_t \bigodot H_{t-1} + (1-Z_t) \bigodot \tilde{H_t} \end{array}
五、长期记忆网络(LSTM)
长期记忆网络包含三部分关键的控制门
- 遗忘门:将值朝0减少
- 输入门:决定不是忽略掉输入数据
- 输出门:决定是不是使用隐状态
门
I_t = \sigma(X_tW_{xi} + H_{t-1}W_{hi} + b_i)\\ F_t = \sigma(X_tW_{xf} + H_{t-1}W_{hf} + b_f)\\ O_t = \sigma(X_tW_{xo} + H_{t-1}W_{ho} + b_o)\\

候选记忆单元
\tilde{C_t} = tanh(X_tW_{xc} + H_{t-1}W_{hc} + b_c)

记忆单元
C_t = F_t \bigodot C_{t-1} + I_t \bigodot \tilde{C_t}

隐状态
H_t = O_t \bigodot tanh(C_t)

总结
\begin{array}{l} I_t = \sigma(X_tW_{xi} + H_{t-1}W_{hi} + b_i) \\ F_t = \sigma(X_tW_{xf} + H_{t-1}W_{hf} + b_f) \\ O_t = \sigma(X_tW_{xo} + H_{t-1}W_{ho} + b_o) \\ \tilde{C_t} = tanh(X_tW_{xc} + H_{t-1}W_{hc} + b_c) \\ C_t = F_t \bigodot C_{t-1} + I_t \bigodot \tilde{C_t} \\ H_t = O_t \bigodot tanh(C_t) \end{array}
LSTM与RNN的区别就是RNN试图记住前面所有的内容来预测后一个内容,而LSTM因为引入了一个记忆单元即C,通过记忆单元使得LSTM有能力自由选择每个时间步里面记忆的内容。
假设现在有一个场景:期末考试周,我们在每一个时间步要考一门课程。假设我们到了第t步,现在要靠线性代数,那么我们的输入就是X_t 就是我们复习的线性代数的内容,中间的过程就是我们复习过程中形成的记忆,考完线性代数后我们就会形成一个线代的记忆C_t和状态H_t , 假设C_{t-1}和H_{t-1}是我们考完高等数学之后的记忆和状态。
首先是遗忘门 F_t:我们现在复习的是线性代数,那么我们就要尽可能的将C_{t-1}高数中和线性代数无关的那一部分的内容遗忘掉,好腾出空间来记忆线性代数的内容,那么如何做到这一点呢?我们得到的F_t是一个n维的向量(n的大小与你的记忆单元的维度相同),每一个维度的值都是0到1之间 F_t=[0,0.1,0.15,0.98,…,0.23] , 我们做运算 F_t\bigodot C_{t-1} ,即将两个向量对应元素相乘,达到的效果就是我们把高数中与现代无关的部分尽量忘掉,而与现代相关的部分保留下来。
然后是更新门 I_t:\tilde{C_t}是通过输入学习到的新的记忆,但是这个记忆并不是都有用。例如我们在复习线性代数的时候,我们学习的知识可能包含与考点无关的内容,对我们的考试没有帮助。我们就通过I_t来过滤,通过做计算I_t\bigodot \tilde{C_t}。然后我们将高数中有用的知识和当下与考点相关的内容结合起来,就形成了新的记忆C_t=F_t\bigodot C_{t-1} + I_t\bigodot \tilde{C_t}
最后是输出门 O_t:当我们带着C_t去考试的时候,我们首先做一个tanh(C_t)的操作,即将我们记忆的内容转化为我们答题的能力,但是我们考试的内容并不是包含了我们记忆的所有知识,那么我们需要做计算O_t\bigodot tanh(C_t)用到部分的知识来答题。
