博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
循环神经网络
阅读量:3938 次
发布时间:2019-05-23

本文共 9979 字,大约阅读时间需要 33 分钟。

1.DNN稠密的神经网络

可以理解成dense或是deep,这种稠密或是深度的神经网络其实就是一种全连接层。

1.1全连接的使用有哪些弊端

我们用一个预测下雨的问题来举例子:

例如我们想要预测一个下雨的问题,我们拿到一个当前的时间节点的特征然后预测一个现在是否要下雨的情况,是没有任何意义的,我们直接开开窗户看一眼就可以了。

所以我们更多的是应用之前几天的数据来预测这一天是不是要下雨的问题。这样我们输入其实是之前的几天的一个情况,每天三个数据,有三天,这样就有9个输入数据了,这样就可以使用一个全连接的网络来进行实现了。

这样子看起来是没有什么问题的,但是如果我们需要100天的输入预测第101天的情况,这个时候输入的维度就太多了。这样我们在得出最后结果的时候就需要训练太多的参数了,这样子我们很难得到最后我们想要的理想模型。

所以我们在解决这种序列出现的数据的时候我们可能很难使用简单的全连接神经网络来解决问题。

2.RNN循环神经网络

其实就是对线性层的一个复用。主要用于对序列数据进行处理

2.1什么样的数据是序列数据

那么什么样子的数据是具有序列的数据呢?

在这里插入图片描述
就像上图这样子前一个信息后一个信息,他们相互依赖的情况下,其实有一个很好的例子就是自然语言,例如我爱你和你爱我,这两句就是完全不一样的意思,但是两者具体有什么区别呢?
区别其实就在顺序这个地方上。

2.2使用什么思想来解决

我们其实可以看一下卷积层的参数是十分少的,但是全连接的参数就需要特别多,这是因为卷积有一个参数共享的思想在里面。所以我们同时使用卷积层和全连接层的时候,其实是全连接层在占用大部分的参数开销。

每个RNN Cell其实是一个线性层

在这里插入图片描述
我们可以看到其实就是每次的上一级输出再传递给下一级,作为下一级的输入存在,也就是体现出序列当中上一层对下一层的影响问题。每一层不仅对外输出,其实还把输出加入到了下一层。h0其实是一个先验信息,就是初始的信息。如果没有一个很合适的先验信息,我们也可以做成一个全零来进行输入。
但是我们注意,这里的左右两种表示方法是相互等价的,也就是说右边的图当中的所有RNN Cell其实是同一个东西。整个运算流程当中,其实我们只生产一个RNN Cell
在这里插入图片描述
在这里插入图片描述
其实我们就是反复的使用循环使用同一个线性层来处理。
那我我们实际上是通过怎样的矩阵运算将上一次的输出和这一次的输入融合在一起的呢?
首先我们这里像清楚这是矩阵加和不是矩阵合并,然后仔细看一下这里是一维矩阵,所以我们需要保证其feture的个数是相同的。所以我们肯定是需要矩阵乘法来转化size。所以下面的图片中的内容,我们是分别乘以参数矩阵的到最后的结果。然后再将两者进行加和。
在这里插入图片描述

但是实际上,程序在帮助我们进行计算的过程中,做了什么呢?其实他是将两个输入矩阵合并之后一起乘上参数矩阵的,使用线性代数知识我们不难发现这是等价的。

在这里插入图片描述

2.3如何实现RNN

2.3.1使用库中已经有的RNN模块来进行实现

一种就是我们直接调用一个其实我们这里只要设置一个输入的维度一个隐层的维度就可以了。

在这里插入图片描述
那么我们上一个测试代码:

2.3.1.1先使用一个RNN Cell来完成操作

代码具体如下:

import numpyimport torch#这里我们是定义一些我们需要使用的一些参数我们先记在这里batch_size=1seq_len=3input_size=4hidden_size=2#只有RNN里才用cell中没有这个概念num_layer=1#这里我们创建一个随机的数据集来测试一下,这里我们主要主义的是这些数据的传入先后顺序的问题。dataset=torch.randn(seq_len,batch_size,input_size)class CNNcell(torch.nn.Module):    def __init__(self):        super(CNN,self).__init__()        #这里我们定义一个循环神经网络来测试一下,这里只需要使用两个参数输入的维度和隐层的维度。        #当然这里隐层的维度其实就是输出的维度。        self.rnn=torch.nn.RNNCell(input_size=input_size,hidden_size=hidden_size)        #这里我们注意我们每一个batch都要定义一个对应的隐层情况        self.hidden=torch.zeros(batch_size,hidden_size)    def forward(self,x):        print(x.shape)        print(self.hidden.shape)        #这里我们其实也是需要使用循环来进行处理,不是rnn cell自己给我们处理。        #这里我们使用的仅仅是一个cell,我们每次送入其中的只是这个序列当中的一个。        for seq_idx,input in enumerate(x):            self.hidden=self.rnn(input,self.hidden)        print(self.hidden.shape)modol=CNNcell()modol(dataset)   #真正的在进行编码的时候我们最关键的其实就是搞清楚这些维度的关系,这里多了一个我们不熟悉的序列

