机器学习-->深度学习-->RNN,LSTM

来源:互联网 发布:sublime js代码美化 编辑:程序博客网 时间:2024/05/21 11:59

本篇博文将把我所学习的循环神经网络(RNN)和长短时记忆神经网络(LSTM)做一个详细的总结分析。

RNN

为什么会有循环神经网络

传统的神经网络(包括CNN),输入输出都是互相独立的 。但是在一些场景中,后续的输出和之前的内容是相关的。比如一段句子的输出“我在CSDN上发表了一篇文_”,这里后续的输出和前面的文字有着很大的联系。传统的神经网络对这样的任务无法很好的进行预测。

循环神经网络结构

简单来看,就是把序列按时间展开

这里写图片描述

这里写图片描述 是时间t处的输入。

这里写图片描述 是时间t处的“记忆”,这里写图片描述,由上面的结构可以看出St包含了St-1,St-2等之前的信息。f是一些激活函数,例如sigmoid,tanh,reLu等。

这里写图片描述 是时间t处的输出,可能是softmax输出的属于每个候选词的概率,这里写图片描述

可以把隐状态St视作“记忆体”,捕捉了之前时间点上的的信息。

输出Ot由当前时间及之前所有的“记忆”共同计算得到。

实际应用中,St不能保留和利用之前所有的信息。

不同与CNN,RNN神经网络都共享一组参数(U,V,W),极大减小了需要训练和预估计的参数数量。

在某些实际场景中,不需要每个时刻都输出一个Ot,只需要最后output即可。

我觉得可以这样理解RNN,把多个传统神经网络的隐藏层神经元连接起来,每个神经网络表示某一时刻输入输出的一个模型。这样把多个连续时刻的模型连在一起,就可以结合上下文(前后时刻的内容)来进行分析预测了。

双向RNN

有些请看下,序列不只是依赖当前的情况还可能依赖后面的情况。比如补全一段文字里面的某一个词。

这里写图片描述

深层双向RNN

和双向RNN的区别是每一步/每一个时间点,我们设定多层结构

这里写图片描述

BPTT(BackPropagation Through Time)算法

在DNN或者CNN中用BP算法来更新参数,BP算法的核心就是利用链式求导法则计算error对每个参数的偏导。

而BPTT和BP是一个思路,也是损失函数对参数求偏导,只不过既然有step,就和时间t有关系。

这里写图片描述
其中W,U,V为参数矩阵

在t时刻的交叉熵损失函数:

这里写图片描述

所有时刻的交叉熵损失函数:

这里写图片描述

损失函数对参数W求偏导:

这里写图片描述

例如在t3时刻对参数W求偏导得:

这里写图片描述

其中S3等于:

这里写图片描述

明显S3依赖于S2,根据链式求导法则:

这里写图片描述

这里写图片描述

于是由 这里写图片描述 可推得:

这里写图片描述

这样就求得了损失函数对参数W的偏导,再由:这里写图片描述 来更新参数W。

LSTM

上面介绍的RNN神经网络解决了前后信息保存的问题。但是它有一个很要命的缺陷:随着时间距离的增大,RNN会丧失联系距它很远信息的能力。

LSTM大体结构

LSTM是RNN的一个变种,其大体结构和RNN相差不大,区别在于他的记忆细胞被改造了。

这里写图片描述

这里写图片描述:可理解为激活函数层,例如执行sigmoid,tanh等激活。

这里写图片描述:表示对向量或矩阵做元素级操作,或者说是逐点的执行运算,比如加法,减法等运算。

这里写图片描述:信息矩阵的传送方向。

这里写图片描述:向量或矩阵的连接运算。

这里写图片描述:复制操作。

细胞状态

这里写图片描述

上图中的实黑线可理解为记忆细胞C在传送带上沿着时间t在传送。h(t-1)表示上一时刻的输出,我们可以看出,实黑线上记忆细胞C以非线性的形式,保存了每个当前时刻的上一时刻的输出信息,而记忆细胞沿着传送带源源不断的传送,则最终的记忆细胞保存了之前所有的信息(虽然通过非线性的过滤,只能选择性的保留部分信息)。

这里的这里写图片描述表示执行sigmoid操作,输出一个在(0,1)范围内的概率值,信息乘以这个概率值则可实现信息过滤的功能。同理这里写图片描述表示执行双曲正切。

第一步:忘记门

这里写图片描述

这里写图片描述

这里表示将上一时刻的输出h(t-1)矩阵和当前时刻的输入Xt矩阵连接成一个矩阵,然后放进sigmoid函数内,得到一个截断概率,这个阶段概率再与C(t-1)相乘,则表示会从上一个传来的状态细胞 中丢弃什么信息,保存什么信息。故称为忘记门。

第二步:决定增加哪些新的信息到“细胞状态”中

这里写图片描述

  1. Sigmoid层决定什么值需要更新(输出一个概率值,决定过滤哪些信息)
  2. Tanh层创建一个新的候选值 向量 这里写图片描述
  3. 上述两步是为状态更新做准备。

第三步:更新细胞状态

这一步是整个LSTM的关键。

这里写图片描述

  1. 更新C(t-1)为Ct。
  2. 旧状态C(t-1)与忘记门得出概率 ft 相乘,决定忘记上一时刻细胞状态C(t-1)的哪部分信息,也即是过滤掉细胞状态里的部分信息。
  3. 加上 这里写图片描述 ,这就是过滤后的候选值
  4. 上面两项求和,第一项时过滤后的细胞状态信息;第二项是候选值信息。两项求和得到新的细胞状态。

第四步:基于细胞状态得到输出

这里写图片描述

  1. 第一个公式是用sigmoid函数输出一个0到1的概率值,也就是确定细胞状态哪部分将输出。

  2. Ct是更新后的细胞状态,用tanh函数处理细胞状态(得到一个-1到1之间的值),在将他和sigmoid输出的概率作乘积,输出我们确定要输出的那部分信息

至此LSTM神经网络内部结构大概就是如此。

我们再把LSTM和RNN做个对比:

在RNN中的记忆细胞信息是直接通过一些简单的线性加权然后再线性变换得到,如下图所示:

这里写图片描述

那么有:这里写图片描述

而在整个LSTM更新过程,都是先产生一个过滤概率,然后再添加一些信息,在求和等等,其实质和RNN基本一致,都是去掉旧信息里面不重要部分,再加上新信息里面重要的部分。只是过滤信息方式上面略有不同。

总结

LSTM为什么用sigmoid函数?

在传统的神经网络(包括CNN)中,激活函数 一般不用sigmoid函数,因为sigmoid函数很容易产生梯度消失现象。那么LSTM中为什么还要用sigmoid函数?

我个人的理解是:在LSTM中,激活函数并不是仅仅用来激活,刷选过滤信息,激活函数还有一个更重要的目的要实现,就是要使得记忆细胞在沿着传送带不断传送之前的记忆时,还需要保证信息不会膨胀,要保证信息量在一定范围内。那么这就必须使用sigmoid函数作为激活函数,reLu函数做不到这一点,我们可以看看sigmoid函数和reLu函数图像就可明白。

sigmoid函数图像:
这里写图片描述

reLu函数图像:大于0时,有多少信息就传多少信息。
这里写图片描述

很明显sigmoid函数始终是在0到1之间,而relu则不一定,故激活函数若用reLu函数,那么记忆信息在传递过程中可能会膨胀的越来越大。

为什么LSTM比RNN更能解决长时间依赖的问题

我们回顾上面RNN反向更新参数的结论:

在RNN中记忆细胞的正向传递公式:

这里写图片描述,这里面f可以假定视为tanh函数,很明显这是一个复合函数,复合函数求偏导时,是一个连乘的形式,我们结合传统神经网络里面BP算法方向更新参数的过程可知,根据链式求导 法则,在最后一层依次向前求参数偏导得(上面有详细的公式推导):

这里写图片描述

这里写图片描述

这里写图片描述

很显然,根据这个求偏导的公式可知,距离当前时刻越远,那么链式法则求偏导,连乘的项越多(注意这里面全是一项乘另外一项然后再乘以另外一项等等,只要有一项很小甚至接近于0,他对整体梯度的影响很大,并且随着距离梯度累积的越来越多,其梯度变化就会越来越小或者是越来越大),则就很有可能产生梯度消失或梯度爆炸现象。也即是距离当前越近,越不容易产生梯度消失(学到的越多),越远越容易产生梯度消失(学到的越少)。所以RNN会丧失保存距离很远之前的信息的能力。

而在LSTM中记忆细胞信息传递公式为:

这里写图片描述

它并不是一个复合函数(即外面没有套一层函数),而仅仅是两项求和的形式,那么在求偏导时,也是两项求和的形式,这时根据链式求导法则,梯度往前更新时,可以视为沿着两条轴向回传,一项等于0接近0,整体的梯度也不会接近0。这里的思路和残差神经网络里面避免梯度消失的思路有点类似。

