Python深度学习(循环神经网络)--学习笔记(十三)

6.2 理解循环神经网络

  • 目前见过的所有神经网络(比如密集连接网络和卷积神经网络)都有一个特点,那就是它们都没有记忆。它们单独处理每个输入,在输入与输入之间没有保存任何状态。对于这样的网络,要想处理数据点的序列或时间序列,你需要向网络同时展示整个序列,即将序列转换成单个数据点。例如,在IMDB示例中就是这么做的:将全部电影评论转换为一个大向量,然后一次性处理。这种网络叫作前馈网络(feedforward network)。
  • 与此相反,当你在阅读这个句子时,你是一个词一个词地阅读(或者说,眼睛一次扫视一次扫视地阅读),同时会记住之前的内容。这让你能够动态理解这个句子所传达的含义。生物智能以渐进的方式处理信息,同时保存一个关于所处理内容的内部模型,这个模型是根据过去的信息构建的,并随着新信息的进入而不断更新。
  • 循环神经网络(RNN,recurrent neural network)采用同样的原理,不过是一个极其简化的版本:它处理序列的方式是,遍历所有序列数据,并保存一个状态(state),其中包含与已查看内容相关的信息。实际上,RNN是一类具有内部环的神经网络。在处理两个不同的独立序列(比如两条不同的IMDB评论)之间,RNN状态会被重置,因此,你仍可以将一个序列看作单个数据点,即网络的单个输入。真正改变的是,数据点不再是在单个步骤中进行处理,相反,网络内部会对序列元素进行遍历。
  • 用Numpy来实现一个简单的RNN的前向传递,这个RNN的输入是一个张量序列,将其编码成大小为(timesteps, input_features)的二维张量。它对时间步(timestep)进行遍历,在每个时间步,它考虑 t t t时刻的当前状态与 t t t时刻的输入(形状为(input_features)),对二者计算得到 t t t时刻的输出。然后,我们将下一个时间步的状态设置为上一个时间步的输出。对于第一个时间步,上一个时间步的输出没有定义,所以它没有当前状态。因此,你需要将状态初始化为一个全零向量,这叫作网络的初始状态(initial state)。
# RNN伪代码
state_t = 0 # t时刻的状态
for input_t in input_sequences: # 对序列元素进行遍历
	output_t = f(input_t, state_t)
	state_t = output_t # 前一次的输出变成下一次迭代的状态
  • 函数f:从输入和状态到输出的变换,其参数包括两个句子(W和U)和一个偏置向量。类似于前馈网络中密集连接层所做的变换。
# 更详细的RNN伪代码
state_t = 0
for input_t in input_sequence:
	output_t = activation(dot(W, input_t) + dot(U, state_t) + b)
	state_t = output_t
# 简单RNN的Numpy实现
import numpy as np

timesteps = 100 # 输入序列的时间步数
input_features = 32 # 输入特征空间的维度
output_features = 64 # 输出特征空间的维度

# 输入数据:随机噪声,仅作为示例
inputs = np.random.random((timesteps, input_features))

# 初始状态:全零向量
state_t = np.zeros((output_features, ))

# 创建随机的权重矩阵
W = np.random.random((output_features, input_features))
U = np.random.random((output_features, output_features))
b = np.random.random((output_features,))

successive_outputs = []
# input_t是形状为(input_features, )的向量
for input_t in  inputs:
    output_t = np.tanh(np.dot(W, input_t) + np.dot(U, state_t) + b)
    successive_outputs.append(output_t)
    state_t = output_t

final_output_sequence = np.stack(successive_outputs, axis=0)
print(final_output_sequence.shape)
  • RNN是一个for循环,它重复使用循环前一次迭代的计算结果,仅此而已。当然,你可以构建许多不同的RNN,它们都满足于上述定义。这个例子只是最简单的RNN表述之一。RNN的特征在于其时间步函数,比如前面例子中的函数:
output_t = np.tanh(np.dot(W, input_t) + np.dot(U, state_t) + b)
  • 本例中,最终输出是一个形状为(timesteps, output_features)的二维张量,其中每个时间步是循环在t时刻的输出。输出张量中的每个时间步t包含输入序列中时间步0~t的信息,即关于全部过去的信息。因此,在多数情况下,你并不需要这个所有输出组成的序列,你只需要最后一个输出(循环结束时的output_t),因为它已经包含了整个序列的信息。
