好文分享—— RNN基本原理以及基于Pytorch实践
转载自https://blog.csdn.net/hei653779919/article/details/102868128 作者 隔壁的NLP小哥
RNN神经网络和基于Pytorch的实践
本文主要讲述了RNN循环神经网络的基本原理和利用pytorch进行序列生成的实践,原理的部分主要参考 https://blog.csdn.net/HappyRocking/article/details/83657993,实践的部分主要参考的是《深度学习原理和Pytorch实战》。在这里向作者表示感谢。
本文主要包含以下三个部分
- RNN问题引入
- RNN基本原理说明
- 基于pytorch实现RNN的序列生成任务
1、RNN问题引入
1.1 序列生成问题
对于一段给定的文本,一段给定的语音信号等等,我们都可以将其称为是一个序列。序列中的元素可以是一个符号,一个字符,一个单词等等。以文本序列为例,假设现在我们有一个句子是由W1W2…Wn个单词所构成。其中每一个Wi是一个单词。那么,我们如何根据给定的句子,构建出一个模型来实现自动生成新的序列的功能呢?这个问题就是序列生成问题。
1.2 序列生成问题的实际应用
在自然语言处理的相关任务中,通常可以通过序列生成来完成单词预测,句子生成等任务。
在语音信号处理中,可以通过已经有的声音信号来生成语音句子。
在股票金融中,可以通过将每天的股价构建出一个时间序列来预测未来新的股票价格。
2、RNN基本原理说明
2.1 基本神经网络的结构
基本的神经网络主要包含以下三个部分:
1、输入层:主要负责接收神经网络中的训练和测试数据。
2、隐藏层:主要负责整个神经网络的特征学习。
3、输出层:负责产生模型计算后的结果。
在RNN中,其基本神经网络的结构也是由这三种结构所构成的。在本文所提出的问题中,输入层接收的数据是序列中的一个元素,我们通过RNN网络和当前输入的元素来一步一步生成新的元素。直到整个序列元素生成结束。将模型的产生的结果通过输出层进行输出。
2.2 基本的RNN网络的结构
与我们所熟知的基本的前馈神经网络相比,RNN网络最大的不同之处在于它的网络中,各个隐藏层的节点的之间是具有一定联系的。在一般的前馈神经网络中,其隐藏层的节点的输入数据全部都是来自输入层的。但是在RNN神经网络中,其隐藏层节点的数据来源不仅包含输入层输入的数据,还包含同层的前一个隐藏节点的输出。所以其隐藏层的节点以及对应的输出节点的结构如下。
其中xi表示当前的输入节点,hi表示当前的隐藏层节点,hi-1表示同层的上一个隐藏状态节点。对于我们的序列生成问题而言,序列中的每一个单词是xi,可以确定的是,每一个单词xi不可能是独立的,各个输入节点的之间是存在一定的关系的。那么就需要我们的网络结构能够捕捉和感知到这种联系信息。我们将其称为单词xi的上下文。根据RNN中隐藏层节点的输入来看,hi单元的输入包含了xi和hi-1这两个部分,xi提供了当前词汇的特征,而hi-1则提供的了上下文的信息。这恰好是我们要完成序列生成所需要的。
根据上面的分析,我们可以得出来基本的RNN神经网络的结构如下:
通过上述的结构,我们不难发现基本的RNN网络机构有这样几个特点:
1、输入层的每一个节点之和隐藏层中一个与其对应的节点相连接,不与其他隐藏层的节点相连接。
2、隐藏层的每一个节点hi的输出受到前一个状态节点和输入节点两个节点的影响。
3、输入节点的数量和输出节点数量相同。
2.3 RNN相关参数的计算
首先,在原来的基本结构图上加入参数:
由上面的图不难发现,RNN中的相关权重主要分为输入层到隐藏层的权重u,隐藏层到输出层的权重v,以及隐藏层节点之间的传播权重w。同时可以发现,输入层每个节点到隐藏层节点的输入权重u相同,同理v和w也相同。
首先的给出RNN前向传播的计算过程:
本文主要是面对初学者,所以尽量减少繁杂的数学公式,主要给出上面两个核心公式。hi表示当前的状态节点的信息。oi表示当前节点,b表示偏置,f表示激活函数。w,u,v表示权重参数,xi表示当前输入节点。
2.4 RNN的实际训练
RNN的训练的核心思想也是反向传播的方式,主要应用的是BPTT算法,这是一种对于随时间的反向传播算法,其主要的训练方式也是沿着需要优化的参数的负梯度不断寻找更优的点,直到收敛。BPTT的计算公式在这里就不列出来了,有兴趣的可以参考RNN 与 LSTM 的原理详解这篇文章。
下面介绍一下RNN在序列生成问题中的训练方式,RNN网络从左到右依次读入序列中的一个字符,并且输入一种可能的字符预测,这个预测马上匹配相应下一个字符并得到反馈误差ei。这个误差可以使用交叉熵的方式进行衡量。我们可以每个序列遍历完成之后,将累计的误差进行反向传播,更新所有的权重。进入到下一个序列进行训练。就这样,RNN周而复始的进行训练,直到所有的序列的训练都结束。
虽然RNN在每一个时刻仅仅读入一个元素,但是由于RNN的同层连接,因此多步之前输入的元素可能还会对当前的元素产生影响。所以输入给RNN的相当于是整个句子序列。
2.5 基于Pytorch的RNN的核心代码
import torch
import torch.nn
class RnnCore(nn.Module):def __init__(self,inputSize,hiddenSize,outputSize,layers=1):### inputSize:输入层的大小(一般就是输入节点的数量)# hiddenSize:RNN隐藏层节点的数量# outputSize:输出层元素的数量# layers:隐藏层的层数##super(RnnCore,self).__init__()self.hidden_size = hiddenSizeself.num_layers = layers#下面开始定义一个基本的网络结构#首先是定义输入层 nn.Embedding#通过embedding层的操作,将输入的序列的input_size个节点的序列编码成hidden_size个节点的序列#输入给隐藏层self.embedding = nn.Embedding(inputSize,hiddenSize)#然后定义RNN层### 第一个hidden_size:也就是input_size,指的是RNN网络的输入节点的数量,#经过embedding层之后变成了 hiddensize# hidden_size:RNN层网络的节点的数量#num_layers:rnn的层数#bartch_firts :将数据的第一个维度置位为batch的大小#输入数据x原来的size为 length_seq,bathc_size,input_size#转换之后变为 batch_size,length_seq,input_sizeself.rnn = nn.RNN(hiddenSize,hiddenSize,layers,batch_first=True)#定义全连接的输出层self.fc = nn.Linear(hiddenSize,outputSize)#最后定义softmax层,从outputSize数量的元素中找出最终的预测字符self.softmax = nn.LogSoftmax(dim=1)#下面定义前向传播的的过程### inputs:输入数据# hidden :隐藏层的初始化信息def forward(self,inputs,hidden):#inputs的尺寸为: (batch_size,num_step,data_dim)# num_step:一个序列的长度,扫描完一个序列需要若干个时间步# data_dim :序列中每一个元素的向量维度# x 的尺寸为:batch_size,num_step,hidden_sizex = self.embedding(inputs)output,hidden = self.rnn(x,hidden)#获取最后一个时间步的结果。# output的size (batch_size,hidden_size)output = output[:,-1,:]#将隐藏层的最后结果输入到输出层,获取最后的结果output = self.fc(output)return output,hidden#下面定义隐藏的初始化方式def initHidden(self):# 包括 层数,batch数 以及隐藏层的节点数return Variable(torch.zeros(self.num_layers,1,self,hidden_size))
下面给出一份我自己的版本,对原作者的代码有了一定的新的解释
from torch import nn
import torchclass RNN(nn.Module):#定义一个模型模块def __init__(self,D_in,H,D_out,layers=1): #输入的维度,隐藏层维度和输出层维度,以及隐藏层的个数super(RNN, self).__init__()#对父类进行初始化self.H=Hself.num_layers=layers#定义网络层,首先是embedding层self.embedding=nn.Embedding(D_in,H) #embedding层的目的是将输入的向量转为隐藏层的维度#定义RNN层,使用现成的nn.RNN模型即可#batch_first将数据的第一个位置置为batch的大小,因为要进行批训练self.rnn=nn.RNN(H,H,layers,batch_first=True)#定义全连接的输出层self.fc=nn.Linear(H,D_out)#定义softmax层,从D_out数量的元素中找出最终的预测字符#下面定义向前传播的过程##inputs:输入数据##hidden:隐藏层的初始化信息def forward(self,inputs,hidden):#inputs数据的尺寸为:(batch_size,num_step,data_dim)#data_dim:序列中每一个元素的向量维度# x的尺寸为:batch_size,num_step,hidden_sizex=self.embedding(inputs)#得到输入数据的embeddings,一层一层算,把一个batch的所有embeddings进行计算output,hidden=self.rnn(x,hidden)#获得最后一个时间步的结果#output的size:(batch_size,hidden_size),因为RNN的output中会插入一个维度,这个维度# 和rnn中的layers相关,最后一层rnn出来后的output是在(batch_size,layers,hidden)中#最后一层,也就是layers=-1,才可以得到output=output[:,-1,:]##经过全连接层得到预测结果output=self.fc(output)return output,hidden
3 基于Pytorch实现的序列生成实战
3.1 任务描述
观察以下序列:
01
0011
000111
00001111
…………
不难发下其规律:
1、它们都只包含0和1
2、它们的长度不相等
3、0和1的数量是相同的,出现是连续的
4、通用的表示为 ‘0’* n + ‘1’ *n,n表示0和1出现的数量
这个序列在计算机中,我们称其为上下文无关文法,简单的说,就是可以被一组替代规则所生成,而与所处的上下文本身是无关的。
我们举例来说明这种序列的应用
The evidence was convincing 这是一个名词[n] + 动词[v]的结构
继续拓展可以变成
The evidence the layer provided was convincing NNVV结构
…………
3.2任务分析
1、如果出现的序列是0000,那么下一位是0还是1显然不能确定
2、如果出现的序列是00001,那么下一位是1
3、如果序列是00001111,此时0和1的数量相同,显然这个序列下一步应该结束
下面我们使用RNN来完成这个序列生成的任务。主要可以分为训练学习和序列生成两个步骤,在训练阶段,RNN尝试用前面的字符来预测下一个,在生成阶段,RNN会根据给点的种子来生成一个完整的序列。
#encoding=utf-8
import torch
import torch.nn as nn
import torch.optim
from torch.autograd import Variablefrom collections import Counterimport matplotlib
import matplotlib.pyplot as plt
from matplotlib import rc
import numpy as np#首先构建RNN网络
class SimpleRnn(nn.Module):def __init__(self,input_size,hidden_size,output_size,num_layers=1):super(SimpleRnn,self).__init__()self.hidden_size = hidden_sizeself.num_layers = num_layers#一个embedding层self.embedding = nn.Embedding(input_size,hidden_size)###pytorch的RNN层,batch_first表示可以让输入的张量表示第一个维度batch的指标#在定义这个部件的时候,需要制定输入节点的数量input_size 隐藏层节点的数量hidden_size#和RNN层的数目#我们世界上使用nn.RNN 来构造多层RNN的,只不过每一层RNN的神经元的数量要相同self.rnn = nn.RNN(hidden_size,hidden_size,num_layers,batch_first= True)#输出全连接层self.fc = nn.Linear(hidden_size,output_size)#最后的logsoftmax层self.softmax = nn.LogSoftmax(dim=1)def forward(self,inputs,hidden):'''基本运算过程:先进行embedding层的计算在将这个向量转换为一个hidden_size维度的向量。input的尺寸为 batch_size,num_step,data_dim'''x = self.embedding(inputs)#此时x的size为batch_size,num_step.hidden_size#从输入层到隐藏层的计算output,hidden = self.rnn(x,hidden)#从输出中选择最后一个时间步的数值,output中包含了所有时间步的结果#output的输出size batch_size,num_step,hidden_sizeoutput = output[:,-1,:]#此时output的尺寸为[batch_szie,hidden_size]#最后输入全连接层output = self.fc(output)#此时output的尺寸为 batch_size,hidden_size#输入softmaxoutput = self.softmax(output)return output,hidden#对隐藏层进行初始化#注意尺寸的大小为layer_size,batch_size,hidden_sizedef initHidden(self):return Variable(torch.zeros(self.num_layers,1,self.hidden_size))#下面是训练以及校验的过程
#首先生成01字符串类的数据以及样本的数量
train_set = []
validset = []
sample = 2000#训练样本中最大的n值
sz = 10#定义n的不同权重,我们按照10:6:4:3:1:1....来配置n=1,2,3,4,5
probablity = 1.0 *np.array([10,6,4,3,1,1,1,1,1,1])#保证n的最大值是sz
probablity = probablity[:sz]#归一化,将权重变成概率
probablity = probablity / sum(probablity)#开始生成sample这么多个样本,
#每一个样本的长度是根据概率来定义的
for m in range(2000):#对于随机生成的字符串,随机选择一个n,n被选择的权重记录在probablity#n表示长度#range生成序列,p表示通过之前定义的probablity的概率分布进行抽样n = np.random.choice(range(1,sz+1),p=probablity)#生成程度为2n这个字符串,用list的形式完成记录inputs = [0]*n + [1]*n#在最前面插入3表示开始字符,在结尾插入2表示结束符inputs.insert(0,3)inputs.append(2)train_set.append(inputs)#在生成sample/10的校验样本
for m in range(sample // 10):n =np.random.choice(range(1,sz+1),p=probablity)inputs = [0] * n + [1] *ninputs.insert(0,3)inputs.append(2)validset.append(inputs)#再生成若干个n特别大的样本用于校验
for m in range(2):n = sz + minputs = [0] * n + [1] *ninputs.insert(0,3)inputs.append(2)validset.append(inputs)#下面是训练过程
#输入的size是4,可能的值为0,1,2,3
#输出size为3 可能为 0,1 2rnn = SimpleRnn(input_size=4, hidden_size=2, output_size=3)
criterion = torch.nn.NLLLoss() #定义交叉熵函数
optimizer = torch.optim.Adam(rnn.parameters(),lr=0.001) #采用Adam算法#重复进行50次试验
num_epoch = 50
results = []
for epoch in range(num_epoch):train_loss = 0np.random.shuffle(train_set)#对每一个序列进行训练for i,seq in enumerate(train_set):loss = 0hidden = rnn.initHidden() #初始化隐含层的神经元、#对于每一个序列的所有字符进行循环for t in range(len(seq)-1):#当前字符作为输入,下一个字符作为标签x = Variable(torch.LongTensor([seq[t]]).unsqueeze(0))# x的size为 batch_size=1。time_steps=1,data_dimension = 1y = Variable(torch.LongTensor([seq[t+1]]))#y的size batch_size =1 data_dimension =1output,hidden = rnn(x,hidden)#output 的size:batch_size,output_size=3#hidden尺寸 layer_size = 1,batch_size = 1,hidden_sizeloss += criterion(output,y)#计算每一个字符的损失数值loss = 1.0 * loss / len(seq)optimizer.zero_grad()loss.backward()optimizer.step()train_loss += loss#打印结果if i>0 and i % 500 ==0:print('第{}轮,第{}个,训练平均loss:{:.2f}'.format(epoch,i,train_loss.data.numpy()/i))#下面在校验集上进行测试valid_loss = 0errors = 0show_out_p =''show_out_t = ''for i,seq in enumerate(validset):loss = 0outstring = ''targets = ''diff = 0hidden = rnn.initHidden()for t in range(len(seq)-1):x = Variable(torch.LongTensor([seq[t]]).unsqueeze(0))y = Variable(torch.LongTensor([seq[t+1]]))output,hidden = rnn(x,hidden)data = output.data.numpy()print("the output is ",data)#获取最大概率输出mm = torch.max(output,1)[1][0]#以字符的形式添加到outputstring中outstring += str(mm.data.numpy())targets += str(y.data.numpy()[0])loss += criterion(output,y) #计算损失函数#输出模型预测字符串和目标字符串之间差异的字符数量diff += 1 - mm.eq(y).data.numpy()[0]loss = 1.0 * loss / len(seq)valid_loss += loss#计算累计的错误数errors += diffif np.random.rand() < 0.1:#以0,1的概率记录一个输出的字符串show_out_p += outstring show_out_t += targets#打印结果print(output[0][2].data.numpy())print('第{}轮,训练loss: {:.2f},校验loss:{:.2f},错误率:{:.2f}'.format(epoch,train_loss.data.numpy()/len(train_set),valid_loss.data.numpy()/len(validset),1.0*errors/len(validset)))print("the show output is: ",show_out_p)print("the show taget is: ",show_out_t)results.append([train_loss.data.numpy()/len(train_set),valid_loss/len(train_set),1.0*errors/len(validset)])
4 RNN网络结构扩展
本部分的内容主要参考参考RNN 与 LSTM 的原理详解这篇文章。根据我们上文所叙述的RNN网络的结构,我们直到RNN的基本网络结构是一个输入单元对应一个隐藏单元,一个隐藏单元对应一个输出单元。那么,我们可以对于这种基本结构进行扩展。
1、多输入,单输出的结构
2、单输入、多出结构
3、编码解码结构
好文分享—— RNN基本原理以及基于Pytorch实践相关推荐
- RNN知识+LSTM知识+encoder-decoder+ctc+基于pytorch的crnn网络结构
一.基础知识: 下图是一个循环神经网络实现语言模型的示例,可以看出其是基于当前的输入与过去的输入序列,预测序列的下一个字符. 序列特点就是某一步的输出不仅依赖于这一步的输入,还依赖于其他步的输入或输出 ...
- 银行股价预测——基于pytorch框架RNN神经网络
银行股价预测--基于pytorch框架RNN神经网络 任务目标 数据来源 完整代码 流程分析 1.导包 2.读入数据并做预处理 3.构建单隐藏层Rnn模型 4.设计超参数,训练模型 5.加载模型,绘图 ...
- 一文掌握MobileNetV1和MobileNetV2(基于pytorch实现的人像背景虚化)
目录 一.概述 二.MobileNetV1原理和实现 2.1 原理 2.1.1 深度卷积 2.1.2 逐像素卷积 2.2 Pytorch实现 三.MobileNetV2原理和实现 3.1 原理 3.1 ...
- 深度学习必备书籍——《Python深度学习 基于Pytorch》
作为一名机器学习|深度学习的博主,想和大家分享几本深度学习的书籍,让大家更快的入手深度学习,成为AI达人!今天给大家介绍的是:<Python深度学习 基于Pytorch> 文章目录 一.背 ...
- Kornia开源可微分计算机视觉库,基于Pytorch,GitHub 3000星
点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 编辑丨机器之心 OpenCV 创始人 Gary Bradski 等人近期发表了一篇 Kornia 的综 ...
- [资源]基于 Pytorch 的 TorchGAN开源了!
之前推荐过一个基于 TensorFlow 的 GAN 框架–谷歌开源的 GAN 库–TFGAN. 而最近也有一个新的 GAN 框架工具,并且是基于 Pytorch 实现的,项目地址如下: https: ...
- pytorch 语义分割loss_vedaseg:基于pytorch的开源语义分割工具库,更多模型支持,更易拓展...
加入极市专业CV交流群,与6000+来自腾讯,华为,百度,北大,清华,中科院等名企名校视觉开发者互动交流!更有机会与李开复老师等大牛群内互动! 同时提供每月大咖直播分享.真实项目需求对接.干货资讯汇总 ...
- Github | 基于Pytorch可微分计算机视觉库
点上方蓝字计算机视觉联盟获取更多干货 在右上方 ··· 设为星标 ★,与你不见不散 仅作学术分享,不代表本公众号立场,侵权联系删除 转载于:机器之心 AI博士笔记系列推荐 周志华<机器学习> ...
- EasyBert,基于Pytorch的Bert应用
向AI转型的程序员都关注了这个号???????????? 机器学习AI算法工程 公众号:datayx EasyBert 基于Pytorch的Bert应用,包括命名实体识别.情感分析.文本分类以及文 ...
- 组件分享之后端组件——基于Golang实现的高级消息队列协议 (AMQP) 的消息代理garagemq...
组件分享之后端组件--基于Golang实现的高级消息队列协议 (AMQP) 的消息代理garagemq 背景 近期正在探索前端.后端.系统端各类常用组件与工具,对其一些常见的组件进行再次整理一下,形成 ...
最新文章
- in和exists的区别与执行效率问题解析
- linux blkid命令 查看块设备文件系统类型、LABEL、UUID信息 简介
- 并发编程-09安全发布对象+单例模式详解
- 深圳上海场 | 神策 2019 数据驱动大会「PPT 下载」新鲜出炉!
- c语言求前15项中偶数项的和,偶数前n项和
- 自然语言处理 —— 2.3 词嵌入的特性
- MySQL教程(二)—— 关于在ACCESS中使用SQL语句
- 华为效仿苹果卖高价手机?滴滴顺风车开放灰度测试;苹果官微被投诉“攻陷”| 极客头条...
- vue 大屏滚动实现 利用marquee和element-ui table
- 3d胆码计算机方法,3D百十个位定胆技巧准确率95
- 什么是pid控制算法_控制算法原理及实现之PID(以飞控为例)
- [thrift] thrift基本原理及使用
- 嵌入式常用裸机编程框架
- 从零搭建一辆ROS小车
- 洛谷 P2123 皇后游戏 解题报告
- 操作系统--01计算机系统概述
- 51单片机使用PWM调速
- C#基础语法————变量
- 旧版macOS官方下载地址
- java 去掉前后逗号_java 怎样去掉最后面那个逗号
热门文章
- JS中var和let
- Microsoft Expression blend 3 新功能简介
- Critical dependency: require function dependencies cannot be statically extracted
- Ember.js中文介绍
- linux进程(线程间)间通信-eventfd
- python封装为php库,从PHP运行Python脚本作为库
- java收_收java的小程序……谢谢
- phoenix hbase java_spark通过phoenix读写hbase(Java版)
- IDEA中单元测试使用Scanner控制台无法输入
- linux python2.7 post_在Python 2.7下面使用webpy框架