时间序列预测实战(二十五)PyTorch实现Seq2Seq进行多元和单元预测(附代码+数据集+完整解析)

基础篇-认识时间序列常用数据集 最近在筹备写知识星球的文章,主要想把自己看完论文之后偶然产生的想法idea,以及实验验证过程记录下来。其实平时有挺多想法,但大多没有形成文字,或是跑跑代码就丢掉了,回过头想查资料就很麻烦,特别是知道自己曾经有过灵感,但就想不起来就很急。目前已经完成了一部分,本篇先分享出来,请大家给点意见。 阅读详情
AI摘要

本文介绍了基于Seq2Seq思想自研的时间序列模型,阐述了Seq2Seq的编码器 - 解码器原理,使用ETTh1.csv数据集进行实战。给出参数讲解和完整代码,展示了训练模型和预测结果,包括未知数据、测试集、CSV文件生成及模型拟合效果的可视化,最后推荐了时间序列专栏。

一、本文介绍

本文给大家带来的时间序列模型是Seq2Seq,这个概念相信大家都不陌生了,网上的讲解已经满天飞了,但是本文给大家带来的是我在Seq2Seq思想上开发的一个模型和新的架构,架构前面的文章已经说过很多次了,其是专门为新手开发的,而且为了方便大家使用只定义了一个文件方便大家复制粘贴,架构功能包括:结果可视化、支持单元预测、多元预测、模型拟合效果检测、预测未知数据、以及滚动长期预测功能。Seq2Seq模型是一种处理序列数据的深度学习模型,广泛用于机器翻译、语音识别和文本摘要等任务(也能用于时间序列)。其核心思想是编码器-解码器。

需要注意得是本文的模型和结构均为自研,在Seq2Seq的思想上进行了一定的扩展。

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

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

预测功能效果展示(不是测试集是预测未知数据,所以图中没有对比的数据)->

损失截图(损失这里我先展示一个训练过程中的后面会自动生成损失图像)-> 

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

​

测试集状况->

这个结构是我发这么多基础模型里效果最好的,模型拟合和测试集表现均为最好。

目录

一、本文介绍

二、Seq2Seq思想原理

2.1 Seq2Seq的基本原理

2.1.1 编码器-解码器介绍

2.1.2 编码器-解码器结构图

三、数据集介绍

四、参数讲解 

五、完整代码

六、训练模型 

七、预测结果

7.1 预测未知数据效果图

7.2 测试集效果图 

7.3 CSV文件生成效果图 

7.4 检验模型拟合效果图

八、全文总结


二、Seq2Seq思想原理


2.1 Seq2Seq的基本原理

Seq2Seq(Sequence-to-Sequence)模型是一种处理序列数据的深度学习模型,广泛用于机器翻译、语音识别和文本摘要等任务。其核心思想是将一个序列(如一句话)转换成另一个序列,这两个序列的长度可以不同。

Seq2Seq模型主要包括以下几个机制和原理:

1. 编码器-解码器架构:Seq2Seq模型通常由两部分组成,编码器和解码器。编码器负责读取并理解输入序列,将其转换成一个固定长度的上下文向量(context vector)。解码器则利用这个上下文向量生成目标序列(其实最核心的就是这个,下面都是具体的应用了)。

2. 循环神经网络(RNN):在传统的Seq2Seq模型中,编码器和解码器通常是循环神经网络(如LSTM或GRU)。RNN可以处理不同长度的输入序列,并在其隐藏层保持序列的状态信息。

3.注意力机制(Attention):注意力机制是后来引入Seq2Seq模型的一项重要改进。它允许模型在生成每个目标词时“关注”输入序列的不同部分,从而提高了模型处理长句子时的效果和准确性。

4. 长短期记忆网络(LSTM)/门控循环单元(GRU):为了解决RNN中的长期依赖问题,LSTM和GRU这样的网络结构被引入。它们能更好地捕捉序列中的长期依赖关系。

