7

优化RNN

 3 years ago
source link: http://www.feiguyunai.com/index.php/2020/11/23/rnn-optim/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

3.4 优化RNN

文章目录

RNN擅长处理序列数据,尤其当后面数据与前面数据有依赖关系时。不过,如果这种依赖涉及长依赖的话,使用RNN的效果将受到较大影响,因长依赖就意味着时间步就要大,而根据梯度的关联规则,会发现累乘会导致激活函数导数的累乘,如果取tanh或sigmoid函数作为激活函数的话,那么必然是一堆小数在做乘法,结果就是越乘越小。随着时间序列的不断深入,小数的累乘就会导致梯度越来越小直到接近于0,这就是“梯度消失“现象。实际使用中,会优先选择tanh函数,原因是tanh函数相对于sigmoid函数来说梯度较大,收敛速度更快且引起梯度消失更慢。通常减缓梯度消失的几种方法:

1、选取更好的激活函数,如Relu激活函数。ReLU函数的左侧导数为0,右侧导数恒为1,这就避免了“梯度消失“的发生。但大于1的导数容易导致“梯度爆炸“,但设定合适的阈值可以解决这个问题。

2、加入BN层,其优点包括可加速收敛、控制过拟合,可以少用或不用Dropout和正则、降低网络对初始化权重不敏感,且能允许使用较大的学习率等。

3、改变传播结构,LSTM结构可以有效解决这个问题。接下来我们将介绍LSTM相关内容。

这些方法中,LSTM比其他几种方法效果要好,LSTM的核心思想就是增加一条记忆以往信息的传送带,这条传送带通过各种更新信息相加,而从有效避免梯度消失问题。

LSTM成功用于许多应用(如语言建模,手势预测,用户建模)。 LSTM基于记忆的序列建模架构非常有影响力——它启发了许多最新的改进方法,例如Transformers。

3.4.1 LSTMCell

1.RNN与LSTM的关系图

Ib6R3e2.png!mobile

图1-14 RNN与LSTM的关系

从图1-14 可知,RNN对应LSTM中方框部分,方框部分的信息就是当前状态信息,该信息将通过输入门之后加入到传送带C中。

2.LSTMCell整体架构图