故LSTM与RNN最大不同之处,就是在方向更新参数时把一个练乘形式变成一个求和的形式,这样即使在学习距离很远的信息时,也不会出现很严重的梯度消失问题。

利用pytorch实现RNN分类

#coding:utf-8import torchfrom torch import nnfrom torch.autograd import Variableimport torchvision.datasets as dsetsimport torchvision.transforms as transforms#import matplotlib.pyplot as plttorch.manual_seed(1)    # reproducible# Hyper ParametersEPOCH = 1               # 训练整批数据次数,为了节省时间只训练一次BATCH_SIZE = 64TIME_STEP = 28          # rnn 时间步数/图片高度INPUT_SIZE = 28         # rnn 每步输入值 / 图片宽度LR = 0.01               # 学习率DOWNLOAD_MNIST = True   # set to True if haven't download the data# Mnist digital datasettrain_data = dsets.MNIST(    root='./mnist/',    train=True,                         # this is training data    transform=transforms.ToTensor(),    # Converts a PIL.Image or numpy.ndarray to    # torch.FloatTensor of shape (C x H x W) and normalize in the range [0.0, 1.0]    download=DOWNLOAD_MNIST,            # download it if you don't have it)# 批训练50 samples,1 channel,28*28,故(50,1,28,28)train_loader = torch.utils.data.DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)# 为了节省时间,只测试前2000个样本test_data = dsets.MNIST(root='./mnist/', train=False, transform=transforms.ToTensor())test_x = Variable(test_data.test_data, volatile=True).type(torch.FloatTensor)[:2000]/255.   # shape (2000, 28, 28) value in range(0,1)test_y = test_data.test_labels.numpy().squeeze()[:2000]    # covert to numpy arrayclass RNN(nn.Module):    def __init__(self):        super(RNN, self).__init__()        self.rnn = nn.LSTM(         # 如果用单纯的RNN,则很难收敛            input_size=INPUT_SIZE,  # 输入特征数量,这里是图片宽度            hidden_size=64,         # rnn 隐藏层神经元            num_layers=1,           # RNN的层数            batch_first=True,       # input会以batch_size为第一维度的特征集(batch_size,time_step,input_size)        )        self.out = nn.Linear(64, 10) #最后的全连接层    def forward(self, x,h):        # 输入的批数据x shape (batch, time_step, input_size)(64,28,28)        # 输出的数据,包括每一步的输出r_out shape (batch, time_step, output_size)        # h_n shape (n_layers, batch, hidden_size)        # h_c shape (n_layers, batch, hidden_size)        # 每次的输入包括输入数据和之前的记忆数据        r_out, new_h = self.rnn(x, h)# 每次输入到RNN里面的都是当前的输入和上一次的状态信息,初始的状态信息为None        # choose r_out at the last time step        out = self.out(r_out[:, -1, :]) ##只要最后一步的output        return out,new_hrnn = RNN()print(rnn)optimizer = torch.optim.Adam(rnn.parameters(), lr=LR)   # optimize all cnn parametersloss_func = nn.CrossEntropyLoss()                       # the target label is not one-hottedh=None# training and testingfor epoch in range(EPOCH):    for step, (x, y) in enumerate(train_loader):        # gives batch data        b_x = Variable(x.view(-1, 28, 28)) #x此时的shape(batch_size,1,28,28),x.size(0)=batch_size,x.view将其变为(batch_size*1,28,28)        b_y = Variable(y)                               # batch y        output,h_state = rnn(b_x,h)                               # rnn output        loss = loss_func(output, b_y)                   # cross entropy loss        optimizer.zero_grad()                           # clear gradients for this training step        loss.backward()                                 # backpropagation, compute gradients        optimizer.step()                                # apply gradients        if step % 50 == 0:            test_output,new_h = rnn(test_x,h)  #注意shape要和上面保存一致  (samples, time_step, input_size)            pred_y = torch.max(test_output, 1)[1].data.numpy().squeeze()            accuracy = sum(pred_y == test_y) / float(test_y.size)            print('Epoch: ', epoch, '| train loss: %.4f' % loss.data[0], '| test accuracy: %.2f' % accuracy)# print 10 predictions from test datatest_output,new_h = rnn(test_x[:10].view(-1, 28, 28),h)pred_y = torch.max(test_output, 1)[1].data.numpy().squeeze()print(pred_y, 'prediction number')print(test_y[:10], 'real number')
原创粉丝点击