个人总结:我个人觉得Seq2Seq模型就像是学习语言的人。首先通过“编码器”理解输入的句子,然后用“解码器”来表达新的句子。就像我们学外语时先理解一句话的意思,再用自己的语言表达出来。加上“注意力机制”,模型还能更聪明地关注输入句子中最重要的部分,就好比我们在听别人说话时会注意对方重点强调的内容。


2.1.1 编码器-解码器介绍

通过上面我们知道Seq2Seq的主要核心思想是编码器和解码器,Seq2Seq一开始被发明出来是用于一些本文处理的,但是本文是时间序列领域的文章,所以我主要讲解一下编码器-解码器在时间序列领域的应用(大家需要注意的是这里的编码器和解码器和Transformer当中的还不一样是有着根本的区别的)。Seq2Seq模型的编码器和解码器如下工作:

编码器在时间序列领域的作用是理解和编码输入序列的历史数据。

1. 处理时间序列输入:编码器接收时间序列数据,例如过去几天的股票价格或气温记录。这些数据通常是连续的数值。

2. 特征提取:通过RNN(本文是用的GRU,类似于LSTM和RNN以后也会单独出文章)网络,编码器可以捕捉时间序列的特征和内在模式。RNN通过其时间递归结构,有效地处理时间序列数据中的时序依赖关系。

3. 上下文向量:编码器输出一个上下文向量,该向量是输入时间序列的压缩表示,包含了对过去数据的理解和总结。

解码器在时间序列领域的作用是基于编码器提供的信息来预测未来的时间序列数据。

1. 初始状态和输入:解码器的初始状态通常由编码器的最终状态设置。解码器的第一个输入可能是序列的最后一个观测值或特殊标记(我设置为解码器的输入是编码器的最后一个输出,因为我觉得这个状态的过去信息是最足的)。

2. 逐步预测:在每个时间步,解码器基于当前状态和前一步的预测输出(或初始输入)来生成下一个时间点的预测值。

3. 迭代更新:解码器的输出用于更新其状态,并作为下一个时间步的输入。这个过程在生成整个预测序列期间重复进行。

4. 序列生成:这个过程持续进行,直到生成了预定长度的预测序列。

总结:在时间序列领域,Seq2Seq模型的编码器-解码器结构使其能够有效处理具有复杂时间依赖性的序列数据。编码器学习并压缩历史数据的关键信息,而解码器则利用这些信息来预测未来的趋势和模式。


2.1.2 编码器-解码器结构图

下面的图是我编码器-解码器的结构图,其中包含了编码器(Encoder)和解码器(Decoder)的具体实现细节(需要注意的是这个图和本文模型无关,这里举出来只是为了让大家对编码器和解码器的流程有一个更清晰的认知)。下面是对这个图示的详细解释:

编码器部分(左侧)

  • 嵌入层(Embed):这个层将输入的一热编码(one-hot vector)转换为嵌入向量。在此图中,嵌入层将每个输入token转换为一个256维的向量。

  • GRU层:门控循环单元(Gated Recurrent Unit,GRU)是RNN的一种变体,可以捕捉长期依赖关系,同时缓解了传统RNN的梯度消失问题。在此模型中,每个时间步的GRU层接收前一时间步的隐藏状态和当前时间步的嵌入向量,然后输出新的隐藏状态。

  • 时间步(t):时间步是序列中的位置指示,从t=−2开始,直至t=0,表示输入序列的处理进程。

  • 位置标记(Pos. = T):这可能表示当前处理的token位于输入序列的最后一个位置,意味着编码器即将完成对输入序列的处理。

解码器部分(右侧)

  • 嵌入层:解码器的嵌入层将一热编码的输出token转换为嵌入向量。

  • GRU层:解码器的GRU层接收来自上一时间步的隐藏状态和当前时间步的嵌入向量(或初始状态从编码器传来的上下文向量),然后输出新的隐藏状态。

  • 全连接层:在每个时间步,全连接层将GRU的输出转换为词汇表大小的向量,这个向量包含了输出序列中下一个token的概率分布。

  • 输出概率:这是解码器产生的,表示下一个可能输出token的概率分布。

  • SOS:这是“Start of Sequence”的缩写,表示解码器开始生成序列的信号。

  • 时间步(t):在解码器部分,时间步从t=1开始,直至t=T+1,其中T可能表示目标序列的长度。

