Tensorflow深度学习之循环神经网络

@Tensorflow深度学习之循环神经网络

11.1 序列表示方法

具有先后顺序的数据一般叫做序列,比如随时间而变化的商品价格数据就是非常典型的序列。

文字编码为数值的过程:Word Embedding

one-hot编码方式缺点:

  1. one-hot编码的向量是高纬度而且极其稀疏的,大量的位置是0,计算效率较低i,同时也不利于神经网络的训练。
  2. 忽略了单词语义的相关性。

衡量词向量之间相关度的方法:余弦相关度

其中a和b代表了两个词向量

11.1.1 Embedding层

在神经网络中,单词的表示向量可以直接通过训练的方式得到,我们把单词的表示层叫做Embedding层。负责吧单词编码为某个词向量v,它接受的是采用数字编码的单词编号i。

Embedding层

在Tensorflow中,可以通过layers.Embedding来定义一个word Embedding层。

x = tf.range(10) # 生成 10 个单词的数字编码
x = tf.random.shuffle(x) # 打散
# 创建共 10 个单词,每个单词用长度为 4 的向量表示的层
net = layers.Embedding(10, 4)
out = net(x) # 获取词向量

out

可以直接查看Embedding层内部的查询表table

net.embeeddings

out
查看net.embeddings张量的可优化属性为True,即可以通过梯度下降法进行优化。

net.embeddings.trainable

out

11.1.2 预训练的词向量

Embedding层的查询表示随机初始化的,须要从零开始训练。实际上,我们可以使用预训练的Word Embedding模型来得到单词的表示方法,基于预训练模型的词向量相当于迁移了整个语义空间的知识,往往可以得到更好的性能。
应用广泛的预训练模型:word2vec和Glove

# 从预训练模型中加载词向量表
embed_glove = load_embed('glove.6B.50d.txt')
# 直接利用预训练的词向量表初始化 Embedding 层
net.set_weights([embed_glove])

11.2 循环神经网络

在每个时间戳t,网络层接受当前时间戳的输入x(t)和上一个时间戳的网络状态向量h(t-1),经过以下函数交换后得到当前时间戳的新状态向量h(t),并写入内存状态中。

在这里插入图片描述
RNN
在时间戳上进行折叠后:
折叠的RNN
基本的循环神经网络

11.3 RNN层使用方法

11.3.1 SimpleRNNCell

cell = layers.SimpleRNNCell(3) # 创建 RNN Cell,内存向量长度为 3
cell.build(input_shape=(None,4)) # 输出特征长度 n=4
cell.trainable_variables # 打印 wxh, whh, b 张量

OUT
此时,须要用户自行初始化向量h(0)并记录每个时间戳上的h(t)。
对于SimpleRNNCell来说,o(t)=h(t),并没有经过额外的线性层转换,是同一个对象。在循环神经网络的初始阶段,状态向量h(0)一般初始化为全0向量。

# 初始化状态向量,用列表包裹,统一格式
h0 = [tf.zeros([4, 64])]
x = tf.random.normal([4, 80, 100]) # 生成输入张量,4 个 80 单词的句子
xt = x[:,0,:] # 所有句子的第 1 个单词
# 构建输入特征 n=100,序列长度 s=80,状态长度=64 的 Cell
cell = layers.SimpleRNNCell(64)
out, h1 = cell(xt, h0) # 前向计算
print(out.shape, h1[0].shape)

out

print(id(out), id(h1[0]))

out
两者的id一致,即状态向量直接作为输出向量。

11.3.2 多层的SipleRNNCell网络

循环神经网络容易出现梯度弥散和梯度爆炸的现象,深层的循环神经网络训练起来非常困难,目前常见的循环神经网络模型层数一般控制在十层以内。

第一种方式

x = tf.random.normal([4,80,100])
xt = x[:,0,:] # 取第一个时间戳的输入 x0
# 构建 2 个 Cell,先 cell0,后 cell1,内存状态向量长度都为 64
cell0 = layers.SimpleRNNCell(64)
cell1 = layers.SimpleRNNCell(64)
h0 = [tf.zeros([4,64])] # cell0 的初始状态向量
h1 = [tf.zeros([4,64])] # cell1 的初始状态向量

在时间轴上循环计算多次来实现整个网络的前向计算,每个时间戳上的输入x(t)首先通过第一层,得到输出out0,再通过第二层,得到输出out1。

for xt in tf.unstack(x, axis=1):
# xt 作为输入,输出为 out0
out0, h0 = cell0(xt, h0)
# 上一个 cell 的输出 out0 作为本 cell 的输入
out1, h1 = cell1(out0, h1)

上述方式先完成一个时间戳上的输入在所有层上的传播,再循环计算完所有时间戳上的输入。

第二种方式

也可先完成输入在第一层上所有时间戳的计算,并保存第一层在所有时间戳上的输出列表,再计算第二层、第三层等的传播。

# 保存上一层的所有时间戳上面的输出
middle_sequences = []
# 计算第一层的所有时间戳上的输出,并保存
for xt in tf.unstack(x, axis=1):
out0, h0 = cell0(xt, h0)
middle_sequences.append(out0)
# 计算第二层的所有时间戳上的输出
# 如果不是末层,需要保存所有时间戳上面的输出
for xt in middle_sequences:
out1, h1 = cell1(xt, h1)

使用该种方式,我们需要额外的list来保存上一层所有时间戳上面的状态信息。

11.3.3 SimpleRNN层

前向运算过程

layer = layers.SimpleRNN(64) # 创建状态向量长度为 64 的 SimpleRNN 层
x = tf.random.normal([4, 80, 100])
out = layer(x) # 和普通卷积网络一样,一行代码即可获得输出
out.shape

out
如果需要返回时间戳上的输出列表,可以设置return_sequeences=True参数

# 创建 RNN 层时,设置返回所有时间戳上的输出
layer = layers.SimpleRNN(64,return_sequences=True)
out = layer(x) # 前向计算
out # 输出,自动进行了 concat 操作

中间维度的80即为时间戳维度
同样,对于多层循环神经网络,我们可以堆叠多个SimpleRNN

net = keras.Sequential([ # 构建 2 层 RNN 网络
# 除最末层外,都需要返回所有时间戳的输出,用作下一层的输入
layers.SimpleRNN(64, return_sequences=True),
layers.SimpleRNN(64),
])
out = net(x) # 前向计算

版权声明:本文为weixin_50510992原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。