NNDL 作业11 LSTM:避免梯度消失分析+numpy代码+nn.LSTMCell+nn.LSTM实现

习题6-4  推导LSTM网络中参数的梯度, 并分析其避免梯度消失的效果

首先需要明确的是,RNN 中的梯度消失/梯度爆炸和普通的 MLP 或者深层 CNN 中梯度消失/梯度爆炸的含义不一样。MLP/CNN 中不同的层有不同的参数,各是各的梯度;而 RNN 中同样的权重在各个时间步共享,最终的梯度为各个时间步的梯之和。

因此,RNN 中总的梯度是不会消失的。即便梯度越传越弱,那也只是远距离的梯度消失,由于近距离的梯度不会消失,所有梯度之和便不会消失。RNN 所谓梯度消失的真正含义是,梯度被近距离梯度主导,导致模型难以学到远距离的依赖关系。

多条求导路径,最后将这些求导路径相加得到最终的梯度,只要保证有一条远距离路径梯度不消失,总的远距离梯度就不会消失,这里我们只关注C_{t-1}传到C_{t} 的过程

下图箭头为为从C_{t-1}传到C_{t} 的过程的相反方向,即反向传播的方向,值得注意的是,从C_{t-1}传到C_{t} ,包括从C_{t-1}传到h_{t-1},再从h_{t-1}传到C_{t}

可以求得公式 

可以看到,这个梯度是由四个式子相加而成,其他姑且不用看,就看这个f_{t}控制好f_{t}就能让它梯度不消失。

也就是说,如果想让过去的影响现在的输出,让f_{t}为1就可以了,之后在上图最上面那条路径中梯度会直接从C_{t}流到C_{t-1},梯度不会消失,f_{t}的大小由训练学习得到的(它会学习到什么时候就不记前头的了),直到有输入x得到的f_{t}为0,则和前面的信息就没有关系了。

就是反向传到较远的地方,它的梯度是有多个式子的和(多条路到达),而不是仅仅是几个变量相乘的单一式子。

注意:梯度消失现象可以改善,但是梯度爆炸还是可能会出现的。

LSTM依然不能完全解决梯度消失这个问题,有文献表示序列长度一般到了三百多仍然会出现梯度消失现象。如果想彻底规避这个问题,还是transformer好用。(现在还没学 transformer,浅期待一下)

 参考老师给的鱼书上的看法分析,避免梯度消失主要有两个原因,一个是使用对应元素的乘积,一个是遗忘门的使用。

 

习题6-3P  编程实现下图LSTM运行过程

同学提出,未发现输入。可以适当改动例题,增加该输入。

实现LSTM算子,可参考实验教材代码。

1. 使用Numpy实现LSTM算子

import numpy
import numpy as np
def sigmoid(x):#该题的输入不是贼正就是贼负,需要得到的结果结果不是0就是1,干脆自己定义一个简单的函数充当sigmoid
    if x>0:
     return 1
    else:
      return 0

Wi=np.array([0,100,0,-10])
Wf=np.array([0,100,0,10])
Wc=np.array([1,0,0,0])
Wo=np.array([0,0,100,-10])
x=np.array([[1,0,0],[3,1,0],[2,0,0],[4,1,0],[2,0,0],[1,0,1],[3,-1,0],[6,1,0],[1,0,1]])
rem=0#首先让初始的ft为0
r=[]
yy=[]
for j in x:
    new_element = [1]
    j=np.append(j,new_element)#把那个1放进去
    i1=j*Wi
    i2=sum(i1)
    i3=sigmoid(i2)
    f1 = j* Wf
    f2 = sum(f1)
    f3 = sigmoid(f2)
    c1 = j * Wc
    c2 = sum(c1)
    o1 = j * Wo
    o2 = sum(o1)
    o3 = sigmoid(o2)
    r.append(rem)
    rem=i3*c2+f3*rem
    y=rem*o3
    yy.append(y)
print("当前的remember为:", r)
print("输出为:",yy)



可以看到,结果与题目中一致。

2. 使用nn.LSTMCell实现