整个过程是这样的:编码器读取输入序列的token,并逐个更新其隐藏状态。当编码器读取完所有的输入token后,最后的隐藏状态(ℎ0​)被传递到解码器作为其初始状态。解码器从一个特殊的SOS token开始,逐步生成输出序列的token。在每一步,解码器基于当前的隐藏状态和上一步产生的token,预测下一个token,直到生成EOS token,表示序列生成结束。

概念部分的就讲这么多,网上有很多概念理解的好博客,大家有兴趣都可以自己查找看看,本文是实战博客内容不多讲啦~ 


三、数据集介绍

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

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

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

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

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


四、参数讲解 


    parser.add_argument('-model', type=str, default='RNN', 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='M', help='[M, S, MS],多元预测多元,单元预测单元,多元预测单元')

    # 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=2)
    # 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输入的特征数不包含时间那一列!!!

8

featurestr[M, S, MS],多元预测多元,单元预测单元,多元预测单元
9lrfloat学习率大小

10

drop_out

float丢弃概率
11epochsint训练轮次

12

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

14

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

16

layer_numintlstm层数
17use_gpubool是否使用GPU

18

deviceintGPU编号
19trainbool是否进行训练

20

predictbool是否进行预测

21

inspect_fitbool是否进行检验模型
22lr_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.utils.data import DataLoader
from torch.utils.data import Dataset
from tqdm import tqdm
import torch.nn.functional as F

# 随机数种子
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.figure(figsize=(10, 5))
    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 RNNEncoder(nn.Module):
    def __init__(self, rnn_num_layers=1, input_feature_len=1, sequence_len=168, hidden_size=100, bidirectional=False):
        super().__init__()
        self.sequence_len = sequence_len
        self.hidden_size = hidden_size
        self.input_feature_len = input_feature_len
        self.num_layers = rnn_num_layers
        self.rnn_directions = 2 if bidirectional else 1
        self.gru = nn.GRU(
            num_layers=rnn_num_layers,
            input_size=input_feature_len,
            hidden_size=hidden_size,
            batch_first=True,
            bidirectional=bidirectional
        )

    def forward(self, input_seq):
        ht = torch.zeros(self.num_layers * self.rnn_directions, input_seq.size(0), self.hidden_size, device='cuda')
        if input_seq.ndim < 3:
            input_seq.unsqueeze_(2)
        gru_out, hidden = self.gru(input_seq, ht)
        if self.rnn_directions > 1:
            gru_out = gru_out.view(input_seq.size(0), self.sequence_len, self.rnn_directions, self.hidden_size)
            gru_out = torch.sum(gru_out, axis=2)
        return gru_out, hidden.squeeze(0)

class AttentionDecoderCell(nn.Module):
    def __init__(self, input_feature_len, out_put, sequence_len, hidden_size):
        super().__init__()
        # attention - inputs - (decoder_inputs, prev_hidden)
        self.attention_linear = nn.Linear(hidden_size + input_feature_len, sequence_len)
        # attention_combine - inputs - (decoder_inputs, attention * encoder_outputs)
        self.decoder_rnn_cell = nn.GRUCell(
            input_size=hidden_size,
            hidden_size=hidden_size,
        )
        self.out = nn.Linear(hidden_size, input_feature_len)

    def forward(self, encoder_output, prev_hidden, y):
        if prev_hidden.ndimension() == 3:
            prev_hidden = prev_hidden[-1]  # 保留最后一层的信息
        attention_input = torch.cat((prev_hidden, y), axis=1)
        attention_weights = F.softmax(self.attention_linear(attention_input), dim=-1).unsqueeze(1)
        attention_combine = torch.bmm(attention_weights, encoder_output).squeeze(1)
        rnn_hidden = self.decoder_rnn_cell(attention_combine, prev_hidden)
        output = self.out(rnn_hidden)
        return output, rnn_hidden


