RNN循环神经网络之代码实现

RNN循环神经网络原理介绍详见RNN循环神经网络之原理详解。本篇主要通过Pytorch实现RNN,包括手动编写代码实现及基于Pytorch自带的RNN去实现。

一、手动代码实现RNN

1.1 通过简单代码熟悉RNN前向传播和反向传播

先导入所需的库

import torch
import torch.nn as nn
import torch.optim as optim
device="cuda" if torch.cuda.is_available() else "cpu"

手动实现RNNCELL

(为什么叫RNNCELL呢?因为RNN是按序列进行迭代计算的,RNNCELL只是负责处理某一个时刻数据的神经单元)

class SimpleRNN_1(nn.Module):                       #手动实现RNNCELL
    def __init__(self,input_size,hidden_size,output_size):
        super(SimpleRNN_1,self).__init__()
        #分别初始化U、W、V及偏置bh和by
        #U、W、V的具体含义详见“RNN循环神经网络之原理详解”
        #此处采用了nn.Paramter去定义了各个需要优化的参数
        self.U=nn.Parameter(torch.randn(input_size,hidden_size).to(device))
        self.W=nn.Parameter(torch.randn(hidden_size,hidden_size).to(device))
        self.V=nn.Parameter(torch.randn(hidden_size,output_size).to(device))
        self.bh=nn.Parameter(torch.zeros(hidden_size).to(device))
        self.by=nn.Parameter(torch.zeros(output_size).to(device))
    def forward(self,x,h_prev):
        #h_prev代表前一时刻的隐藏状态,进行RNN前向传播,并输出新的隐藏状态h和输出值y
        h=torch.tanh(torch.mm(x,self.Wxh)+torch.mm(h_prev,self.Whh)+self.bh)
        y=torch.mm(h,self.Why)+self.by
        return y,h

由于只是通过代码让大家熟悉RNN的前向及反向传播过程,所以所用的训练数据都是随机进行初始化的,无真实含义。

input_size=10    #定义输入层神经元个数
hidden_size=20   #定义隐藏层神经元个数
output_size=5    #定义输出层神经元个数
rnn_1=SimpleRNN_1(input_size,hidden_size,output_size)
#定义3个训练数据,例如可将x1想象为“我”的词向量,“x2”为“爱”的词向量,“x3”为“北京”的词向量
x1=torch.randn(1,input_size).to(device)
x2=torch.randn(1,input_size).to(device)
x3=torch.randn(1,input_size).to(device)
#初始化隐藏状态h_prev的值
h_prev=torch.zeros(1,hidden_size).to(device)
criterion=nn.MSELoss()
#定义目标值target1、target2和targer3
target1=torch.randn(1,output_size).to(device)
target2=torch.randn(1,output_size).to(device)
target3=torch.randn(1,output_size).to(device)
optimizer=optim.SGD(rnn_1.parameters(),lr=0.01)

进行模型训练

for epoch in range(100):
    optimizer.zero_grad()
    y1,h1=rnn_1(x1,h_prev)
    y2,h2=rnn_1(x2,h1)
    y3,h3=rnn_1(x3,h2)
    loss1=criterion(y1,target1)
    loss2=criterion(y2,target2)
    loss3=criterion(y3,target3)
    total_loss=loss1+loss2+loss3    #损失值为三个时间状态损失值之和
    total_loss.backward()   #进行反向传播
    optimizer.step()
    if epoch % 10 ==0:
        print(f"Epoch {epoch},loss:{total_loss.item()}")

输出结果为:

Epoch 0,loss:34.923057556152344
Epoch 10,loss:3.1045074462890625
Epoch 20,loss:0.7735685110092163
Epoch 30,loss:0.23181605339050293
Epoch 40,loss:0.07589025050401688
Epoch 50,loss:0.026361465454101562
Epoch 60,loss:0.009438274428248405
Epoch 70,loss:0.003402857342734933
Epoch 80,loss:0.0012187340762466192
Epoch 90,loss:0.00043166097020730376

