PyTorch学习笔记:LSTM和LSTMP的原理及其手写复现

https://download.csdn.net/download/qq_73462282/88209373?spm=1001.2101.3001.9500

以上链接是关于循环神经网络的思维导图,有兴趣的小伙伴可自行下载


目录

前言

LSTM学习

LSTM基本结构:

 2.LSTM结构详解​编辑

1.1 遗忘门

 1.2 输入门

 1.3 细胞状态

 1.4 输出门

接下来我们看公式

接下来我们看参数

接下来,我们来实现代码

首先定义常量和引入官方API

自定义lstm模型 

 接下来我们添加proj_size,代码如下:

 接下来修改我们自己写的lstm模型,添加prev_size

修改后得到如下代码(增加prej)


前言

首先再开始之前,我想问一下在座的各位实现过LSTM吗,不会的扣1,会的小伙伴们扣脚指头,嘿嘿,开玩笑的,我也不知道,那么接下来我们一起学习如何写吧!

推荐教学视频:

30、PyTorch LSTM和LSTMP的原理及其手写复现_哔哩哔哩_bilibili

推荐博客讲解:

循环神经网络(超详细|附代码训练)_后来后来啊的博客-CSDN博客

长短时记忆网络(LSTM)(超详细 |附训练代码)_后来后来啊的博客-CSDN博客

递归神经网络(超详细|附训练代码)_后来后来啊的博客-CSDN博客

继上次写了RNN,还可以学医RNNCELL,rnncell可以理解为单步的迭代神经网络,有很多步去迭代,然后输出,所以不论是gru,rnn还是lstm都是单步计算

rnn官网学习笔记:Pytorch学习笔记:RNN的原理及其手写复现_后来后来啊的博客-CSDN博客

rnncell学习官方链接:RNNCell — PyTorch 2.0 documentation

其他博主学习链接:【PyTorch学习笔记】21:nn.RNN和nn.RNNCell的使用_LauZyHou的博客-CSDN博客


今日重点(敲黑板!!)

LSTM学习

首先还是先放我们pytorch官网链接,有兴趣的小伙伴可以自行去官网查看最详细的讲解:LSTM — PyTorch 2.0 documentation

然后可以看看大牛写的博客(很深入):Understanding LSTM Networks -- colah's blog

为防止有些宝子们等不起github,所以这里放一篇转载的(中译):理解LSTM (Understanding LSTM Networks翻译) - 知乎 (zhihu.com)


相比较rnn,rnn只有输入和隐含状态,相比较而言,lstm多了很多门,包括输入门、输出门、遗忘门、记忆单元组成的结构,我们来 观看以下图

LSTM基本结构:

 2.LSTM结构详解

比如上面这条横线看着像传送爱带 ,其实是一个细胞单元(细胞状态),整个lstm其实 都是依靠细胞状态来更新历史信息的

我们可以查看官网给出的信息

 在上图中

 纠正翻译出错的地方:  输入门是上图中的it, ct是细胞单元(或者叫 记忆单元、细胞状态),ht是隐藏输出,最终输出ht,最后是哈达玛内积(点积,内积,哈达玛积的区别_哈达玛乘积_SaltyFish_Go的博客-CSDN博客)

3.LSTM运行步骤

若下图没看懂,该博主做了更加详细的介绍:Lstm(循环神经网络)_lstm神经网络_一 笑的博客-CSDN博客

 以及如何从RNN起步,一步一步通俗理解LSTM_rnn lstm_v_JULY_v的博客-CSDN博客博主中的动图

1.1 遗忘门

 1.2 输入门

 1.3 细胞状态

 1.4 输出门

所以很多都是使用lstm进行大工程制作


接下来我们看公式

相比于rnn中只有一个 h(t-1),我们只需要提供h0即可 

 但是在lstm中,我们可以看到存在h(t-1)和c(t-1),所以我们需要提供h0和c0,这就是lstm相对于rnn,多了一个初始状态


接下来我们看参数