LSTM 通过精心设计的称作为“门”的结构来去除或者增加信息到传送带C状态的能力。门是一种让信息选择式通过的方法。他们通过含激活 sigmoid 神经网络层形成门(如图1-15中 RRrmuen.png!mobile

等信息进行过滤。

3eqeQfF.png!mobile

图1-15 LSTM架构图

LSTM三个门的主要功能:

Zbyye2B.png!mobile

输入门对当前信息C ̃_t进行过滤,其表示式为:

VjqIveB.png!mobile

2.LSTMCell的详细结构

nYJVnei.png!mobile

图1-16 LSTM的详细内部结构图

为简明起见,图1-16中假设输入为3个元素的向量,隐藏状态及传送信息都是2个元素的向量。隐藏状态与输入拼接成5个元素的向量。实际应用中输入、状态一般加上批量等维度,如[batch,input_size],如果在LSTM模块中还需加上序列长度,形如[batch,seg_len,input_size]。multiplication是指哈达玛积,S表示Softmoid,T表示Tanh。

3.4.2 LSTMCell

LSTMCell这是LSTM的一个单元,如图1-15所示。该单元可以用PyTorch的 nn.LSTMCell模块来实现,该模块构建LSTM中的一个Cell,同一层会共享这一个Cell,但要手动处理每个时刻的迭代计算过程。如果要建立多层的LSTM,就要建立多个nn.LSTMCell。

1 .构造方法

构造方法和nn.RNNcell类似,依次传入feature_len和hidden_len,因为这只是一个计算单元,所以不涉及层数。

2. forward方法

回顾一下nn.RNNCell的forward方法,它是:

ht=nn.rnncell(x,ht-1)

即上一时刻的输出ht−1经nn.RNNCell前向计算得到这一时刻的ht。

对于nn.LSTMCell也是类似,但因为LSTM计算单元中还涉及对上一时刻的记忆Ct−1的使用,所以是

ht,ct=lstmcell(xt,(ht-1,ct-1))

因为输入xt只是t时刻的输入,不涉及seq_len,所以其shape是[batch,feature_len]

而ht和Ct在这里只是t时刻本层的隐藏单元和记忆单元,不涉及num_layers,所以其shape是 [batch,hidden_len]

3.4.3 一层的例子

每个时刻传入新的输入xt和上一时刻的隐藏单元ht−1和记忆单元Ct−1,并把这两个单元更新。

import torch
from torch import nn
 
# 一层的LSTM计算单元,输入的feature_len=100,隐藏单元和记忆单元hidden_len=20
cell = nn.LSTMCell(input_size=100, hidden_size=20)
 
# 初始化隐藏单元h和记忆单元C,取batch=3
h = torch.zeros(3, 20)
C = torch.zeros(3, 20)
 
# 这里是seq_len=10个时刻的输入,每个时刻shape都是[batch,feature_len]
xs = [torch.randn(3, 100) for _ in range(10)]
 
# 对每个时刻,传入输入x_t和上个时刻的h_{t-1}和C_{t-1}
for xt in xs:
    h, C = cell(xt, (h, C))
 
print(h.shape)  # torch.Size([3, 20])
print(C.shape)  # torch.Size([3, 20])

3.4.4 两层的例子

在最底下一层l0层和上面的例子一样,上层还需要接受下层的输出ht作为当前输入,然后同样是依赖本层上一时刻的h和C更新本层的h和C。注意LSTM单元的输入输出结构,向上层传递的是h而不是C。

import torch
from torch import nn
 
# 输入的feature_len=100,变到该层隐藏单元和记忆单元hidden_len=30
cell_l0 = nn.LSTMCell(input_size=100, hidden_size=30)
# hidden_len从l0层的30变到这一层的20
cell_l1 = nn.LSTMCell(input_size=30, hidden_size=20)
 
# 分别初始化l0层和l1层的隐藏单元h和记忆单元C,取batch=3
# 注意l0层的hidden_len=30
h_l0 = torch.zeros(3, 30)
C_l0 = torch.zeros(3, 30)
# 而到l1层之后hidden_len=20
h_l1 = torch.zeros(3, 20)
C_l1 = torch.zeros(3, 20)
 
# 这里是seq_len=10个时刻的输入,每个时刻shape都是[batch,feature_len]
xs = [torch.randn(3, 100) for _ in range(10)]
 
# 对每个时刻,从下到上计算本时刻的所有层
for xt in xs:
    h_l0, C_l0 = cell_l0(xt, (h_l0, C_l0))  # l0层直接接受xt输入
    h_l1, C_l1 = cell_l1(h_l0, (h_l1, C_l1))  # l1层接受l0层的输出h为输入
 
# 最后shape是不变的
print(h_l0.shape)  # torch.Size([3, 30])
print(C_l0.shape)  # torch.Size([3, 30])
print(h_l1.shape)  # torch.Size([3, 20])
print(C_l1.shape)  # torch.Size([3, 20])

3.4.5 PyTorch的LSTM模块

https://zhuanlan.zhihu.com/p/139617364

1、多层LSTM的网络架构

6Jfq6vJ.png!mobile

图1-17 多层LSTM架构图

2、LSTM模块

class torch.nn.LSTM(*args, **kwargs)
参数有:
    input_size:x的特征维度
    hidden_size:隐藏层的特征维度
    num_layers:lstm隐层的层数,默认为1
    bias:False则bihbih=0和bhhbhh=0. 默认为True
    batch_first:True则输入输出的数据格式为 (batch, seq, feature),默认为:False
    dropout:除最后一层,每一层的输出都进行dropout,默认为: 0
    bidirectional:True则为双向lstm默认为False

3、输入

input(seq_len, batch, input_size)

参数有:

seq_len:序列长度,在NLP中就是句子长度,一般都会用pad_sequence补齐长度

batch:每次喂给网络的数据条数,在NLP中就是一次喂给网络多少个句子

input_size:特征维度,和前面定义网络结构的input_size一致

fMBJjqN.png!mobile

图1-18 LSTM的输入格式

如果LSTM的参数 batch_first=True,则要求输入的格式是:input(batch, seq_len, input_size)

4、隐含层

LSTM有两个输入是 h0 和 c0,可以理解成网络的初始化参数,用随机数生成即可。

h0(num_layers * num_directions, batch, hidden_size)
c0(num_layers * num_directions, batch, hidden_size)

参数:

num_layers:隐藏层数

num_directions:如果是单向循环网络,则num_directions=1,双向则num_directions=2

batch:输入数据的batch

hidden_size:隐藏层神经元个数

注意,如果我们定义的input格式是:

input(batch, seq_len, input_size)

则H和C的格式也是要变的:

h0(batc,num_layers * num_directions, h, hidden_size)
c0(batc,num_layers * num_directions, h, hidden_size)

5、输出

LSTM的输出是一个tuple,如下:

output,(ht, ct) = net(input)

output: 最后一个状态的隐藏层的神经元输出

ht:最后一个状态的隐含层的状态值

ct:最后一个状态的隐含层的遗忘门值

output的默认维度是:

output(seq_len, batch, hidden_size * num_directions)
ht(num_layers * num_directions, batch, hidden_size)
ct(num_layers * num_directions, batch, hidden_size)

和input的情况类似,如果我们前面定义的input格式是:

input(batch, seq_len, input_size)

则ht和ct的格式也是要变的:

ht(batc,num_layers * num_directions, h, hidden_size)
ct(batc,num_layers * num_directions, h, hidden_size)

3.5 LSTM实现文本生实例

本实例使用2014人民日报上的一些新闻,使用PyTorch提供的nn.LSTM模型,根据提示字符串预测给定长度的语句。整个处理与3.3小节基本相同,构建模型时稍有不同,LSTM有两个变量h和c,RNN只要一个h。

3.6 GRU结构

https://blog.csdn.net/Jerr__y/article/details/58598296

LSTM门比较多,状态有C和H,其中C就相当于中间变量一样,输出只有H。有关LSTM的改进方案比较多,其中比较著名的变种 GRU(Gated Recurrent Unit ),这是由 Cho, et al. (2014) 提出。在 GRU 中,如 下图所示,只有两个门:重置门(reset gate)和更新门(update gate)。同时在这个结构中,把细胞状态和隐藏状态进行了合并。最后模型比标准的 LSTM 结构要简单,而且这个结构后来也非常流行。

uIJRb2j.png!mobile

图1-19 GRU模型结构图

其中,r_t 表示重置门,z_t表示更新门。重置门决定是否将之前的状态(h_(t-1))忘记。当r_t趋于 0 的时候,前一个时刻的状态信息 h_(t-1)将被忘掉,隐藏状态 h_t会被重置为当前输入的信息。更新门决定是否要将隐藏状态更新为新的状态 h ̃_t (更新门的作用相当于合并了 LSTM 中的遗忘门和输入门)。

和 LSTM 比较一下:

(1) GRU 少一个门,同时少了传送带状态 C_t。

(2) 在 LSTM 中,通过遗忘门和输入门控制信息的保留和传入;GRU 则通过重置门来控制是否要保留原来隐藏状态的信息,但是不再限制当前信息的传入。

(3) 在 LSTM 中,虽然得到了新的传送带状态 C_t,但是它仅仅作为一个中间变量,不直接输出,而是需要经过一个过滤的处理:

h_t=O_t 〖⊗tanh⁡(C〗_t)

同样,在 GRU 中, 虽然 (2) 中也得到了新的隐藏状态h_t, 但是还不能直接输出,而是通过更新门来控制最后的输出:

h_t=(1-z_t )⊗h_(t-1)+z_t⊗h ̃_t

保留以往隐藏状态(h_(t-1))和保留当前隐藏状态(h ̃_t)之间是一种互斥关系,这点从上面这个公式也可说明。如果z_t 越接近1,则(1-z_t )就越接近于0;反之,也成立。

3.7 biRNN结构

1.biRNN的网络架构

图1-20 为biRNN的网络架构图,从图中还可以看出,对于一个序列,一次向前遍历得到左LSTM,向后遍历得到右LSTM。隐层矢量直接通过拼接(concat)得到,最终损失函数直接相加,具体如下图。

r2UzMv.png!mobile

图1-20 biRNN模型结构图

2、biRNN的不足

(1)biRNN模型的前向和后向LSTM模型是分开训练的,故它不是真正双向学习的模型,即biRNN无法同时向前和向后学习。

(2)biRNN如果层数多了,将出现自己看到自己的情况。下图中第2行第2列中的A|CD,此结果是第一层Bi LSTM的B位置输出内容,包括正向A和反向CD,然后直接拼接就得到A| CD。下图中第3行第2列中的ABCD, 此结果为正向BCD和反向AB | D拼接而成,当前位置需要预测的是B,但B已经在这里ABCD出现了。所以对于Bi LSTM,只要层数增加,会有一个“看到你自己”的问题。

AFBBZfR.png!mobile

图1-21 多层biRNN将自己看到自己示意图


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK