循环神经网络
文章目录
提示:以下是本篇文章正文内容,下面案例可供参考
一、序列模型
统计工具
- 在时间 t t t观察到 x t x_t xt,那么得到 T T T个不独立的随机变量 ( x 1 , x 2 , . . . , x T ) ∽ p ( X ) (x_1,x_2,...,x_T) \backsim p(\bf{X}) (x1,x2,...,xT)∽p(X)
- 使用条件概率展开
p
(
a
,
b
)
=
p
(
a
)
p
(
b
∣
a
)
=
p
(
b
)
p
(
a
∣
b
)
p(a,b)=p(a)p(b|a)=p(b)p(a|b)
p(a,b)=p(a)p(b∣a)=p(b)p(a∣b)
推广到 T T T个随机变量有
p ( X ) = p(\bf{X})= p(X)= p ( x 1 ) ⋅ p ( x 2 ∣ x 1 ) ⋅ p ( x 3 ∣ x 1 , x 2 ) ⋅ . . . p ( x T ∣ x 1 , . . . x T − 1 ) 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(x1)⋅p(x2∣x1)⋅p(x3∣x1,x2)⋅...p(xT∣x1,...xT−1)
或者反过来
p ( X ) = p(\bf{X})= p(X)= p ( x T ) ⋅ p ( x T − 1 ∣ x T ) ⋅ p ( x T − 2 ∣ x T , x T − 1 ) ⋅ . . . p ( x T ∣ x 2 , . . . x T ) 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(xT)⋅p(xT−1∣xT)⋅p(xT−2∣xT,xT−1)⋅...p(xT∣x2,...xT)
反序的意义,例如知道未来的事情退过去的事情,物理上不一定可行是指如果未来的事情依赖于过去的事情而产生,那么就没法反推(这一点我还没有理解,既然未来的事情是作为已知条件,过去有和未来有联系,怎么就无法反推出过去的事情呢?) - 对条件概率建模
p ( x T ∣ x 1 , . . . x t − 1 ) = p ( x t ∣ h ( x 1 , . . . , x t − 1 ) ) p(x_T|x_1,...x_{t-1}) = p(x_t|h(x_1,...,x_{t-1})) p(xT∣x1,...xt−1)=p(xt∣h(x1,...,xt−1))
给定 t − 1 t-1 t−1个数据,求第 t t t个数据,假设用前 t − 1 t-1 t−1个数据建立一个函数 h h h, h h h就是一个模型,通过 h t − 1 h_{t-1} ht−1求 h t h_t ht。通过对前面的数据建模,预测后一个数据,称为自回归模型,核心是求 h h h - 如何求解
f
f
f
方案A-马尔科夫假设
假设当前数据只跟 τ \tau τ个过去数据点相关
P ( x T ∣ x 1 , . . . x t − 1 ) = P ( x T ∣ x t − τ , . . . x t − 1 ) = P ( x t ∣ h ( x t − τ , . . . , x t − 1 ) ) 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})) P(xT∣x1,...xt−1)=P(xT∣xt−τ,...xt−1)=P(xt∣h(xt−τ,...,xt−1))
方案B-潜变量模型
引入潜变量 h t h_t ht来表示过去的信息 h t = g ( h t − 1 , x t − 1 ) h_t=g(h_{t-1},x_{t-1}) ht=g(ht−1,xt−1)
这样 x t ^ = P ( x t ∣ h t , x t − 1 ) \hat{x_t}=P(x_t|h_t,x_{t-1}) xt^=P(xt∣ht,xt−1)
二、语言模型
1.语言模型简介
- 给定文本序列 x 1 , . . . , x T x_1,...,x_T x1,...,xT, 语言模型的目标是估计联合概率 p ( x 1 , . . . x T ) p(x_1,...x_T) p(x1,...xT)
- 它的作用包括:做预训练模型(egBERT,GPT-3);生成文本,给定前面几个词,不断的使用 x t ∽ p ( x t ∣ x 1 , . . . , x t − 1 ) x_t \backsim p(x_t|x_1,...,x_{t-1}) xt∽p(xt∣x1,...,xt−1) ; 判断多个序列中那个更常见,e.g “to recognize speech” vs “to wreck a nice beach”
2.使用计数来建模
- 假设序列长度为2,我们预测 p ( x , x ′ ) = p ( x ) p ( x ′ ∣ x ) = n ( x ) n n ( x , x ′ ) n ( x ) p(x, x^\prime) = p(x)p(x^\prime|x) = \frac{n(x)}{n}\frac{n(x,x^\prime)}{n(x)} p(x,x′)=p(x)p(x′∣x)=nn(x)n(x)n(x,x′)
- 这里n是总词数, n ( x ) , n ( x , x ′ ) n(x),n(x,x^\prime) n(x),n(x,x′)是单个单词和连续单词对的出现次数
- 很容易扩展到3的情况 p ( x , x ′ , x ′ ′ ) = p ( x ) p ( x ′ ∣ x ) p ( ′ ′ ∣ x , x ′ ) = n ( x ) n n ( x , x ′ ) n ( x ) n ( x , x ′ , x ′ ′ ) n ( x , x ′ ) 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)} p(x,x′,x′′)=p(x)p(x′∣x)p(′′∣x,x′)=nn(x)n(x)n(x,x′)n(x,x′)n(x,x′,x′′)
- 注意,这里的序列 ( x , x ′ ) (x_,x^{\prime}) (x,x′)是一个有序的元组
2.N元语法
- 当序列很长时,因为文本量不够大,很可能 n ( x 1 , . . . x T ) ≤ 1 n(x_1,...x_T)\le1 n(x1,...xT)≤1
- 使用马尔科夫假设可以缓解这个问题
- 一元语法 p ( x 1 , x 2 , x 3 , x 4 ) = p ( x 1 ) p ( x 2 ) p ( x 3 ) p ( x 4 ) = n ( x 1 ) n n ( x 2 ) n n ( x 3 ) n n ( x 4 ) n 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(x1,x2,x3,x4)=p(x1)p(x2)p(x3)p(x4)=nn(x1)nn(x2)nn(x3)nn(x4)
- 二元语法 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 ) = n ( x 1 ) n n ( x 1 , x 2 ) n ( x 1 ) n ( x 2 , x 3 ) n ( x 2 ) 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_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(x1,x2,x3,x4)=p(x1)p(x2∣x1)p(x3∣x2)p(x4∣x3)=nn(x1)n(x1)n(x1,x2)n(x2)n(x2,x3)n(x3)n(x3,x4)
- 三元语法 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 ) = n ( x 1 ) n n ( x 1 , x 2 ) n ( x 1 ) n ( x 1 , x 2 , x 3 ) n ( x 1 , x 2 ) n ( x 2 , x 3 , x 4 ) n ( x 2 , 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} p(x1,x2,x3,x4)=p(x1)p(x2∣x1)p(x3∣x1,x2)p(x4∣x2,x3)=nn(x1)n(x1)n(x1,x2)n(x1,x2)n(x1,x2,x3)n(x2,x3n(x2,x3,x4)
三、RNN模型
使用循环神经网络的语言模型
训练的过程是输入‘你’ 预测 ‘好’,输入‘好’预测 ‘,’,以此类推,然后通过输出与观察值做损失函数来学习权重
循环神经网络
困惑度(perplexity)
- 衡量一个语言模型的好坏可以用平均交叉熵
π = 1 t ∑ i = 1 t − l o g p ( x t ∣ x t − 1 ) \pi=\frac{1}{t}\sum_{i=1}^{t}-logp(x_t|x_{t-1}) π=t1i=1∑t−logp(xt∣xt−1)
- p p p是语言模型的预测概率, x t x_{t} xt是真实词
- 历史原因NLP使用困惑度 e x p ( π ) exp(\pi) exp(π)来衡量,1表示完美,无穷大是最差情况
梯度裁剪
- 迭代 中计算这 T T T个时间步上的梯度,在反向传播过程中产生长度为 O ( T ) O(T) O(T)的矩阵乘法链,导致数值不稳定
- 梯度裁剪能有效预防梯度爆炸
- 如果梯度长度超过
θ
\theta
θ,那么把梯度长度拖回长度
θ
\theta
θ
g ⟵ m i n ( 1 , θ ∥ g ∥ ) g g \longleftarrow min(1,\frac{\theta}{\lVert g\rVert})g g⟵min(1,∥g∥θ)g
上面的过程如何在代码上实现?
模型的输入是什么?
假设我们现在有一个训练的数据集“The Time Machine”这篇小说,我们取其中连续的10000个词元(这里一个词元代表一个英文字符)作为训练的数据集,我们先进行文本处理(NLP)
batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
其中num_steps 就是上文中的
T
T
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']
vocab是一个28维的向量,26个英文字母加上一个空格和用来表示其他符号的 ‘< unk >’ 。
很多读者可能不理解batch_size和num_steps。我们下面用一个简单的例子来理解
假设我们有一个用于训练的序列,如果我们每次只输入一个字符,那么是非常浪费时间的,如何能高效的使用训练数据呢?
我们定义num_steps = 5,那么就按照5这个长度将这个序列划分为下面的5块,然后我么将这5块装进一个盒子里。现在我们定义batch_size = 2,那么我就每次从盒子中取两块放在一起,你可以按顺序取,也可以随机取。
假设我们随机取得“the t”,“e by ”这两块,那么我们就得到了一个2X5的矩阵
X
X
X
X
=
[
‘
t
’
‘
h
’
‘
e
’
‘
’
‘
t
’
‘
e
’
‘
’
‘
b
’
‘
y
’
‘
’
]
X=\left[ \begin{array}{c} ‘t’ & ‘h’ & ‘e’ & ‘\;’ & ‘t’\\ ‘e’ & ‘\;’ & ‘b’ & ‘y’ & ‘\;’\\ \end{array} \right]
X=[‘t’‘e’‘h’‘’‘e’‘b’‘’‘y’‘t’‘’]
如果用
Y
Y
Y表示训练数据的标签的话,就是取与
X
X
X的每一个元素在序列中的后一个元素,因为我们是用前一个字符预测下一个字符,得到的
Y
Y
Y就如下:
Y
=
[
‘
h
’
‘
e
’
‘
’
‘
t
’
‘
i
’
‘
’
‘
b
’
‘
y
’
‘
’
‘
h
’
]
Y=\left[ \begin{array}{c} ‘h’ & ‘e’ & ‘\;’ & ‘t’ & ‘i’\\ ‘\;’ & ‘b’ & ‘y’ & ‘\;’ & ‘h’\\ \end{array} \right]
Y=[‘h’‘’‘e’‘b’‘’‘y’‘t’‘’‘i’‘h’]
回到我们刚才的代码中的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]]))
可以看到它是(tensor,tensor)这样的结构,你可能已经发现了,(tensor,tensor) = ( X X X, Y Y 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]])
模型细节
每一个时间步包含了n个神经元
四、门控循环单元(GRU)
关注一个序列
- 不是每一个观察值都同等重要
- 只记住相关的观察需要:能关注的机制(更新门);能遗忘的机制(重置门)
门
R
t
=
σ
(
X
t
W
x
r
+
H
t
−
1
W
h
r
+
b
r
)
Z
t
=
σ
(
X
t
W
x
z
+
H
t
−
1
W
h
z
+
b
r
)
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)
Rt=σ(XtWxr+Ht−1Whr+br)Zt=σ(XtWxz+Ht−1Whz+br)
候选隐藏状态
H
t
~
=
t
a
n
h
(
X
t
W
x
h
+
(
R
t
⨀
H
t
−
1
)
W
h
h
+
b
h
)
\tilde{H_t} = tanh(X_tW_{xh} + (R_t \bigodot H_{t-1}) W_{hh} + b_h)
Ht~=tanh(XtWxh+(Rt⨀Ht−1)Whh+bh)
隐状态
H
t
=
Z
t
⨀
H
t
−
1
+
(
1
−
Z
t
)
⨀
H
t
~
H_t = Z_t \bigodot H_{t-1} + (1-Z_t) \bigodot \tilde{H_t}
Ht=Zt⨀Ht−1+(1−Zt)⨀Ht~
总结
R
t
=
σ
(
X
t
W
x
r
+
H
t
−
1
W
h
r
+
b
r
)
Z
t
=
σ
(
X
t
W
x
z
+
H
t
−
1
W
h
z
+
b
r
)
H
t
~
=
t
a
n
h
(
X
t
W
x
h
+
(
R
t
⨀
H
t
−
1
)
W
h
h
+
b
h
)
H
t
=
Z
t
⨀
H
t
−
1
+
(
1
−
Z
t
)
⨀
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}
Rt=σ(XtWxr+Ht−1Whr+br)Zt=σ(XtWxz+Ht−1Whz+br)Ht~=tanh(XtWxh+(Rt⨀Ht−1)Whh+bh)Ht=Zt⨀Ht−1+(1−Zt)⨀Ht~
五、长期记忆网络(LSTM)
长期记忆网络包含三部分关键的控制门
- 遗忘门:将值朝0减少
- 输入门:决定不是忽略掉输入数据
- 输出门:决定是不是使用隐状态
门
I
t
=
σ
(
X
t
W
x
i
+
H
t
−
1
W
h
i
+
b
i
)
F
t
=
σ
(
X
t
W
x
f
+
H
t
−
1
W
h
f
+
b
f
)
O
t
=
σ
(
X
t
W
x
o
+
H
t
−
1
W
h
o
+
b
o
)
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)\\
It=σ(XtWxi+Ht−1Whi+bi)Ft=σ(XtWxf+Ht−1Whf+bf)Ot=σ(XtWxo+Ht−1Who+bo)
候选记忆单元
C
t
~
=
t
a
n
h
(
X
t
W
x
c
+
H
t
−
1
W
h
c
+
b
c
)
\tilde{C_t} = tanh(X_tW_{xc} + H_{t-1}W_{hc} + b_c)
Ct~=tanh(XtWxc+Ht−1Whc+bc)
记忆单元
C
t
=
F
t
⨀
C
t
−
1
+
I
t
⨀
C
t
~
C_t = F_t \bigodot C_{t-1} + I_t \bigodot \tilde{C_t}
Ct=Ft⨀Ct−1+It⨀Ct~
隐状态
H
t
=
O
t
⨀
t
a
n
h
(
C
t
)
H_t = O_t \bigodot tanh(C_t)
Ht=Ot⨀tanh(Ct)
总结
I
t
=
σ
(
X
t
W
x
i
+
H
t
−
1
W
h
i
+
b
i
)
F
t
=
σ
(
X
t
W
x
f
+
H
t
−
1
W
h
f
+
b
f
)
O
t
=
σ
(
X
t
W
x
o
+
H
t
−
1
W
h
o
+
b
o
)
C
t
~
=
t
a
n
h
(
X
t
W
x
c
+
H
t
−
1
W
h
c
+
b
c
)
C
t
=
F
t
⨀
C
t
−
1
+
I
t
⨀
C
t
~
H
t
=
O
t
⨀
t
a
n
h
(
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}
It=σ(XtWxi+Ht−1Whi+bi)Ft=σ(XtWxf+Ht−1Whf+bf)Ot=σ(XtWxo+Ht−1Who+bo)Ct~=tanh(XtWxc+Ht−1Whc+bc)Ct=Ft⨀Ct−1+It⨀Ct~Ht=Ot⨀tanh(Ct)
LSTM与RNN的区别就是RNN试图记住前面所有的内容来预测后一个内容,而LSTM因为引入了一个记忆单元即
C
C
C,通过记忆单元使得LSTM有能力自由选择每个时间步里面记忆的内容。
假设现在有一个场景:期末考试周,我们在每一个时间步要考一门课程。假设我们到了第
t
t
t步,现在要靠线性代数,那么我们的输入就是
X
t
X_t
Xt 就是我们复习的线性代数的内容,中间的过程就是我们复习过程中形成的记忆,考完线性代数后我们就会形成一个线代的记忆
C
t
C_t
Ct和状态
H
t
H_t
Ht , 假设
C
t
−
1
C_{t-1}
Ct−1和
H
t
−
1
H_{t-1}
Ht−1是我们考完高等数学之后的记忆和状态。
首先是遗忘门
F
t
F_t
Ft:我们现在复习的是线性代数,那么我们就要尽可能的将
C
t
−
1
C_{t-1}
Ct−1高数中和线性代数无关的那一部分的内容遗忘掉,好腾出空间来记忆线性代数的内容,那么如何做到这一点呢?我们得到的
F
t
F_t
Ft是一个n维的向量(n的大小与你的记忆单元的维度相同),每一个维度的值都是0到1之间
F
t
F_t
Ft=[0,0.1,0.15,0.98,…,0.23] , 我们做运算
F
t
⨀
C
t
−
1
F_t\bigodot C_{t-1}
Ft⨀Ct−1 ,即将两个向量对应元素相乘,达到的效果就是我们把高数中与现代无关的部分尽量忘掉,而与现代相关的部分保留下来。
然后是更新门
I
t
I_t
It:
C
t
~
\tilde{C_t}
Ct~是通过输入学习到的新的记忆,但是这个记忆并不是都有用。例如我们在复习线性代数的时候,我们学习的知识可能包含与考点无关的内容,对我们的考试没有帮助。我们就通过
I
t
I_t
It来过滤,通过做计算
I
t
⨀
C
t
~
I_t\bigodot \tilde{C_t}
It⨀Ct~。然后我们将高数中有用的知识和当下与考点相关的内容结合起来,就形成了新的记忆
C
t
=
F
t
⨀
C
t
−
1
+
I
t
⨀
C
t
~
C_t=F_t\bigodot C_{t-1} + I_t\bigodot \tilde{C_t}
Ct=Ft⨀Ct−1+It⨀Ct~
最后是输出门
O
t
O_t
Ot:当我们带着
C
t
C_t
Ct去考试的时候,我们首先做一个
t
a
n
h
(
C
t
)
tanh(C_t)
tanh(Ct)的操作,即将我们记忆的内容转化为我们答题的能力,但是我们考试的内容并不是包含了我们记忆的所有知识,那么我们需要做计算
O
t
⨀
t
a
n
h
(
C
t
)
O_t\bigodot tanh(C_t)
Ot⨀tanh(Ct)用到部分的知识来答题。