2.3.1.2RNN Cell和RNN的区别

这里我们其实只是实现了一个简单的RNNCell主要是体现其功能,RNN则是一个具体的网络,更加复杂如下图就是一个RNN网络:

在这里插入图片描述
可以看到其实一个RNN网络当中是有很多的RNNCell来解决问题的,一下子我们就搞明白两者的关系了。
这里我们注意几个事情,
1.首先以一个事情就是这四个方向上的内容都是些干什么的内容:
1).左侧是进入的其实是隐层数据。对应的右侧的也就是隐层的输出数据。
2).下侧进入的其实是输入数据。对应的上侧输出的就是输出数据。
2.。另外一个就是这些颜色相同的,其实都是一个内容展开的。颜色相同的都是一个内容。

2.3.1.3 再使用一个RNN模块来完成操作

import numpyimport torch#同样先定义相关的数据batch_size=1seq_len=3input_size=4hidden_size=2num_layer=6 #这里我们多出来的唯一一个就是层数,其实就是rnn进行了几次的内容#这里我们获得数据集的情况,还和之前是相同的dataset=torch.randn(seq_len,batch_size,input_size)class RNN(torch.nn.Module):    def __init__(self):        super(RNN,self).__init__()        #这里我们是定义一个隐层的情况,这里我们是设置一个隐层的初始数据        self.hidden=torch.zeros(num_layer,batch_size,hidden_size)        #这里我们注意其实我们并不需要输入seq—len了,是自动进行识别的        self.rnn1=torch.nn.RNN(input_size=input_size,hidden_size=hidden_size,num_layers=num_layer)        def forward(self,x):        #注意这里和RNNcell是不同的,这里是返回一个元组的。一个是输出一个是隐层        x,self.hidden=self.rnn1(x,self.hidden)        print(x.shape)#这里其实是[seq_len,batch_size,hidden_size]        print(x)        print(self.hidden.shape)#这里其实是[layer_num,batch_size,hidden_size]        print(self.hidden)model=RNN()out=model(dataset)

2.3.1.4 另外的参数

batch_first

在这里插入图片描述
这其实就是是否将batch作为第一个维度的问题,这样有时候方便构造数据集

2.4更多的实践

2.4.1第一个实践

我们来看第一个实践的过程就是下面的例子,就是我们尝试使用循环神经网络完成下面的字符串转化。

在这里插入图片描述
我们先看一下发现这个字母不能直接进行处理,所以我们先要定义一个字典,然后对原有字符串进行转化,转化为我们喜闻乐见的数字
在这里插入图片描述
接下来我们发现这里转化之后,我们还是不能顺利的处理。因为我们输入都是一个向量,这里只是一位,这不好。因为一维的话,我们很难说清楚谁比谁更大,谁比谁影响更多,所以我们要再进行一次转化,就是转化为单热向量,我们注意这里一定要有多少种设置多少维度,注意才能保证相互之间不存在影响。也就是进行如下转化:
在这里插入图片描述
这里输出的为四个类别,其实这就成了一个多分类的问题,这些我们熟,我们只需要使用一个交叉熵就可以了。
每一层的具体情况顺利的也就出来了。
在这里插入图片描述
事情谈明白了,我们直接上代码:

2.4.1.1第一个实现(RnnCell)

先来一个使用rnn cell实现的情况

