初学者关于ConvLSTM的理解

这篇具有很好参考价值的文章主要介绍了初学者关于ConvLSTM的理解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

最近在着手于使用ConvLSTM进行时空序列预测问题,由于本人刚接触深度学习,很多代码都还理不清,故想到自己通过记录来加深对模型的理解,肯定会有很多问题和不专业的地方,若有网友看见,请不吝指教,谢谢。

ConvLSTM是施博士在《Convolutional LSTM Network: A Machine Learning Approach for Precipitation Nowcasting》论文首次提出,ConvLSTM同时拥有LSTM的时间序列处理能力和CNN的空间特征处理能力,可用于处理如降雨天气预报等时空序列问题。

在此,不仔细讨论ConvLSTM的原理,但要知道一个基本的就是ConvLSTM是直接基于LSTM改出来的(我的理解),LSTM中每个时间点输入的是一维数据,而ConvLSTM就是LSTM每个时间点中输入二维数据,然后在这个时间点里卷积存入有价值的信息。

convlstm,深度学习,神经网络,人工智能

 这张图片也摘至网络,想主要说明的一点就是ConvLSTM中的层数就指的是LSTM的层数,每个时间点前后有h和c的传入传出,每层之间的时间点之间有着h的传入和传出。

本人选择CDSN中最常见的,基于Pytorch深度学习框架的ConvLSTM代码,在他人已有注解的情况下,逐行进行代码更详细的注解,供像我这样的新手学习。

import numpy as np
from torch.utils.data import Dataset,DataLoader
import torch
import torch.nn as nn
"""
定义ConvLSTM每一层的、每个时间点的模型单元,及其计算。
"""
class ConvLSTMCell(nn.Module):

    def __init__(self, input_dim, hidden_dim, kernel_size, bias):
        """
        单元输入参数如下:
        input_dim: 输入张量对应的通道数,对于彩图为3,灰图为1。
        hidden_dim: 隐藏状态的神经单元个数,也就是隐藏层的节点数,应该可以按计算需要“随意”设置。
        kernel_size: (int, int),卷积核,并且卷积核通常都需要为奇数。
        bias: bool,单元计算时,是否加偏置,通常都要加,也就是True。
        """
        super(ConvLSTMCell, self).__init__()   #self:实例化对象,__init__()定义时该函数就自动运行,
                                               #super()是实例self把ConvLSTMCell的父类nn.Modele的__init__()里的东西传到自己的__init__()里
                                               #总之,这句是搭建神经网络结构必不可少的。         
        self.input_dim = input_dim
        self.hidden_dim = hidden_dim
        self.kernel_size = kernel_size
        self.padding = kernel_size[0] // 2, kernel_size[1] // 2   #//表示除法后取整数,为使池化后图片依然对称,故这样操作。
        self.bias = bias
        """
        nn.Conv2D(in_channels,out_channels,kernel_size,stride,padding,dilation=1,groups=1,bias)
        二维的卷积神经网络
        """
        self.conv = nn.Conv2d(in_channels=self.input_dim + self.hidden_dim,   #每个单元的输入为上个单元的h和这个单元的x,
                                                                              #所以h和x要连接在一起,在x的通道数上与h的维度上相连。
                              out_channels=4 * self.hidden_dim,   #输入门,遗忘门,输出门,激活门是LSTM的体现,
                                                                  #每个门的维度和隐藏层维度一样,这样才便于进行+和*的操作
                                                                  #输出了四个门,连接在一起,后面会想办法把门的输出单独分开,只要想要的。
                              kernel_size=self.kernel_size,
                              padding=self.padding,
                              bias=self.bias)
 
    def forward(self, input_tensor, cur_state):   
        """
        input_tensor:此时还是四维张量,还未考虑len_seq,[batch_size,channels,h,w],[b,c,h,w]。
        cur_state:每个时间点单元内,包含两个状态张量:h和c。
        """
        h_cur, c_cur = cur_state   #h_cur的size为[batch_size,hidden_dim,height,width],c_cur的size相同,也就是h和c的size与input_tensor相同
 
        combined = torch.cat([input_tensor, h_cur], dim=1)  #把input_tensor与状态张量h,沿input_tensor通道维度(h的节点个数),串联。
                                                            #combined:[batch_size,input_dim+hidden_dim,height,weight]  
        
        combined_conv = self.conv(combined)   #Conv2d的输入,[batch_size,channels,height,width]
                                              #Conv2d的输出,[batch_size,output_dim,height,width],这里output_dim=input_dim+hidden_dim  
        
        cc_i, cc_f, cc_o, cc_g = torch.split(combined_conv, self.hidden_dim, dim=1)   #将conv的输出combined_conv([batch_size,output_dim,height,width])
                                                                                      #分成output_dim这个维度去分块,每个块包含hidden_dim个节点信息
                                                                                      #四个块分别对于i,f,o,g四道门,每道门的size为[b,hidden_dim,h,w]
        i = torch.sigmoid(cc_i)   # 输入门
        f = torch.sigmoid(cc_f)   # 遗忘门
        o = torch.sigmoid(cc_o)   # 输出门
        g = torch.tanh(cc_g)   #激活门
 
        c_next = f * c_cur + i * g   #主线,遗忘门选择遗忘的+被激活一次的输入,更新长期记忆。
        h_next = o * torch.tanh(c_next)   #短期记忆,通过主线的激活和输出门后,更新短期记忆(即每个单元的输出)。
 
        return h_next, c_next   #输出当前时间点输出给下一个单元的,两个状态张量。
 
    def init_hidden(self, batch_size, image_size):
        """
        初始状态张量的定义,也就是说定义还未开始时输入给单元的h和c。
        """
        height, width = image_size
        init_h = torch.zeros(batch_size, self.hidden_dim, height, width, device=self.conv.weight.device)   #初始输入0张量
        init_c = torch.zeros(batch_size, self.hidden_dim, height, width, device=self.conv.weight.device)   #[b,hidden_dim,h,w]
                                                                                                           #self.conv.weight.device表示创建tensor存放的设备
                                                                                                           #和conv2d进行的设备相同     
        return (init_h,init_c)    
