时间序列预测实战(二十二)TCN-LSTM实现单元和多元长期预测(专为新手编写的自研架构)

时间序列模型SCINet(代码解析) 基于因果神经网络改进的SCINet长时间序列预测模型,在公共数据集上表现仅次于Nlinear 阅读详情
AI摘要

本文介绍利用个人编写的TCN-LSTM架构进行时间序列建模,涵盖TCN和LSTM框架原理,包括因果卷积、扩张卷积等。还介绍了数据集,给出参数讲解和完整代码,展示训练模型和预测结果,包括未知数据、测试集等效果图,检验模型拟合效果良好。


一、本文介绍

本篇文章给大家带来的是利用我个人编写的架构进行TCN-LSTM时间序列卷积进行时间序列建模(专门为了时间序列领域新人编写的架构,简单不同于市面上用GPT写的代码),包括结果可视化、支持单元预测、多元预测、模型拟合效果检测、预测未知数据、以及滚动长期预测功能。该结构是一个通用架构任何模型嵌入其中都可运行。下面来介绍一下TCN时间序列卷积的基本原理:时间序列卷积(Temporal Convolutional Network, TCN)通过一系列卷积层处理数据,每个层都能捕捉到不同时间范围内的模式。LSTM作为多元预测机制和单元预测机制的优点是可以处理序列数据中的长期依赖关系,从而可以捕捉到数据中的复杂模式和规律。它可以自适应地学习和调整模型参数,从而提高模型的预测性能和泛化能力。

  专栏目录:时间序列预测目录:深度学习、机器学习、融合模型、创新模型实战案例

专栏: 时间序列预测专栏:基础知识+数据分析+机器学习+深度学习+Transformer+创新模型

预测功能效果展示(不是测试集是预测未知数据)->

​

损失截图(损失绘图功能已补上运行即可显示)-> 

根据损失来看模型的拟合效果还是很好的,但后面还是做了检验模型拟合效果的功能让大家真正的评估模型的效果。

检验模型拟合情况->

(从下面的图片可以看出模型拟合的情况很好,同时比上一篇博客单独用TCN的效果要好) 

目录

一、本文介绍

二、TCN的框架原理

2.1 TCN的主要思想

2.1.1 因果卷积

2.1.2  扩张卷积

2.1.3 无便宜填充

2.2 LSTM的主要思想 

2.2.1 忘记门

2.2.2 输入门

2.2.3 输出门

三、数据集介绍

四、参数讲解 

五、完整代码

六、训练模型 

七、预测结果

7.1 预测未知数据效果图

7.2 测试集效果图 

7.3 CSV文件生成效果图 

7.4 检验模型拟合效果图

八、全文总结


二、TCN的框架原理

2.1 TCN的主要思想

TCN,即Temporal Convolutional Network,是一种专门用于时间序列数据处理的神经网络架构。TCN的关键特点包括:

1. 因果卷积:TCN使用因果卷积来确保在预测未来值时,只会使用当前和过去的信息,而不会出现信息泄露。

2. 扩张卷积:通过扩张卷积,TCN可以在不丢失时间分辨率的情况下增加感受野(即模型可以观察到的历史信息范围)。扩张卷积通过间隔地应用卷积核来实现。

3. 无偏移填充:为了保持输出的时间长度与输入相同,TCN在卷积操作前使用了一种特殊的填充方式。

下图是时间序列卷积的示意图

下面我来分别介绍这几种机制->


2.1.1 因果卷积

