机器学习第二十五周周报 ConvLSTM

文章目录

  • week 25 ConvLSTM
  • 摘要
  • Abstract
  • 一、李宏毅机器学习
  • 二、文献阅读
    • 1. 题目
    • 2. abstract
    • 3. 网络架构
      • 3.1降水预报问题的建模
      • 3.2Convolutional LSTM
      • 3.3编码-预测结构
    • 4. 文献解读
      • 4.1 Introduction
      • 4.2 创新点
      • 4.3 实验过程
        • 4.3.1Moving-MNIST Dataset
        • 4.3.2雷达回波数据集
      • 4.4 结论
  • 三、基于pytorch实现ConvLSTM
    • 1.实验内容
    • 2.实验结果
    • 3.实验数据集
      • 3.1数据集处理
    • 4.模型及训练过程实现
    • 小结
    • 参考文献

week 25 ConvLSTM

摘要

本文主要讨论ConvLSTM的模型。本文简要介绍了自注意力机制运行逻辑。其次本文展示了题为Convolutional LSTM Network: A Machine Learning Approach for Precipitation Nowcasting的论文主要内容。该论文将降水预报问题建模为时空序列预测问题,并根据FC-LSTM结构进行扩展,提出了ConvLSTM。该结构改善了FC-LSTM的缺点,通过其局部邻域的输入和过去状态来确定网格中某个单元的未来状态。该文在多个数据集上进行实验,从数据角度证明了该网络的优越性。最后,本文基于pytorch实现了ConvLSTM模型并在KTH数据集上进行验证。

Abstract

This article mainly discusses the model of ConvLSTM. This article briefly introduces the operating logic of the self-attention mechanism. Secondly, this paper presents the main content of the paper entitled Convolutional LSTM Network: A Machine Learning Approach for Precipitation Nowcasting. In this paper, this paper models the precipitation prediction problem as a spatiotemporal series prediction problem, and proposes the ConvLSTM by extending it according to the FC-LSTM structure. This structure improves on the shortcomings of FC-LSTM by determining the future state of an element in the grid through the input and past state of its local neighborhood. In this paper, experiments are carried out on multiple datasets to prove the superiority of the network from the perspective of data. Finally, this article implements the ConvLSTM model based on pytorch and validates on the KTH dataset.

一、李宏毅机器学习

8029043ece92c4a6a60541eb53067ad

二、文献阅读

1. 题目

题目:Convolutional LSTM Network: A Machine Learning Approach for Precipitation Nowcasting

作者:Xingjian Shi, Zhourong Chen, Hao Wang, Dit-Yan Yeung, Wai-kin Wong, Wang-chun Woo

链接:https://arxiv.org/pdf/1506.04214.pdf

发布:NIPS’15: Proceedings of the 28th International Conference on Neural Information Processing Systems - Volume 1December 2015Pages 802–810

2. abstract

在本文中,将降水临近预报表述为时空序列预测问题,其中输入和预测目标都是时空序列。通过扩展全连接 LSTM (FC-LSTM) 以在输入到状态和状态到状态转换中都具有卷积结构,提出了卷积 LSTM (ConvLSTM) 并用它来构建端到端降水临近预报问题的可训练模型。实验表明,ConvLSTM 网络可以更好地捕获时空相关性,并且始终优于 FC-LSTM 和最先进的降水临近预报操作 ROVER 算法。

This paper formulates precipitation nowcasting as a spatiotemporal sequence forecasting problem in which both the input and the prediction target are spatiotemporal sequences. By extending the fully connected LSTM (FC-LSTM) to have convolutional structures in both the input-to-state and
state-to-state transitions, this paper proposes the convolutional LSTM (ConvLSTM) and uses it to build an end-to-end trainable model for the precipitation nowcasting problem. Experiments show that ConvLSTM network captures spatiotemporal correlations better and consistently outperforms FC-LSTM and the state-of-the-art operational ROVER algorithm for precipitation nowcasting.

3. 网络架构

3.1降水预报问题的建模

对流降水临近预报一直是天气预报领域的一个重要问题。该任务的目标是准确、及时地预测局部区域在相对较短的时间内(例如0-6小时)的降雨强度。

假设在一个由M行和N列组成的 M × N M\times N M×N网格表示的空间区域内观测到一个动态系统。在网络个的每个单元格内,都有随着时间变化的P个观测值。因此,任何时刻的观测值都可以使用张量 X ∈ R P × M × N \mathcal X\in \mathbf R^{P\times M\times N} XRP×M×N来表示,其中 R \mathbf R R表示观测到的特征的域。如果定期记录观测值,将得到 X ^ 1 , X ^ 2 , … , X ^ t \mathcal {\hat X}_1,\mathcal {\hat X}_2,\dots, \mathcal {\hat X}_t X^1,X^2,,X^t​。时空序列预测问题是在给定前J个观测值的情况下,预测未来最可能的长度K序列:
X ^ t + 1 , … , X ^ t + K = argmax X t + 1 , … , X t + K p ( X t + 1 , … , X t + K ∣ X ^ t − J + 1 , X ^ t − J + 2 , … , X t ) (1) \mathcal {\hat X}_{t+1},\dots,\mathcal {\hat X}_{t+K}=\text{argmax}_{\mathcal X_{t+1},\dots, \mathcal X_{t+K}}p(\mathcal {X_{t+1}},\dots, \mathcal{X}_{t+K}|\mathcal {\hat X}_{t-J+1},\mathcal {\hat X}_{t-J+2},\dots, \mathcal {X}_t) \tag{1} X^t+1,,X^t+K=argmaxXt+1,,Xt+Kp(Xt+1,,Xt+KX^tJ+1,X^tJ+2,,Xt)(1)
对于降水预报,每个时间戳的观测都是一幅二维雷达回波图。若将地图划分为平铺的、不重叠的贴片,并将贴片内的像素作为其测量值。

LSTM不再赘述直接描述网络结构

3.2Convolutional LSTM