参数:

  • input_size – 输入 x 中预期要素的数量

  • hidden_size – 处于隐藏状态 h 的特征数量

  • num_layers – 循环层数。例如,设置意味着将两个 LSTM 堆叠在一起以形成堆叠的 LSTM, 第二个 LSTM 接收第一个 LSTM 的输出,并且 计算最终结果。默认值:1num_layers=2

  • bias – 如果为False ,则图层不使用偏置权重b_ih和b_hh。 默认值:True

  • batch_first – 如果 ,则提供输入和输出张量 作为 (批处理, 序列, 特征) 而不是 (序列, 批处理, 特征)。 请注意,这不适用于隐藏状态或单元格状态。请参阅的 有关详细信息,请参阅下面的输入/输出部分。默认值:TrueFalse

  • dropout – 如果非零,则在每个输出端引入 Dropout 层 LSTM 层(最后一层除外),输出概率等于dropout 。默认值:0

  • 双向 – 如果True ,则变为双向 LSTM。默认值:False

  • proj_size – 如果 ,将使用具有相应大小的投影的 LSTM。默认值:0> 0


    实例化完以后,传入输入

Inputs: input, (h_0, c_0)
#如上,传入input序列:batch size * sequence length * input size(bs = True)
#俩个初始状态以元组组合起来,和rnn保持一致

注意如果有projetion size啧输出proj_size而不是hidden_size 


然后是output

Outputs: output, (h_n, c_n)
#输出大小;batch size * sequence length * hidden size * output
#元组表示形式是表示最后一时刻表示的隐含状态和细胞状态
#如果要output的话,一般是many to many的建模,例如输入是一个序列,输出也是一个序列,且都要
#(例如文本多音字 预测等)
#一般如果要h_n,一般是many to one的建模,一般只取最后时刻的一个状态

接下来,我们来实现代码

以便更加深入了解

首先定义常量和引入官方API

#实现lstm和lstmp的源码
import torch
import torch.nn as nn

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

#调用官方LSTM API
lstm_layer = nn.LSTM(i_size, h_size, batch_first=True)
output, (h_final, c_final) = lstm_layer(input, (h0.unsqueeze(0), c0.unsqueeze(0)))
# print(output)
# for k,v in lstm_layer.named_parameters() :
#     print(k, v) #打印出 lstm权重,具体的张量是什么,为了更清晰,可以print(k, v.shape)

打印出v.shape是

weight_ih_l0 torch.Size([20, 4]) #20的原因是4*5,对input进行线性变换
weight_hh_l0 torch.Size([20, 5]) #20的原因是4*5,维度为5(linear层,input size)
bias_ih_l0 torch.Size([20]) #20 = 4*5 ,有四个bias_ih
bias_hh_l0 torch.Size([20]) #同理

自定义lstm模型 

#自己写一个lstm模型
def lstm_forward(input, initial_states, w_ih, w_hh, b_ih, b_hh):
    h0, c0 = initial_states #初始状态
    bs, T ,i_size = input.shape #T是时间维度,也就是序列的长度
    h_size = w_ih.shape[0] // 4

    prev_h = h0
    prev_c = c0
    #w_ih #维度是[4 * h_size, i_size]  h_size及是hidden_size,所以要进行扩维
    batch_w_ih = w_ih.unsqueeze(0).tile(bs, 1, 1) #现在batch是1,但是需要bs,所以进行tile
    # w_hh #维度是[4 * h_size, h_size] h_size及是hidden_size,所以要进行扩维
    batch_w_hh = w_hh.unsqueeze(0).tile(bs, 1, 1) #同理

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

    for t in range(T):
        x = input[:, t, :] #当期时刻的输入向量 , 维度为[bs, i_size],需要对x进行扩维
        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),ceell门(c),输出门(o)
        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.tanh(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)

打印官方和自己定义 的,输出张量基本一致

 接下来我们添加proj_size,代码如下:

