当前位置: 首页 > article >正文

深度学习作业十一 LSTM

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

LSTM的结构如上。在证明LSTM可以避免梯度消失时,大部分资料都是用\frac{\partial C_t}{\partial C_{t-1}}证明的,其实从结构图中不难看出,C_t不仅与遗忘门f_t和输入门i_t有关,同时还影响着外部状态h_t,所以用\frac{\partial C_t}{\partial C_{t-1}}证明再好不过了。学姐的博客(NNDL 作业十一 LSTM-CSDN博客)中也证明了为什么可以用\frac{\partial C_t}{\partial C_{t-1}}证明,我也跟着证了一下。

以上图为例,在T=3时,对其中一个参数W_{xf}求偏导。

所以我们可以看到\frac{\partial C_t}{\partial C_{t-1}}存在于各个路径之中。若\frac{\partial C_t}{\partial C_{t-1}}的梯度可以趋于1,就可以避免梯度消失。

那么他的梯度是如何趋于1的呢?

神经网络在参数学习过程中可以通过自己控制这些参数相互配合来让\frac{\partial C_t}{\partial C_{t-1}}趋近于1,当有多个\frac{\partial C_t}{\partial C_{t-1}}进行连乘时,那么这条路径上的梯度可能不会消失(但是其他的路径依然可能会发生梯度消失)。

那是如何配合的呢?

LSTM的三个门(输入门、遗忘门、输出门)为每个时间步提供了控制信号,决定了哪些信息需要被保留,哪些信息需要被遗忘,这种机制使得信息可以有效地流动,而不会因为过多的层数或者时间步数而导致梯度消失。

  • 遗忘门f_t控制着历史信息的遗忘程度。通过控制f_t的值,LSTM能够防止不必要的梯度消失,因为当 ftf_tft​ 接近1时,梯度可以较好地在时间上流动。
  • 输入门i_t允许新信息进入细胞状态,从而帮助模型捕捉新的有价值的信息,而不会因梯度衰减导致模型无法学习新特征。
  • 输出门o_t控制着输出的内容,通过控制信息流的输出,可以避免信息的过度消失。

所以,总结一下,LSTM避免梯度消失的原因:

  • 细胞状态的线性传递:细胞状态几乎没有通过非线性激活函数,因此梯度在反向传播时可以稳定地传递,不会迅速衰减。
  • 门控机制的控制:遗忘门、输入门和输出门通过选择性地遗忘和更新信息,避免了梯度因为不必要的更新而消失。
  • 长程依赖的保持:细胞状态的“记忆”能力使得LSTM能够在较长的时间跨度内保持有效的梯度流动,从而解决了传统RNN中的梯度消失问题。

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

1. 使用Numpy实现LSTM算子

代码如下:

# ====使用Numpy实现LSTM算子=================================================
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))


# 权重
input_weight = np.array([1, 0, 0, 0])
inputgate_weight = np.array([0, 100, 0, -10])
forgetgate_weight = np.array([0, 100, 0, 10])
outputgate_weight = np.array([0, 0, 100, -10])

# 输入
input = np.array(
    [[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]])

y = []  # 输出
c_t = 0  # 内部状态

for x in input:
    g_t = tanh(np.matmul(input_weight, x))  # 候选状态
    i_t = np.round(sigmoid(np.matmul(inputgate_weight, x)))  # 输入门
    after_inputgate = g_t * i_t  # 候选状态经过输入门
    f_t = np.round(sigmoid(np.matmul(forgetgate_weight, x)))  # 遗忘门
    after_forgetgate = f_t * c_t  # 内部状态经过遗忘门
    c_t = np.add(after_inputgate, after_forgetgate)  # 新的内部状态
    o_t = np.round(sigmoid(np.matmul(outputgate_weight, x)))  # 输出门
    after_outputgate = o_t * tanh(c_t)  # 激活后新的内部状态经过输出门
    y.append(round(after_outputgate, 2))  # 输出

print('输出:', y)

 运行结果:

2. 使用nn.LSTMCell实现

