• 一文读懂官方给出torch.nn.RNN API的参数及手写RNN API复现


    理论部分 

    官方给出的文档解释: 

     计算公式:

    该公式对应的结构框图:

            其中 xt 表示当前 t 时刻的输入,Wih表示 “输入层” 到 “隐藏层” 的权重矩阵。即 Wih 会将输入映射至隐藏层。bih表示输入层到隐藏层的偏置,ht-1表示 t-1 时刻的状态,ht 表示t时刻的隐藏状态,Whh表示隐藏层到隐藏层的权重矩阵,bhh表示隐藏层到隐藏层的偏置。
            `torch.nn.RNN` 是PyTorch中的一个循环神经网络(RNN)模块,用于构建RNN模型。它具有许多参数,以下是一些常用参数的解释:

    参数: 

    input_size (int):输入数据的特征大小(特征维度),即每个时间步的输入向量 xt 的维度。例如,如果你的输入数据是单词的嵌入向量,每个单词表示为一个100维的向量,那么`input_size`将等于100。
    hidden_size (int):隐藏层的特征大小,即每个时间步的隐藏状态向量ht的维度。它决定了模型的表示能力和记忆能力。较大的`hidden_size`通常允许模型学习更复杂的模式,但也需要更多的计算资源。
    
    num_layers (int,可选):RNN的层数,用于堆叠多个RNN层,默认值为1。当层数大于1时,RNN会变为多层RNN。多层RNN可以捕捉更复杂的时间依赖关系,但也会增加模型的复杂性。
    
    nonlinearity (str,可选):指定激活函数,默认值为'tanh'。可选值有'tanh'和'relu'。
    bias (bool,可选):如果设置为True,则在RNN中添加偏置项。默认值为True。偏差项通常有助于模型更好地拟合数据。
    
    batch_first (bool,可选):一个布尔值,确定输入数据的维度顺序。如果设置为True,则输入数据的形状为(batch_size, seq_len, input_size)。否则,默认输入数据的形状为(seq_len, batch_size, input_size)。默认值为False。
    
    dropout (float,可选):如果非零,则在除最后一层之外的每个RNN层之间添加dropout层,其丢弃概率为dropout。默认值为0。这有助于防止过拟合。
    bidirectional (bool,可选):一个布尔值,确定是否使用双向RNN。如果设置为True,RNN将同时在时间步的正向和反向方向上运行(则使用双向RNN),以捕捉前后的上下文信息。默认值为False。

            一旦你创建了`torch.nn.RNN`模块,你可以将输入数据传递给它,然后使用输出来进行进一步的处理或者连接其他层。模型的隐藏状态也可以在需要时访问,以进行序列数据的持久化记忆或其他操作。

    input : 对于没有批次的输入,即没有batch的输入,其形状大小为一个二维的张量,尺寸为:序列长度(总时间步数)✖input_size(输入数据的特征大小,即每个时间步的输入向量xt的维度)。
    当batch_first = False,那么input的尺寸大小为:序列长度✖batch_size(批次大小)✖input_size。
    当batch_first = True,那么input的尺寸大小为:batch_size(批次大小)✖序列长度✖input_size。

    ------------------------------------------------RNN网络input维度解释-------------------------------------------------

            xt是一个n维向量,RNN(递归神经网络)的输入将是一整个序列,也就是X = [ x1,…,xt-1,xt,xt+1,…xT ],T表示序列的长度,这也就是说RNN中一个样本就是一个序列。
            对于语言模型,每一个xt将代表一个词向量,一整个序列就代表一句话(也就是一个样本),T就是这句话包含的单词数量。又由于在神经网络中,我们的输入通常是多个样本作为一个批次的,所以在RNN中数据通常是三维的,也就是[ batch_size, seq_len, input_size ]或者[ seq_len, batch_size, input_size ],其中,batch_size 表示批次大小,也就是一个批次含有多少个序列(句子);seq_len表示一个序列(句子)的长度,也就是按时间序列展开每个样本有多少个可见的RNN cell;input_size 表示某时刻输入数据的维度,即xt的维度,也就是这个输入数据的特征数目(features)。

    一个句子:一个sample

    一个句子由n个词:n个timestep(seq_len)

    一个词是k维的词向量:k个feature

            例如:一个数据集包含五句话(天气真好)(你是谁啊)(我是小明)(明天打球)(武汉加油)。数据集的维度就是(batch_size, time_step, feature_dim)= (5, 4, word_embedding)。

    对于这样一个数据集,输入RNN的时候是什么情况?

            RNN是每个time_step输入一次数据,那么for循环time_step1时,进入网络的数据就是(天,你,我,明, 武)每句话的第一个字进入网络,然后依次往后,这里我们最简单的理解就是同时有batch_size个RNN在处理数据,每个RNN处理一个字,那么time_step1的输出就是(batch_size, hidden_size),整个batch处理完输出为(batch_size, time_step, hidden_size)。

    ---------------------------------------------------------------------------------------------------------------------------------

    h_0: 对于没有批次的输入,其形状大小为一个二维的张量,尺寸为:D(单层RNN网络D取1,双层RNN网络D取2)*RNN层数✖hidden_size(隐藏层的特征大小,即每个时间步的隐藏状态向量ht的维度)。
    对于有批次的输入,其形状大小为一个三维的张量,尺寸为:D(单层RNN网络D取1,双层RNN网络D取2)*RNN层数✖batch_size✖hidden_size(隐藏层的特征大小,即每个时间步的隐藏状态向量ht的维度)。
    如果不提供初始隐藏状态h_0,默认情况下取0。

    RNN的输出变量

    output 总输出

            `torch.nn.RNN`模块的主要输出是一个包含RNN模型在每个时间步的输出的张量,通常称为`output`。这个输出张量的形状取决于输入数据的形状、RNN的参数设置以及输入序列的长度。

            具体来说,`output`张量的形状为`( batch_size, sequence_length, num_directions * hidden_size)`,其中:

    - `sequence_length`是输入序列的长度,即时间步的数量。
    - `batch_size`是输入数据的批次大小,即一次处理多少个序列。
    - `num_directions`是一个可选的参数,如果你的RNN是双向的(`bidirectional=True`),则`num_directions`为2;如果是单向的,则为1。
    - `hidden_size`是RNN的隐藏状态大小,即每个时间步的隐藏状态向量的维度。

            这个`output`张量包含了RNN在每个时间步的输出。通常,你可以在训练后对这些输出进行进一步处理,例如用于分类、回归或序列到序列的任务,或者用于获得序列中的某些信息。

    h_n 最后一个时间步的输出 

            此外,`torch.nn.RNN`还返回一个包含最后一个时间步的隐藏状态的张量,通常称为`h_n`。这个张量的形状为`(num_layers * num_directions, batch_size, hidden_size)`,其中:

    - `num_layers`是RNN模型的层数。
    - `num_directions`是一个可选的参数,如果你的RNN是双向的,则`num_directions`为2;如果是单向的,则为1。
    - `hidden_size`是RNN的隐藏状态大小,即每个时间步的隐藏状态向量的维度。

            `h_n`张量包含了每个层的最后一个时间步的隐藏状态,可以用于进行额外的处理或者作为下一个时间步的初始隐藏状态。

            综上所述,`output`和`h_n`是`torch.nn.RNN`模块的两个主要输出,它们提供了RNN在输入序列上的输出信息和最终的隐藏状态信息。

    RNN网路的权重参数

    weight_ih_l[k] : 输入层到隐藏层的权重矩阵,这是输入到隐藏层[k]的权重参数,其中k表示层的索引。当k=0时,形状为一个二维张量,尺寸大小为:hidden_size✖input_size。

    weight_hh_l[k] : 隐藏层到隐藏层的权重矩阵,用于控制前一个时间步的隐藏状态如何影响当前时间步的隐藏状态。形状为一个二维张量,尺寸大小为:hidden_size✖hidden_size。

    bias_ih_l[k] : 这是输入到隐藏层[k]的偏差(bias)参数,用于偏置输入数据对隐藏状态的影响。形状为一个一维张量,尺寸大小:hidden_size。

    bias_hh_l[k] : 这是隐藏层[k]到自身的偏差(bias)参数,用于偏置前一个时间步的隐藏状态对当前时间步的隐藏状态的影响。形状为一个一维张量,尺寸大小:hidden_size。


            注意,如果你的RNN模型有多个层(`num_layers`大于1),那么每个层都会有一组权重参数。通常,这些权重参数是在模型初始化时随机初始化的,然后通过反向传播进行训练。
            这些权重参数是RNN模型的核心组成部分,它们决定了模型如何处理输入序列并学习时间依赖关系。在训练过程中,这些参数会根据损失函数的梯度进行更新,以逐渐提高模型的性能。

           

    代码部分 

     调用官方给出的单层RNN API函数

    1. import torch
    2. import torch.nn as nn
    3. # 单向RNN逐行实现
    4. batch_size, T = 2, 3 # 批大小,输入序列长度
    5. input_size, hidden_size = 2, 3 # input_size:输入数据的特征大小,即每个时间步的输入向量xt的维度。
    6. # hidden_size:隐藏层的特征大小,即每个时间步的隐藏状态向量ht的维度。
    7. input = torch.randn(batch_size, T, input_size) # 随机初始化一个输入
    8. h_prev = torch.zeros(batch_size, hidden_size) # 初始隐藏状态,形状大小为batch_size*hidden_size
    9. # step1 调用pytorch RNN API
    10. rnn = nn.RNN(input_size=input_size, hidden_size=hidden_size, batch_first=True)
    11. print("rnn API output: ")
    12. rnn_output, state_final = rnn(input, h_prev.unsqueeze(0)) # 使用unsqueeze在h_prev的第0维扩充一维
    13. print("rnn_output:", rnn_output)
    14. print("state_final:", state_final)

    手写单层RNN网络

    1. # step2 手写RNN函数,实现RNN计算原理
    2. def rnn_forward(input, weight_ih, bias_ih, weight_hh, bias_hh, h_prev):
    3. """
    4. :param input:输入。
    5. :param weight_ih: 输入层到隐藏层的权重矩阵。weight_ih的大小:h_dim*input_size
    6. :param bias_ih:输入层到隐藏层的偏置。bias_ih的大小:batch_size*hidden_size
    7. :param weight_hh:隐藏层到隐藏层的权重矩阵。weight_hh的大小:hidden_size✖hidden_size
    8. :param bias_hh:隐藏层到隐藏层的偏置。bias_hh的大小:batch_size*hidden_size
    9. :param h_prev:隐藏状态。h_prev的大小:batch_size*hidden_size
    10. :return: h_out:总输出; h_prev.unsqueeze(0):最后时刻的输出
    11. """
    12. batch_size, T, input_size = input.shape # 将input的形状拆解出来。batch_size表示批次大小,T表示序列长度,或时间步数,input_size表示输入xt的维度。
    13. h_dim = weight_ih.shape[0] # 隐藏状态的维度
    14. h_out = torch.zeros(batch_size, T, h_dim) # 初始化一个输出(状态)矩阵
    15. for t in range(T):
    16. x = input[:, t, :].unsqueeze(2) # 获取当前时刻输入. x的大小:batch_size*input_size*1
    17. # 由于input是三维张量:
    18. # 第一维度是batch_size,全部拿;
    19. # 第二维度是时间,我们拿当前第 t 时刻的输入;
    20. # 第三维度是特征维度,全部拿。
    21. w_ih_batch = weight_ih.unsqueeze(0).tile(batch_size, 1, 1) # w_ih_batch的大小:batch_size*h_dim*input_size
    22. w_hh_batch = weight_hh.unsqueeze(0).tile(batch_size, 1, 1) # w_hh_batch的大小:batch_size*h_dim*h_dim
    23. w_times_x = torch.bmm(w_ih_batch, x).squeeze(-1) # batch_size*h_dim
    24. w_times_h = torch.bmm(w_hh_batch, h_prev.unsqueeze(2)).squeeze(-1) # batch_size*h_dim
    25. h_prev = torch.tanh(w_times_x+bias_ih+w_times_h+bias_hh)
    26. h_out[:, t, :] = h_prev
    27. return h_out, h_prev.unsqueeze(0)

            用官方RNN API的参数喂入到我们自己写的rnn_forward函数中来验证我们的函数输出的结果与官方API输出的结果是否一致。 

    1. # 验证rnn_forward的正确性
    2. # for p,n in rnn.named_parameters():
    3. # print(p, n) # p表示参数名称,n表示参数取值
    4. custom_rnn_out, custom_state_final = \
    5. rnn_forward(input, rnn.weight_ih_l0, rnn.bias_ih_l0,
    6. rnn.weight_hh_l0, rnn.bias_hh_l0, h_prev)
    7. print("rnn_forward function output: ")
    8. print("custom_rnn_out:", custom_rnn_out)
    9. print("custom_state_final:", custom_state_final)

    打印结果:

    1. rnn API output:
    2. rnn_output: tensor([[[ 0.5228, -0.8081, -0.5678],
    3. [ 0.6968, -0.9421, -0.9379],
    4. [ 0.1090, -0.7488, -0.9370]],
    5. [[-0.3539, -0.1466, -0.3064],
    6. [-0.0056, -0.8178, -0.7956],
    7. [ 0.7735, 0.1453, 0.1368]]], grad_fn=)
    8. state_final: tensor([[[ 0.1090, -0.7488, -0.9370],
    9. [ 0.7735, 0.1453, 0.1368]]], grad_fn=)
    10. rnn_forward function output:
    11. custom_rnn_out: tensor([[[ 0.5228, -0.8081, -0.5678],
    12. [ 0.6968, -0.9421, -0.9379],
    13. [ 0.1090, -0.7488, -0.9370]],
    14. [[-0.3539, -0.1466, -0.3064],
    15. [-0.0056, -0.8178, -0.7956],
    16. [ 0.7735, 0.1453, 0.1368]]], grad_fn=)
    17. custom_state_final: tensor([[[ 0.1090, -0.7488, -0.9370],
    18. [ 0.7735, 0.1453, 0.1368]]], grad_fn=)

    发现结果一致。

    代码部分可参考自:29、PyTorch RNN的原理及其手写复现_哔哩哔哩_bilibili本期直播视频主要讲解序列建模中RNN模型的原理、PyTorch API讲解以及如何逐行实现RNN算法。如果大家觉得本期视频有收获,欢迎支持或转发。直播深度学习算法原理与项目源码讲解,欢迎关注我的直播间:https://live.bilibili.com/14297368?spm_id_from=333.999.0.0, 视频播放量 24437、弹幕量 312、点赞数 650、投硬币枚数 573、收藏人数 981、转发人数 75, 视频作者 deep_thoughts, 作者简介 在有限的生命里怎么样把握住时间专注做点自己喜欢做的同时对别人也有价值的事情,是我们应该时常自查反省的(纯公益分享不接任何广告或合作),相关视频:55 循环神经网络 RNN 的实现【动手学深度学习v2】,【基于pytorch的】循环神经网络和LSTM的基本原理讲解与代码实现!,pytorch-LSTM原理及代码,54 循环神经网络 RNN【动手学深度学习v2】,【循环神经网络】5分钟搞懂RNN,3D动画深入浅出,30、PyTorch LSTM和LSTMP的原理及其手写复现,PyTorch深度学习快速入门教程(绝对通俗易懂!)【小土堆】,20分钟掌握RNN与LSTM原理及其结构应用(Seq2Seq & Attention),57 长短期记忆网络(LSTM)【动手学深度学习v2】,真-极度易懂的LSTM (代码)icon-default.png?t=N7T8https://www.bilibili.com/video/BV13i4y1R7jB/?spm_id_from=333.788&vd_source=fb7bfda367c76676e2483b9b60485e57

    RNN输入输出详解


    可参考下面这篇博文:

    PyTorch nn.RNN 参数全解析_Iareges的博客-CSDN博客 

  • 相关阅读:
    SSH在线考试系统
    Redis数据库安装(Windows)
    JVM知识总结(性能调优)
    【Java数据结构】详解Stack与Queue(一)
    【语音识别】基于LMS算法消除嘈杂的鸟类语音信号中的噪声后识别其对应的鸟类物种(Matlab代码实现)
    React学习总结
    Smartbi融入“多维建模”能力,满足一站式BI需求
    [计算几何] 2 二维凸包/笨蛋(我)也能看懂的二维凸包算法
    maven 编译.../maven-metadata.xml 报错
    2022年在湖北考的建设厅七大员可以全国通用吗?甘建二
  • 原文地址:https://blog.csdn.net/m0_48241022/article/details/132701824