1.2 将一个数据(词)一个数据(词)依次输入到模型,然后进行反向传播

以“我爱北京天安门”为例,将按照“我”、“爱”、“北京”、“天安门”这4个词按顺序依次输入到模型中进行前向传播,然后进行反向传播

定义RNNCELL,和1.1代码一样,仅仅是为了区分,将模型命名为SimpleRNN_2。

class SimpleRNN_2(nn.Module):                       #手动实现RNNCELL
    def __init__(self,input_size,hidden_size,output_size):
        super(SimpleRNN_1,self).__init__()
        #分别初始化U、W、V及偏置bh和by
        #U、W、V的具体含义详见“RNN循环神经网络之原理详解”
        #此处采用了nn.Paramter去定义了各个需要优化的参数
        self.U=nn.Parameter(torch.randn(input_size,hidden_size).to(device))
        self.W=nn.Parameter(torch.randn(hidden_size,hidden_size).to(device))
        self.V=nn.Parameter(torch.randn(hidden_size,output_size).to(device))
        self.bh=nn.Parameter(torch.zeros(hidden_size).to(device))
        self.by=nn.Parameter(torch.zeros(output_size).to(device))
    def forward(self,x,h_prev):
        #h_prev代表前一时刻的隐藏状态,进行RNN前向传播,并输出新的隐藏状态h和输出值y
        h=torch.tanh(torch.mm(x,self.Wxh)+torch.mm(h_prev,self.Whh)+self.bh)
        y=torch.mm(h,self.Why)+self.by
        return y,h

定义训练数据

input_size=10
hidden_size=20
output_size=5
rnn_2=SimpleRNN_2(input_size,hidden_size,output_size)
x=torch.randn(100,1,input_size).to(device)   #假设有100个词,每个词的向量为input_size维
y=torch.randn(100,1,output_size).to(device)
optimizer2=optim.Adam(rnn_2.parameters(),lr=0.01)
loss=nn.MSELoss()     #定义损失函数为MSE
h_prev=torch.zeros(1,hidden_size).to(device)  #初始化隐藏状态

进行模型训练

for epoch in range(100):
    loss_all=0.0                  #初始化损失函数值
    for t in range(x.shape[0]):   #一个词一个词的输入模型中
        if t==0:
            y_pre,h=rnn_2(x[t],h_prev)   #t=0时刻,采用初始化隐藏状态
        else:
            y_pre,h=rnn_2(x[t],h)
        loss_all=loss_all+loss(y_pre,y[t])     #更新损失函数值,是所有时刻的损失值之和
        loss_all=loss_all.to(device)
    optimizer2.zero_grad()
    loss_all.backward(retain_graph=True)       #反向传播
    optimizer2.step()
    if epoch % 10==0:
        print("epoch is:",epoch,"loss is:",loss_all.item())

输出结果为:

epoch is: 0 loss is: 2135.439697265625
epoch is: 10 loss is: 1681.2464599609375
epoch is: 20 loss is: 1343.6063232421875
epoch is: 30 loss is: 1209.5137939453125
epoch is: 40 loss is: 989.8624267578125
epoch is: 50 loss is: 855.2536010742188
epoch is: 60 loss is: 732.1452026367188
epoch is: 70 loss is: 623.844970703125
epoch is: 80 loss is: 632.3255615234375
epoch is: 90 loss is: 533.6336669921875

1.3 将训练数据以序列的形式输入到模型,然后进行反向传播

所谓序列形式,就是区别于1.2中一个词一次词输入的形式:比如,每次输入的数据是5个词组成的词向量,用这前5个词去预测第6个词:1-5个词去预测第6个词,2-6个词去预测第7个词...