FC-LSTM在处理时空数据中的主要缺点是它在输入到状态和状态到状态转换中的全连接的使用,其中没有空间信息被编码。为了克服这个问题,进行针对性设计,

  • 输入是 X 1 , X 2 , … , X t \mathcal {X}_1,\mathcal {X}_2,\dots, \mathcal {X}_t X1,X2,,Xt
  • 单元输出 C 1 , C 2 , … , C t \mathcal C_1,\mathcal C_2,\dots, \mathcal C_t C1,C2,,Ct
  • 隐藏状态 H 1 , … , H t \mathcal H_1,\dots, \mathcal H_t H1,,Ht
  • ConvLSTM的门: i t , f t , o t i_t,f_t,o_t it,ft,ot

以上均为3D张量,其最后两个维度是空间维度

image-20240106183201762

ConvLSTM 通过其局域邻域的输入和过去状态来确定网格中某个单元的未来状态。通过在状态到状态和输入到状态转换中使用卷积算子轻松实现,如上图。ConvLSTM的关键方程如下面,*为卷积算子, ∘ \circ 为卷积算子

若将状态视为移动对象的隐式表示,则具有较大内核的ConvLSTM应该能够捕获更快的运动,而具有较小卷积核的可以捕获较慢的运动。此外,FC-LSTM可以看作所有特征均在一个单元格上的ConvLSTM。

为了确保状态具有与输入相同的行数和相同数量的列,在应用卷积操作之前需要padding。在这里,隐藏状态的embedding在可以将边界点视为使用外部世界的状态进行计算。

image-20240106191731428

3.3编码-预测结构

ConvLSTM可以作为更为复杂结构的模块。对于时空序列预测问题,使用下图所示的结构。两个网络、一个编码网络和一个预测网络。预测网络的初始状态和单元输出均是从编码网络的最后一个状态复制得到。这两个网络都是通过堆叠几个ConvLSTM层来形成的,由于预测目标具有与输入相同的位数,故将预测网

4. 文献解读

4.1 Introduction

对流降水临近预报一直是天气预报领域的一个重要问题。该任务的目标是准确、及时地预测局部区域在相对较短的时间内(例如0-6小时)的降雨强度。ROVER算法是领域现有的最先进算法。深度学习的最新进展,特别是循环神经网络 (RNN) 和长短期记忆 (LSTM) 模型,提供了一些关于如何解决这个问题的途径。[2]中提出的开创性的 LSTM 编码器-解码器框架通过训练时间级联的 LSTM。但以往的模型均采用的全连接LSTM(FC-LSTM)层没有考虑空间相关性。

在本文中,提出了一种用于降水临近预报的新型ConvLSTM网络。将降水临近预报表述为一个时空序列预测问题,可以在[2]中提出的通用序列到序列学习框架下解决。为了更好地模拟时空关系,将FC-LSTM的思想扩展到在输入-状态和状态-状态转换两者中具有卷积结构的COVLSTM。当对合成Moving-MNIST数据集和雷达回波数据集进行评估时,ConvLSTM模型始终优于FC-LSTM和最先进的操作ROVER算法。

4.2 创新点

  1. 将降水预报问题建模为时空序列预测问题,从而在[2]中提出的框架内解决。
  2. 根据FC-LSTM结构进行扩展,提出了ConvLSTM,该结构改善了FC-LSTM的缺点,通过其局部邻域的输入和过去状态来确定网格中某个单元的未来状态。
  3. 将ConvLSTM网络与一个合成MovingMNIST数据集中的FC-LSTM网络进行比较。建立了一个新的雷达回波数据集,并将该模型与基于几种常用降水预报指标的最先进的ROVER算法进行了比较。

4.3 实验过程

首先将ConvLSTM网络与一个合成MovingMNIST数据集中的FC-LSTM网络进行比较,以获得对模型的行为的一些基本理解。用不同的规格运行模型根据[3]中的层和内核大小,并研究一些“域外域”情况。为了验证模型在更具有挑战性的降水预报问题上的有效性,建立了一个新的雷达回波数据集,并将模型与基于几种常用降水预报指标的最先进的ROVER算法进行了比较。

在python环境下实现了该模型,并在配备了单个NVIDIA K20 GPU的计算机上运行所有实验。

4.3.1Moving-MNIST Dataset

数据预处理以及部分训练参数:使用类似[3]中描述的生成过程。数据集中所有数据实例长20帧(输入10帧,预测10帧),并包含在 64 × 64 64\times 64 64×64patch内的手写数字。移动数字从MNIST数据集中500位子集中随机选择。开始位置和速度方向都是随机选择的,速度振幅是随机选择的。该生成过程重复了15000次,得到了包含10000条训练序列、2000条验证序列和3000条测试序列的数据集。通过使用反向传播时间(BPTT)最小化交叉熵损失来训练所有LSTM模型,并且RMSProp,学习速率为10×3,衰减率为0.9。此外,在验证集上执行early-stop。

实验中使用的网络框架:对于FC-LSTM网络,使用了与[3]中具有两个2048节点的LSTM层的无条件未来预测器模型相同的结构。对于ConvLSTM网络,将patch size设置为4×4,这样每个64×64帧由16×16×16张量表示。用不同的层数测试模型的三种变体。

  • 1层网络包含一个包含256个隐藏状态的ConvLSTM层
  • 2层网络有两个ConvLSTM层,每个层有128个隐藏状态
  • 3层网络在三个ConvLSTM层中分别有128、64和64隐藏状态

所有输入到状态和状态到状态的内核大小为5×5

实验表明,ConvLSTM 网络的性能始终优于 FC-LSTM 网络,更深层的网络结构可以提供更好的结果

image-20240106203941577

上图为ConvLSTM 网络与 FC-LSTM 网络在 Moving-MNIST 数据集上的比较结果,即各算法在测试集上的平均交叉熵损失。‘-5x5’和‘-1x1’表示相应的状态到状态内核大小,即5×5或1×1。 “256”、“128”和“64”指的是 ConvLSTM 层中隐藏状态的数量。 ‘(5x5)’和‘(9x9)’表示输入到状态的内核大小。

可以看出ConvLSTM在参数更少的情况下,达到了更好的结果

4.3.2雷达回波数据集

所使用的雷达回波数据集是2011年至2013年在香港收集的3年天气雷达强度的子集。由于不是每天都下雨,预报目标是降水,选择前97个雨天形成数据集。