nn.LSTMCell 是一个 单时间步 的 LSTM 单元,通常用于手动循环计算单个时间步的输出。它接受当前时间步的输入以及前一时间步的隐藏状态和细胞状态作为输入,并返回当前时间步的隐藏状态和细胞状态。也就是说,nn.LSTMCell 不会自动处理序列的时间步,需要用户在循环中手动传递每个时间步的输入。

nn.LSTMCell(input_size, hidden_size)

  • input_size (int):

    • 输入张量的特征维度。即每个时间步输入数据的大小,形状为 (batch_size, input_size)
    • 例如,如果输入是一个有 10 个特征的向量,则 input_size 为 10。
  • hidden_size (int):

    • LSTM 单元的隐藏状态和细胞状态的维度。即每个时间步的输出的维度。
    • 这是 LSTM 的 "容量" 或 "记忆能力",通常也决定了网络中隐藏状态的维度。
    • 例如,如果 hidden_size 为 20,那么每个时间步的隐藏状态和细胞状态的维度就是 20。

代码如下:

# ====使用nn.LSTMCell实现======================================
import torch
import torch.nn as nn

# 实例化
input_size = 4
hidden_size = 1
cell = nn.LSTMCell(input_size=input_size, hidden_size=hidden_size)
# 修改模型参数 weight_ih.shape=(4*hidden_size, input_size),weight_hh.shape=(4*hidden_size, hidden_size),
# weight_ih、weight_hh分别为输入x、隐层h分别与输入门、遗忘门、候选、输出门的权重
cell.weight_ih.data = torch.tensor([[0, 100, 0, -10], [0, 100, 0, 10], [1, 0, 0, 0], [0, 0, 100, -10]],
                                   dtype=torch.float32)
cell.weight_hh.data = torch.zeros(4, 1)
print('cell.weight_ih.shape:', cell.weight_ih.shape)
print('cell.weight_hh.shape', cell.weight_hh.shape)
# 初始化h_0,c_0
h_t = torch.zeros(1, 1)
c_t = torch.zeros(1, 1)
# 模型输入input_0.shape=(batch,seq_len,input_size)
input_0 = 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]]], dtype=torch.float32)
# 交换前两维顺序,方便遍历input.shape=(seq_len,batch,input_size)
input = torch.transpose(input_0, 1, 0)
print('input.shape:', input.shape)
output = []
# 调用
for x in input:
    h_t, c_t = cell(x, (h_t, c_t))
    output.append(np.around(h_t.item(), decimals=3))  # 保留3位小数
print('output:', output)

运行结果:

3. 使用nn.LSTM实现

nn.LSTM 是一个 多时间步 的 LSTM 层,能够自动处理输入序列并在序列中传播信息。它内部实现了时间步的循环,用户只需要一次性传入整个序列的输入,nn.LSTM 会自动执行所有时间步的计算,并返回整个序列的隐藏状态和细胞状态。适用于处理序列数据,尤其是在大多数深度学习任务中,直接使用 nn.LSTM 进行序列建模。

与nn.LSTMcell对比一下:

nn.LSTM(input_size, hidden_size, num_layers=1, bias=True, batch_first=False, dropout=0, bidirectional=False) 

  • input_size (int):

    • 输入数据的特征维度,表示每个时间步输入数据的大小,形状为 (batch_size, seq_len, input_size)(seq_len, batch_size, input_size)(具体取决于 batch_first 参数)。
    • 例如,如果输入是一个包含 10 个特征的向量,则 input_size 为 10。
  • hidden_size (int):

    • 隐藏状态的维度,即每个 LSTM 单元中隐层的大小。决定了网络的记忆容量,也决定了每个时间步的输出大小。
    • 例如,hidden_size=20 时,每个时间步的输出大小是 20。
  • num_layers (int, 默认值=1):

    • LSTM 层的层数。通常,num_layers=1 表示只有一层 LSTM 单元。如果 num_layers > 1,则 LSTM 将堆叠多个 LSTM 层(每层 LSTM 都会接收上一层的输出作为输入)。
    • 例如,num_layers=2 表示有 2 层 LSTM,第二层的输入是第一层的输出。
  • bias (bool, 默认值=True):

    • 是否在 LSTM 层中使用偏置。大多数情况下,偏置是必要的,但也可以禁用它,具体取决于任务需求。
    • True 表示使用偏置,False 表示不使用偏置。
  • batch_first (bool, 默认值=False):

    • 控制输入和输出张量的形状。如果设置为 True,则输入和输出张量的形状为 (batch_size, seq_len, input_size)(batch_size, seq_len, hidden_size);如果设置为 False(默认值),则形状为 (seq_len, batch_size, input_size)(seq_len, batch_size, hidden_size)
    • True 会使输入和输出张量的第一维是 batch_sizeFalse 会使第一维是 seq_len
  • dropout (float, 默认值=0):

    • 在 LSTM 层之间应用的 dropout 概率。当 num_layers > 1 时,每一层之间都会应用这个 dropout。该值在 [0, 1] 之间。
    • dropout=0 表示不应用 dropout,dropout=0.5 表示有 50% 的概率丢弃连接。
  • bidirectional (bool, 默认值=False):

    • 是否使用双向 LSTM。如果设置为 True,LSTM 会同时在正向和反向两个方向上处理序列数据,从而能够捕捉序列的前后文信息。双向 LSTM 会将正向和反向的隐藏状态连接起来,输出的 hidden_size 会是原来的一倍。
    • True 表示使用双向 LSTM,False 表示使用单向 LSTM。

代码如下:

# ====使用nn.LSTM实现=========================================
# LSTM
# 实例化
input_size = 4
hidden_size = 1
lstm = nn.LSTM(input_size=input_size, hidden_size=hidden_size, batch_first=True)
# 修改模型参数
lstm.weight_ih_l0.data = torch.tensor([[0, 100, 0, -10], [0, 100, 0, 10], [1, 0, 0, 0], [0, 0, 100, -10]],
                                      dtype=torch.float32)
lstm.weight_hh_l0.data = torch.zeros(4, 1)
# 模型输入input.shape=(batch,seq_len,input_size)
input = 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]]], dtype=torch.float32)
# 初始化h_0,c_0
h_t = torch.zeros(1, 1, 1)
c_t = torch.zeros(1, 1, 1)
# 调用
output, (h_t, c_t) = lstm(input, (h_t, c_t))
rounded_output = torch.round(output * 1000) / 1000  # 保留3位小数
print(rounded_output)

运行结果:

从三种实现的方式来看,运行出的结果基本一致。 

参考:

LSTM参数梯度推导与实现:对抗梯度消失,

NNDL 作业十一 LSTM-CSDN博客

这次的分享就到这里,下次再见~


http://www.kler.cn/a/448594.html

相关文章:

  • JAVA:组合模式(Composite Pattern)的技术指南
  • ruoyi 请求参数类型不匹配,参数[giftId]要求类型为:‘java.lang.Long‘,但输入值为:‘orderGiftUnionList
  • 【Qt】显示类控件:QLabel、QLCDNumber、QProgressBar、QCalendarWidget
  • Oracle:数据库的顶尖认证
  • 数智化医院分布式计算框架融合人工智能方向初步实现与能力转换浅析
  • 智能座舱进阶-应用框架层-Handler分析
  • 【LeetCode】52、N 皇后 II
  • Python的sklearn中的RandomForestRegressor使用详解
  • MySQL InnoDB 存储引擎 Redo Log(重做日志)详解
  • KMP模式匹配算法——详细讲解、清晰易懂
  • THM:Vulnerability Capstone[WriteUP]
  • Python中SKlearn的K-means使用详解
  • Flutter组件————Container
  • Windows下使用git配置gitee远程仓库
  • 【C语言】后端开发。数据一致性和分布式锁
  • 基于springboot的电影订票系统
  • SpringMVC的URL组成,以及URI中对/斜杠的处理,解决IllegalStateException: Ambiguous mapping
  • 在 Sanic 应用中使用内存缓存管理 IP 黑名单
  • 霍尔传感器在汽车车门把手上的应用
  • 前端安全——敏感信息泄露
  • Redis——缓存穿透
  • 黑马程序员Java笔记整理(day07)
  • VS2022(Visual Studio)中显示行数(c#)
  • GIT安装过程
  • vue项目两种路由模式原理和应用
  • C/C++面试