class EncoderDecoderWrapper(nn.Module):
    def __init__(self, input_size, output_size, hidden_size, num_layers, pred_len, window_size, teacher_forcing=0.3):
        super().__init__()
        self.encoder = RNNEncoder(num_layers, input_size, window_size, hidden_size)
        self.decoder_cell = AttentionDecoderCell(input_size, output_size,  window_size, hidden_size)
        self.output_size = output_size
        self.input_size = input_size
        self.pred_len = pred_len
        self.teacher_forcing = teacher_forcing
        self.linear = nn.Linear(input_size,output_size)


    def __call__(self, xb, yb=None):
        input_seq = xb
        encoder_output, encoder_hidden = self.encoder(input_seq)
        prev_hidden = encoder_hidden
        if torch.cuda.is_available():
            outputs = torch.zeros(self.pred_len, input_seq.size(0), self.input_size, device='cuda')
        else:
            outputs = torch.zeros(input_seq.size(0), self.output_size)
        y_prev = input_seq[:, -1, :]
        for i in range(self.pred_len):
            if (yb is not None) and (i > 0) and (torch.rand(1) < self.teacher_forcing):
                y_prev = yb[:, i].unsqueeze(1)
            rnn_output, prev_hidden = self.decoder_cell(encoder_output, prev_hidden, y_prev)
            y_prev = rnn_output
            outputs[i, :, :] = rnn_output
        outputs = outputs.permute(1, 0, 2)
        if self.output_size == 1:
            outputs = self.linear(outputs)
        return outputs



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])
    plt.figure(figsize=(10, 5))
    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.figure(figsize=(10, 5))
    # 绘制历史数据
    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)
    plt.figure(figsize=(10, 5))
    # 为预测数据生成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='RNN', 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='M', help='[M, S, MS],多元预测多元,单元预测单元,多元预测单元')

    # 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=128, help="隐藏层单元数")
    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 = EncoderDecoderWrapper(args.input_size, args.output_size, args.hidden_size, args.laryer_num, args.pre_len, args.window_size).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 预测未知数据效果图

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


7.2 测试集效果图 

测试集上的表现->

​


7.3 CSV文件生成效果图 

同时我也可以将输出结果用csv文件保存,但是功能还没有做,我在另一篇informer的文章里实习了这个功能大家如果有需要可以评论区留言,有时间我会移植过来,最近一直在搞图像领域的文章因为时间序列看的人还是太少了。

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

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

​

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

​


7.4 检验模型拟合效果图

检验模型拟合情况->

(从下面的图片可以看出模型拟合的情况还行,上一篇RNN的有一点过拟合了其实,则会个表现还是很正常的) 


八、全文总结

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

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

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

​

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

相关推荐

PyTorch实现Seq2Seq模型详解

Seq2Seq是一种循环神经网络的变种,是一种端到端的模型,包括 Encoder编码器和 Decoder解码器部分,在nlp任务中是一种重要的模型,在翻译,文本自动摘要生成和机器人自动问答以及一些回归预测任务上有着广泛的运用。Seq2Seq其实就是结构的网络,它的输入是一个序列,输出也是一个序列。在Encoder编码中,将序列转换成一个固定长度的向量,然后通过Decoder将该向量转换成我们想要的序列输出出来Encoder和Decoder一般都是RNN,通常为LSTM或者GRU。

xziyuan的博客 1846

时间序列中的6大类10种异常值处理方法(从根源上提高预测精度)

本文介绍的内容是在时间序列中异常值处理的方法,当我进行时间序列分析建模收集数据的过程中,往往都存在着一些特数据情况导致数据中存在着一些异常值,这些异常值往往会导致模型识别到不正常的模式从而无法准确的预测, (我试验过一个数据集清楚之后MAE的精度甚至能提升0.1左右),所以对于异常值的检测和处理对于时间序列来说是十分重要的,本篇文章需要配合我的另一篇文章配合阅读,另一篇文章介绍了如何进行异常值检测,所以本文篇文章下面来介绍当我们检测出异常值之后如何进行异常值处理。

Snu77的博客 1万+

【深度学习时间序列预测案例】零基础入门经典深度学习时间序列预测项目实战(附代码+数据集+原理介绍)

