Pytorch深度学习实践(10)循环神经网络RNN

循环神经网络RNN

RNN看起来很复杂,但实际上就是线性层的一种复用

例如,对于一个下雨的数据集,收集了每天的温度、气压等信息,用来预测未来是否会下雨

  • 使用全连接层,将数据集中的温度、气压等信息作为下雨的的影响因素,做逻辑回归

    缺点:计算量较大

  • 由于这组数据带有序列性,因此可以考虑使用RNN循环神经网络

    RNN使用权重共享的特点,来减少模型的计算量

rnn中,对于每一步的输出,不仅取决于当前的变量,同时还受到前面数据的影响,适用于序列数据,即数据存在先序和后序关系

基本概念

RNN的本质是一个线性层

在这里插入图片描述

  • 将每一项的输入 x i x_i xi输入到RNN Cell中,同时,因为数据存在序列性,因此每一个的输出还要受到前面数据的影响,即将当前的数据与前面的输出的数据进行融合,得到 h i h_i hihidden
  • RNN Cell就是一个线性层,每一个线性层的权重相同,即权重参数共享

RNN网络的计算

每一步的计算不仅取决于当前的输入 x i x_i xi,还受到之前输出 h i − 1 h_{i-1} hi1的影响
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,hn1)
最后还要经过激活函数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+Whhht1+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(Vht)ht=f(Uxt+Wht1)
因此,再反向传播过程中,梯度可以初步写为:
∂ 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} wLt=OtLthtOtwht
而又可以发现,由于权重共享,因此 h t h_t ht里还包含 h t − 1 h_{t-1} ht1,还需要加上对 h t − 1 h_{t-1} ht1对参数的求导
∂ 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} wLt=OtLthtOtwht+OtLthtOtht1htwht1
h t − 1 h_{t-1} ht1里还包含 h t − 2 h_{t-2} ht2,不断往下,最终可以写为:
∂ 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} wLt=i=0tOtLthtOthihtwhi
即,RNN神经网络中反向传播算法利用的是时间反向传播算法,需要求解所有时间步的梯度之后。利用多变量链式求导法则求解梯度

RNN中梯度消失与梯度爆炸

RNN反向传播过程可以看出, ∂ h t ∂ h i \frac{\partial h_t}{\partial h_i} hiht的计算量比较大,是一个连乘递归的过程

  • ∂ h t ∂ h i \frac{\partial h_t}{\partial h_i} hiht每一项都比较小( < 1 <1 <1)时,连乘起来的结果趋近于0,因此导致梯度趋近于0,发生梯度消失的问题,且随着 t t t越往前,梯度消失的现象越明显(因为越往前,递归项越多,梯度消失越多)
  • ∂ h t ∂ h i \frac{\partial h_t}{\partial h_i} hiht都比较大时,连乘起来结果非常大,因此导致梯度非常大,计算复杂,发生梯度爆炸的问题,且随着 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 h1hn,维度为 ( 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神经网络需要的数据是矩阵向量,因此需要先对字符串中每个字符进行编码,写到一个字典里:

characterindex
e e e0
h h h1
l l l2
o o o3

从而将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()

最终输出结果:

在这里插入图片描述

损失函数曲线:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值