"""
定义整个ConvLSTM按序列和按层数的结构和计算。
输入介绍:
        五维数据,[batch_size,len_seq,channels,height,width] or [l,b,c,h,w]。
输出介绍:
        输出两个列表:layer_output_list和last_state_list。
        列表0:layer_output_list--单层列表,每个元素表示一层LSTM层的输出h状态,每个元素的size=[b,l,hidden_dim,h,w]。
        列表1:last_state_list--双层列表,每个元素是一个二元列表[h,c],表示每一层的最后一个时间单元的输出状态[h,c],
               h.size=c.size=[b,hidden_dim,h,w]
使用示例:
        >> x = torch.rand((64, 20, 1, 64, 64))
        >> convlstm = ConvLSTM(1, 30, (3,3), 1, True, True, False)
        >> _,last_states = convlstm(x)
        >> h = last_states[0][0]  #第一个0表示要第1层的列表,第二个0表示要h的张量。
"""
class ConvLSTM(nn.Module):
 
    """
    输入参数如下:
    input_dim:输入张量对应的通道数,对于彩图为3,灰图为1。
    hidden_dim:h,c两个状态张量的节点数,当多层的时候,可以是一个列表,表示每一层中状态张量的节点数。
    kernel_size:卷积核的尺寸,默认所有层的卷积核尺寸都是一样的,也可以设定不同的lstm层的卷积核尺寸不同。
    num_layers:lstm的层数,需要与len(hidden_dim)相等。
    batch_first:dimension 0位置是否是batch,是则True。
    bias:是否加偏置,通常都要加,也就是True。
    return_all_layers:是否返回所有lstm层的h状态。
    
    """
    
    def __init__(self, input_dim, hidden_dim, kernel_size, num_layers,
                 batch_first=True, bias=True, return_all_layers=False):
        super(ConvLSTM, self).__init__()

        self._check_kernel_size_consistency(kernel_size)   #后面def了的,检查卷积核是不是列表或元组。
        
        kernel_size = self._extend_for_multilayer(kernel_size, num_layers) # 如果为多层,将卷积核以列表的形式分入多层,每层卷积核相同。
        hidden_dim = self._extend_for_multilayer(hidden_dim, num_layers) # 如果为多层,将隐藏节点数以列表的形式分入多层,每层卷积核相同。
        
        if not len(kernel_size) == len(hidden_dim) == num_layers: # 判断卷积层数和LSTM层数的一致性,若不同,则报错。
            raise ValueError('Inconsistent list length.')
 
        self.input_dim = input_dim
        self.hidden_dim = hidden_dim
        self.kernel_size = kernel_size
        self.num_layers = num_layers
        self.batch_first = batch_first
        self.bias = bias
        self.return_all_layers = return_all_layers   #一般都为False。
 
        cell_list = []   #每个ConvLSTMCell会存入该列表中。
        for i in range(0, self.num_layers): # 当LSTM为多层,每一层的单元输入。
            if i==0:
                cur_input_dim = self.input_dim   #一层的时候,单元输入就为input_dim,多层的时候,单元第一层输入为input_dim。
            else:
                cur_input_dim = self.hidden_dim[i - 1]   #多层的时候,单元输入为对应的,前一层的隐藏层节点情况。
            
            cell_list.append(ConvLSTMCell(input_dim=cur_input_dim,
                                          hidden_dim=self.hidden_dim[i],
                                          kernel_size=self.kernel_size[i],
                                          bias=self.bias))

        self.cell_list = nn.ModuleList(cell_list) # 把定义的多个LSTM层串联成网络模型,ModuleList中模型可以自动更新参数。
 
    def forward(self, input_tensor, hidden_state=None):
        """
        input_tensor: 5D张量,[l, b, c, h, w] 或者 [b, l, c, h, w]
        hidden_state: 第一次输入为None,
        Returns:last_state_list, layer_output
        """
        if not self.batch_first:
            input_tensor = input_tensor.permute(1, 0, 2, 3, 4)   # (t, b, c, h, w) -> (b, t, c, h, w)
 
        if hidden_state is not None:
            raise NotImplementedError()
        else:
            b, _, _, h, w = input_tensor.size()  # 自动获取 b,h,w信息。
            hidden_state = self._init_hidden(batch_size=b,image_size=(h, w))
 
        layer_output_list = []
        last_state_list = []
 
        seq_len = input_tensor.size(1)  #根据输入张量获取lstm的长度。
        cur_layer_input = input_tensor   #主线记忆的第一次输入为input_tensor。
 
        for layer_idx in range(self.num_layers):  #逐层计算。
 
            h, c = hidden_state[layer_idx]   #获取每一层的短期和主线记忆。
            output_inner = []
            for t in range(seq_len):  #序列里逐个计算,然后更新。
                h, c = self.cell_list[layer_idx](input_tensor=cur_layer_input[:, t, :, :, :],cur_state=[h, c])
                output_inner.append(h)  #第layer_idx层的第t个stamp的输出状态。
 
            layer_output = torch.stack(output_inner, dim=1)  #将第layer_idx层的所有stamp的输出状态串联起来。
            cur_layer_input = layer_output   #准备第layer_idx+1层的输入张量,其实就是上一层的所有stamp的输出状态。
 
            layer_output_list.append(layer_output)  #当前层(第layer_idx层)的所有timestamp的h状态的串联后,分层存入列表中。
            last_state_list.append([h, c])  #当前层(第layer_idx层)的最后一个stamp的输出状态的[h,c],存入列表中。
 
        if not self.return_all_layers:   #当不返回所有层时
            layer_output_list = layer_output_list[-1:]  #只取最后一层的所有timestamp的h状态。
            last_state_list = last_state_list[-1:]   #只取最后一层的最后的stamp的输出状态[h,c]。
 
        return layer_output_list, last_state_list
 
    def _init_hidden(self, batch_size, image_size):
        """
        所有lstm层的第一个时间点单元的输入状态。
        """
        init_states = []
        for i in range(self.num_layers):
            init_states.append(self.cell_list[i].init_hidden(batch_size, image_size))   #每层初始单元,输入h和c,存为1个列表。
        return init_states
 
    @staticmethod   #静态方法,不需要访问任何实例和属性,纯粹地通过传入参数并返回数据的功能性方法。
    def _check_kernel_size_consistency(kernel_size):
        """
        检测输入的kernel_size是否符合要求,要求kernel_size的格式是list或tuple
        """
        if not (isinstance(kernel_size, tuple) or
                (isinstance(kernel_size, list) and all([isinstance(elem, tuple) for elem in kernel_size]))):
            raise ValueError('`kernel_size` must be tuple or list of tuples')
 
    @staticmethod   
    def _extend_for_multilayer(param, num_layers):
        """
        扩展到LSTM多层的情况
        """
        if not isinstance(param, list):
            param = [param] * num_layers
        return param