# 调用官方LSTM API
lstm_layer = nn.LSTM(i_size, h_size, batch_first=True)
output, (h_final, c_final) = lstm_layer(input, (h0.unsqueeze(0), c0.unsqueeze(0)), proj_size = proj_size)
print(output)
for k,v in lstm_layer.named_parameters() :
    print(k, v) #打印出 lstm权重,具体的张量是什么,为了更清晰,可以print(k, v.shape)

会出现报错

 这是因为,我们在设置了proj_size以后,h0会变成proj_size维度,所以前面的定义改为;以及proj_size放到实例化中去

#定义常量
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, proj_size)
# 调用官方LSTM API
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)
for k,v in lstm_layer.named_parameters() :
    print(k, v) #打印出 lstm权重,具体的张量是什么,为了更清晰,可以print(k, v.shape)

运行后的 到

 

多出来了一个weight_hr_l0,且hidden_size现在是3,而不是5

我们打印 下值shape

print(output.shape, h_final.shape, c_final.shape)

得到output.shape是2*3*3,不是2*3*5,h大小也为3,但是c的大小仍然是5,可见只对输出进行压缩,不会对细胞状态进行压缩,这就是prej的作用

 接下来修改我们自己写的lstm模型,添加prev_size

我们需要进行如下定义,设置w_hr为None,若不为None,则存在prej

def lstm_forward(input, initial_states, w_ih, w_hh, b_ih, b_hh, w_hr=None):

那么我们还要修改output_size的值,进行如下判断

且对 w_hr也要进行引入一个batch的维度

    if w_hr is not None:
        p_size, _ = w_hr.shape[0]
        output_size = p_size
        batch_w_hr = w_hr.unsqueeze(0).tile(bs, 1, 1) #[bs, p_size, h_size]
    else:
        output_size = h_size

在下面prev_h维度为[bs, h_size],需要降低

        prev_h = o_t * torch.tanh(prev_c) #[bs, h_size]

所以我们也要进行压缩,得到output变小

        if w_hr is not None:
            prev_h = torch.bmm(batch_w_hr , prev_h.unsqueeze(-1)) #[bs, p_size, 1]
            prev_h = prev_h.squeeze(-1) # [bs, p_size]

修改后得到如下代码(增加prej)

#自己写一个lstm模型
def lstm_forward(input, initial_states, w_ih, w_hh, b_ih, b_hh, w_hr=None):
    h0, c0 = initial_states #初始状态
    bs, T ,i_size = input.shape #T是时间维度,也就是序列的长度
    h_size = w_ih.shape[0] // 4

    prev_h = h0
    prev_c = c0
    #w_ih #维度是[4 * h_size, i_size]  h_size及是hidden_size,所以要进行扩维
    batch_w_ih = w_ih.unsqueeze(0).tile(bs, 1, 1) #现在batch是1,但是需要bs,所以进行tile
    # w_hh #维度是[4 * h_size, h_size] h_size及是hidden_size,所以要进行扩维
    batch_w_hh = w_hh.unsqueeze(0).tile(bs, 1, 1) #同理

    if w_hr is not None:
        p_size = w_hr.shape[0]
        output_size = p_size
        batch_w_hr = w_hr.unsqueeze(0).tile(bs, 1, 1) #[bs, p_size, h_size]
    else:
        output_size = h_size

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

    for t in range(T):
        x = input[:, t, :] #当期时刻的输入向量 , 维度为[bs, i_size],需要对x进行扩维
        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),ceell门(c),输出门(o)
        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.tanh(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) #[bs, h_size]

        if w_hr is not None:
            prev_h = torch.bmm(batch_w_hr , prev_h.unsqueeze(-1)) #[bs, p_size, 1]
            prev_h = prev_h.squeeze(-1) # [bs, p_size]

        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, lstm_layer.weight_hr_l0)

print(output_custom)

结果为

以上就是lstm的全部内容了,有兴趣的小伙伴建议观看视频,更加有利于自己的理解:30、PyTorch LSTM和LSTMP的原理及其手写复现_哔哩哔哩_bilibili

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值