6.2.1 Keras中的循环层
  • 上面Numpy的简单实现,对应一个实际的Keras层,即SimpleRNN层。
from keras.layers import SimpleRNN
  • 二者有一点区别:SimpleRNN层能够像其他Keras层一样处理序列批量,而不是像Numpy示例哪有只能处理单个序列。因此,它接收形状为(batch_size, timesteps, input_features)的输入,而不是(timesteps, input_features)。
  • 与Keras中的所有循环层一样,SimpleRNN可以在两种不同的模式下运行:一种是返回每个时间步连续输出的完整序列,即形状为(batch_size, timesteps, output_features)的三维张量;另一种是只返回每个输入序列的最终输出,即形状为(batch_size, output_features)的二维张量。这两种模式由return_sequences这个构造函数参数来控制。
# 只返回最后一个时间步的输出。
from keras.models import Sequential
from keras.layers import Embedding, SimpleRNN

model = Sequential()
model.add(Embedding(10000, 32))
model.add(SimpleRNN(32))
print(model.summary())

# 返回完整的状态序列
model = Sequential()
model.add(Embedding(10000, 32))
model.add(SimpleRNN(32, return_sequences=True))
print(model.summary())
  • 为了提高网络的表示能力,将多个循环层逐个堆叠有时也是很有用的。在这种情况下,你需要让所有中间层都返回完整的输出序列。
# 多个循环层的堆叠
model = Sequential()
model.add(Embedding(10000, 32))
model.add(SimpleRNN(32, return_sequences=True))
model.add(SimpleRNN(32, return_sequences=True))
model.add(SimpleRNN(32, return_sequences=True))
model.add(SimpleRNN(32))
print(model.summary())
  • 在第3章,处理这个数据集的第一个简单方法得到的测试精度是88%。不幸的是,与这个基准相比,这个小型网络的表现并不好(验证精度只有85%)。问题的部分原因在于,输入只考虑前500个单词,而不是整个序列,因此,RNN获得的信息比前面的基准模型更少。另一部分原因在于,SimpleRNN不擅长处理长序列,比如文本。
6.2.2 理解LSTM层和GRU层
  • SimpleRNN并不是Keras中唯一可用的循环层,还有另外两个:LSTM和GRU。在实践中总会用到其中之一,因为SimpleRNN通常过于简化,没有实用价值。SimpleRNN的最大问题是,在时刻t,理论上来说,它应该能够记住许多时间步之前见过的消息,但实际上它是不可能学到这种长期依赖的。其原因在于梯度消失(vanishing gradient problem),这一效应类似于在层数较多的非循环网络(即前馈网络)中观察到的效应:随着层数的增加,网络最终变得无法训练。Hochreiter、Schmidhuber和Bengio在20世纪90年代初研究了这一效应的理论原因。LSTM层和GRU层都是为了解决这个问题而设计的。
  • 先来看LSTM层。其背后的长短期记忆(LSTM,long short-term memory)算法由Hochreiter和Schmidhuber在1997年开发,是二人研究梯度消失问题的重要成果。
  • LSTM层是SimpleRNN层的一种变体,它增加了一种携带信息跨越多个时间步的方法。假设有一条传送带,其运行方法平行于你所处理的序列。序列中的信息可以在可以在任意位置跳上传送带,然后被传送到更晚的时间步,并在需要时原封不动地跳回来。这实际上就是LSTM的原理:它保存信息以便后面使用,从而防止较早期的信号在处理过程中逐渐消失。(对于LSTM的具体结构,本文就不详细论述了,推荐一篇文章:https://zhuanlan.zhihu.com/p/32085405,作者对LSTM进行了详尽的讲解、通俗易懂)。
6.2.3 Keras中一个LSTM的具体例子
  • 现在我们来看一个更实际的问题:使用LSTM层来创建一个模型,然后在IMDB数据上训练模型。这网络与前面介绍的SimpleRNN网络类似。你只需指定LSTM层的输出维度,其他所有参数(有很多)都使用Keras默认值。Keras具有很好的默认值,无须手动调参,模型通常也能正常运行。
from keras.layers import Embedding, Dense, LSTM
from keras.models import Sequential

model = Sequential()
model
  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值