本专栏整理了《深度学习时间序列预测案例》,内包含了各种不同的基于深度学习模型的时间序列预测方法,例如LSTM、GRU、CNN(一维卷积、二维卷积)、LSTM-CNN、BiLSTM、Self-Attention、LSTM-Attention、Transformer等经典模型,包含项目原理以及源码,每一个项目实例都附带有完整的代码+数据集。

优快云 精品推荐 4万+

针对Informer中时序数据ETT油温数据分析

电力变压器油温数据,由国家电网提供,该数据集是来自中国同一个省的两个不同县的变压器数据,时间跨度为2年,原始数据每分钟记录一次(用 m 标记),每个数据集包含2年 * 365天 * 24小时 * 60分钟 = 1,051,200数据点。由于数据量的庞大,将数据粒度改为每15分钟1个数据点,记为ETTm1、ETTm2,或一个小时级别粒度的数据集变体使用(用 h标记),记为ETTh1、ETTh2。每个数据点均包含8维特征,包括数据点记录日期,预测目标值oil temperature和6个不同类型功率负载特征。

creeper的博客 1万+

LSTM实现多变量输入多步预测(Seq2Seq多步预测)时间序列预测(PyTorch版)

本专栏整理了《深度学习时间序列预测案例》,内包含了各种不同的基于深度学习模型的时间序列预测方法,例如LSTM、GRU、CNN(一维卷积、二维卷积)、LSTM-CNN、BiLSTM、Self-Attention、LSTM-Attention、Transformer等经典模型,包含项目原理以及源码,每一个项目实例都附带有完整的代码+数据集。

优快云 精品推荐 5228

LSTM实现多变量输入多步预测(直接多输出)时间序列预测(PyTorch版)

本专栏整理了《深度学习时间序列预测案例》,内包含了各种不同的基于深度学习模型的时间序列预测方法,例如LSTM、GRU、CNN(一维卷积、二维卷积)、LSTM-CNN、BiLSTM、Self-Attention、LSTM-Attention、Transformer等经典模型,💥💥💥包含项目原理以及源码,每一个项目实例都附带有完整的代码+数据集。

优快云 精品推荐 4886

博客摘录「 时间序列预测实战(二十五)PyTorch实现Seq2Seq进行多元和单元预测(附代码+数据集+完整解析)」2024年8月15日

这是解码器产生的,表示下一个可能输出token的概率分布。

biubiubiuKG的博客 11

ETDataset:收集了电力变压器数据集以支持对长序列预测问题的进一步研究