import numpyimport torch#同样的我们设置一些初始量input_size=4hidden_size=4batch_size=1#这里是方便我们数字转字符的情况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_onehot=[one_hot_lookup[j] for j in x_data]#这里的理解就很关键了,这里的-1到底指的是什么,这里应当指的是seq_size。想要完全理解为什么我们需要从最后开始捋一下#这个input_size是什么?每个张量最后落到一个格子上,只能存储一个数据,这里的数据只有0,1。所以上面转化之后其实是把一个数字转化为了是个数据#所以一个input_size其实指的是一个独热向量的四个维度。#既然是独热向量,那也就是一个字,所以这里必须是一个seq_len才可以顺利进行处理。#这里我们其实可以换一个方式理解,RNN有一个关键字是batch_size_first,就是是否把batch——size放在前面,但是为什么要专门设置这么一个东西呢?#当然是在逻辑上好理解才进行设置,所以我们可以把batch-size提到前面去,把后面两个连在一起就好理解了。#类似的我们也可以理解隐层的设置inputs=torch.Tensor(x_onehot).view(-1,batch_size,input_size)labels=torch.LongTensor(y_data).view(-1,1)class Moudle_Rnncell(torch.nn.Module):    def __init__(self,input_size,batch_size,hidden_size):        super(Moudle_Rnncell,self).__init__()        #这里我们注意一下虽然全局变量也有一个hidden_size但是这里我们使用的是shi        self.hidden_size=hidden_size        self.batch_size=batch_size        self.input_size=input_size        self.rnncell=torch.nn.RNNCell(input_size=input_size,hidden_size=hidden_size)    def forward(self,input,hidden):        hidden=self.rnncell(input,hidden)        return hidden        def init_hidden(self):        return torch.zeros(self.batch_size,self.hidden_size)        #这里我们注意是为一个rnncell在产生一个隐层的数据。#我们传入数据产生一个实例net=Moudle_Rnncell(input_size=input_size,batch_size=batch_size,hidden_size=hidden_size)#这里我们想这样的一个问题,为什么这个损失函数是在nn当中的。#这里其实就是理解一下就行,forward和backward我们正常来算的话其实forward是必须要最后走到损失的。#所以这个损失函数必须也是forward的一个组成部分。所以就和所有的有forward方法的在一起。critierion=torch.nn.CrossEntropyLoss()#这些有反馈的过程,都是在optim当中的optimizer=torch.optim.Adam(net.parameters(),lr=0.1)for epoch in range(15):    loss=0    optimizer.zero_grad()    hidden=net.init_hidden()    for input,label in zip(inputs,labels):        hidden=net(input,hidden)        #这里有一个关键点,这里的损失并不是使用item之后再进行加和的,这里是直接进行加和的。        #这里是因为我们在计算的不是每个rnn的损失来进行反馈是对seq—len中的所有元素,一起做损失。        loss+=critierion(hidden,label)        #接下来的内容其实是我们在输出当前的预测情况        _,indx=hidden.max(dim=1)        print(idx2char[indx.item()],end='')    loss.backward()    optimizer.step()    #这里的epoch+1是因为循环是从0开始计算的。    print(',epoch[%d/15] loss = %.4f'%(epoch+1,loss.item()))

2.4.1.2第二个实现(Rnn)

之后我们再使用一个rnn来实现一个

import numpyimport torch#初始化变量batch_size=1seq_len=5layer_num=3#注意这里都是独热向量的四维hidden_size=4input_size=4#这里我们直接将数据转化为数字。其实是简化了x_data=[1,0,2,2,3]y_data=[3,1,2,3,2]#我们定义转化方向idx2char=['e','h','l','o']one_hot_lookup=[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]one_hot_vector=[one_hot_lookup[j] for j in x_data]#转换为tensor的过程#这里如果不进行重构的话,出不来哪个batchsize那一层inputs=torch.Tensor(one_hot_vector).view(seq_len,batch_size,input_size)labels=torch.LongTensor(y_data)#接下来就是定义我们的模型class MoudleRnn(torch.nn.Module):    def __init__(self,batch_size,seq_len,layer_num,hidden_size,input_size):        super(MoudleRnn,self).__init__()        self.batch_size=batch_size        self.seq_len=seq_len        self.layer_num=layer_num        self.hidden_size=hidden_size        self.input_size=input_size        self.rnn=torch.nn.RNN(input_size=self.input_size,hidden_size=hidden_size,num_layers=layer_num)        self.hidden=torch.zeros(layer_num,batch_size,hidden_size)        def forward(self,input):        output,self.hidden=self.rnn(input,self.hidden)        return output.view(-1,hidden_size)        #这里我们注意一个事情就是我们这里虽然不使用self但是我们必须把他接进来,因为是默认传入的        def init_hidden(self):        hidden=torch.zeros(self.layer_num,self.batch_size,self.hidden_size)        return hiddennet=MoudleRnn(batch_size=batch_size,seq_len=seq_len,input_size=input_size,layer_num=layer_num,hidden_size=hidden_size)critrition=torch.nn.CrossEntropyLoss()optimizer=torch.optim.Adam(net.parameters(),lr=0.1)for epoch in range(15):    loss=0    critrition.zero_grad()    output=net(inputs)    #这里其实我们看一下这两个输入到底是什么情况,前面的output的输入是seqlen×batchsize×hiddensize    #之后的y_data是seqlen×1.    #所以这个输入的形式不对应,我们需要调整这个形式    #所以我们上面使用了output.view(-1,hidden_size)的方式将其调整了。    loss=critrition(output,labels)    loss.backward()    optimizer.step()    #接下来我们其实是要打印这个    _,idx=output.max(dim=1)    idx=idx.data.numpy()    print('predict:',''.join([idx2char[x] for x in indx]),end='')    print('epoch=[%d/15] loss=%.3f'%(epoch+1,loss.item())

2.4.1.3存在的问题

独热向量的问题:

1.首先有一个问题就是长度太长的问题,我们如果对字母使用一个独热向量,那么还行,最多有26个,但是如果我们对单词来使用独热向量,那么可就要了命了。这个数据就太大了。这时候维度的诅咒就开始出现了
2.所有的数据都集中在坐标轴上,十分的稀疏。
3.是硬编码的变换,不是学习出来的。
这个时候为了解决这些问题就出现了一个embeding层(嵌入层),就是将这个高维度的稀疏的独热向量映射成为低维度的、稠密的向量。这里就是我们常说的数据的降维。
其实也是一种矩阵乘法:高低的是输入的维度,左右的宽度是输出的维度。显然这个内容也可以进行反向传播。
在这里插入图片描述
这里我们可以使用下面的函数来完成这个操作,但是注意一点必须是对长整型类型的Tensor才能完成操作。

torch.nn.Embedding(num_embedings=x,embeding_dim)#前面一个是输入的维度,后面是输出的dim。

这里其实是自动对最后一行进行改变,前面的维度是什么情况,是没有任何影响的。是会原封不动的把前面的继承下来的。

类似的线性模型也是如此:也是只转化最后的一层的特性。
在这里插入图片描述
之后就是交叉熵损失函数的问题:也是中间可以加入维度的
在这里插入图片描述

2.4.1.4 我们对独热向量进行优化

这里其实我们只定义一个模型就可以了

class MoudleRnn(torch.nn.Module):    def __init__(self,batch_size,seq_len,layer_num,hidden_size,input_size,embedding_size,output_size):        super(MoudleRnn,self).__init__()        self.batch_size=batch_size        self.seq_len=seq_len        self.layer_num=layer_num        self.hidden_size=hidden_size        self.input_size=input_size        self.embedding_size=embedding_size        self.output_size=output_size                #这里是我们提供的将独热向量进行压缩的操作        self.emb=torch.nn.Embedding(num_embeddings=self.input_size,embedding_dim=self.embedding_size)        self.rnn=torch.nn.RNN(input_size=self.input_size,hidden_size=hidden_size,num_layers=layer_num)        #这里我们直接对输出的output进行再次操作转换,使用的技巧是,线性层直接对最后的一层进行操作        self.fc=torch.nn.Linear(self.hidden_size,self.output_size)        self.hidden=torch.zeros(layer_num,batch_size,hidden_size)        def forward(self,input):        input=emb(input)        output,_=self.rnn(input,self.hidden)        output=fc(output)        return output.view(-1,hidden_size)        #这里我们注意一个事情就是我们这里虽然不使用self但是我们必须把他接进来,因为是默认传入的

3.其他的类似网络

3.1LSTM的问题

lstm的大致示意图是如下图所示的:

在这里插入图片描述
有两个特点:
1.遗忘门
2.直接传播来解决梯度消失的问题

我们一般是认为这个网络的效果是要比RNN要好的多,但是同样的我们也可以看到这个东西的运算是十分复杂的,所以我们真正的要去使用的时候,对我们计算的要求是更高的。

在这里插入图片描述
我们可以看到前面的四个都是来自于x(t )和h(t-1)

3.2GRU

可以在一定程度上缓解LSTM运算复杂的情况。

在这里插入图片描述

转载地址:http://jkywi.baihongyu.com/

你可能感兴趣的文章
ubuntu下 rc.local的脚本不运行
查看>>
Linux下简单Makefile文件的编写
查看>>
linux下配置JDK JAVA环境
查看>>
解决Ubuntu 14.04 grub选择启动项10秒等待时间
查看>>
Linux查看某个进程并停止
查看>>
HID攻击进阶——WHID injector
查看>>
Wi-Fi定位劫持
查看>>
WiFi定位劫持·续篇——GPS劫持
查看>>
WiFi攻击进阶版——Deauth攻击
查看>>
iPhone锁屏却锁不住个人信息,iOS安全性真的很高吗?
查看>>
python沙箱逃逸小结
查看>>
共享充电,是雪中送炭还是暗藏危险?——恶意充电宝实验
查看>>
恶意充电宝的克星——USB安全接口
查看>>
重放攻击之无线门铃
查看>>
高端大气上档次·玩转微信摇色子
查看>>
揭秘微信分享背后的陷阱
查看>>
轻松劫持无人机,安全问题令人堪忧
查看>>
隐形的监控——无线键盘侦听
查看>>
涨姿势|无线键盘潜在安全隐患分析
查看>>
血族手游Lua脚本及资源文件解密
查看>>