LSTM的手动推导与代码逐行实现


前言

LSTM是RNN的一个优秀的变种模型,继承了大部分RNN模型的特性,同时解决了梯度反传过程由于逐步缩减而产生的梯度消失问题。在本次学习中,展示了LSTM的手动推导过程,用代码逐行模拟实现LSTM的运算过程,并与PyTorch API输出的结果验证是否一致。


一、LSTM的手推过程

  1. LSTM与RNN不同在于传递中多了一个memory cell,如下图所示,这是我们要手推LSTM模型的内部结构示意图。图中的遗忘门得到ft矩阵、输入门得到lt矩阵、输出门得到ot矩阵和得到gt矩阵的过程是类似的,都是先将w矩阵和v矩阵竖着拼接,乘上横着拼接的ht-1矩阵和Xt矩阵得到的;之后将ft矩阵乘上ct-1矩阵得到Scf矩阵,加上lt矩阵与gt矩阵相乘得到的Sig矩阵后,得到ct矩阵;再将ct矩阵作为tanh激活函数的输入与ot矩阵相乘得到ht矩阵,ht矩阵与参数wy矩阵相乘,得到sy矩阵;最后用softmax得到概率分布。
    在这里插入图片描述
  2. 如下图所示,这是LSTM整个运算过程中需要用到的计算公式。
    在这里插入图片描述
  3. 如下图所示,对参数进行初始化,将Ct-1和ht-1设为[(1@1)],Xt设为[(2&3&4)]。
    在这里插入图片描述
  4. 正向运算
    将初始化好的参数代入公式中,得到我们想要的输出。
    在这里插入图片描述
  5. 反向运算
    反向运算中的计算过程比较复杂,因此计算中要多加小心。
    在这里插入图片描述
  6. 通过反向运算得到的gradient,更新参数。
    在这里插入图片描述

二、LSTM代码实现

1、PyTorch API实现

首先定义一些张量,实例化对象lstm_layer,然后调用正态分布随机函数torch.randn生成input、c0和h0作为lstm_layer的输入后,得到lstm_layer的输出。

# 定义常量
bs, T, i_size, h_size = 2, 3, 4, 5
# proj_size = 3
input = torch.randn(bs, T, i_size)  # 输入序列
c0 = torch.randn(bs, h_size)  # 初始值,不需要训练
h0 = torch.randn(bs, h_size)
# h0 = torch.randn(bs, proj_size)

# 调用官方LSTM API
lstm_layer = nn.LSTM(i_size, h_size, batch_first=True)
# lstm_layer = nn.LSTM(i_size, h_size, batch_first=True, proj_size=proj_size)
output, (h_final, c_final) = lstm_layer(input, (h0.unsqueeze(0), c0.unsqueeze(0)))
print(output)
print(output.shape, h_final.shape, c_final.shape)

查看输出结果。
在这里插入图片描述

2、代码逐行实现LSTM

手写一个lstm_forward函数,手动地模拟LSTM的运算过程,并且与PyTorch LSTM API进行比较,验证输出结果。

def lstm_forward(input, initial_states, w_ih, w_hh, b_ih, b_hh):
    h0, c0 = initial_states  # 初始状态
    bs, T, i_size = input.shape
    h_size = w_ih.shape[0] // 4

    prev_h = h0
    prev_c = c0
    batch_w_ih = w_ih.unsqueeze(0).tile(bs, 1, 1)  # [bs, 4*h_size, i_size]
    batch_w_hh = w_hh.unsqueeze(0).tile(bs, 1, 1)  # [bs, 4*h_size, h_size]

    output_size = h_size
    output = torch.zeros(bs, T, output_size)  # 输出序列

    for t in range(T):
        x = input[:, t, :]  # 当前时刻的输入向量,[bs, i_size]
        w_times_x = torch.bmm(batch_w_ih, x.unsqueeze(-1))  # [bs, 4*h_size, 1]
        w_times_x = w_times_x.squeeze(-1)  # (bs, 4*h_size)

        w_times_h_prev = torch.bmm(batch_w_hh, prev_h.unsqueeze(-1))  # [bs, 4*h_size, 1]
        w_times_h_prev = w_times_h_prev.squeeze(-1)  # [bs, 4*h_size]

        # 分别计算输入门(i)、遗忘门(f)、cell门(g)、输出门(0)
        i_t = torch.sigmoid(w_times_x[:, :h_size] + w_times_h_prev[:, :h_size] + b_ih[:h_size] + b_hh[:h_size])
        f_t = torch.sigmoid(w_times_x[:, h_size:2 * h_size] + w_times_h_prev[:, h_size:2 * h_size]
                            + b_ih[h_size:2 * h_size] + b_hh[h_size:2 * h_size])
        g_t = torch.tanh(w_times_x[:, 2 * h_size:3 * h_size] + w_times_h_prev[:, 2 * h_size:3 * h_size]
                         + b_ih[2 * h_size:3 * h_size] + b_hh[2 * h_size:3 * h_size])
        o_t = torch.sigmoid(w_times_x[:, 3 * h_size:4 * h_size] + w_times_h_prev[:, 3 * h_size:4 * h_size]
                            + b_ih[3 * h_size:4 * h_size] + b_hh[3 * h_size:4 * h_size])
        prev_c = f_t * prev_c + i_t * g_t
        prev_h = o_t * torch.tanh(prev_c)

        output[:, t, :] = prev_h

    return output, (prev_h, prev_c)