电力变压器数据集(ETDataset) 在此Github存储库中,我们提供了一些可用于长序列时间序列问题的数据集。 所有数据集均已进行预处理,并存储为.csv文件。 该数据集的范围从2016/07至2018/07,我们将尽快更新至2019年。 数据集列表(更新中) ETT-small :2个站点的2个电力变压器的数据,包括负载,油温。 ETT-large :39个站点的39个电力变压器的数据,包括负载,油温。 ETT-full :在39个站点的69个变电站的数据,包括负载,油温,位置,气候,需求。 如果使用此数据集,请引用Informer @ AAAI2021 Best Paper Award : @inproceedings{haoyietal-informer-2021, author = {Haoyi Zhou and Sha

时间序列预测实战(二十六)PyTorch实现Seq2Seq(LSTM)(结果可视化+自动保存结果)

本文给大家带来的时间序列模型是Seq2Seq,这个概念相信大家都不陌生了,网上的讲解已经满天飞了,但是本文给大家带来的是我在Seq2Seq思想上开发的一个模型和新的架构,其是专门为新手开发的,而且为了方便大家使用只定义了一个文件方便大家复制粘贴,架构功能包括:结果可视化、支持单元预测、多元预测、模型拟合效果检测、预测未知数据、以及滚动长期预测功能。Seq2Seq模型是一种处理序列数据的深度学习思想,广泛用于机器翻译、语音识别和文本摘要等任务(也能用于时间序列)。其核心思想是编码器-解码器。

Snu77的博客 1万+

FNet模型实现滚动长期预测并可视化结果,定制化数据集进行预测

本博客将介绍一种新的时间序列预测模型——FNet它通过使用傅里叶变换代替自注意力机制,旨在解决传统Transformer模型中的效率问题。FNet模型通过简单的线性变换,包括非参数化的傅里叶变换,来“混合”输入令牌,从而实现了快速且高效的处理方式。这种创新的方法在保持了相对较高的准确性的同时,显著提高了训练速度,特别是在处理长序列数据时更显优势。FNet的工作原理,并通过一个实战案例展示如何实现基于FNet的可视化结果和滚动长期预测。预测类型->多元预测、单元预测、长期预测。适用对象->受硬件所限制的时候,FNet是一种基于Transformer编码器架构的模型,通过替换自注意力子层为简单的线性变换,特别是傅里叶变换,来加速处理过程。FNet架构中的每一层由一个傅里叶混合子层和一个前馈子层组成(下图中的白色框)。傅里叶子层应用2D离散傅里叶变换(DFT)到其输入,一维DFT沿序列维度和隐藏维度。总结:FNet相对于传统的Transformer的改进其实就一点就是将注意力机制替换为傅里叶变换,所以其精度并没有提升(我觉得反而有下降,但是论文内相等,但是从我的实验角度结果分析精度是有下降的

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

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

PyTorch搭建LSTM实现多变量多步长时间序列预测(五):seq2seq

PyTorch搭建LSTM实现多变量多步长时间序列预测(五):seq2seq

KI的博客 2万+

Miniconda3迁移(附图详细过程)

服务器上目录占满,想要将conda迁移到其他目录下的操作方法,详细教程。

2301_77554343的博客 6945

PyTorch中实现LSTM多步长时间序列预测的几种方法总结(负荷预测)

PyTorch中实现LSTM多步长时间序列预测的几种方法总结(负荷预测)

KI的博客 2万+

岭回归的MATLAB步骤

MATLAB 实现框架,它涵盖了从数据导入到岭回归的步骤,包括计算共线性、使用 MAE、MSE、R²、MAPE 进行评价,以及绘制相应的可视化图表。

subject625Ruben的博客 1090

IEDA激活码失效?教你5分钟快速续期的方法

本文针对IEDA激活码失效问题,提供了一套从快速应急到深度排查的完整解决方案。文章不仅详解了5分钟快速续期与清理缓存等应急方法,更深入剖析了授权机制、离线激活流程,并指导如何构建可持续的授权管理策略,帮助开发者彻底解决激活困扰,确保开发环境稳定。

a0b1c2d3的专栏 274

基于Pytorch+Seq2Seq实现一元多步时间序列预测(保姆级教程以及对时间序列预测的相关思考)

序列到序列模型,也称encoder-decoder模型。其中Seq2Seq(序列到序列),强调目的,将输入序列转化为输出序列;Encoder-Decoder(编码器-解码器),强调模型实现的方法。1)Encoder部分:将输入序列转化为固定长度的上下文向量;在每个时间步,读入输入序列的一个元素,并更新隐藏层状态,并将最终的隐藏层状态或其变换作为上下文向量。2)Decoder部分:将上下文向量转化为输出序列;在每个时间步,基于上一时间步的输出、隐藏层状态、上下文向量来生成当前时间步的输出;

Ri-yao的博客 1913

电路板功能测试系统-FCT for PCBA

电路板功能测试系统用于电路板组件(PCBA)的功能测试(FCT),满足自动化大批量生产的要求。  ● 用户根据自己需要定义测试步骤,灵活方便  ● 适合工业自动化大批量生产应用  ● 系统测试精度高,稳定可靠   一、电路板功能测试系统概述 电路板测试过程通常由多个步骤组成,每单个步骤中需要对多个物理量(电压、电流、电阻、频率、波形等)进行测试。在每个步骤测试时

6814

【时序预测完整教程】以气温预测为例说明论文组成及PyTorch代码管道构建

时间序列预测论文组成及PyTorch代码管道构建详解。

全性保真,不以物累形。 7373

从理论到实践:利用FilterSolutions高效设计7阶椭圆低通滤波器

本文详细介绍了如何利用FilterSolutions软件高效设计7阶椭圆低通滤波器。文章从理论入手,对比了巴特沃斯、切比雪夫等经典滤波器特性,阐述了在要求高衰减陡度(如62dB抑制)和严格通带波纹(0.2dB)时,椭圆滤波器是最优选择。通过分步实战教程,指导读者完成从软件参数设置、性能验证到实际电路布局与调试的全过程,助力射频与高速数字信号处理工程师将理论转化为可靠设计。

jupi8的博客 559

133.在 Vue3 中使用 OpenLayers 实现画多边形、任意编辑、遮罩与剪切处理功能

《Vue3+OpenLayers实现多边形绘制与区域处理》摘要:本文介绍如何基于Vue3+OpenLayers+ol-ext实现地图多边形交互功能。核心功能包括:鼠标绘制任意多边形、拖动顶点编辑边界、遮罩显示(非选区变红)、选区剪切显示及功能重置。技术方案采用OpenLayers处理地图渲染,ol-ext实现高级遮罩/剪切效果,ElementPlus提供UI控件。该方案适用于城市规划、园区展示等需要区域标记的场景,完整代码展示了从初始化、绘制、编辑到特效处理的全流程实现。

小彭的博客 963

Seq2Seq的PyTorch实现

本文介绍一下如何使用 PyTorch 复现 Seq2Seq,实现简单的机器翻译应用,请先简单阅读论文Learning Phrase Representations using RNN Encoder–Decoder for Statistical Machine Translation(2014),了解清楚Seq2Seq结构是什么样的,之后再阅读本篇文章,可达到事半功倍的效果 我看了很多Seq2Seq网络结构图,感觉PyTorch官方提供的这个图是最好理解的 首先,从上面的图可以很明显的看出,Seq2Se

mathor的博客 5117

某城商银行生产容器云平台详细设计方案分享及经验总结

AS3模式的CIS容器部署在K8S集群内,作为F5的控制器,CIS容器通过对K8S集群内Configmap资源进行监控,将Configmap资源中的内容转化为F5的配置,将配置推送给 F5 ,CIS容器还通过K8S标准的API接口完成业务发现和实时监听集群内Pod的变化,一旦Pod发生变化,相应的更新会及时推送给F5,并完成更新。容器技术发展非常迅速,现在应用大多以容器作为承载介质,前两年在容器云上承载的应用类型主要是Web类,随着容器云领域的不断发展,越来越多的应用类型将迁移至容器云,包括有状态应用。

qq_61890005的博客 858

PyTorch 进行多步时间序列预测详细教程

PyTorch 进行多步时间序列预测详细教程

晚来天欲雪,能饮一杯无? 7496

GoAnywhere MFT 身份验证绕过漏洞(CVE-2024-0204)

GoAnywhere MFT 身份验证绕过漏洞(CVE-2024-0204)

weixin_43567873的博客 387

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

时间序列预测实战(二十六)PyTorch实现Seq2Seq-Attention机制优化多元与单元预测(附代码+数据集+完整解析)

本文详细介绍了如何使用PyTorch实现Seq2Seq-Attention机制优化时间序列预测任务,包括多元预测和单元预测。通过分析Attention机制的工作原理和数学本质,提供了完整的PyTorch实现代码,并展示了在电力负荷预测中的实战应用。文章还包含数据预处理、模型训练技巧和结果可视化等实用内容,帮助开发者快速掌握这一技术。

weixin_28736355的博客 313

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

上一篇: 时间序列预测实战(二十四)PyTorch实现RNN进行多元和单元预测(附代码+数据集+完整解析)
下一篇: 时间序列预测实战(二十六)PyTorch实现Seq2Seq(LSTM)(结果可视化+自动保存结果)
Snu77
Snu77 优质创作者: 人工智能技术领域 优质创作者: 人工智能技术领域
博客等级 码龄5年 11万+粉丝 934原创
评论 47
成就一亿技术人!
拼手气红包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、付费专栏及课程。

余额充值