x = torch.rand((64, 20, 1, 64, 64))
convlstm = ConvLSTM(1, 30, (3,3), 1, True, True, False)
_, last_states = convlstm(x)
h = last_states[0][0]  # 第一个0表示要第一层的列表,第二个0表示要列表里第一个位置的h输出。

设定ConvLSTM为1层,每层隐藏单元节点数为30,卷积核(3,3),这个程序运行后可以发现,输入x的size为[64,20,1,64,64],输出的第一层h的size为[64,30,64,64],符合预期。文章来源地址https://www.toymoban.com/news/detail-515792.html

到了这里,关于初学者关于ConvLSTM的理解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【深度学习】受限玻尔兹曼机 (RBM) 初学者指南

            受限玻尔兹曼机(Restricted Boltzmann Machine,RBM)是一种基于能量模型的人工神经网络。它只有一个隐层,将输入层和隐层中的每个神经元互相连接,但不同层的神经元之间没有连接。RBM是一种无向的概率图模型,可以用于特征提取、数据降维、协同过滤等任务。它

    2024年02月13日
    浏览(38)
  • 关于这款开源的ES的ORM框架-Easy-Es适合初学者入手不?

    最近笔者为了捡回以前自学的ES知识,准备重新对ES的一些基础使用做个大致学习总结。然后在摸鱼逛开源社区时无意中发现了一款不错的ElasticSearch插件-Easy-ES,可称之为“ES界的MyBatis-Plus”。联想到之前每次用RestHighLevelClient写一些DSL操作时都很麻烦(复杂点的搜索代码量确实

    2024年02月07日
    浏览(45)
  • 爬虫,初学者指南

    1.想目标地址发起请求,携带heards和不携带heards的区别 request模块用于测速发送数据的连通性,通过回复可以看出418,Connection:close表示未获取到服务器的返回值,需要添加heards信息,此服务器拒绝非浏览器发送的请求。 上图可以看出添加了头信息headers之后成功获取了返回值

    2024年02月07日
    浏览(51)
  • Groovy初学者指南

    本文已收录至Github,推荐阅读 👉 Java随想录 微信公众号:Java随想录 目录 摘要 Groovy与Java的联系和区别 Groovy的语法 动态类型 元编程 处理集合的便捷方法 闭包 运算符重载 控制流 条件语句 循环语句 字符串处理 字符串插值 多行字符串 集合与迭代 列表(List) 映射(Map) 迭代器

    2024年02月05日
    浏览(53)
  • 守护进程(初学者必备)

    目录 一.进程组和会话 二.守护进程的概念 三.守护线程的特点 四.守护进程创建的基本步骤 1.进程组的相关概念: 进程除了有进程的PID之外还有一个进程组,进程组是由一个进程或者多个进程组成。通常他们与同一作业相关联可以收到同一终端的信号 每个进程组有唯一的进程

    2024年02月08日
    浏览(52)
  • QuantumultX 初学者傻瓜教程

    我这里以“orz”大佬来介绍。 大佬集成了分流、策略、去广告、比价、boxjs等功能,并给了非常详细的任务订阅列表,非常方便。 项目地址:https://github.com/Orz-3/QuantumultX TG频道:https://t.me/Orzmini TG群组:https://t.me/Orz_mini 食用方法:就是把远程配置文件下到本地覆盖再进行设置

    2024年02月08日
    浏览(41)
  • C语言初学者自序

    在这篇博客的开头,我首先说明,本人是金融专业大一的菜鸟。为什么会学习C语言以及与计算机的不解之缘,我会在下面一一解答。 我与计算机的故事,得从我幼年时说起。当然,这不是老太婆裹脚——又臭又长的故事。毕竟我自己也时时回顾这段经历,它有如晨钟暮鼓。

    2023年04月09日
    浏览(35)
  • ChatGPT初学者最佳实践

    2022年11月底,ChatGPT引爆了新一轮AI的革命,也让人们意识到AI真的能够大幅度提高人们的工作效率,甚至有人担心自己的工作会因为AI不保。这种居安思危的意识是正确的,但是正如锛凿斧锯的出现,并没有让木匠这个行业消失,而是让这个行业以更高效的方式工作。所以作为

    2024年02月05日
    浏览(45)
  • linux初学者小命令

    进程 :进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。 bash执行命令的过程,以’ls’命令为例: 第一步. 读取输入信息 :shell通过STDIN(标准输入)的getline()函数得到用户的输入

    2024年02月13日
    浏览(38)
  • 初学者该如何入手云计算

    妥妥的适合零基础入门云计算专业的学习路径,请收好。 我们将云计算的学习划分为4个阶段,基础阶段、初级阶段、应用阶段、进阶阶段。 (1)基础阶段 在基础阶段需要掌握通用的知识,有了扎实的基础后面才能走的更远,比如计算机组成原理、计算机网络、操作系统、

    2024年02月02日
    浏览(93)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包