数据预处理:首先通过设置 P = Z − min ⁡ ( Z ) max ⁡ ( Z ) − min ⁡ ( Z ) P=\frac{Z-\min(Z)}{\max(Z)-\min(Z)} P=max(Z)min(Z)Zmin(Z)将强度值Z转换为灰度像素P,并在中心 330 × 330 330\times 330 330×330区域裁剪雷达图。之后将带有半径10的磁盘过滤器应用至 100 × 100 100\times 100 100×100范围内,并调整雷达图的大小。为了减少测量仪器带来的噪声,进一步去除了一些噪声区域的像素值,这些区域是通过将K-Means聚类方法应用于每月像素平均值来确定的。天气雷达数据每6分钟记录一次,因此每天有240帧。为了获得不相交的训练、测试和验证子集,将每个日序列划分为40个不重叠的帧块,并随机分配4个块进行训练,1块用于测试并且1块用于验证。数据实例是使用一个20帧宽的滑动窗口从这些块中分割出来的。因此,雷达回波数据集包含8148个训练序列,2037个测试序列和2037个验证序列,所有序列都有20帧长(输入5帧,预测15帧)。

训练以及模型参数设置

  • patch size设置为2,并训练一个包含64个隐藏状态和3×3个内核的2层ConvLSTM网络

  • 对于ROVER算法,在验证集上调整光流估计器的参数,并使用最佳参数报告测试结果。

    此外,还尝试了三种不同的Rover初始化方案:

    • ROVER 1计算最后两个观测帧的光流,然后进行半拉格朗日平流;
    • ROVER 2以最后两个流场的平均值初始化速度;
    • ROVER 3给出最后三个流场的加权平均值(权重分别为0.7、0.2和0.1)初始化
  • 此外,还训练了一个具有两个 2000 节点 LS TM层的 FC-LSTM 网络。

无论是 ConvLSTM 网络还是 FC-LSTM 网络都优化了 15 个预测的交叉

使用几种常用的降水预报指标,即雨量均方误差(雨量均方误差)、关键成功指数(CSI)、虚警率(Far)、检测概率(POD)以及相关性来评价这些方法。

降雨量 MSE 指标定义为预测降雨量与实际降雨量之间的平均平方误差。

三个技能分数定义为

  • C S I = h i t s h i t s + m i s s e s + f a l s e a l a r m s CSI=\frac{hits}{hits+misses+falsealarms} CSI=hits+misses+falsealarmshits

  • F A R = f a l s e a l a r m s h i t s + f a l s e a l a r m s FAR=\frac{falsealarms}{hits+falsealarms} FAR=hits+falsealarmsfalsealarms

  • P O D = h i t s h i t s + m i s s e s POD=\frac{hits}{hits+misses} POD=hits+misseshits

预测框架P与地面真框架T的相关性定义为:
∑ i , j P i , j T i . j ( ∑ i , j P i , j 2 ) ( ∑ i , j T i , j 2 ) + ϵ \frac{\sum_{i,j}P_{i,j}T_{i.j}}{\sqrt{(\sum_{i,j}P_{i,j}^2)(\sum_{i,j}T_{i,j}^2)+\epsilon}} (i,jPi,j2)(i,jTi,j2)+ϵ i,jPi,jTi.j
其中 ϵ = 1 0 − 9 \epsilon=10^{-9} ϵ=109

下图为比较不同模型 15 个预测步骤的平均得分

image-20240106205328229

下图为基于四种降水临近预报指标的不同模型随时间的比较结果

image-20240106205811162

ConvLSTM能够更准确地预测未来的降水等高线,特别是在边界上。虽然 ROVER2 可以给出比 ConvLSTM 更清晰的预测,但它会触发更多的虚假警报,而且通常比 ConvLSTM 更不精确。另外,ConvLSTM的模糊效应可能是由于任务本身的不确定性造成的。

4.4 结论

在这两个数据集上进行的实验结果得出以下结论:

  • ConvLSTM在处理时空相关性方面优于FC-LSTM.
  • 使得状态到状态卷积核的大小大于1对于捕获时空运动模式是必要的。
  • 更深层次的模型可以较少的参数产生更好的结果。
  • ConvLSTM在降水预报方面的性能优于ROVER。

三、基于pytorch实现ConvLSTM

1.实验内容

基于pytorch实现ConvLSTM并使用KTH数据集进行测试

2.实验结果

训练过程如下

Epochs[1/50]--batch[0/402]--Acc: 0.1562--loss: 1.7924
Epochs[1/50]--batch[50/402]--Acc: 0.4375--loss: 1.6179
Epochs[1/50]--batch[100/402]--Acc: 0.375--loss: 1.3734
Epochs[1/50]--batch[150/402]--Acc: 0.3438--loss: 1.2532
Epochs[1/50]--batch[200/402]--Acc: 0.4375--loss: 1.2269
Epochs[1/50]--batch[250/402]--Acc: 0.5625--loss: 0.925
Epochs[1/50]--batch[300/402]--Acc: 0.5938--loss: 0.8918
Epochs[1/50]--batch[350/402]--Acc: 0.5--loss: 1.085
Epochs[1/50]--Acc on val 0.5182
Epochs[30/50]--Acc on val 0.6551

3.实验数据集

本实验使用KTH数据集,共有六个类别,包括Boxing(拳击)、Handclapping(鼓掌)、Handwaving(挥手)、Jogging(慢跑)、Running(快跑)和Walking(行走)。共计600个视频文件。

3.1数据集处理

is_gray:是否转换为灰度图

frame_len以该长度对视频进行分割

transforms:进行图像增强

__init__:初始化操作

def load_avi_frames:数据载入;

  • 创建一个视频捕获对象,用于读取视频文件;
  • 循环读取视频帧,直到视频结束;
  • 检查是否成功读取到帧图像数据;
  • 将原始图片转换为灰度图,因为后续数据集用作分类所以转换为单通道的灰度图可以降低计算量;
  • 返回得到一个4维数组

def data_process:样本构建

  • 缓存预处理结果的修饰器
  • 循环遍历每个目录下的视频文件,并得到该目录下所有视频文件的名称
  • 开始遍历当前文件夹中的每个视频文件
  • 根据文件名获取对应的人物编号
  • 读取得到原始的视频数据;
  • 根据每个视频以固定长度进行采样构造样本,其中sub_frames的形状为[frame_len,120,160,channels]`;
  • 返回最后构造完成的样本数据。

def generate_batch:实现一个辅助函数来处理每个小批量样本的数据

  • 遍历小批量样本中的每个样本;
  • 循环对视频里的每一帧进行图像增强,其中frame的形状为[height, width, channels],在进行图像增强经过ToTensor()变换后形状会变成[channels,height,width]且每个像素值的范围会被缩放至
  • 将所有样本堆叠构造得到一个小批量标准数据,其形状为[batch_size, frame_len, channels, height, width]

def load_train_val_test_data:编码实现迭代器的构建

  • 返回data_process方法采样得到的原始样本数据;
  • 构建得到测试集对应的迭代器,其中generate_batch方法将作为参数传入到类DataLoader中进行使用;
  • 构建得到训练集和验证集对应的迭代器。
class KTHData(object):"""载入KTH数据集,下载地址:https://www.csc.kth.se/cvap/actions/ 一共包含6个zip压缩包"""DATA_DIR = os.path.join(DATA_HOME, 'kth')CATEGORIES = ["boxing", "handclapping", "handwaving", "jogging", "running", "walking"]TRAIN_PEOPLE_ID = [1, 2, 4, 5, 6, 7, 9, 11, 12, 15, 17, 18, 20, 21, 22, 23, 24]  # 25*0.7 = 17VAL_PEOPLE_ID = [3, 8, 10, 19, 25]  # 25*0.2 = 5TEST_PEOPLE_ID = [13, 14, 16]  # 25*0.1 = 3FILE_PATH = os.path.join(DATA_DIR, 'kth.pt')def __init__(self, frame_len=15,batch_size=4,is_sample_shuffle=True,is_gray=True,transforms=None):self.frame_len = frame_len  # 即time_step, 以FRAME_LEN为长度进行分割self.batch_size = batch_sizeself.is_sample_shuffle = is_sample_shuffleself.is_gray = is_grayself.transforms = transforms@staticmethoddef load_avi_frames(path=None, is_gray=False):"""用来读取每一个.avi格式的文件:param path::return:"""import cv2logging.info(f" ## 正在读取原始文件: {path}并划分数据")video = cv2.VideoCapture(path)frames = []while video.isOpened():ret, frame = video.read()  # frame: (120, 160, 3) <class 'numpy.ndarray'>if not ret:  # ret是一个布尔值,表示是否成功读取帧图像的数据,frame是读取到的帧图像数据。breakif is_gray:frame = Image.fromarray(frame)frame = frame.convert("L")frame = np.array(frame.getdata()).reshape((120, 160, 1))frames.append(frame)logging.info(f" ## 该视频一共有{len(frames)}帧")return np.array(frames, dtype=np.uint8)  # [n, height, width, channels]# 必须要转换成np.uint8类型,否则transforms.ToTensor()中的标准化会无效@process_cache(unique_key=["frame_len", "is_gray"])def data_process(self, file_path=None):train_data, val_data, test_data = [], [], []for label, dir_name in enumerate(self.CATEGORIES):  # 遍历每个文件夹video_dir = os.path.join(self.DATA_DIR, dir_name)  # 构造每个文件夹的路径video_names = os.listdir(video_dir)  # 列出当前文件夹的所有文件for name in video_names:  # 遍历当前文件夹中的每个视频people_id = int(name[6:8])  # 取人员编号video_path = os.path.join(video_dir, name)  # 得到文件的绝对路径frames = self.load_avi_frames(video_path, self.is_gray)  # 读取该文件s_idx, e_idx = 0, self.frame_lenwhile e_idx <= len(frames):  # 开始采样样本logging.info(f" ## 截取帧子序列 [{s_idx}:{e_idx}]")sub_frames = frames[s_idx:e_idx]  # [frame_len, 120, 160, channels]if people_id in self.TRAIN_PEOPLE_ID:train_data.append((sub_frames, label))elif people_id in self.VAL_PEOPLE_ID:val_data.append((sub_frames, label))elif people_id in self.TEST_PEOPLE_ID:test_data.append((sub_frames, label))else:raise ValueError(f"people id {people_id} 有误")s_idx, e_idx = e_idx, e_idx + self.frame_lenlogging.info(f" ## 原始数据划分完毕,训练集、验证集和测试集的数量分别为: "f"{len(train_data)}-{len(val_data)}-{len(test_data)}")data = {"train_data": train_data, "val_data": val_data, "test_data": test_data}return datadef generate_batch(self, data_batch):""":param data_batch::return: 每个batch的形状[batch_size, frame_len, channels, height, width][batch_size, ]"""batch_frames, batch_label = [], []for (frames, label) in data_batch:  # 开始对一个batch中的每一个样本进行处理。# frames的形状为 [frame_len, height, width,channels]if self.transforms is not None:# 遍历序列里的每一帧,frame的形状[height, width, channels]# 经过transforms.ToTensor()后的形状为[channels, height, width]frames = torch.stack([self.transforms(frame) for frame in frames],dim=0)  # [frame_len, channels, height, width]else:frames = torch.tensor(frames.transpose(0, 3, 1, 2))  # [frame_len, channels, height, width]logging.info(f"{frames.shape}")batch_frames.append(frames)  # [[frame_len, channels, height, width], [], []]batch_label.append(label)batch_frames = torch.stack(batch_frames, dim=0)  # [batch_size, frame_len, channels, height, width]batch_label = torch.tensor(batch_label, dtype=torch.long)return batch_frames, batch_labeldef load_train_val_test_data(self, is_train=False):data = self.data_process(file_path=self.FILE_PATH)if not is_train:test_data = data['test_data']test_iter = DataLoader(test_data, batch_size=self.batch_size,shuffle=True, collate_fn=self.generate_batch)logging.info(f" ## 测试集构建完毕,一共{len(test_data)}个样本")return test_itertrain_data, val_data = data['train_data'], data['val_data']train_iter = DataLoader(train_data, batch_size=self.batch_size,  # 构造DataLoadershuffle=self.is_sample_shuffle,collate_fn=self.generate_batch)val_iter = DataLoader(val_data, batch_size=self.batch_size,shuffle=False, collate_fn=self.generate_batch)logging.info(f" ## 训练集和验证集构建完毕,样本数量为{len(train_data)}:{len(val_data)}")return train_iter, val_iterdef show_example(self, file_path=None, row=3, col=5, begin_id=10):"""可视化:param file_path::param row::param col::param begin_id::return:"""import matplotlib.pyplot as pltif file_path is None:file_path = os.path.join(self.DATA_DIR, self.CATEGORIES[0])file_path = os.path.join(file_path, 'person01_boxing_d1_uncomp.avi')frames = self.load_avi_frames(file_path)fig, ax = plt.subplots(row, col)for i, axi in enumerate(ax.flat):  # , figsize=(18, 10)image = frames[i + begin_id]axi.set_xlabel(f'Frame{i + begin_id}')axi.imshow(image)axi.set(xticks=[], yticks=[])plt.tight_layout()plt.show()

4.模型及训练过程实现

参照论文实现ConvLSTM

ConvLSTMCell:记忆单元

class ConvLSTMCell(nn.Module):def __init__(self, in_channels, out_channels, kernel_size, bias):"""Initialize ConvLSTM cell.Parameters----------in_channels: int 输入特征图的通道数out_channels: int 输出特征图的通道数kernel_size: (int, int) 卷积核的宽和高bias: bool 是否使用偏置"""super(ConvLSTMCell, self).__init__()self.in_channels = in_channelsself.out_channels = out_channelsself.kernel_size = kernel_sizeself.padding = kernel_size[0] // 2, kernel_size[1] // 2# 需要强制进行padding以保证每次卷积后形状不发生变化# 根据之前第4.3.2节内容的介绍,在stride=1的情况下,padding = kernel_size // 2# 如:卷积核为3×3则需要padding=1即可# 在下面的卷积操作中stride使用的是默认值1self.bias = biasself.conv = nn.Conv2d(in_channels=self.in_channels + self.out_channels,out_channels=4 * self.out_channels,kernel_size=self.kernel_size,padding=self.padding,bias=self.bias)def forward(self, input_tensor, last_state):""":param input_tensor: 当前时刻的输入x_t, 形状为[batch_size, in_channels, height, width]:param last_state: 上一时刻的状态c_{t-1}和h_{t-1}, 形状均为 [batch_size, out_channels, height, width]:return:"""h_last, c_last = last_statecombined_input = torch.cat([input_tensor, h_last], dim=1)# [batch_size, in_channels+out_channels, height, width]combined_conv = self.conv(combined_input)  # [batch_size, 4 * out_channels, height, width]cc_i, cc_f, cc_o, cc_g = torch.split(combined_conv, self.out_channels, dim=1)# 分割得到每个门对应的卷积计算结果,形状均为 [batch_size, out_channels, height, width]i = torch.sigmoid(cc_i)f = torch.sigmoid(cc_f)o = torch.sigmoid(cc_o)g = torch.tanh(cc_g)c_next = f * c_last + i * g  # [batch_size, out_channels, height, width]h_next = o * torch.tanh(c_next)  # [batch_size, out_channels, height, width]return h_next, c_nextdef init_hidden(self, batch_size, image_size):"""初始化记忆单元的C和H:param batch_size::param image_size::return:"""height, width = image_sizereturn (torch.zeros(batch_size, self.out_channels, height, width, device=self.conv.weight.device),torch.zeros(batch_size, self.out_channels, height, width, device=self.conv.weight.device))

ConvLSTM:模型

class ConvLSTM(nn.Module):"""Parameters:in_channels: 输入特征图的通道数,为整型out_channels: 每一层输出特征图的通道数,可为整型也可以是列表;为整型时表示每一层的输出通道数均相等,为列表时则列表的长度必须等于num_layer例如 out_channels =[32,64,128] 表示3层ConvLSTM的输出特征图通道数分别为32、64和128,且此时的num_layer也必须为3kernel_size:  每一层中卷积核的长和宽,可以为一个tuple,如(3,3)表示每一层的卷积核窗口大小均为3x3;也可以是一个列表分别用来指定每一层卷积核的大小,如[(3,3),(5,5),(7,7)]表示3层卷积各种的窗口大小此时需要注意的是,如果为列表也报保证其长度等于num_layernum_layers: ConvLSTM堆叠的层数batch_first: 输入数据的第1个维度是否为批大小bias: 卷积中是否使用偏置return_all_layers: 是否返回每一层各个时刻的输出结果Input:A tensor of size B, T, C, H, W or T, B, C, H, W[Batch_size, Time_step, Channels, Height, Width]  or [Time_step, Batch_size, Channels, Height, Width]Output:当return_all_layers 为 True 时:layer_output_list: 每一层的输出结果,包含有num_layer个元素的列表,每个元素的形状为[batch_size, time_step, out_channels, height, width]last_states: 每一层最后一个时刻的输出结果,同样是包含有num_layer个元素的列表,列表中的每个元素均为一个包含有两个张量的列表,如last_states[-1][0]和last_states[-1][1]分别表示最后一层最后一个时刻的h和clayer_output_list[-1][:, -1] == last_states[-1][0]shape:  [Batch_size, Channels, Height, Width]当return_all_layers 为 False 时:layer_output_list: 最后一层每个时刻的输出,形状为 [batch_size, time_step, out_channels, height, width]last_states: 最后一层最后一个时刻的输出,形状为 [batch_size, out_channels, height, width]Example:>> model = ConvLSTM(in_channels=3,out_channels=2,kernel_size=(3, 3),num_layers=3,batch_first=True,bias=True,return_all_layers=True)x = torch.rand((1, 4, 3, 5, 5)) # [batch_size, time_step, channels, height, width]layer_output_list, last_states = model(x)"""def __init__(self, in_channels, out_channels, kernel_size, num_layers,batch_first=False, bias=True, return_all_layers=False):super(ConvLSTM, self).__init__()self._check_kernel_size_consistency(kernel_size)# 检查kernel_size是否符合上面说的取值情况# Make sure that both `kernel_size` and `out_channels` are lists having len == num_layerskernel_size = self._extend_for_multilayer(kernel_size, num_layers)out_channels = self._extend_for_multilayer(out_channels, num_layers)# 将kernel_size和out_channels扩展到多层时的情况if not len(kernel_size) == len(out_channels) == num_layers:raise ValueError('len(kernel_size) == len(out_channels) == num_layers 三者的值必须相等')self.in_channels = in_channelsself.out_channels = out_channelsself.kernel_size = kernel_sizeself.num_layers = num_layersself.batch_first = batch_firstself.bias = biasself.return_all_layers = return_all_layerscell_list = []for i in range(0, self.num_layers):  # 实例化每一层的ConvLSTM记忆单cur_in_channels = self.in_channels if i == 0 else self.out_channels[i - 1]# 当前层的输入通道数,除了第一层为self.in_channels之外,其它的均为上一层的输出通道数cell_list.append(ConvLSTMCell(in_channels=cur_in_channels, out_channels=self.out_channels[i],kernel_size=self.kernel_size[i], bias=self.bias))self.cell_list = nn.ModuleList(cell_list)# 必须要放到nn.ModuleList,否则在GPU上云运行时会报错张量不在同一个设备上的问题def forward(self, input_tensor, hidden_state=None):"""Parameters----------input_tensor: todo5-D Tensor: [Batch_size, Time_step, Channels, Height, Width]  or[Time_step, Batch_size, Channels, Height, Width]hidden_state: todoNone. todo implement statefulReturns-------last_state_list, layer_output"""if not self.batch_first:# 将(t, b, c, h, w) 转为 (b, t, c, h, w)input_tensor = input_tensor.permute(1, 0, 2, 3, 4)batch_size, time_step, _, height, width = input_tensor.size()# Implement stateful ConvLSTMif hidden_state is not None:raise NotImplementedError()else:# Since the init is done in forward. Can send image size herehidden_state = self._init_hidden(batch_size=batch_size,image_size=(height, width))layer_output_list = []  # 保存每一层的输出h,每个元素的形状为[batch_size, time_step, out_channels, height, width]last_state_list = []  # 保存每一层最后一个时刻的输出h和c,即[(h,c),(h,c)...]cur_layer_input = input_tensor  # [batch_size, time_step, in_channels, height, width]for layer_idx in range(self.num_layers):h, c = hidden_state[layer_idx]  # 开始遍历每一层的ConvLSTM记忆单元,并取对应的初始值# h 和 c 的形状均为[batch_size, out_channels, height, width]output_inner = []cur_layer_cell = self.cell_list[layer_idx]  # 为一个ConvLSTMCell记忆单元for t in range(time_step):  # 对于每一层的记忆单元,按照时间维度展开进行计算h, c = cur_layer_cell(input_tensor=cur_layer_input[:, t, :, :, :], last_state=[h, c])output_inner.append(h)  # 当前层,每个时刻的输出h, 形状为 [batch_size, out_channels, height, width]layer_output = torch.stack(output_inner, dim=1)  # [batch_size, time_step, out_channels, height, width]cur_layer_input = layer_output  # 当前层的输出h,作为下一层的输入layer_output_list.append(layer_output)last_state_list.append([h, c])if not self.return_all_layers:layer_output_list = layer_output_list[-1:]last_state_list = last_state_list[-1:]return layer_output_list, last_state_listdef _init_hidden(self, batch_size, image_size):"""init_states中的每个元素为一个tuple,包含C和H两个部分,如 [(h,c),(h,c)...]形状均为 [batch_size, out_channels, height, width]:param batch_size::param image_size::return:"""init_states = []for i in range(self.num_layers):  # 初始化每一层的初始值init_states.append(self.cell_list[i].init_hidden(batch_size, image_size))return init_states@staticmethoddef _check_kernel_size_consistency(kernel_size):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')@staticmethoddef _extend_for_multilayer(param, num_layers):if not isinstance(param, list):param = [param] * num_layersreturn param

ConvLSTMKTH:针对数据集进行改进

class ConvLSTMKTH(nn.Module):def __init__(self, config=None):super().__init__()self.conv_lstm = ConvLSTM(config.in_channels, config.out_channels,config.kernel_size, config.num_layers, config.batch_first)self.max_pool = nn.MaxPool2d(kernel_size=(5, 5), stride=2, padding=2)self.hidden_dim = (config.width * config.height) // 4 * self.conv_lstm.out_channels[-1]# 除以4是因为长宽均要除以stride, 使用self.conv_lstm.out_channels[-1]# 主要是为了兼容out_channels传入整型或列表的情况,因为传入整型的话在ConvLSTM的初始化方法中_extend_for_multilayer()# 方法也会将其扩充一个listself.classifier = nn.Sequential(nn.Flatten(),nn.Linear(self.hidden_dim, config.num_classes))def forward(self, x, labels=None):""":param x: [batch_size, time_step, channels, height, width]:param labels: [batch_size,]:return: logits: [batch_size, num_classes]"""_, layer_output = self.conv_lstm(x)# layer_output: [h:[batch_size, out_channels, height, width], c:[batch_size, out_channels, height, width]]pool_output = self.max_pool(layer_output[-1][0])  # [batch_size, out_channels, height//2, width//2]logits = self.classifier(pool_output)  # [batch_size, num_classes]if labels is not None:loss_fct = nn.CrossEntropyLoss(reduction='mean')loss = loss_fct(logits, labels)return loss, logitselse:return logits

ModelConfig:模型参数设置

class ModelConfig(object):def __init__(self):self.batch_size = 32self.epochs = 30self.learning_rate = 3e-3self.num_classes = 6self.in_channels = 1self.out_channels = [32,32]self.kernel_size = [(3, 3), (3, 3)]self.num_layers = len(self.out_channels)self.height = 60  # 原始大小为120self.width = 80  # 原始大小为160self.time_step = 15self.num_warmup_steps = 200self.model_save_path = 'model.pt'self.summary_writer_dir = "runs/model"self.device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')# 判断是否存在GPU设备,其中0表示指定第0块设备logging.info("### 将当前配置打印到日志文件中 ")for key, value in self.__dict__.items():logging.info(f"### {key} = {value}")

训练过程

def train(config):trans = transforms.Compose([transforms.ToTensor(),transforms.Resize((config.height, config.width)),transforms.RandomHorizontalFlip(0.5)])data_load = KTHData(frame_len=config.time_step,batch_size=config.batch_size,transforms=trans)train_iter, val_iter = data_load.load_train_val_test_data(is_train=True)model = ConvLSTMKTH(config)if os.path.exists(config.model_save_path):logging.info(f" # 载入模型{config.model_save_path}进行追加训练...")checkpoint = torch.load(config.model_save_path)model.load_state_dict(checkpoint)optimizer = torch.optim.Adam(model.parameters(), lr=config.learning_rate)writer = SummaryWriter(config.summary_writer_dir)model = model.to(config.device)max_test_acc = 0steps = len(train_iter) * config.epochsscheduler = optimization.get_cosine_schedule_with_warmup(optimizer, num_warmup_steps=config.num_warmup_steps,num_training_steps=steps, num_cycles=2)for epoch in range(config.epochs):for i, (x, y) in enumerate(train_iter):x, y = x.to(config.device), y.to(config.device)loss, logits = model(x, y)optimizer.zero_grad()loss.backward()optimizer.step()  # 执行梯度下降scheduler.step()if i % 50 == 0:acc = (logits.argmax(1) == y).float().mean()logging.info(f"Epochs[{epoch + 1}/{config.epochs}]--batch[{i}/{len(train_iter)}]"f"--Acc: {round(acc.item(), 4)}--loss: {round(loss.item(), 4)}")writer.add_scalar('Training/Accuracy', acc, scheduler.last_epoch)writer.add_scalar('Training/Loss', loss.item(), scheduler.last_epoch)test_acc = evaluate(val_iter, model, config.device)logging.info(f"Epochs[{epoch + 1}/{config.epochs}]--Acc on val {test_acc}")writer.add_scalar('Testing/Accuracy', test_acc, scheduler.last_epoch)if test_acc > max_test_acc:max_test_acc = test_accstate_dict = deepcopy(model.state_dict())torch.save(state_dict, config.model_save_path)

模型评估

def evaluate(data_iter, model, device):model.eval()with torch.no_grad():acc_sum, n = 0.0, 0for x, y in data_iter:x, y = x.to(device), y.to(device)logits = model(x)acc_sum += (logits.argmax(1) == y).float().sum().item()n += len(y)model.train()return acc_sum / n

使用模型进行预测

def inference(config, ):trans = transforms.Compose([transforms.ToTensor(),transforms.Resize((config.height, config.width)),transforms.RandomHorizontalFlip(0.5)])data_load = KTHData(frame_len=config.time_step,batch_size=config.batch_size,transforms=trans)test_iter = data_load.load_train_val_test_data(is_train=False)model = ConvLSTMKTH(config)model.to(config.device)model.eval()if os.path.exists(config.model_save_path):logging.info(f" # 载入模型进行推理……")checkpoint = torch.load(config.model_save_path)model.load_state_dict(checkpoint)else:raise ValueError(f" # 模型{config.model_save_path}不存在!")first_batch = next(iter(test_iter))with torch.no_grad():logits = model(first_batch[0].to(config.device))y_pred = logits.argmax(1)logging.info(f"真实标签为:{first_batch[1]}")logging.info(f"预测标签为:{y_pred}")

小结

本文主要介绍了自注意力机制以及ConvLSTM,在上周的学习中论文将二者结合,从而实现了时空序列预测领域中较好的结果。本文在KTH数据集上实现了该结构,根据数据集构造了迭代器以及进行了模型重构。最后在该环境下进行了模型有效性验证,得到了较好的结果。

下周将继续阅读序列预测相关论文

参考文献

[1] Shi, X.; Chen, Z.; Wang, H.; Yeung, D.-Y.; Wong, W.-K.;and Woo, W.-c. 2015. Convolutional lstm network: A machine learning approach for precipitation nowcasting. In NIPS 2015, 802–810.

[2]I. Sutskever, O. Vinyals, and Q. V. Le. Sequence to sequence learning with neural networks. In NIPS, pages 3104–3112, 2014.

[3]N. Srivastava, E. Mansimov, and R. Salakhutdinov. Unsupervised learning of video representations using lstms. In ICML, 2015.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/618617.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【自控实验】4. 数字仿真实验

本科课程实验报告&#xff0c;有太多公式和图片了&#xff0c;干脆直接转成图片了 仅分享和记录&#xff0c;不保证全对 使用matlab中的simulink进行仿真 实验内容 线性连续控制系统的数字仿真 根据开环传递函数G(S)的不同&#xff0c;完成两个线性连续控制系统的仿真。 …

Java-网络爬虫(三)

文章目录 前言一、爬虫的分类二、跳转页面的爬取三、网页去重四、综合案例1. 案例三 上篇&#xff1a;Java-网络爬虫(二) 前言 上篇文章介绍了 webMagic&#xff0c;通过一个简单的入门案例&#xff0c;对 webMagic 的核心对象和四大组件都做了简要的说明&#xff0c;以下内容…

突然又对 Go 感兴趣,GOPATH entry cannot start with shell metacharacter 错误

打发无聊时间&#xff0c;水文一篇&#xff5e; 事情是这样的&#xff0c;因为我们上架的渠道包基本是定制化混淆出包&#xff0c; 混淆出包有一个关键点就是指定映射文件&#xff0c;映射文件的内容有一部分是使用外部工具在打包前按照一定规律随机生成包名、类名&#xff0c…

用通俗易懂的方式讲解:如何用大语言模型构建一个知识问答系统

传统搜索系统基于关键字匹配&#xff0c;在面向&#xff1a;游戏攻略、技术图谱、知识库等业务场景时&#xff0c;缺少对用户问题理解和答案二次处理能力。 本文探索使用大语言模型&#xff08;Large Language Model, LLM&#xff09;&#xff0c;通过其对自然语言理解和生成的…

Python+Selenium做自动化测试(超详细整理)

一、项目介绍 目的 测试某官方网站登录功能模块可以正常使用【文末有配套视频教程和免费的资料文档领取】 用例 1.输入格式正确的用户名和正确的密码&#xff0c;验证是否登录成功&#xff1b; 2.输入格式正确的用户名和不正确的密码&#xff0c;验证是否登录失败&#xff…

apache、nginx、php 隐藏版本号

apache、nginx、php 隐藏版本号 针对的系统都是CentOS 1、没配置之前 1.1 Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.2.24 mod_wsgi/3.4 Python/2.7.5 1.2 Server: nginx/1.16.0 1.3 X-Powered-By&#xff1a;7.2.24 2、配置信息 不知道具体位置&#xff0c;可…

VirtualBox安装linuxmint-21.2虚拟机并配置网络

VirtualBox安装linuxmint-21.2虚拟机并配置网络 适用于在VirtualBox平台上安装linuxmint-21.2虚拟机。 1. 安装准备 1.1 安装平台 Windows 11 1.2. 软件信息 软件名称软件版本安装路径Oracle VM VirtualBoxVirtualBox-7.0.12-159484D:\softwareCentOS7CentOS-7.9.2009E:\…

STM32——OLED实验

1.OLED简介 OLED&#xff0c;即有机发光二极管 OLED引脚说明 引脚说明&#xff1a; 1、CS&#xff1a;OLED片选信号&#xff08;低电平有效&#xff09; 2、WR&#xff1a;向OLED写入数据 3、RD&#xff1a;向OLED读取数据 4、D[7:0]&#xff1a;8位双向数据线&#xff0c;有…

kubebuilder+code-generator开发k8s的controller

本文记录用kubebuilder和code-generator开发k8s的crd控制器。 概览 和k8s.io/code-generator类似&#xff0c;是一个码生成工具&#xff0c;用于为你的CRD生成kubernetes-style API实现。区别在于&#xff1a; Kubebuilder不会生成informers、listers、clientsets&#xff0c…

宠物服务新篇章:预约小程序带来的变革

随着科技的进步和互联网的普及&#xff0c;小程序已经成为了一种非常受欢迎的应用形式。对于宠物门店来说&#xff0c;开发一个预约小程序可以大大提高客户体验和管理效率。下面是一份宠物门店预约小程序的开发指南。 浏览器搜索乔拓云&#xff0c;登录乔拓云网后台&#xff0c…

大数据仓库开发规范示例

大数据仓库开发规范示例 一、前提概要二、数仓分层原则及定义2.1 数仓分层原则2.2 数仓分层定义 三、数仓公共开发规范3.1 分层调用规范3.2 数据类型规范3.3 数据冗余规范3.4 NULL字段处理规范3.5 公共字段规范3.6 数据表处理规范3.7 事实表划分规范 四、数仓各层开发规范4.1 分…

二十四、同域名下JSESSIONID重叠导致退出

同域名下JSESSIONID重叠导致退出 近期在开发项目的时候发现,如果同域名的情况下,如果把一个单页面无登录系统嵌套进入另外一个系统,那么会出现相互退出的问题。 思考解决方案 一、清除掉嵌套的系统的JSESSIONID,意思就是嵌套系统不设置JSESSIONID 1找寻出问题接口 在无痕…

【电源专题】案例:在EN脚加个电阻就能解决电源下电输出振荡?

案例背景:在某产品上使用一颗升压芯片发现下电输出波形振荡,但此产品并不是第一个使用此升压芯片的。早先此升压芯片使用在其他产品上没有报过这个异常。 分析方法:使用DEMO板,查看标准DEMO板无异常。将异常板卡上的参数与全部换到DEMO板上发现同样存在异常。 推测原因:…

Maya参考图的导入和层的应用

参考视频&#xff1a;08.参考图的导入和层的应用_哔哩哔哩_bilibili 前视图/右视图模式下导入图形 创建图层 锁定后可以避免图片位置的移动 前视图和右视图要根据参照物对齐 与模型保持一定距离&#xff0c;同时把该参照图添加到图层中 模型可以添加到图层2中

Qt应用开发(安卓篇)——Linux下Qt15.5.2配置Android

目录 一、前言 二、Qt安装 三&#xff1a;JDK安装 四&#xff1a;安装SDK&#xff0c;NDK 五、其他事项 六、新建项目 一、前言 看网上教程&#xff0c;多数是windows环境下的&#xff0c;配置也很简单&#xff0c;想不到自己配置的时候却遇到很多问题&#xff0c;传了一…

0_项目git地址——正点原子minifly与crazyflie

1、说明&#xff1a; 在每个专栏的第一篇文章&#xff0c;笔者都会贴出项目的git地址&#xff0c;方便后来者学习和复现&#xff1b; 下面介绍两个项目的官网资料和git地址&#xff0c;最后给出两者的对比&#xff1b; 2、正点原子minifly (1)minifly官网资料下载中心&#…

【深度学习每日小知识】Training Data 训练数据

训练数据是机器学习的基本组成部分&#xff0c;在模型的开发和性能中起着至关重要的作用。它是指用于训练机器学习算法的标记或注释数据集。以下是与训练数据相关的一些关键方面和注意事项。 Quantity 数量 训练数据的数量很重要&#xff0c;因为它会影响模型的泛化能力。通常…

Flink standalone集群部署配置

文章目录 简介软件依赖部署方案二、安装1.下载并解压2.ssh免密登录3.修改配置文件3.启动集群4.访问 Web UI 简介 Flink独立模式&#xff08;Standalone&#xff09;是部署 Flink 最基本也是最简单的方式&#xff1a;所需要的所有 Flink 组件&#xff0c; 都只是操作系统上运行…

Python--装饰器

在 Python 中&#xff0c;装饰器是一种特殊类型的函数&#xff0c;它们用于修改或增强其他函数或方法的行为。装饰器本质上是一个函数&#xff0c;它接受一个函数作为参数&#xff0c;并返回一个新的函数。使用装饰器可以在不修改原函数代码的前提下&#xff0c;给函数添加新的…

Linux 网络设置与基础服务

一 配置网络设置 主机名 hostname IP地址/netmask ifconfig &#xff1b; ip a 路由&#xff1a;默认网关 route -n DNS服务器 cat /etc/resolv.conf 网络连接状态 ss netstat 域名解析 ns…