output_custom, (h_final_custom, c_final_custom) = lstm_forward(input, (h0, c0), lstm_layer.weight_ih_l0,
                                                               lstm_layer.weight_hh_l0, lstm_layer.bias_ih_l0,
                                                               lstm_layer.bias_hh_l0)
print(output_custom)

查看输出结果以及并用torch.allclose验证手动模拟结果是否与PyTorch API一致。
在这里插入图片描述


总结

在本次学习中,通过对LSTM的手动推导与LSTM运算过程的代码逐行实现,了解到LSTM模型,以及加深了自己对LSTM模型的理解与推导。在上次的学习中,我学习到RNN不能处理长依赖问题,而LSTM是一种特殊的RNN,比较适用于解决长依赖问题,而且LSTM与RNN一样是链式结构,但是结构不相同,有4个input,分别指外界存储到Memory里面的值和3个gate(Input Gate、Forget Gate和Output Gate)的信号,它们都是以比较简单的方式起作用。

  • 9
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一个使用Python手动实现LSTM多变量预测的基本源代码: ``` import numpy as np # 定义LSTM类 class LSTM: def __init__(self, input_size, hidden_size, output_size): self.input_size = input_size self.hidden_size = hidden_size self.output_size = output_size # 初始化权重和偏置 self.Wf = np.random.randn(hidden_size, input_size + hidden_size) self.Wi = np.random.randn(hidden_size, input_size + hidden_size) self.Wo = np.random.randn(hidden_size, input_size + hidden_size) self.Wc = np.random.randn(hidden_size, input_size + hidden_size) self.Wy = np.random.randn(output_size, hidden_size) self.bf = np.zeros((hidden_size, 1)) self.bi = np.zeros((hidden_size, 1)) self.bo = np.zeros((hidden_size, 1)) self.bc = np.zeros((hidden_size, 1)) self.by = np.zeros((output_size, 1)) # 定义激活函数 def sigmoid(self, x): return 1 / (1 + np.exp(-x)) def tanh(self, x): return np.tanh(x) # 定义前向传播函数 def forward(self, xt, ht_minus_one, ct_minus_one): self.concat = np.vstack((ht_minus_one, xt)) ft = self.sigmoid(np.dot(self.Wf, self.concat) + self.bf) it = self.sigmoid(np.dot(self.Wi, self.concat) + self.bi) ot = self.sigmoid(np.dot(self.Wo, self.concat) + self.bo) ctilde_t = self.tanh(np.dot(self.Wc, self.concat) + self.bc) ct = ft * ct_minus_one + it * ctilde_t ht = ot * self.tanh(ct) yt = np.dot(self.Wy, ht) + self.by return yt, ht, ct def train(self, X, y, lr=0.01, epochs=100): for epoch in range(epochs): for i in range(len(X)): xt = X[i] yt = y[i] yt_pred, ht, ct = self.forward(xt, ht_minus_one, ct_minus_one) # 计算梯度 dWy = np.dot((yt_pred - yt), ht) dby = yt_pred - yt delta = np.dot(self.Wy.T, yt_pred - yt) * (1 - ht**2) dWo = delta * self.sigmoid(np.dot(self.Wo, self.concat) + self.bo) * (1 - self.sigmoid(np.dot(self.Wo, self.concat) + self.bo)) dWi = delta * self.sigmoid(np.dot(self.Wi, self.concat) + self.bi) * (1 - self.sigmoid(np.dot(self.Wi, self.concat) + self.bi)) dWf = delta * self.sigmoid(np.dot(self.Wf, self.concat) + self.bf) * (1 - self.sigmoid(np.dot(self.Wf, self.concat) + self.bf)) dWc = delta * (1 - ctilde_t**2) * self.sigmoid(np.dot(self.Wc, self.concat) + self.bc) * (1 - self.sigmoid(np.dot(self.Wc, self.concat) + self.bc)) # 更新权重和偏置 self.Wy -= lr * dWy self.by -= lr * dby self.Wo -= lr * dWo self.bo -= lr * dby self.Wi -= lr * dWi self.bi -= lr * dby self.Wf -= lr * dWf self.bf -= lr * dby self.Wc -= lr * dWc self.bc -= lr * dby # 定义输入和输出数据 X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]) y = np.array([[0], [1], [1], [0]]) # 初始化LSTM模型 lstm = LSTM(2, 4, 1) # 训练模型 lstm.train(X, y) # 进预测 for i in range(len(X)): xt = X[i] yt_pred, _, _ = lstm.forward(xt, ht_minus_one, ct_minus_one) print(f"Input: {xt}, Predicted Output: {yt_pred}") ``` 这是一个简单的LSTM模型,用于进逻辑异或运算的预测。输入数据`X`是一个4x2的数组,输出数据`y`是一个4x1的数组。模型使用一个4维的隐藏状态并输出一个预测值。以上代码只是一个基本的框架,可以根据需要进进一步的优化和改进。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值