这里的使用与np.rnncell啥的差不多,可以参考我之前写的文章,可以帮助怎样看文档,详细情况参考官方文档https://pytorch.org/docs/stable/generated/torch.nn.LSTMCell.html#torch.nn.LSTMCell

实例化 

torch.nn.LSTMCell(input_sizehidden_sizebias=Truedevice=Nonedtype=None)

输入:Inputs: input, (h_0, c_0)包含输入值和初始的内部状态和外部状态,具体形状:

输出:Outputs:(h_1, c_1)包含该时间步的输出的内部状态和外部状态,具体形状:

参数:,具体形状:

值得注意的是:

weight_ih :形状:4*hidden_size, input_size

其中为啥放的4*hidden_size,因为放的是“W_ii|W_if|W_ig|W_io”,即分别由x_{t}i_{t}f_{t}\widetilde{c_{t}}o_{t}的权重。

weight_hh:形状:4*hidden_size, hidden_size

放的是“W_hi|W_hf|W_hg|W_ho”,即分别由h_{t-1}i_{t}f_{t}\widetilde{c_{t}}o_{t}的权重。

import torch
import  torch.nn as nn
torch.set_printoptions(precision=4,sci_mode=False)#设置不用科学计数法输出,这个个人喜好,设也行
#构建初始值
x=torch.Tensor([[[1,0,0,1]],[[3,1,0,1]],[[2,0,0,1]],[[4,1,0,1]],[[2,0,0,1]],[[1,0,1,1]],[[3,-1,0,1]],[[6,1,0,1]],[[1,0,1,1]]])#[seq_len,batchsize,input_size]
h0=torch.zeros(1,1)#(batchsize,hidden_size)
c0=torch.zeros(1,1)#(batchsize,hidden_size)
#实例化
l=nn.LSTMCell(4,1)#[feature_len,hidden_len,num_layers]
#调整参数
l.weight_ih.data= torch.tensor([[0,100,0,-10],[0,100,0,10],[1,0,0,0],[0,0,100,-10]]).float()# (W_ii|W_if|W_ig|W_io), of shape (4*hidden_size, input_size)
l.weight_hh.data=torch.zeros(4,1)#(W_hi|W_hf|W_hg|W_ho), of shape (4*hidden_size, hidden_size)
h=[]
#forward()
for j in x:
  (h0, c0) = l(j, (h0, c0))
  h.append(torch.squeeze(h0).tolist())
h = [f'{num:.4f}' for num in h]
print("输出为:",h)

 结果:

3. 使用nn.LSTM实现

这里的使用与np.rnn啥的差不多,可以参考我之前写的文章,可以帮助怎样看文档,详细情况参考官方文档https://pytorch.org/docs/stable/generated/torch.nn.LSTM.html#torch.nn.LSTM

实例化 

torch.nn.LSTM(selfinput_sizehidden_sizenum_layers=1bias=Truebatch_first=Falsedropout=0.0bidirectional=Falseproj_size=0device=Nonedtype=None)

输入:Inputs: input, (h_0, c_0)包含输入值和初始的内部状态和外部状态,具体形状:

输出:Outputs: output, (h_n, c_n)包含(每个时间步的隐藏层的外部状态)的输出值和初始的内部状态和外部状态,具体形状:

参数:

值得注意的是:

weight_ih_l[k] :形状:4*hidden_size, input_size

其中为啥放的4*hidden_size,因为放的是“W_ii|W_if|W_ig|W_io”,即分别由x_{t}i_{t}f_{t}\widetilde{c_{t}}o_{t}的权重。

weight_hh_l[k]:形状:4*hidden_size, hidden_size

放的是“W_hi|W_hf|W_hg|W_ho”,即分别由h_{t-1}i_{t}f_{t}\widetilde{c_{t}}o_{t}的权重。

首先访问权重名称方便改变它

weight = l.state_dict() # 使用`state_dict`属性来获取或设置模型的权重
print("权重名称:\n", weight.keys())

