循环神经网络

RNN

rnn起因
现实世界中,元素都是相互连接的,例如语言需要理解上下文的关系来确认表的含义,但是机器要做到这一步却很难。因此,就有了循环神经网络,本质是:拥有记忆能力,会根据记忆的内容来进行推断。输出依赖当前的记忆和输入
RNN是利用顺序的信息,在神经网络中,假设输入和输出相互独立。想要预测句子中的下一个单词,就需要直到它的前面有哪些词语,甚至后边的语句才能给出正确的答案。RNN称循环,是因为它们对序列的每个元素都会执行相同的任务。所有的输出都取决于先前的计算。即RNN是拥有记忆的,可以捕捉到当前为止的计算信息。循环神经网络的提出是基于记忆模型的想法,期望网络能够记住前面出现的特征,并依据特征推断后面的结果,而且整体的网络结构不断的循环,因此称循环神经网络

RNN网络结构及原理

循环神经网络的基本结构很简单,就是将网络的输出保存到一个记忆单元中,该单元和下一次的输入一同进入神经网络中。网络不仅输出结果,还会将结果保存到记忆单元中。
RNN可以被看做是同一个网络的多次赋值,每个网络模块把信息传递给下一个模块
循环神经网络具有特别好的记忆特性,能够将记忆内容应用到当前情景下,但是记忆的时效性很差,即它具有遗忘性。我们会很清楚的记得最近发生的事情而忘记很久以前的事情,循环神经网络也有同样的问题

pytorch 中使用 nn.RNN 类来搭建基于序列的循环神经网络,它的构造函数有以下几个参数:

nput_size:输入数据X的特征值的数目
hidden_size:隐藏层的神经元数量,也就是隐藏层的特征数量
num_layers:循环神经网络的层数,默认值是 1
bias:默认为 True,如果为 false 则表示神经元不使用 bias 偏移参数
batch_first:如果设置为 True,则输入数据的维度中第一个维度就 是 batch 值,默认为 False。默认情况下第一个维度是序列的长度, 第二个维度才是 - - batch,第三个维度是特征数目
dropout:如果不为空,则表示最后跟一个 dropout 层抛弃部分数据,抛弃数据的比例由该参数指定
RNN 中最主要的参数是 input_size 和 hidden_size,这两个参数务必要搞清楚。其余的参数通常不用设置,采用默认值即可

import torch
import torch.nn as nn

rnn = nn.RNN(20, 50, 2)
input = torch.randn(100, 48, 20)
h = torch.randn(2, 48, 50)
output, h = rnn(input, h)
print(output.size(), h.size())
torch.Size([100, 48, 50]) torch.Size([2, 48, 50])
LSTM

LSTM(Long Short Term Memory Networks):长短期记忆网络

标准的循环神经网络只有一个简单的层结构,而LSTM内部有4个层结构:

第一层:遗忘层 -->决定丢弃什么信息

第二层:tanh层 -->用来产生更新值的选项,说明状态的维度加强或减弱

第三层:sigmoid(输入层) -->它的输出值要乘到tanh层的输出上,起到缩放的作用:sigmoid输出为0说明相应维度上的状态不需要更新

第四层:输出层 -->输出值跟状态有关,取决于sigmoid层

pytorch QR分解 pytorch rnn_循环神经网络

lstm = nn.LSTM(10, 25, 2)
input = torch.randn(5, 4, 10)
h = torch.randn(2, 4, 25)
c = torch.randn(2, 4, 25)
output, hn = lstm(input, (h, c))
print(output.size(), hn[0].size(), hn[1].size())
torch.Size([5, 4, 25]) torch.Size([2, 4, 25]) torch.Size([2, 4, 25])
GRU

GRU(gated recurrent utils)与LSTM的不同在于GRU将遗忘门和输入门合成了一个“更新门”,网络不再给出额外的记忆状态,而是将输出结果作为记忆状态不断向后循环传递,网络的输入和输出都变得特别简单

pytorch QR分解 pytorch rnn_pytorch QR分解_02

