循环神经网络RNN
RNN
看起来很复杂,但实际上就是线性层的一种复用
例如,对于一个下雨的数据集,收集了每天的温度、气压等信息,用来预测未来是否会下雨
-
使用全连接层,将数据集中的温度、气压等信息作为下雨的的影响因素,做逻辑回归
缺点:计算量较大
-
由于这组数据带有序列性,因此可以考虑使用
RNN
循环神经网络RNN
使用权重共享的特点,来减少模型的计算量
rnn
中,对于每一步的输出,不仅取决于当前的变量,同时还受到前面数据的影响,适用于序列数据,即数据存在先序和后序关系
基本概念
RNN
的本质是一个线性层
- 将每一项的输入
x
i
x_i
xi输入到
RNN Cell
中,同时,因为数据存在序列性,因此每一个的输出还要受到前面数据的影响,即将当前的数据与前面的输出的数据进行融合,得到 h i h_i hi(hidden
) RNN Cell
就是一个线性层,每一个线性层的权重相同,即权重参数共享
RNN
网络的计算
每一步的计算不仅取决于当前的输入
x
i
x_i
xi,还受到之前输出
h
i
−
1
h_{i-1}
hi−1的影响
h
1
=
L
i
n
e
a
r
(
x
1
,
h
0
)
h
2
=
L
i
n
e
a
r
(
x
2
,
h
1
)
⋮
h
n
=
L
i
n
e
a
r
(
x
n
,
h
n
−
1
)
h_1 = Linear(x_1, h_0) \\ h_2 = Linear(x_2, h_1) \\ \vdots \\ h_n = Linear(x_n, h_{n-1})
h1=Linear(x1,h0)h2=Linear(x2,h1)⋮hn=Linear(xn,hn−1)
最后还要经过激活函数tanh
进行非线性变换,得到最终的输出
h
t
=
t
a
n
h
(
W
i
h
x
t
+
b
i
h
+
W
h
h
h
t
−
1
+
b
h
h
)
h_t = tanh(W_{ih}x_t + b_{ih} + W_{hh}h_{t-1} + b_{hh})
ht=tanh(Wihxt+bih+Whhht−1+bhh)
以这种循环的形式,把具有序列性的数据一个个输入进去,再把输出结果一个个循环一样的输出,这种计算方式就是循环神经网络
RNN
网络的反向传播
由上,可以将RNN
网络的数学模型写为如下格式:
O
t
=
g
(
V
⋅
h
t
)
h
t
=
f
(
U
⋅
x
t
+
W
⋅
h
t
−
1
)
O_t = g(V\cdot h_t) \\ h_t = f(U\cdot x_t + W\cdot h_{t-1})
Ot=g(V⋅ht)ht=f(U⋅xt+W⋅ht−1)
因此,再反向传播过程中,梯度可以初步写为:
∂
L
t
∂
w
=
∂
L
t
∂
O
t
⋅
∂
O
t
∂
h
t
⋅
∂
h
t
∂
w
\frac{\partial L_t}{\partial w} = \frac{\partial L_t}{\partial O_t} \cdot \frac{\partial O_t}{\partial h_t} \cdot \frac{\partial h_t}{\partial w}
∂w∂Lt=∂Ot∂Lt⋅∂ht∂Ot⋅∂w∂ht
而又可以发现,由于权重共享,因此
h
t
h_t
ht里还包含
h
t
−
1
h_{t-1}
ht−1,还需要加上对
h
t
−
1
h_{t-1}
ht−1对参数的求导
∂
L
t
∂
w
=
∂
L
t
∂
O
t
⋅
∂
O
t
∂
h
t
⋅
∂
h
t
∂
w
+
∂
L
t
∂
O
t
⋅
∂
O
t
∂
h
t
⋅
∂
h
t
∂
h
t
−
1
⋅
∂
h
t
−
1
∂
w
\frac{\partial L_t}{\partial w} = \frac{\partial L_t}{\partial O_t} \cdot \frac{\partial O_t}{\partial h_t} \cdot \frac{\partial h_t}{\partial w} + \frac{\partial L_t}{\partial O_t} \cdot \frac{\partial O_t}{\partial h_t} \cdot \frac{\partial h_t}{\partial h_{t-1}} \cdot \frac{\partial h_{t-1}}{\partial w}
∂w∂Lt=∂Ot∂Lt⋅∂ht∂Ot⋅∂w∂ht+∂Ot∂Lt⋅∂ht∂Ot⋅∂ht−1∂ht⋅∂w∂ht−1
而
h
t
−
1
h_{t-1}
ht−1里还包含
h
t
−
2
h_{t-2}
ht−2,不断往下,最终可以写为:
∂
L
t
∂
w
=
∑
i
=
0
t
∂
L
t
∂
O
t
∂
O
t
∂
h
t
∂
h
t
∂
h
i
∂
h
i
∂
w
\frac{\partial L_t}{\partial w} = \sum _{i=0}^{t}\frac{\partial L_t}{\partial O_t}\frac{\partial O_t}{\partial h_t}\frac{\partial h_t}{\partial h_i}\frac{\partial h_i}{\partial w}
∂w∂Lt=i=0∑t∂Ot∂Lt∂ht∂Ot∂hi∂ht∂w∂hi
即,RNN
神经网络中反向传播算法利用的是时间反向传播算法,需要求解所有时间步的梯度之后。利用多变量链式求导法则求解梯度
RNN
中梯度消失与梯度爆炸
由RNN
反向传播过程可以看出,
∂
h
t
∂
h
i
\frac{\partial h_t}{\partial h_i}
∂hi∂ht的计算量比较大,是一个连乘递归的过程
- 当 ∂ h t ∂ h i \frac{\partial h_t}{\partial h_i} ∂hi∂ht每一项都比较小( < 1 <1 <1)时,连乘起来的结果趋近于0,因此导致梯度趋近于0,发生梯度消失的问题,且随着 t t t越往前,梯度消失的现象越明显(因为越往前,递归项越多,梯度消失越多)
- 当 ∂ h t ∂ h i \frac{\partial h_t}{\partial h_i} ∂hi∂ht都比较大时,连乘起来结果非常大,因此导致梯度非常大,计算复杂,发生梯度爆炸的问题,且随着 t t t越往前,梯度爆炸的现象越明显(因为越往前,递归项越多,梯度爆炸越多)
Pytorch
代码实现
手写循环实现
定义RNN Cell
cell = torch.nn.RNNCell(input_size=input_size, hidden_size=hidden_size)
hidden
层的输出
hidden = cell(input, hidden)
关键:需要仔细考虑好维度的问题
- 输入的维度: ( b a t c h , i n p u t _ s i z e ) = b × x (batch, input\_size)=b×x (batch,input_size)=b×x
hidden
的维度: ( b a t c h , h i d d e n _ s i z e ) = b × h (batch, hidden\_size) = b×h (batch,hidden_size)=b×h- 输出的维度: ( b a t c h , h i d d e n _ s i z e ) = b × h (batch, hidden\_size) = b×h (batch,hidden_size)=b×h
- 整个序列需要构造的维度: ( s e q L e n , b a t c h , i n p u t _ s i z e ) (seqLen, batch, input\_size) (seqLen,batch,input_size)(序列长度,批量大小,输入维度)
import torch
########### 初始化参数 ##########
batch_size = 1
seq_len = 3
input_size = 4
hidden_size = 2
########## 构造cell ##########
cell = torch.nn.RNNCell(input_size=input_size, hidden_size=hidden_size)
########## 数据集 ##########
dataset = torch.randn(seq_len, batch_size, input_size)
hidden = torch.zeros(batch_size, hidden_size)
########## 循环计算 ##########
for idx, input in enumerate(dataset):
print('='*20, idx, '='*20)
print('Input size:', input.shape)
hidden = cell(input, hidden)
print('Outputs size:', hidden.shape)
print(hidden)
使用torch.nn.RNN
构造网络
cell = torch.nn.RNN(input_size = input_size,
hidden_size = hidden_size,
num_layers = num_layers) # 网络层数
输出结果
out, hidden = cell(inputs, hidden)
- 参数中的
hidden
代表 h 0 h_0 h0,维度为 ( n u m _ l a y e r s , b a t c h _ s i z e , h i d d e n _ s i z e ) (num\_layers, batch\_size,hidden\_size) (num_layers,batch_size,hidden_size) - 输出结果中的
hidden
代表最后的输出 h n h_n hn,维度为 ( n u m _ l a y e r s , b a t c h _ s i z e , h i d d e n _ s i z e ) (num\_layers, batch\_size,hidden\_size) (num_layers,batch_size,hidden_size) - 输出结果中的
out
代表 h 1 − h n h_1 - h_n h1−hn,维度为 ( s e q L e n , b a t c h , i n p u t _ s i z e ) (seqLen, batch, input\_size) (seqLen,batch,input_size)
layer
设置多层RNN
import torch
########### 初始化参数 ##########
batch_size = 1
seq_len = 3
input_size = 4
hidden_size = 2
num_layers = 1
########## 网络构建 ###########
cell = torch.nn.RNN(input_size=input_size,
hidden_size=hidden_size,
num_layers=num_layers)
########## 数据集定义 ##########
inputs = torch.randn(seq_len, batch_size, input_size)
hidden = torch.zeros(num_layers, batch_size, hidden_size)
########## 模型计算 ##########
out, hidden = cell(inputs, hidden)
print('Outputs size:', out.shape)
print('Output:', out)
print('Hidden size:', hidden.shape)
print("Hidden:", hidden)
简单实战
hello
→
\to
→ ohlol
数据预处理
首先要对输入的数据进项变化
由于我们输入的数据是字符串,而RNN
神经网络需要的数据是矩阵向量,因此需要先对字符串中每个字符进行编码,写到一个字典里:
character | index |
---|---|
e e e | 0 |
h h h | 1 |
l l l | 2 |
o o o | 3 |
从而将hello
转化为编码10223
然后再使用独热编码,即one-hot
,将其转化为矩阵格式,得到独热向量:
0
1
0
0
1
0
0
0
0
0
1
0
0
0
1
0
0
0
0
1
\begin{matrix} 0 \ &1 \ & 0 \ & 0 \\ 1 \ &0 \ & 0 \ & 0 \\ 0 \ &0 \ & 1 \ & 0 \\ 0 \ &0 \ & 1 \ & 0 \\ 0 \ &0 \ & 0 \ & 1 \\ \end{matrix}
0 1 0 0 0 1 0 0 0 0 0 0 1 1 0 00001
因此就确定了input_size
为4
one-hot
:0 → \to → 10000 10000 10000(第一位为1)
1 → \to → 01000 01000 01000(第二位为1)
…
整体过程如下所示:
输出数据的处理
最终输出的是类别概率,即四个字符h、e、l、o
四个类别的概率,即最终转变为一个分类问题
因此最终的输出还要接一个softmax
函数和交叉熵损失
代码实现
import torch
import matplotlib.pyplot as plt
########## 初始化具体参数 ##########
input_size = 4
hidden_size = 4
num_layers = 1
batch_size = 1
seq_len = 5
########## 构造数据集 ##########
idx2char = ['e', 'h', 'l', 'o']
x_data = [1, 0, 2, 2, 3]
y_data = [3, 1, 2, 3, 2]
one_hot_lookup = [[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]] # 独热编码矩阵
x_one_hot = [one_hot_lookup[x] for x in x_data]
inputs = torch.Tensor(x_one_hot).view(seq_len, batch_size, input_size)
labels = torch.LongTensor(y_data)
########### 定义模型 ##########
class Model(torch.nn.Module):
def __init__(self, input_size, hidden_size, batch_size, num_layers=1):
super(Model, self).__init__()
## 初始化参数
self.input_size = input_size
self.hidden_size = hidden_size
self.batch_size = batch_size
self.num_layers = num_layers
## 定义RNN模型
self.rnn = torch.nn.RNN(input_size=self.input_size,
hidden_size=self.hidden_size,
num_layers=self.num_layers)
def forward(self, x):
## 初始化 h0
hidden = torch.zeros(self.num_layers,
self.batch_size,
self.hidden_size)
## 模型计算
out, _ = self.rnn(x, hidden)
return out.view(-1, self.hidden_size)
model = Model(input_size, hidden_size, batch_size, num_layers)
########## 定义损失函数和优化器 ##########
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.05)
########## 模型训练 ##########
epoch_history = []
loss_history = []
for epoch in range(15):
optimizer.zero_grad()
# forward
outputs = model(inputs)
loss = criterion(outputs, labels)
# forward
loss.backward()
# updata
optimizer.step()
epoch_history.append(epoch)
loss_history.append(loss.item())
_, idx = outputs.max(dim=1) # 取结果的概率最大值
idx = idx.data.numpy()
print('Predicted:', ''.join([idx2char[x] for x in idx]), end='')
print(',Epoch [%d/15] loss = %.3f' % (epoch + 1, loss.item()))
plt.plot(epoch_history, loss_history)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()
最终输出结果:
损失函数曲线: