Tensorflow梯度相关,LSTM,GRU

我们把梯度值接近于 0 的现象叫做梯度弥散(Gradient Vanishing),把梯度值远大于 1 的 现象叫做梯度爆炸(Gradient Exploding)。梯度弥散和梯度爆炸是神经网络优化过程中间比 较容易出现的两种情况,也是不利于网络训练的。

𝜃′ =𝜃−𝜂∇𝜃L

当出现梯度弥散时,∇𝜃L ≈ 0,此时𝜃′ ≈ 𝜃,也就是说每次梯度更新后参数基本保持不变, 神经网络的参数长时间得不到更新,具体表现为L几乎保持不变,其它评测指标,如准确 度,也保持不变。当出现梯度爆炸时,∇𝜃L ≫ 1,此时梯度的更新步长η∇𝜃L非常大,使得 更新后的𝜃′与𝜃差距很大,网络L出现突变现象,甚至可能出现来回震荡、不收敛的现象。

通过推导循环神经网络的梯度传播公式,我们发现循环神经网络很容易出现梯度弥散 和梯度爆炸的现象。那么怎么解决这两个问题呢?

梯度爆炸可以通过梯度裁剪(Gradient Clipping)的方式在一定程度上的解决。梯度裁剪 与张量限幅非常类似,也是通过将梯度张量的数值或者范数限制在某个较小的区间内,从 而将远大于 1 的梯度值减少,避免出现梯度爆炸。

在深度学习中,有 3 种常用的梯度裁剪方式。
❑ 直接对张量的数值进行限幅,使得张量𝑾的所有元素𝑤𝑖𝑗 ∈ [min, max]。在 TensorFlow中,可以通过 tf.clip_by_value()函数来实现。

a=tf.random.uniform([2,2])
tf.clip_by_value(a,0.4,0.6) # 梯度值裁剪
<tf.Tensor: id=110, shape=(2, 2), dtype=float32, numpy=
array([[0.4086107, 0.4      ],
       [0.4      , 0.6      ]], dtype=float32)>
a
<tf.Tensor: id=106, shape=(2, 2), dtype=float32, numpy=
array([[0.4086107 , 0.38549483],
       [0.2081635 , 0.9825165 ]], dtype=float32)>

1:tf.random_normal:

正态分布产生的随机值:常用的参数就是shape,和dtype了,但是也包括方差和均值;

参数(shape,stddev,mean,dtype)

2:tf.random_uniform 

默然是在0到1之间产生随机数:

但是也可以通过maxval指定上界,通过minval指定下界

//感觉这个理解才正确?

❑ 通过限制梯度张量𝑾的范数来实现梯度裁剪。比如对𝑾的二范数‖𝑾‖2约束在[0,max] 之间,如果‖𝑾‖2大于max值,则按照

W = \frac{W}{\left \| W \right \|_{2}}\cdot max

方式将‖𝑾′‖2约束max内。可以通过 tf.clip_by_norm 函数方便的实现梯度张量𝑾裁剪。例如:

a=tf.random.uniform([2,2]) * 5
# 按范数方式裁剪
b = tf.clip_by_norm(a, 5) 

tf.norm(a),tf.norm(b)
(<tf.Tensor: id=162, shape=(), dtype=float32, numpy=6.430176>,
 <tf.Tensor: id=167, shape=(), dtype=float32, numpy=5.0>)

a
<tf.Tensor: id=141, shape=(2, 2), dtype=float32, numpy=
array([[3.4196944, 1.4182079],
       [1.8361145, 4.9264812]], dtype=float32)>

b
<tf.Tensor: id=157, shape=(2, 2), dtype=float32, numpy=
array([[2.6590989, 1.1027755],
       [1.4277327, 3.8307517]], dtype=float32)>

numpy=6.430176 是a里面数值平方和的平方根。

可以看到,对于大于max的 L2 范数的张量,通过裁剪后范数值缩减为 5。

❑ 神经网络的更新方向是由所有参数的梯度张量𝑾共同表示的,前两种方式只考虑单个 梯度张量的限幅,会出现网络更新方向发生变动的情况。如果能够考虑所有参数的梯 度𝑾的范数,实现等比例的缩放,那么就能既很好地限制网络的梯度值,同时不改变 网络的更新方向。这就是第三种梯度裁剪的方式:全局范数裁剪。在 TensorFlow 中, 可以通过 tf.clip_by_global_norm 函数快捷地缩放整体网络梯度𝑾的范数。

在网络训练时,梯度裁剪一般在计算出梯度后,梯度更新之前进行。

对于梯度弥散现象,可以通过增大学习率、减少网络深度、添加 Skip Connection 等一系列的措施抑制。

增大学习率𝜂可以在一定程度防止梯度弥散现象,当出现梯度弥散时,网络的梯度∇𝜃 L 接近于 0,此时若学习率𝜂也较小,如η = 1e − 5,则梯度更新步长更加微小。通过增大学 习率,如令𝜂 = 1e − 2,有可能使得网络的状态得到快速更新,从而逃离梯度弥散区域。

对于深层次的神经网络,梯度由最末层逐渐向首层传播,梯度弥散一般更有可能出现 在网络的开始数层。在深度残差网络出现之前,几十上百层的深层网络训练起来非常困 难,前面数层的网络梯度极容易出现梯度离散现象,从而使得网络参数长时间得不到更 新。深度残差网络较好地克服了梯度弥散现象,从而让神经网络层数达到成百上千。一般 来说,减少网络深度可以减轻梯度弥散现象,但是网络层数减少后,网络表达能力也会偏 弱,需要用户自行平衡。

循环神经网络除了训练困难,还有一个更严重的问题,那就是短时记忆(Short-term memory)

循环神经网络在处理较长的句子时,往往只能够理解有限长度内的信 息,而对于位于较长范围类的有用信息往往不能够很好的利用起来。我们把这种现象叫做 短时记忆。

LSTM网络

1997 年,瑞士人工智能科学家 Jürgen Schmidhuber 提出了长短 时记忆网络(Long Short-Term Memory,简称 LSTM)。LSTM 相对于基础的 RNN 网络来 说,记忆能力更强,更擅长处理较长的序列信号数据,LSTM 提出后,被广泛应用在序列 预测、自然语言处理等任务中,几乎取代了基础的 RNN 模型。

在 LSTM 中,有两个状态向量𝒄和 ,其中𝒄作为 LSTM 的内部状态向量,可以理解为 LSTM 的内存状态向量 Memory,而 表示 LSTM 的输出向量。相对于基础的 RNN 来说, LSTM 把内部 Memory 和输出分开为两个变量,同时利用三个门控:输入门(Input Gate)、 遗忘门(Forget Gate)和输出门(Output Gate)来控制内部信息的流动。

门控机制可以理解为控制数据流通量的一种手段,类比于水阀门:当水阀门全部打开 时,水流畅通无阻地通过;当水阀门全部关闭时,水流完全被隔断。在 LSTM 中,阀门开 和程度利用门控值向量𝒈表示,如图 11.15 所示,通过𝜎(𝒈)激活函数将门控制压缩到[0,1] 之间区间,当𝜎(𝒈) = 0时,门控全部关闭,输出𝒐 = 0;当𝜎(𝒈) = 1时,门控全部打开,输 出𝒐 = 𝒙。通过门控机制可以较好地控制数据的流量程度。

嗯,有一堆公式,基本了解了LSTM网络的作用原理。

LSTMCell 的用法和 SimpleRNNCell 基本一致,区别在于 LSTM 的状态变量 List 有两 个,即[ 𝑡,𝒄𝑡],需要分别初始化,其中List第一个元素为 𝑡,第二个元素为𝒄𝑡。调用cell 完成前向运算时,返回两个元素,第一个元素为cell的输出,也就是 𝑡,第二个元素为 cell的更新后的状态List:[ 𝑡,𝒄𝑡]。首先新建一个状态向量长度h=64的LSTMCell,其中 状态向量𝒄𝑡和输出向量 𝑡的长度都为h,

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import datasets, layers, optimizers, Sequential, metrics
x = tf.random.normal([2,80,100])
xt = x[:, 0, :]  #所以这个xtshape是(2,100)
cell = layers.LSTMCell(64) # 创建 LSTM Cell
# 初始化状态和输出 List,[h,c]
state = [tf.zeros([2,64]),tf.zeros([2,64])]
# 查看返回元素的 id
id(out),id(state[0]),id(state[1])

out.shape
TensorShape([2, 64])

通过在时间戳上展开循环运算,即可完成一次层的前向传播,写法与基础的 RNN 一 样。

通过 layers.LSTM 层可以方便的一次完成整个序列的运算。首先新建 LSTM 网络层。

经过 LSTM 层前向传播后,默认只会返回最后一个时间戳的输出,如果需要返回每个时间 戳上面的输出,需要设置 return_sequences=True 标志。

# 创建 LSTM 层时,设置返回每个时间戳上的输出
layer = layers.LSTM(64, return_sequences=True)
# 前向计算,每个时间戳上的输出自动进行了 concat,拼成一个张量 
out = layer(x)

out.shape
TensorShape([2, 80, 64])

对于多层神经网络,可以通过 Sequential 容器包裹多层 LSTM 层,并设置所有非末层 网络 return_sequences=True,这是因为非末层的 LSTM 层需要上一层在所有时间戳的输出作为输入。因为需要,所以要有保留?

# 和 CNN 网络一样,LSTM 也可以简单地层层堆叠
net = keras.Sequential([
    layers.LSTM(64, return_sequences=True), # 非末层需要返回所有时间戳输出
    layers.LSTM(64) ])
# 一次通过网络模型,即可得到最末层、最后一个时间戳的输出 out = net(x)

GRU网络:

LSTM 具有更长的记忆能力,在大部分序列任务上面都取得了比基础的 RNN 模型更好 的性能表现,更重要的是,LSTM 不容易出现梯度弥散现象。但是 LSTM 结构相对较复杂,计算代价较高,模型参数量较大。因此,科学家们尝试简化 LSTM 内部的计算流程, 特别是减少门控数量。

研究发现,遗忘门是 LSTM 中最重要的门控 [2],甚至发现只有遗 忘门的简化版网络在多个基准数据集上面优于标准 LSTM 网络。在众多的简化版 LSTM 中,门控循环网络(Gated Recurrent Unit,简称 GRU)是应用最广泛的 RNN 变种之一。GRU 把内部状态向量和输出向量合并,统一为状态向量 ,门控数量也减少到 2 个:复位门 (Reset Gate)和更新门(Update Gate)。

所以GRU网络就只有一个输出网络

 

在 TensorFlow 中,也有 Cell 方式和层方式实现 GRU 网络。GRUCell 和 GRU 层的使用方法和之前的 SimpleRNNCell、LSTMCell、SimpleRNN 和 LSTM 非常类似。首 先是 GRUCell 的使用,创建 GRU Cell 对象,并在时间轴上循环展开运算。

所以在这里就学习完成了LSTM以及GRU网络。

然后这里开始了实战

前面我们介绍了情感分类问题,并利用 SimpleRNN 模型完成了情感分类问题的实战, 在介绍完更为强大的 LSTM 和 GRU 网络后,我们将网络模型进行升级。得益于 TensorFlow 在循环神经网络相关接口的格式统一,在原来的代码基础上面只需要修改少量 几处,便可以完美的升级到 LSTM 模型或 GRU 模型。

 

在情感分类任务时,Embedding 层是从零开始训练的。实际上,对于文本处理任务来 说,领域知识大部分是共享的,因此我们能够利用在其它任务上训练好的词向量来初始化 Embedding 层,完成领域知识迁移。基于预训练的 Embedding 层开始训练,少量样本时也 能取得不错的效果。

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值