class SimpleRNN_3(nn.Module):
    def __init__(self,input_size,hidden_size,output_size):
        super(SimpleRNN_3,self).__init__()
        self.U=nn.Parameter(torch.randn(input_size,hidden_size).to(device))
        self.W=nn.Parameter(torch.randn(hidden_size,hidden_size).to(device))
        self.V=nn.Parameter(torch.randn(hidden_size,output_size).to(device))
        self.bh=nn.Parameter(torch.zeros(hidden_size).to(device))
        self.by=nn.Parameter(torch.zeros(output_size).to(device))
    def forward(self,x,h_prev):
        seq=x.shape[0]   #获取输入数据X的第0维:即输入序列包含的数据个数
        for i in range(seq):
            if i==0:
                h=torch.tanh(torch.mm(x[i],self.U)+torch.mm(h_prev,self.W)+self.bh)
            else:
                h=torch.tanh(torch.mm(x[i],self.U)+torch.mm(h,self.W)+self.bh)
        y=torch.mm(h,self.V)+self.by
        return y,h

定义训练数据

input_size=10
hidden_size=20
output_size=5
rnn_3=SimpleRNN_3(input_size,hidden_size,output_size)
x=torch.randn(10,1,input_size).to(device)   #由于再前向传播时需要进行矩阵相乘(在SimpleRNN_3的forward函数中要遍历x中的每个向量,向量不能相乘,所以需要加个1,变为1xinput_size维的矩阵)
y=torch.randn(10,1,output_size).to(device)
optimizer3=optim.Adam(rnn_3.parameters(),lr=0.01)
loss=nn.MSELoss()
h_prev=torch.zeros(1,hidden_size).to(device)

进行模型训练

for epoch in range(100):
    optimizer3.zero_grad()
    y_pre,h=rnn_3(x,h_prev)
    loss_all=loss(y_pre,y[-1])
    loss_all.backward()
    optimizer3.step()
    if epoch % 10==0:
        print("epoch is:",epoch,"loss is:",loss_all.item())

输出结果为:

epoch is: 0 loss is: 4.718745231628418
epoch is: 10 loss is: 0.26759418845176697
epoch is: 20 loss is: 0.2974125146865845
epoch is: 30 loss is: 0.04763997346162796
epoch is: 40 loss is: 0.03225860744714737
epoch is: 50 loss is: 0.005860757082700729
epoch is: 60 loss is: 0.003772950265556574
epoch is: 70 loss is: 0.0016446646768599749
epoch is: 80 loss is: 0.00038170343032106757
epoch is: 90 loss is: 0.00014042628754395992

1.4 按batch输入到模型,batch中的元素为一个个序列,然后进行反向传播

此时,输入数据X的形状为[batch_size,seq,1,input_size],其中seq为序列的个数。

class SimpleRNN_4(nn.Module):
    def __init__(self,input_size,hidden_size,output_size):
        super(SimpleRNN_5,self).__init__()
        self.Wxh=nn.Parameter(torch.randn(input_size,hidden_size).to(device))
        self.Whh=nn.Parameter(torch.randn(hidden_size,hidden_size).to(device))
        self.Why=nn.Parameter(torch.randn(hidden_size,output_size).to(device))
        self.bh=nn.Parameter(torch.zeros(hidden_size).to(device))
        self.by=nn.Parameter(torch.zeros(output_size).to(device))
    def forward(self,x,h_prev):
        seq=x.shape[1]
        for i in range(seq):
            if i==0:
                h=torch.tanh(x[:,i,:,:]@self.Wxh+h_prev@self.Whh+self.bh)   #一定要注意,这里不使用torch.mm,而使用@,原因是torch.mm为二维矩阵相乘,这里x[:,i,:,:]的维度早已超出了二维矩阵的维度
            else:
                h=torch.tanh(x[:,i,:,:]@self.Wxh+h@self.Whh+self.bh)
        y=h@self.Why+self.by
        return y,h

定义训练数据

