zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Python-Tensorflow-循环神经网络

2023-09-14 09:13:04 时间

循环神经网络(Recurrent Neural Network,RNN)很多实时情况都能通过时间序列模型来描述。

例如,如果你想写一个文档,单词的顺序很重要,当前的单词肯定取决于以前的单词。如果把注意力放在文字写作上...一个单词中的下一个字符取决于之前的字符(例如,The quick brown f...,下一个字母是 o 的概率很高),如下图所示。关键思想是在给定上下文的情况下产生下一个字符的分布,然后从分布中取样产生下一个候选字符:

 

关于“The quick brown fox”句子的预测示例

图 1 关于“The quick brown fox”句子的预测示例


一个简单的变体是存储多个预测值,并创建一个预测扩展树,如下图所示:


关于“The quick brown fox”句子的预测树示例
图 2 关于“The quick brown fox”句子的预测树示例


基于序列的模型可以用在很多领域中。在音乐中,一首曲子的下一个音符肯定取决于前面的音符,而在视频领域,电影中的下一帧肯定与先前的帧有关。此外,在某些情况下,视频的当前帧、单词、字符或音符不仅仅取决于过去的信号,而且还取决于未来的信号。

基于时间序列的模型可以用RNN来描述,其中,时刻 i 输入为 Xi,输出为 Yi,时刻 [0,i-1] 区间的状态信息被反馈至网络。这种反馈过去状态的思想被循环描述出来,如下图所示:


反馈的描述

图 3 反馈的描述


展开(unfolding)网络可以更清晰地表达循环关系,如下图所示:


循环单元的展开
图 4 循环单元的展开


最简单的 RNN 单元由简单的 tanh 函数组成,即双曲正切函数,如下图所示:

简单的 tanh 单元
图 5 简单的 tanh 单元

梯度消失与梯度爆炸

由于存在两个稳定性问题,训练 RNN 是很困难的。由于反馈环路的缘故,梯度可以很快地发散到无穷大,或者迅速变为 0。如下图所示:


梯度示例
图 6 梯度示例


在这两种情况下,网络将停止学习任何有用的东西。梯度爆炸的问题可以通过一个简单的策略来解决,就是梯度裁剪。梯度消失的问题则难以解决,它涉及更复杂的 RNN 基本单元(例如长短时记忆(LSTM)网络或门控循环单元(GRU))的定义。先来讨论梯度爆炸和梯度裁剪:

梯度裁剪包括对梯度限定最大值,以使其不能无界增长。如下图所示,该方法提供了一个解决梯度爆炸问题的简单方案:


梯度裁剪示例
图 7 梯度裁剪示例


解决梯度消失需要一个更复杂的记忆模型,它可以有选择地忘记以前的状态,只记住真正重要的状态。如下图所示,将输入以概率 p∈[0,1] 写入记忆块 M,并乘以输入的权重。

以类似的方式,以概率 p∈[0,1] 读取输出,并乘以输出的权重。再用一个概率来决定要记住或忘记什么:


记忆单元示例
图 8 记忆单元示例

长短时记忆网络(LSTM)

长短时记忆网络可以控制何时让输入进入神经元,何时记住之前时序中学到的东西,以及何时让输出传递到下一个时间戳。所有这些决策仅仅基于输入就能自我调整。

乍一看,LSTM 看起来很难理解,但事实并非如此。我们用下图来解释它是如何工作的:


一个 LSTM 单元的示例
图 9 一个 LSTM 单元的示例


首先,需要一个逻辑函数 σ 计算出介于 0 和 1 之间的值,并且控制哪个信息片段流经 LSTM 门。请记住,logisitic 函数是可微的,所以它允许反向传播。

然后需要一个运算符 ⊗ 对两个相同维数的矩阵进行点乘产生一个新矩阵,其中新矩阵的第 ij 个元素是两个原始矩阵第 ij 个元素的乘积。同样,需要一个运算符 ⊕ 将两个相同维数的矩阵相加,其中新矩阵的第 ij 个元素是两个原始矩阵第 ij 个元素的和。在这些基本模块中,将 i 时刻的输入 xi 与前一步的输出 yi放在一起。

方程 fi=σ(Wf·[yi-1,xi]+bf) 是逻辑回归函数,通过控制激活门 ⊗ 决定前一个单元状态 Ci-1 中有多少信息应该传输给下一个单元状态 Ci(Wf 是权重矩阵,bf是偏置)。逻辑输出 1 意味着完全保留先前单元状态 Ct-1,输出 0 代表完全忘记 Ci-1 ,输出(0,1)中的数值则代表要传递的信息量。

接着,方程根据当前输入产生新信息,方程 si=σ(Wc·[Yi-1,Xi]+bc) 则能控制有多少新信息通过运算符 ⊕ 被加入到单元状态 Ci 中。利用运算符 ⊗ 和 ⊕,给出公式对单元状态进行更新。

最后,需要确定当前单元状态的哪些信息输出到 Yi。很简单,再次采用逻辑回归方程,通过 ⊗ 运算符控制候选值的哪一部分应该输出。在这里有一点需要注意,单元状态是通过 tanh 函数压缩到 [-1,1]。这部分对应的方程是 Yi=ti*tanh(Ci)。

这看起来像很多数学理论,但有两个好消息。首先,如果你明白想要达到的目标,那么数学部分就不是那么难;其次,你可以使用 LSTM 单元作为标准 RNN 元的黑盒替换,并立即解决梯度消失问题。因此你真的不需要知道所有的数学理论,你只需从库中取出 TensorFlow LSTM 并使用它。

门控循环单元和窥孔LSTM

近年来已经提出了许多 LSTM 的变种模型,其中有两个很受欢迎:窥孔(peephole)LSTM 允许门层查看单元状态,如下图中虚线所示;而门控循环单元(GRU)将隐藏状态和单元状态合并为一个信息通道。

同样,GRU 和窥孔 LSTM 都可以用作标准 RNN 单元的黑盒插件,而不需要知道底层数学理论。这两种单元都可以用来解决梯度消失的问题,并用来构建深度神经网络。


标准LTSM、窥孔LTSM、GRU示例

图 10 标准LTSM、窥孔LTSM、GRU示例

处理向量序列

真正使 RNN 强大的是它能够处理向量序列,其中 RNN 的输入和输出可以是序列,下图很好地说明了这一点,最左边的例子是一个传统(非递归)网络,后面跟着一个序列输出的 RNN,接着跟着一个序列输入的 RNN,其次跟着序列输入和序列输出不同步的 RNN,最后是序列输入和序列输出同步的 RNN。


RNN序列示例
图 11 RNN序列示例


机器翻译是输入序列和输出序列中不同步的一个例子:网络将输入文本作为一个序列读取,读完全文后输出目标语言。

视频分类是输入序列和输出序列同步的一个例子:视频输入是一系列帧,对于每一帧,在输出中提供分类标签。

长短期记忆网络(LSTM)

LSTM的网络机构图如下所示:

LSTM的网络结构

与传统的循环神经网络相比,LSTM仍然是基于xt和ht−1来计算ht,只不过对内部的结构进行了更加精心的设计,加入了输入门it 、遗忘门ft以及输出门ot三个门和一个内部记忆单元ct。输入门控制当前计算的新状态以多大程度更新到记忆单元中;遗忘门控制前一步记忆单元中的信息有多大程度被遗忘掉;输出门控制当前的输出有多大程度上取决于当前的记忆单元。

在经典的LSTM模型中,第t层的更新计算公式为

输入门计算公式

遗忘门计算公式

输出门计算公式

候选层计算公式

记忆单元更新公式

每层输出的信息的计算公式

其中it是通过输入xt和上一步的隐含层输出ht−1进行线性变换,再经过激活函数σ得到的。输入门it的结果是向量,其中每个元素是0到1之间的实数,用于控制各维度流过阀门的信息量;Wi 、Ui两个矩阵和向量bi为输入门的参数,是在训练过程中需要学习得到的。遗忘门ft和输出门ot的计算方式与输入门类似,它们有各自的参数W、U和b。与传统的循环神经网络不同的是,从上一个记忆单元的状态ct−1到当前的状态ct的转移不一定完全取决于激活函数计算得到的状态,还由输入门和遗忘门来共同控制。

在一个训练好的网络中,当输入的序列中没有重要信息时,LSTM的遗忘门的值接近于1,输入门的值接近于0,此时过去的记忆会被保存,从而实现了长期记忆功能;当输入的序列中出现了重要的信息时,LSTM应当把其存入记忆中,此时其输入门的值会接近于1;当输入的序列中出现了重要信息,且该信息意味着之前的记忆不再重要时,输入门的值接近1,而遗忘门的值接近于0,这样旧的记忆被遗忘,新的重要信息被记忆。经过这样的设计,整个网络更容易学习到序列之间的长期依赖。

作者:Evermemo
链接:https://www.jianshu.com/p/0cf7436c33ae
来源:简书

下面跑一下简单的循环神经网络的代码,同样以手写识别为例:

import os
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
#读取mnist数据集 如果没有则会下载
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
#输入图片是28*28
n_inputs = 28 #输入一行,一行有28个数据
max_time = 28 #一共28行
lstm_size = 100 #隐藏单元
n_classes = 10 #10个分类
batch_size = 50 #每批次50个样本
n_batch = mnist.train.num_examples//batch_size #计算一共多少批次

#这里表示第一个维度可以是任意的长度
x = tf.placeholder(tf.float32,[None,784])
#正确的标签
y = tf.placeholder(tf.float32,[None,10])

#初始化权值
weights = tf.Variable(tf.truncated_normal([lstm_size,n_classes],stddev=0.1))
#初始化偏置值
biases = tf.Variable(tf.constant(0.,shape=[n_classes]))

#定义RNN网络
def RNN(X, weights, biases):
    # inputs=[batch_size, max_time, n_inputs]
    inputs = tf.reshape(X, [-1, max_time, n_inputs])
    # 定义LSTM基本CELL
    lstm_cell = tf.contrib.rnn.BasicLSTMCell(lstm_size)
    # final_state[0]是cell state
    # final_state[1]是hidden_state
    outputs, final_state = tf.nn.dynamic_rnn(lstm_cell, inputs, dtype=tf.float32)
    results = tf.nn.softmax(tf.matmul(final_state[1], weights) + biases)
    return results


#计算RNN的返回结果
prediction = RNN(x,weights,biases)
#损失函数
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=prediction,labels=y))
#使用AdamOptimizer进行优化
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
#结果存放在一个布尔型的列表中
correct_prediction = tf.equal(tf.argmax(y,1),tf.argmax(prediction,1))#argmax返回一维张量中最大值所在位置
#求准确率
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
#初始化
init = tf.global_variables_initializer()
# 进行训练
with tf.Session() as sess:
    sess.run(init)
    for epoch in range(6):  # 周期
        for batch in range(n_batch):  # 批次
            batch_xs, batch_ys = mnist.train.next_batch(batch_size)
            sess.run(train_step, feed_dict={x: batch_xs, y: batch_ys})

        acc = sess.run(accuracy, feed_dict={x: mnist.test.images, y: mnist.test.labels})
        print("周期 :" + str(epoch) + "准确率:" + str(acc))