gru = nn.GRU(10, 25, 2)
input = torch.randn(5, 3, 10)
h = torch.randn(2, 3, 25)
output, h = gru(input, h)
print(output.size(), h.size())
torch.Size([5, 3, 25]) torch.Size([2, 3, 25])
循环网络的向后传播(BPTT)

在前向传播的情况下,RNN的输入随着一个时间步前进。在反向传播的情况下,“回到过去”改变权重,因此称为通过实践的反向传播(BPTT)

我们通常把整个序列(单词)看作一个训练样本,所以总的误差是每个时间步(字符)中误差的和。权重在每一个时间步长是相同的(所以可以计算总误差后一起更新)。 1. 使用预测输出和实际输出计算交叉熵误差 2. 网络按照时间步完全展开 3. 对于展开的网络,对于每一个实践步计算权重的梯度 4. 因为对于所有时间步来说,权重都一样,所以对于所有的时间步,可以一起得到梯度(而不是像神经网络一样对不同的隐藏层得到不同的梯度) 5. 随后对循环神经元的权重进行升级

循环神经网络目在自然语言处理中应用最为火热

1.词嵌入(word embedding)

在图像分类问题会使用 one-hot 编码.比如LeNet中一共有10个数字0-9,如果这个数字是2的话类,它的 编码就是 (0,0,1,0, 0,0 ,0,0,0,0),对于分类问题这样表示十分的清楚,但是在自然语言处理中,因为单词的数目过多比如有 10000 个不同的词,那么使用 one-hot 这样的方式来定义,效率就特别低,每个单词都是 10000 维的向量.其中只有一位是 1 , 其余都是 0,特别占用内存,而且也不能体现单词的词性,因为每一个单词都是 one-hot,虽然有些单词在语义上会更加接近.但是 one-hot 没办法体现这个特点,所以 必须使用另外一种方式定义每一个单词

用不同的特征来对每个词汇及逆行表征,相对与不同的特征,不同的单词均有不同的值就是词嵌入
词嵌入不仅对不同的单词实现了特征化的表示,还能通过计算词与词之间的相似度(多维空间中,寻找词向量各个维度的距离相似度)

embedding = nn.Embedding(10, 4)
input = torch.LongTensor([[1, 2, 4, 5, 6], [4, 5, 2, 3, 9]])
output = embedding(input)
print(output.size())
torch.Size([2, 5, 4])

Beam search
在生成第一个词的分布后,可以使用贪心搜索会根据我们的条件语言模型挑选出最有可能输出的第一个词语,但是对于贪心搜索算法来说,我们的单词库中有成百到千万的词汇,去计算每一种单词的组合的可能性是不可行的。所以我们使用近似的搜索办法,使得条件概率最大化或者近似最大化的句子,而不是通过单词去实现。

Beam Search(集束搜索)是一种启发式图搜索算法,通常用在图的解空间比较大的情况下,为了减少搜索所占用的空间和时间,在每一步深度扩展的时候,剪掉一些质量比较差的结点,保留下一些质量较高的结点。虽然Beam Search算法是不完全的,但是用于了解空间较大的系统中,可以减少空间占用和时间。

beam search可以看做是做了约束优化的广度优先搜索,首先使用广度优先策略建立搜索树,树的每层,按照启发代价对节点进行排序,然后仅留下预先确定的个数(Beam width-集束宽度)的节点,仅这些节点在下一层次继续扩展,其他节点被剪切掉。 1. 将初始节点插入到list中 2. 将给节点出堆,如果该节点是目标节点,则算法结束; 3. 否则扩展该节点,取集束宽度的节点入堆。然后到第二步继续循环。 4. 算法结束的条件是找到最优解或者堆为空。

在使用上,集束宽度可以是预先约定的,也可以是变化的,具体可以根据实际场景调整设定。

注意力模型
对于使用编码和解码的RNN模型,我们能够实现较为准确度机器翻译结果。对于短句子来说,其性能是十分良好的,但是如果是很长的句子,翻译的结果就会变差。 我们人类进行人工翻译的时候,都是一部分一部分地进行翻译,引入的注意力机制,和人类的翻译过程非常相似,其也是一部分一部分地进行长句子的翻译。