import torch
import  torch.nn as nn
torch.set_printoptions(precision=4,sci_mode=False)#设置不用科学计数法输出,这个个人喜好,设也行
#构建初始值
x=torch.Tensor([[[1,0,0,1]],[[3,1,0,1]],[[2,0,0,1]],[[4,1,0,1]],[[2,0,0,1]],[[1,0,1,1]],[[3,-1,0,1]],[[6,1,0,1]],[[1,0,1,1]]])#[seq_len,batchsize,input_size]
h0=torch.zeros(1,1,1)#(D∗num_layers,batchsize,hidden_size)
c0=torch.zeros(1,1,1)#(D∗num_layers,batchsize,hidden_size)
#实例化
l=nn.LSTM(4,1,1)#[feature_len,hidden_len,num_layers]
#调整参数
l.weight_ih_l0.data= torch.tensor([[0,100,0,-10],[0,100,0,10],[1,0,0,0],[0,0,100,-10]]).float()# (W_ii|W_if|W_ig|W_io), of shape (4*hidden_size, input_size)
l.weight_hh_l0.data=torch.zeros(4,1)#(W_hi|W_hf|W_hg|W_ho), of shape (4*hidden_size, hidden_size)
#forward()
out, (h, c)=l(x,(h0,c0))
out=torch.squeeze(out)
print(out)

​ 

​【直接调用与自己实现numpy代码比较】

  与numpy里的结果不一样,刚开始确实咋调都是这个值,后来查看了同学的博客,他发现了这是因为激活函数的原因,咱们这个nn.lstm有的是使用tanh的,那我也加上,主要改变就是激活函数的代码,直接拿公式写的函数,没有那些条件语句了,并且加上对c_{t}激活

在最后的输出上,直接调用的输出指的是h_{t},所以为了追求一致,就在自己写的numpy代码加上对c_{t}激活那步:

import numpy as np

def sigmoid(x):
    return 1 / (1 + np.exp(-x))
def tanh(x):
    return (np.exp(x)-np.exp(-x))/ (np.exp(x)+np.exp(-x))
Wi=np.array([0,100,0,-10])
Wf=np.array([0,100,0,10])
Wc=np.array([1,0,0,0])
Wo=np.array([0,0,100,-10])
x=np.array([[1,0,0],[3,1,0],[2,0,0],[4,1,0],[2,0,0],[1,0,1],[3,-1,0],[6,1,0],[1,0,1]])
rem=0#首先让ft为0
r=[]
yy=[]
for j in x:
    new_element = [1]
    j=np.append(j,new_element)#把那个1放进去
    i1=j*Wi
    i2=sum(i1)
    i3=sigmoid(i2)
    f1 = j* Wf
    f2 = sum(f1)
    f3 = sigmoid(f2)
    c1 = j * Wc
    c2 = sum(c1)
    c2 = tanh(c2)
    o1 = j * Wo
    o2 = sum(o1)
    o3 = sigmoid(o2)
    r.append(rem)
    rem=i3*c2+f3*rem#相当于ct
    rem1=tanh(rem)#再激活一下
    y=rem1*o3
    yy.append(y)
yy = [f'{num:.4f}' for num in yy]
print("输出为:",yy)

关于h_{t}:在自己写的numpy代码里,根本就没定义,在直接调用的代码里,里面自带了,那咱们就令其对应权重为0,也相当于没有

看到了Tanh()的梯度函数,粘过来了。

补一个,https://blog.youkuaiyun.com/weixin_41504611/article/details/135026799是写的向量内积和hadamard积(元素对位乘)

这次也是第一次代码完全由自己写的,确实感受不一样,即使代码水平有限,但是很有成就感!

多种激活函数图像及代码

通俗易懂的LSTM

梯度消失原因

https://www.cnblogs.com/sumwailiu/p/13623985.html

梯度消失和爆炸的原因和解决方案(这个可以看一下汇总)

nn.lstm

https://blog.youkuaiyun.com/qq_38975453/article/details/134782034?spm=1001.2014.3001.5502

DL Homework 11-优快云博客

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值