什么是因果卷积?首先我们来确定这个问题

  • 因果关系意味着输出序列中的元素只能依赖于输入序列中它前面的元素。
  • 为了确保输出张量与输入张量具有相同的长度,我们需要进行零填充。
  • 如果我们只用零填充输入张量的左侧,那么因果卷积是有保证的。
  • x_{4}^{'}在下图中是通过组合生成的x_{2},x_{3}​,x_{4}确保不会泄露信息。

此操作生成x_{5}^{'}和x_{6}^{'}它们是无关的,应该在将输出传递到下一层之前删除。

TCN有两个基本原则:

  • 序列的输入和输出长度保持不变。
  • 过去不能有任何泄漏。

为了达到第一点,TCN利用了第二点,TCN利用了因果卷积。1D FCN ( Fully Convolutional Network)


2.1.2 扩张卷积

扩张卷积的工作原理

  1. 增加感受野:在标准的卷积中,卷积核覆盖的区域是连续的。扩张卷积通过在卷积核的各个元素之间插入空格(称为“扩张”)来扩大其覆盖区域。例如,一个扩张率为2的3x3卷积核实际上会覆盖一个5x5的区域,但只使用9个权重。

  2. 保持时间分辨率:与池化操作不同,扩张卷积不会减少数据的时间维度。这意味着输出数据在时间上的分辨率保持不变,这对于时间序列分析和音频处理等领域至关重要。

  3. 间隔应用:扩张卷积通过间隔地应用卷积核来实现其效果。这种间隔方式意味着卷积核可以跨越更大的区域,而不是只聚焦于紧邻的输入单元。

扩张卷积在TCN中的应用

在时间卷积网络(TCN)中,扩张卷积允许模型有效地处理长时间序列。通过增大感受野,TCN可以捕捉到更长范围内的依赖关系,这对于许多序列预测任务(如语音识别、自然语言处理等)非常有用。此外,扩张卷积的使用使得TCN在处理长序列时计算效率更高,因为它避免了重复计算和不必要的参数增加。


2.1.3 无便宜填充

无偏移填充在时间卷积网络(TCN)中的使用是为了保持输出序列的时间长度与输入序列相同,从而允许模型在处理序列数据时保持时间对齐。在传统的卷积操作中,通常会因为卷积核覆盖的范围而导致输出序列的长度减少。为了解决这个问题,TCN采用了无偏移填充的策略。

无偏移填充(Zero Padding)的工作原理

  1. 填充操作:无偏移填充是指在输入序列的开始部分添加适量的零值,以使得卷积操作后的输出序列长度不减少。这种填充方式通常用于时间序列的处理中,确保经过卷积后,时间维度上的长度保持不变。

  2. 保持时间对齐:在处理时间序列数据时,保持时间上的对齐非常重要,尤其是在预测未来事件或者在时间序列上做分类任务时。无偏移填充确保了输入和输出在时间维度上保持对齐。

  3. 防止信息丢失:在没有填充的情况下,卷积操作可能会导致序列边缘的信息丢失,因为卷积核无法完全覆盖这些区域。通过使用无偏移填充,可以保证序列的每个部分都被卷积核等同地处理。

在TCN中的应用

在TCN中,无偏移填充通常与扩张卷积结合使用。通过在卷积层之前适当地添加零值,可以保证即使在使用较大扩张率的情况下,输出序列的长度也与输入序列的长度相同。这样不仅保留了时间序列数据的完整性,还允许模型捕捉到长距离的依赖关系,提高模型对时间序列数据的处理能力。


2.2 LSTM的主要思想 

LSTM通过刻意的设计来实现学习序列关系的同时,又能够避免长期依赖的问题。它的结构示意图如下所示。

在LSTM的结构示意图中,每一条黑线传输着一整个向量,从一个节点的输出到其他节点的输入。其中“+”号代表着运算操作(如矢量的和),而矩形代表着学习到的神经网络层。汇合在一起的线表示向量的连接,分叉的线表示内容被复制,然后分发到不同的位置。

如果上面的LSTM结构图你看着很难理解,但是其实LSTM的本质就是一个带有tanh激活函数的简单RNN,如下图所示。

LSTM这种结构的原理是引入一个称为细胞状态的连接。这个状态细胞用来存放想要的记忆的东西(对应简单LSTM结构中的h,只不过这里面不再只保存上一次状态了,而是通过网络学习存放那些有用的状态),同时在加入三个门,分别是。

        忘记门:决定什么时候将以前的状态忘记。

        输入门:决定什么时候将新的状态加进来。

        输出门:决定什么时候需要把状态和输入放在一起输出。

从字面上可以看出,由于三个门的操作,LSTM在状态的更新和状态是否要作为输入,全部交给了神经网络的训练机制来选择。

下面分别来介绍一下三个门的结构和作用->


2.2.1 忘记门

下图所示为忘记门的操作,忘记门决定模型会从细胞状态中丢弃什么信息。

忘记门会读取前一序列模型的输出h_{t-1}和当前模型的输入X_{t}来控制细胞状态中的每个数是否保留。

例如:在一个语言模型的例子中,假设细胞状态会包含当前主语的性别,于是根据这个状态便可以选择正确的代词。当我们看到新的主语时,应该把新的主语在记忆中更新。忘记们的功能就是先去记忆中找到一千那个旧的主语(并没有真正执行忘记的操作,只是找到而已。

在上图的LSTM的忘记门中,f_{t}代表忘记门的输出, α代表激活函数,W_{f}代表忘记门的权重,x_{t}代表当前模型的输入,h_{t-1}代表前一个序列模型的输出,b_{f}代表忘记门的偏置。


2.2.2 输入门

输入门可以分为两部分功能,一部分是找到那些需要更新的细胞状态。另一部分是把需要更新的信息更新到细胞状态里

在上面输入门的结构中,I_{t}代表要更新的细胞状态,α代表激活函数,x_{t}代表当前模型的输入,h_{t-1}代表前一个序列模型的输出,W_{t}代表计算I_{t}的权重,b_{t}代表计算I_{t}的偏置,_{}C_{t}代表使用tanh所创建的新细胞状态,W_{c}代表计算C_{t}的权重,b_{c}代表计算C_{t}的偏置。

忘记门找到了需要忘掉的信息f_{t}后,在将它与旧状态相乘,丢弃确定需要丢弃的信息。(如果需要丢弃对应位置权重设置为0),然后,将结果加上I_{t} * C_{t}使细胞状态获得新的信息。这样就完成了细胞状态的更新,如下图输入门的更新图所示。

再上图LSTM输入门的更新图中,B_{t}代表忘记门的输出结果, f_{t}代表忘记门的输出结果,B_{t-1}代表前一个序列模型的细胞状态,I_{t}代表要更新的细胞状态,\widetilde{C_{t}}代表使用tanh所创建的新细胞状态。


2.2.3 输出门

如下图LSTM的输出门结构图所示,在输出门中,通过一个激活函数层(实际使用的是Sigmoid激活函数)来确定哪个部分的信息将输出,接着把细胞状态通过tanh进行处理(得到一个在-1~1的值),并将它和Sigmoid门的输出相乘,得出最终想要输出的那个部分,例如,在语言模型中,假设已经输入了一个代词,便会计算出需要输出一个与该代词相关的信息(词向量)

在LSTM的输出门结构图中,O_{t}代表要输出的信息,α代表激活函数,W_{o}代表计算 O_{t}的权重,b_{o}代表计算O_{t}的偏置,B_{t}代表更新后的细胞状态,h_{t}代表当前序列模型的输出结果。


三、数据集介绍

本文是实战讲解文章,上面主要是简单讲解了一下网络结构比较具体的流程还是很复杂的涉及到很多的数学计算,下面我们来讲一讲模型的实战内容,第一部分是我利用的数据集。

本文我们用到的数据集是ETTh1.csv,该数据集是一个用于时间序列预测的电力负荷数据集,它是 ETTh 数据集系列中的一个。ETTh 数据集系列通常用于测试和评估时间序列预测模型。以下是 ETTh1.csv 数据集的一些内容:

数据内容:该数据集通常包含有关电力系统的多种变量,如电力负荷、价格、天气情况等。这些变量可以用于预测未来的电力需求或价格。

时间范围和分辨率:数据通常按小时或天记录,涵盖了数月或数年的时间跨度。具体的时间范围和分辨率可能会根据数据集的版本而异。 

以下是该数据集的部分截图->


四、参数讲解 

    parser.add_argument('-model', type=str, default='TCN-LSTM', help="模型持续更新")
    parser.add_argument('-window_size', type=int, default=126, help="时间窗口大小, window_size > pre_len")
    parser.add_argument('-pre_len', type=int, default=24, help="预测未来数据长度")
    # data
    parser.add_argument('-shuffle', action='store_true', default=True, help="是否打乱数据加载器中的数据顺序")
    parser.add_argument('-data_path', type=str, default='ETTh1-Test.csv', help="你的数据数据地址")
    parser.add_argument('-target', type=str, default='OT', help='你需要预测的特征列,这个值会最后保存在csv文件里')
    parser.add_argument('-input_size', type=int, default=7, help='你的特征个数不算时间那一列')
    parser.add_argument('-output_size', type=int, default=7, help='输出特征个数只有两种选择和你的输入特征一样即输入多少输出多少,另一种就是多元预测单元')
    parser.add_argument('-feature', type=str, default='M', help='[M, S, MS],多元预测多元,单元预测单元,多元预测单元')
    parser.add_argument('-model_dim', type=list, default=[64, 128, 256], help='这个地方是这个TCN卷积的关键部分,它代表了TCN的层数我这里输'
                                                                              '入list中包含三个元素那么我的TCN就是三层,这个根据你的数据复杂度来设置'
                                                                              '层数越多对应数据越复杂但是不要超过5层')

    # learning
    parser.add_argument('-lr', type=float, default=0.001, help="学习率")
    parser.add_argument('-drop_out', type=float, default=0.05, help="随机丢弃概率,防止过拟合")
    parser.add_argument('-epochs', type=int, default=20, help="训练轮次")
    parser.add_argument('-batch_size', type=int, default=16, help="批次大小")
    parser.add_argument('-save_path', type=str, default='models')

    # model
    parser.add_argument('-hidden_size', type=int, default=64, help="隐藏层单元数")
    parser.add_argument('-kernel_sizes', type=int, default=3)
    parser.add_argument('-laryer_num', type=int, default=1)
    # device
    parser.add_argument('-use_gpu', type=bool, default=True)
    parser.add_argument('-device', type=int, default=0, help="只设置最多支持单个gpu训练")

    # option
    parser.add_argument('-train', type=bool, default=True)
    parser.add_argument('-test', type=bool, default=True)
    parser.add_argument('-predict', type=bool, default=True)
    parser.add_argument('-inspect_fit', type=bool, default=True)
    parser.add_argument('-lr-scheduler', type=bool, default=True)

为了大家方便理解,文章中的参数设置我都用的中文,所以大家应该能够更好的理解。下面我在进行一遍讲解。 

参数名称参数类型参数讲解
1modelstr模型名称
2window_sizeint时间窗口大小,用多少条数据去预测未来的数据

3

pre_lenint预测多少条未来的数据
4shufflestore_true是否打乱输入dataloader中的数据,不是数据的顺序

5

data_pathstr你输入数据的地址
6targetstr你想要预测的特征列

7

input_sizeint输入的特征数不包含时间那一列!!!
8output_sizeint输出的特征数只可以是1或者是等于你输入的特征数

9

featurestr[M, S, MS],多元预测多元,单元预测单元,多元预测单元

10

model_dimlist这个地方是这个TCN卷积的关键部分,它代表了TCN的层数我这里输入list中包含三个元素那么我的TCN就是三层,这个根据你的数据复杂度来设置层数越多对应数据越复杂但是不要超过5层!!!!重点部分
11lrfloat学习率大小

12

drop_out

float丢弃概率
13epochsint训练轮次

14

batch_sizeint批次大小
15svae_pathstr模型的保存路径

16

hidden_sizeint隐藏层大小
17kernel_sizeint卷积核大小

18

layer_numintlstm层数
19use_gpubool是否使用GPU

20

deviceintGPU编号
21trainbool是否进行训练
22testbool是否进行测试

23

predictbool是否进行预测

24

inspect_fitbool是否进行检验模型
25lr_schdulerbool是否使用学习率计划


五、完整代码

复制粘贴到一个文件下并且按照上面的从参数讲解配置好参数即可运行~(极其适合新手和刚入门的读者)

import argparse
import time

import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from matplotlib import pyplot as plt
from torch.nn.utils import weight_norm
from torch.utils.data import DataLoader
from torch.utils.data import Dataset
from tqdm import tqdm

# 随机数种子
np.random.seed(0)


class StandardScaler():
    def __init__(self):
        self.mean = 0.
        self.std = 1.

    def fit(self, data):
        self.mean = data.mean(0)
        self.std = data.std(0)

    def transform(self, data):
        mean = torch.from_numpy(self.mean).type_as(data).to(data.device) if torch.is_tensor(data) else self.mean
        std = torch.from_numpy(self.std).type_as(data).to(data.device) if torch.is_tensor(data) else self.std
        return (data - mean) / std

    def inverse_transform(self, data):
        mean = torch.from_numpy(self.mean).type_as(data).to(data.device) if torch.is_tensor(data) else self.mean
        std = torch.from_numpy(self.std).type_as(data).to(data.device) if torch.is_tensor(data) else self.std
        if data.shape[-1] != mean.shape[-1]:
            mean = mean[-1:]
            std = std[-1:]
        return (data * std) + mean


def plot_loss_data(data):
    # 使用Matplotlib绘制线图
    plt.figure()

    plt.plot(data, marker='o')

    # 添加标题
    plt.title("loss results Plot")

    # 显示图例
    plt.legend(["Loss"])

    plt.show()


class TimeSeriesDataset(Dataset):
    def __init__(self, sequences):
        self.sequences = sequences

    def __len__(self):
        return len(self.sequences)

    def __getitem__(self, index):
        sequence, label = self.sequences[index]
        return torch.Tensor(sequence), torch.Tensor(label)


def create_inout_sequences(input_data, tw, pre_len, config):
    # 创建时间序列数据专用的数据分割器
    inout_seq = []
    L = len(input_data)
    for i in range(L - tw):
        train_seq = input_data[i:i + tw]
        if (i + tw + pre_len) > len(input_data):
            break
        if config.feature == 'MS':
            train_label = input_data[:, -1:][i + tw:i + tw + pre_len]
        else:
            train_label = input_data[i + tw:i + tw + pre_len]
        inout_seq.append((train_seq, train_label))
    return inout_seq


def calculate_mae(y_true, y_pred):
    # 平均绝对误差
    mae = np.mean(np.abs(y_true - y_pred))
    return mae


def create_dataloader(config, device):
    print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>创建数据加载器<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
    df = pd.read_csv(config.data_path)  # 填你自己的数据地址,自动选取你最后一列数据为特征列 # 添加你想要预测的特征列
    pre_len = config.pre_len  # 预测未来数据的长度
    train_window = config.window_size  # 观测窗口

    # 将特征列移到末尾
    target_data = df[[config.target]]
    df = df.drop(config.target, axis=1)
    df = pd.concat((df, target_data), axis=1)

    cols_data = df.columns[1:]
    df_data = df[cols_data]

    # 这里加一些数据的预处理, 最后需要的格式是pd.series
    true_data = df_data.values

    # 定义标准化优化器
    # 定义标准化优化器
    scaler = StandardScaler()
    scaler.fit(true_data)

    train_data = true_data[int(0.3 * len(true_data)):]
    valid_data = true_data[int(0.15 * len(true_data)):int(0.30 * len(true_data))]
    test_data = true_data[:int(0.15 * len(true_data))]
    print("训练集尺寸:", len(train_data), "测试集尺寸:", len(test_data), "验证集尺寸:", len(valid_data))

    # 进行标准化处理
    train_data_normalized = scaler.transform(train_data)
    test_data_normalized = scaler.transform(test_data)
    valid_data_normalized = scaler.transform(valid_data)

    # 转化为深度学习模型需要的类型Tensor
    train_data_normalized = torch.FloatTensor(train_data_normalized).to(device)
    test_data_normalized = torch.FloatTensor(test_data_normalized).to(device)
    valid_data_normalized = torch.FloatTensor(valid_data_normalized).to(device)

    # 定义训练器的的输入
    train_inout_seq = create_inout_sequences(train_data_normalized, train_window, pre_len, config)
    test_inout_seq = create_inout_sequences(test_data_normalized, train_window, pre_len, config)
    valid_inout_seq = create_inout_sequences(valid_data_normalized, train_window, pre_len, config)

    # 创建数据集
    train_dataset = TimeSeriesDataset(train_inout_seq)
    test_dataset = TimeSeriesDataset(test_inout_seq)
    valid_dataset = TimeSeriesDataset(valid_inout_seq)

    # 创建 DataLoader
    train_loader = DataLoader(train_dataset, batch_size=args.batch_size, shuffle=True, drop_last=True)
    test_loader = DataLoader(test_dataset, batch_size=args.batch_size, shuffle=False, drop_last=True)
    valid_loader = DataLoader(valid_dataset, batch_size=args.batch_size, shuffle=False, drop_last=True)

    print("通过滑动窗口共有训练集数据:", len(train_inout_seq), "转化为批次数据:", len(train_loader))
    print("通过滑动窗口共有测试集数据:", len(test_inout_seq), "转化为批次数据:", len(test_loader))
    print("通过滑动窗口共有验证集数据:", len(valid_inout_seq), "转化为批次数据:", len(valid_loader))
    print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>创建数据加载器完成<<<<<<<<<<<<<<<<<<<<<<<<<<<")
    return train_loader, test_loader, valid_loader, scaler


class Chomp1d(nn.Module):
    def __init__(self, chomp_size):
        super(Chomp1d, self).__init__()
        self.chomp_size = chomp_size

    def forward(self, x):
        return x[:, :, :-self.chomp_size].contiguous()


class TemporalBlock(nn.Module):
    def __init__(self, n_inputs, n_outputs, kernel_size, stride, dilation, padding, dropout=0.2):
        super(TemporalBlock, self).__init__()
        self.conv1 = weight_norm(nn.Conv1d(n_inputs, n_outputs, kernel_size,
                                           stride=stride, padding=padding, dilation=dilation))
        self.chomp1 = Chomp1d(padding)
        self.relu1 = nn.ReLU()
        self.dropout1 = nn.Dropout(dropout)

        self.conv2 = weight_norm(nn.Conv1d(n_outputs, n_outputs, kernel_size,
                                           stride=stride, padding=padding, dilation=dilation))
        self.chomp2 = Chomp1d(padding)
        self.relu2 = nn.ReLU()
        self.dropout2 = nn.Dropout(dropout)

        self.net = nn.Sequential(self.conv1, self.chomp1, self.relu1, self.dropout1,
                                 self.conv2, self.chomp2, self.relu2, self.dropout2)
        self.downsample = nn.Conv1d(n_inputs, n_outputs, 1) if n_inputs != n_outputs else None
        self.relu = nn.ReLU()
        self.init_weights()

    def init_weights(self):
        self.conv1.weight.data.normal_(0, 0.01)
        self.conv2.weight.data.normal_(0, 0.01)
        if self.downsample is not None:
            self.downsample.weight.data.normal_(0, 0.01)

    def forward(self, x):
        out = self.net(x)
        res = x if self.downsample is None else self.downsample(x)
        return self.relu(out + res)


class TemporalConvNet(nn.Module):
    def __init__(self, num_inputs, outputs, pre_len, num_channels, n_layers, kernel_size=2, dropout=0.2):
        super(TemporalConvNet, self).__init__()
        layers = []
        self.pre_len = pre_len
        self.n_layers = n_layers
        self.hidden_size = num_channels[-2]
        self.hidden = nn.Linear(num_channels[-1], num_channels[-2])
        self.relu = nn.ReLU()
        self.lstm = nn.LSTM(self.hidden_size, self.hidden_size, n_layers, bias=True,
                            batch_first=True)  # output (batch_size, obs_len, hidden_size)
        num_levels = len(num_channels)
        for i in range(num_levels):
            dilation_size = 2 ** i
            in_channels = num_inputs if i == 0 else num_channels[i - 1]
            out_channels = num_channels[i]
            layers += [TemporalBlock(in_channels, out_channels, kernel_size, stride=1, dilation=dilation_size,
                                     padding=(kernel_size - 1) * dilation_size, dropout=dropout)]

        self.network = nn.Sequential(*layers)
        self.linear = nn.Linear(num_channels[-2], outputs)

    def forward(self, x):
        x = x.permute(0, 2, 1)
        x = self.network(x)
        x = x.permute(0, 2, 1)

        batch_size, obs_len, features_size = x.shape  # (batch_size, obs_len, features_size)
        xconcat = self.hidden(x)  # (batch_size, obs_len, hidden_size)
        H = torch.zeros(batch_size, obs_len - 1, self.hidden_size).to(device)  # (batch_size, obs_len-1, hidden_size)
        ht = torch.zeros(self.n_layers, batch_size, self.hidden_size).to(
            device)  # (num_layers, batch_size, hidden_size)
        ct = ht.clone()
        for t in range(obs_len):
            xt = xconcat[:, t, :].view(batch_size, 1, -1)  # (batch_size, 1, hidden_size)
            out, (ht, ct) = self.lstm(xt, (ht, ct))  # ht size (num_layers, batch_size, hidden_size)
            htt = ht[-1, :, :]  # (batch_size, hidden_size)
            if t != obs_len - 1:
                H[:, t, :] = htt
        H = self.relu(H)  # (batch_size, obs_len-1, hidden_size)

        x = self.linear(H)
        return x[:, -self.pre_len:, :]


def train(model, args, scaler, device):
    start_time = time.time()  # 计算起始时间
    model = model
    loss_function = nn.MSELoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=0.005)
    epochs = args.epochs
    model.train()  # 训练模式
    results_loss = []
    for i in tqdm(range(epochs)):
        losss = []
        for seq, labels in train_loader:
            optimizer.zero_grad()

            y_pred = model(seq)

            single_loss = loss_function(y_pred, labels)

            single_loss.backward()

            optimizer.step()
            losss.append(single_loss.detach().cpu().numpy())
        tqdm.write(f"\t Epoch {i + 1} / {epochs}, Loss: {sum(losss) / len(losss)}")
        results_loss.append(sum(losss) / len(losss))


        torch.save(model.state_dict(), 'save_model.pth')
        time.sleep(0.1)

    # valid_loss = valid(model, args, scaler, valid_loader)
    # 尚未引入学习率计划后期补上
    # 保存模型

    print(f">>>>>>>>>>>>>>>>>>>>>>模型已保存,用时:{(time.time() - start_time) / 60:.4f} min<<<<<<<<<<<<<<<<<<")
    plot_loss_data(results_loss)


def valid(model, args, scaler, valid_loader):
    lstm_model = model
    # 加载模型进行预测
    lstm_model.load_state_dict(torch.load('save_model.pth'))
    lstm_model.eval()  # 评估模式
    losss = []

    for seq, labels in valid_loader:
        pred = lstm_model(seq)
        mae = calculate_mae(pred.detach().numpy().cpu(), np.array(labels.detach().cpu()))  # MAE误差计算绝对值(预测值  - 真实值)
        losss.append(mae)

    print("验证集误差MAE:", losss)
    return sum(losss) / len(losss)


def test(model, args, test_loader, scaler):
    # 加载模型进行预测
    losss = []
    model = model
    model.load_state_dict(torch.load('save_model.pth'))
    model.eval()  # 评估模式
    results = []
    labels = []
    for seq, label in test_loader:
        pred = model(seq)
        mae = calculate_mae(pred.detach().cpu().numpy(),
                            np.array(label.detach().cpu()))  # MAE误差计算绝对值(预测值  - 真实值)
        losss.append(mae)
        pred = pred[:, 0, :]
        label = label[:, 0, :]
        pred = scaler.inverse_transform(pred.detach().cpu().numpy())
        label = scaler.inverse_transform(label.detach().cpu().numpy())
        for i in range(len(pred)):
            results.append(pred[i][-1])
            labels.append(label[i][-1])

    print("测试集误差MAE:", losss)
    # 绘制历史数据
    plt.plot(labels, label='TrueValue')

    # 绘制预测数据
    # 注意这里预测数据的起始x坐标是历史数据的最后一个点的x坐标
    plt.plot(results, label='Prediction')

    # 添加标题和图例
    plt.title("test state")
    plt.legend()
    plt.show()


# 检验模型拟合情况
def inspect_model_fit(model, args, train_loader, scaler):
    model = model
    model.load_state_dict(torch.load('save_model.pth'))
    model.eval()  # 评估模式
    results = []
    labels = []

    for seq, label in train_loader:
        pred = model(seq)[:, 0, :]
        label = label[:, 0, :]
        pred = scaler.inverse_transform(pred.detach().cpu().numpy())
        label = scaler.inverse_transform(label.detach().cpu().numpy())
        for i in range(len(pred)):
            results.append(pred[i][-1])
            labels.append(label[i][-1])

    # 绘制历史数据
    plt.plot(labels, label='History')

    # 绘制预测数据
    # 注意这里预测数据的起始x坐标是历史数据的最后一个点的x坐标
    plt.plot(results, label='Prediction')

    # 添加标题和图例
    plt.title("inspect model fit state")
    plt.legend()
    plt.show()


def predict(model, args, device, scaler):
    # 预测未知数据的功能
    df = pd.read_csv(args.data_path)
    df = df.iloc[:, 1:][-args.window_size:].values  # 转换为nadarry
    pre_data = scaler.transform(df)
    tensor_pred = torch.FloatTensor(pre_data).to(device)
    tensor_pred = tensor_pred.unsqueeze(0)  # 单次预测 , 滚动预测功能暂未开发后期补上
    model = model
    model.load_state_dict(torch.load('save_model.pth'))
    model.eval()  # 评估模式

    pred = model(tensor_pred)[0]

    pred = scaler.inverse_transform(pred.detach().cpu().numpy())

    # 假设 df 和 pred 是你的历史和预测数据

    # 计算历史数据的长度
    history_length = len(df[:, -1])

    # 为历史数据生成x轴坐标
    history_x = range(history_length)

    # 为预测数据生成x轴坐标
    # 开始于历史数据的最后一个点的x坐标
    prediction_x = range(history_length - 1, history_length + len(pred[:, -1]) - 1)

    # 绘制历史数据
    plt.plot(history_x, df[:, -1], label='History')

    # 绘制预测数据
    # 注意这里预测数据的起始x坐标是历史数据的最后一个点的x坐标
    plt.plot(prediction_x, pred[:, -1], marker='o', label='Prediction')
    plt.axvline(history_length - 1, color='red')  # 在图像的x位置处画一条红色竖线
    # 添加标题和图例
    plt.title("History and Prediction")
    plt.legend()


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Time Series forecast')
    parser.add_argument('-model', type=str, default='TCN-LSTM', help="模型持续更新")
    parser.add_argument('-window_size', type=int, default=126, help="时间窗口大小, window_size > pre_len")
    parser.add_argument('-pre_len', type=int, default=24, help="预测未来数据长度")
    # data
    parser.add_argument('-shuffle', action='store_true', default=True, help="是否打乱数据加载器中的数据顺序")
    parser.add_argument('-data_path', type=str, default='ETTh1-Test.csv', help="你的数据数据地址")
    parser.add_argument('-target', type=str, default='OT', help='你需要预测的特征列,这个值会最后保存在csv文件里')
    parser.add_argument('-input_size', type=int, default=7, help='你的特征个数不算时间那一列')
    parser.add_argument('-feature', type=str, default='MS', help='[M, S, MS],多元预测多元,单元预测单元,多元预测单元')
    parser.add_argument('-model_dim', type=list, default=[64, 128, 256], help='这个地方是这个TCN卷积的关键部分,它代表了TCN的层数我这里输'
                                                                              '入list中包含三个元素那么我的TCN就是三层,这个根据你的数据复杂度来设置'
                                                                              '层数越多对应数据越复杂但是不要超过5层')

    # learning
    parser.add_argument('-lr', type=float, default=0.001, help="学习率")
    parser.add_argument('-drop_out', type=float, default=0.05, help="随机丢弃概率,防止过拟合")
    parser.add_argument('-epochs', type=int, default=20, help="训练轮次")
    parser.add_argument('-batch_size', type=int, default=16, help="批次大小")
    parser.add_argument('-save_path', type=str, default='models')

    # model
    parser.add_argument('-hidden_size', type=int, default=64, help="隐藏层单元数")
    parser.add_argument('-kernel_sizes', type=int, default=3)
    parser.add_argument('-laryer_num', type=int, default=1)
    # device
    parser.add_argument('-use_gpu', type=bool, default=True)
    parser.add_argument('-device', type=int, default=0, help="只设置最多支持单个gpu训练")

    # option
    parser.add_argument('-train', type=bool, default=True)
    parser.add_argument('-test', type=bool, default=True)
    parser.add_argument('-predict', type=bool, default=True)
    parser.add_argument('-inspect_fit', type=bool, default=True)
    parser.add_argument('-lr-scheduler', type=bool, default=True)
    args = parser.parse_args()

    if isinstance(args.device, int) and args.use_gpu:
        device = torch.device("cuda:" + f'{args.device}')
    else:
        device = torch.device("cpu")
    print("使用设备:", device)
    train_loader, test_loader, valid_loader, scaler = create_dataloader(args, device)

    if args.feature == 'MS' or args.feature == 'S':
        args.output_size = 1
    else:
        args.output_size = args.input_size

    # 实例化模型
    try:
        print(f">>>>>>>>>>>>>>>>>>>>>>>>>开始初始化{args.model}模型<<<<<<<<<<<<<<<<<<<<<<<<<<<")
        model = TemporalConvNet(args.input_size, args.output_size, args.pre_len, args.model_dim, args.laryer_num,
                                args.kernel_sizes).to(device)
        print(f">>>>>>>>>>>>>>>>>>>>>>>>>开始初始化{args.model}模型成功<<<<<<<<<<<<<<<<<<<<<<<<<<<")
    except:
        print(f">>>>>>>>>>>>>>>>>>>>>>>>>开始初始化{args.model}模型失败<<<<<<<<<<<<<<<<<<<<<<<<<<<")

    # 训练模型
    if args.train:
        print(f">>>>>>>>>>>>>>>>>>>>>>>>>开始{args.model}模型训练<<<<<<<<<<<<<<<<<<<<<<<<<<<")
        train(model, args, scaler, device)
    if args.test:
        print(f">>>>>>>>>>>>>>>>>>>>>>>>>开始{args.model}模型测试<<<<<<<<<<<<<<<<<<<<<<<<<<<")
        test(model, args, test_loader, scaler)
    if args.inspect_fit:
        print(f">>>>>>>>>>>>>>>>>>>>>>>>>开始检验{args.model}模型拟合情况<<<<<<<<<<<<<<<<<<<<<<<<<<<")
        inspect_model_fit(model, args, train_loader, scaler)
    if args.predict:
        print(f">>>>>>>>>>>>>>>>>>>>>>>>>预测未来{args.pre_len}条数据<<<<<<<<<<<<<<<<<<<<<<<<<<<")
        predict(model, args, device, scaler)
    plt.show()


六、训练模型 

我们配置好所有参数之后就可以开始训练模型了,根据我前面讲解的参数部分进行配置,不懂得可以评论区留言。


七、预测结果

7.1 预测未知数据效果图

TCN的预测效果图(这里我只预测了未来24个时间段的值为未来一天的预测值)->

7.2 测试集效果图 

测试集上的表现->

同时我也可以将输出结果用csv文件保存,但是功能还没有做,我在另一篇informer的文章里实习了这个功能大家如果有需要可以评论区留言,有时间我会移植过来。

7.3 CSV文件生成效果图 

另一篇文章链接->时间序列预测实战(十九)魔改Informer模型进行滚动长期预测(科研版本,结果可视化)

将滚动预测结果生成了csv文件方便大家对比和评估,以下是我生成的csv文件可以说是非常的直观。

 我们可以利用其进行画图从而评估结果-> 

7.4 检验模型拟合效果图

检验模型拟合情况->

(从下面的图片可以看出模型拟合的情况很好) 


八、全文总结

 到此本文的正式分享内容就结束了,在这里给大家推荐我的时间序列专栏,本专栏目前为新开的平均质量分98分,后期我会根据各种最新的前沿顶会进行论文复现,也会对一些老的模型进行补充,目前本专栏免费阅读(暂时,大家尽早关注不迷路~),如果大家觉得本文帮助到你了,订阅本专栏,关注后续更多的更新~

 专栏回顾: 时间序列预测专栏——持续复习各种顶会内容——科研必备

如果大家有不懂的也可以评论区留言一些报错什么的大家可以讨论讨论看到我也会给大家解答如何解决!最后希望大家工作顺利学业有成!

股票价格预测-LSTM-TCN-GBDT:使用四种算法(LSTM,TCN,GRU,GBDT)进行股票价格的预测和预测结果的检验。有四种算法(LSTM,TCN,GRU,GBDT)用于预测股价并检验预测结果 股票价格预测-LSTM-TCN-GBDT 使用四种算法(LSTM,TCN,GRU,GBDT)进行股票价格的预测和预测结果的检验。 立即下载

相关推荐

电力负荷与天气数据(负荷预测数据).csv

某地2012年到2015年的负荷数据,共106177条数据,采样频率15分钟1次。以及每日最高温度、最低温度、平均温度、相对湿度、降雨量等数据。已经整合在一起。可以直接拿来做负荷预测。

时间卷积网络-长短期记忆神经网络TCN-LSTM实现瓦斯浓度多输入单输出预测附matlab代码

瓦斯浓度预测是煤矿安全生产的重要环节。本文提出了一种基于时间卷积网络(TCN)和长短期记忆神经网络(LSTM)相结合的TCN-LSTM模型,用于瓦斯浓度多输入单输出预测。该模型充分利用了TCN在提取时间序列特征方面的优势,并结合了LSTM对长期依赖关系的建模能力,提高了预测精度。

m0_60703264的博客 1742

【Macbook-Latex安装部署教程】【Win-Texlive镜像下载】【简单易上手】MacTex+Vscode/Texstudio

该教程记录自己在mac()上安装部署Latex环境的经验,整个教程操作简单,配置要求少,易上手。教程分成三部分:Mactex下载与安装,Vscode插件下载与配置文件修改,Texstudio安装。

sunriseOvO的博客 2570

基于遗传算法优化TCN-LSTM开发预测研究(Python代码实现)

时间卷积网络( temporal convolutional network ,TCN)是一种新型的、可以对时间序列数据进行处理的神经网络架构,与传统的卷积神经网络相比,它可以更有效地提取时序数据的特征。图1为TCN模型的单个残差单元。同时和CNN不同TCN中的因果卷积不是双向,而是单向的,即模型中t时刻的输出只由前一层中t时刻及t时刻以前的输入来决定,这样避免了模型不会丢失掉历史信息及受到未来信息的影响,这也使得TCN模型变成了一种严格的时间约束模型。部分理论来源于网络,如有侵权请联系删除。

weixin_46039719的博客 2504

汽水音乐自动化薅羊毛教程:从零搭建稳定赚钱脚本

📖 前言:为什么要学习自动化脚本?在数字时代,时间就是金钱。汽水音乐等App通过观看广告奖励用户金币的模式,本质上是将我们的时间转化为收益。本教程将教你如何用Python自动化这个过程,让程序为你"打工",实现被动收入。通过本教程,你将学到:移动端自动化测试的核心技术Python在自动化领域的实际应用如何将重复性任务转化为自动化流程创造属于自己的"数字员工"第一章:环境准备与基础配置1.1 硬件要求Android手机一部(建议使用备用机)USB数据线。

weixin_44947827的博客 5819

华为Atlas200DK实战指南:从零搭建AI开发环境到人像语义分割模型部署

本文提供了一份详尽的华为Atlas 200DK AI开发环境搭建与模型部署实战指南。文章采用合设环境方案,从SD卡准备、系统镜像烧录、网络配置到必备工具安装,逐步引导开发者完成环境搭建。核心部分详细讲解了如何利用华为ATC工具将TensorFlow模型转换为昇腾专用格式,并最终成功编译和运行人像语义分割模型,实现实时人像识别效果。

weixin_29309213的博客 356

时间序列预测专栏目录 | 深度学习、机器学习、融合模型、创新模型实战案例(附代码+数据集+原理介绍)

给大家推荐一下我的时间序列预测专栏,本专栏平均质量分98分,而且本专栏目前免费阅读,其中涉及机器学习、深度学习、融合模型、个人创新模型、数据分析等一系列有关时间序列的专栏,其中的实战的案例不仅有简单的模型类似于机器学习的ARIMA、Xgboost也有复杂的类似于深度学习的TPA-LSTM,还有个人创新的模型堆叠CNN-GRU-LSTM,同时本专栏的实战案例后期会持续的进行更新,复现各种最新的时间序列预测模型。时间序列预测的初学者、时间序列预测的工作者、数据分析的初学者。

Snu77的博客 1万+

K230开发板实战:YOLOv8n模型部署全流程(附常见报错解决方案)

本文详细介绍了在K230开发板上部署YOLOv8n模型的完整流程,包括模型选型、配置文件优化和运行时调优。特别针对常见的'生成部署文件异常'等问题提供了实用解决方案,帮助开发者高效完成嵌入式AI模型的部署与优化。

g8f9d0s1a2的博客 1048

TCN-Transformer-LSTM多输入单输出回归预测(串联)MATLAB代码实现

多元时间序列预测是众多领域中的关键任务,如金融、能源、交通等。近年来,深度学习模型在这一领域取得了显著进展。本文深入探讨了一种结合时间卷积网络(TCN)、Transformer和长短期记忆网络(LSTM)的新颖架构,旨在提升多元回归预测的准确性和鲁棒性。该模型利用TCN捕获时间序列中的局部模式,Transformer学习序列间的长期依赖关系,并辅以LSTM对序列信息的进一步整合。本文详细阐述了模型架构、关键技术和训练过程,并通过实验验证了其在多个公开数据集上的优越性能,并与其他主流方法进行了对比分析。

matlab_dingdang的博客 957

AI预测彩票,使用chatgpt和lstm神经网络(文末附源码)

AI预测彩票,使用chatgpt和lstm神经网络(文末有彩蛋)

淘淘小窝 1万+

[Dify] 实现“多知识库切换”功能的最佳实践

知识库切换是提升AI问答系统精准度的关键技术。本文介绍了Dify平台实现多知识库切换的三种方式:静态配置、元数据过滤和动态切换,分析了各自的优缺点。最佳实践建议包括:合理分库设计、意图分类、主库优先检索等。文章还提供了示例工作流程和注意事项,帮助构建更智能、精准的知识驱动型问答系统。通过合理切换知识库,可显著提升回答相关性和系统性能。

技术小甜甜:传播可落地技术,帮助个人和企业降低解决真实场景问题的成本 442

TCN-Transformer+LSTM多变量回归预测(Matlab)

多元时间序列预测是众多领域中的关键任务,如金融、能源、交通等。近年来,深度学习模型在这一领域取得了显著进展。本文深入探讨了一种结合时间卷积网络(TCN)、Transformer和长短期记忆网络(LSTM)的新颖架构,旨在提升多元回归预测的准确性和鲁棒性。该模型利用TCN捕获时间序列中的局部模式,Transformer学习序列间的长期依赖关系,并辅以LSTM对序列信息的进一步整合。本文详细阐述了模型架构、关键技术和训练过程,并通过实验验证了其在多个公开数据集上的优越性能,并与其他主流方法进行了对比分析。

matlab_daizuo的博客 1458

微信小程序 - 完美解决“code“:“NO_AUTH“,“message“:“商户号该产品权限未开通,请前往商户平台>产品中心检查后重试“(微信小程序报错问题排查解决)

微信小程序NO_AUTH: 商户号该产品权限未开通,请前往商户平台>产品中心检查后重试?微信小程序提示商户号该产品权限未开通,请前往商户平台>产品中心检查后重试,微信小程序"code":"NO_AUTH"错误码怎么解决,报错信息如下:httpResponseBody[{"code":"NO_AUTH","message":"商户号该产品权限未开通,请前往商户平台>产品中心检查后重试"}],微信小程序支付问题,如何解决这个问题,虚拟支付商户号,虚拟支付商户号里面没有产品支付的页面,排查商户号没问题,商户号配置

🏆 前端领域优质创作者 4447

【Python时序预测系列】基于TCN-LSTM模型实现多变量时间序列预测(案例+源码)

基于TCN-LSTM模型实现多变量时间序列预测(案例+源码)

数据杂坛 1092

MATLAB计算各种复合干热指数,如SDHI、DHMI等

MATLAB编程实现各种复合干热指数

WW、forever的博客 5082

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

多输入多输出 | Matlab实现TCN-GRU时间卷积神经网络结合门控循环单元多输入多输出预测

近年来,随着大数据时代的到来和计算能力的提升,对复杂时间序列数据的预测需求日益增长。传统的预测模型,例如ARIMA和指数平滑法,在处理非线性、多变量以及长序列数据时往往力不从心。而深度学习模型,特别是循环神经网络(RNN)及其变体,由于其强大的非线性拟合能力和对时间序列的天然适应性,成为解决这类问题的有力工具。本文将深入探讨一种基于时间卷积网络(TCN)和门控循环单元(GRU)的混合模型,用于解决多输入多输出的时间序列预测问题,并分析其优势和局限性。

Matlab245的博客 1121

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

高创新 | CEEMDAN-VMD-GRU-Attention双重分解+门控循环单元+注意力机制多元时间序列预测

时间序列预测作为数据科学领域的核心课题,在金融、气象、交通、医疗等诸多领域具有广泛的应用前景。然而,现实世界中的时间序列往往呈现出非线性、非平稳性以及复杂的波动模式,这为精确预测带来了严峻挑战。传统的预测方法,如ARIMA模型,往往难以捕捉这些复杂特征。近年来,随着机器学习和深度学习技术的飞速发展,基于神经网络的时间序列预测方法展现出强大的潜力,尤其是在处理非线性和复杂模式方面。

Matlab_dashi的博客 683

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

上一篇: YOLOv8改进 | Conv篇 | AKConv轻量级架构下的高效检测(既轻量又提点)
下一篇: YOLOv8改进 | 检测头篇 | 给YOLOv8换个RT-DETR的检测头(重塑目标检测前沿技术)
Snu77
Snu77 优质创作者: 人工智能技术领域 优质创作者: 人工智能技术领域
博客等级 码龄5年 11万+粉丝 934原创
评论 87
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Snu77

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值