input_size=10
hidden_size=20
output_size=5
rnn_4=SimpleRNN_4(input_size,hidden_size,output_size)
x=torch.randn(5,10,1,input_size).to(device)   
y=torch.randn(5,10,1,output_size).to(device)
optimizer4=optim.Adam(rnn_4.parameters(),lr=0.01)
loss=nn.MSELoss()
h_prev=torch.zeros(1,hidden_size).to(device)

进行模型训练

for epoch in range(100):
    optimizer4.zero_grad()
    loss_all=0
    for k in range(x.shape[0]):
        y_pre,h=rnn_4(x[k],h_prev)
        loss_all=loss_all+loss(y_pre,y[k][-1])
    loss_all.backward()
    optimizer4.step()
    if epoch % 10==0:
        print("epoch is:",epoch,"loss is:",loss_all.item())

输出结果为:

epoch is: 0 loss is: 13.898061752319336
epoch is: 10 loss is: 3.803760290145874
epoch is: 20 loss is: 2.6271839141845703
epoch is: 30 loss is: 0.9352027177810669
epoch is: 40 loss is: 0.3608148694038391
epoch is: 50 loss is: 0.11155679821968079
epoch is: 60 loss is: 0.03883976861834526
epoch is: 70 loss is: 0.01533182617276907
epoch is: 80 loss is: 0.004870606120675802
epoch is: 90 loss is: 0.0015913982642814517

二、调用pytorch中的RNN包

2.1 nn.RNNCell

主要参数

  • input_size:输入特征的维度。例如,如果某个词的embedding后的维度为100,那么input_size=100;或者一条数据有10个特征,那么input_size=10
  • hidden_size:隐藏状态的维度。表示每个时间步隐藏状态的特征数量,也就是一个隐藏层的神经元个数
  • bias:是否使用偏置项,默认为 True​。
  • nolinearity:非线性激活函数,可选 'tanh'​ 或 'relu'​,默认为 'tanh'
import torch
import torch.nn as nn
# 定义一个 RNNCell 单元
rnn_cell = nn.RNNCell(input_size=10, hidden_size=20)
# 假设输入数据是一个批次大小为 32,特征维度为 10 的张量
input_tensor = torch.randn(32, 10)
# 初始化隐藏状态,维度为 (批次大小, 隐藏状态维度)
h0 = torch.zeros(32, 20)
# 对单个时间步进行前向传播
h1 = rnn_cell(input_tensor, h0)

2.2 nn.RNN

主要参数:

  • Input_size:输入特征的维度。例如,如果某个词的embedding后的维度为100,那么input_size=100;或者一条数据有10个特征,那么input_size=10
  • hidden_size:隐藏状态的维度,也就是一个隐藏层的神经元个数,当然,如果是按batch_size输入时,那么hidden_size是一个多维向量
  • num_layers:RNN的层数,可以支持多层RNN的实现
  • nonlinearity:非线性激活函数,可以选“tanh”、“relu”,默认为“tanh”
  • bias:是否使用偏置项,默认为True
  • batch_first:如果设置为 True​,输入和输出张量的第一个维度是批次大小(batch size),否则,第二个维度是批次大小。默认为False
  • dropout:如果不为0,在除最后一层外的每个 RNN 层的输出上添加一个 Dropout 层,以防止过拟合。默认为0.
  • bidirectional:如果设置为True,使用双向RNN,即同时处理正向和反向序列。
import torch
import torch.nn as nn
# 定义一个 RNN 层
rnn = nn.RNN(input_size=10, hidden_size=20, num_layers=2, batch_first=True)
# 假设输入数据是一个批次大小为 32,序列长度为 5,特征维度为 10 的张量
input_tensor = torch.randn(32, 5, 10)
# 初始化隐藏状态,维度为 (层数 * 方向数, 批次大小, 隐藏状态维度)
h0 = torch.zeros(2 * 1, 32, 20)
# 前向传播
output, hn = rnn(input_tensor, h0)  

nn.RNN和nn.RNNCell区别详见RNN循环神经网络之RNN十问

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值