【时序预测】-TCN 实战 | 完整代码+解析

本项目结合《【时序预测05】-TCN (Temporal Convolutional Networks)》中关于TCN的基础知识,构建了一个 时间序列预测系统,使用 TCN(Temporal Convolutional Network)模型 来预测未来的数据趋势。这个系统从数据加载、模型定义、训练、验证、测试到最终预测都一应俱全,形成了一个完整的端到端预测流程。


🧠 TCN时间序列预测代码结构总览


✅ 1. 导入依赖包

加载所需的库,包括 PyTorch、NumPy、Pandas、绘图工具、标准化工具、命令行参数解析等。


🔄 2. 数据预处理

  • 使用 StandardScaler 对输入特征进行标准化处理(均值为0,方差为1)。
  • 保证特征量纲统一,提升模型训练效果。

📉 3. 绘图函数

  • plot_loss_data(data):用于可视化训练过程中的损失变化,判断模型是否收敛或过拟合。

📦 4. 时间序列数据集定义(TimeSeriesDataset 类)

  • 将时间序列数据划分为输入序列(X)和预测目标(Y)。
  • 支持设置时间窗口 window_size 和预测长度 pre_len
  • 可用于训练、验证、测试等多种场景。

🔁 5. 数据加载器函数(create_dataloader

  • 对原始数据集按比例切分成:
    • 测试集(前15%)
    • 验证集(15%-30%)
    • 训练集(后70%)
  • 将划分好的数据包装为 PyTorch 的 DataLoader,支持批次训练、是否打乱等配置。

🧱 6. TCN 构建模块

🔹6.1 模块( Chomp1d

🔹 6.2 残差块模块(TemporalBlock

  • 每层包含两个 Conv1D 卷积 + Chomp(修正padding)+ ReLU + Dropout。
  • 残差连接保证训练稳定性。

🔹 6.3 全部TCN网络结构(TemporalConvNet

  • 由多层 TemporalBlock 叠加而成,每层使用指数增长的空洞卷积(dilated convolution)。
  • 最后一层接全连接层用于输出。

🎯 7. 训练模块(train

  • 使用 MSELoss 损失函数和 Adam 优化器。
  • 迭代多个 epoch,保存最佳模型。
  • 每轮记录训练损失并可视化。

🧪 8. 验证模块(valid

  • 加载已保存的模型,在验证集上评估性能(主要用 MAE 作为指标)。
  • 输出验证集误差,辅助调参。

🧾 9. 测试模块(test

  • 在测试集上评估模型。
  • 模型预测结果反归一化后与真实值进行可视化对比。
  • 计算 MAE,用于衡量泛化能力。

🔍 10. 拟合情况检查(inspect_model_fit

  • 将训练集上的真实值与模型预测值进行可视化对比。
  • 检查模型是否存在过拟合或欠拟合现象。

🔮 11. 预测未来数据(predict

  • 读取最新一段历史数据(等于窗口大小)。
  • 利用训练好的模型预测未来 pre_len 条数据。
  • 可视化预测结果与历史序列的衔接。

🧰 12. 主函数入口(__main__

  • 使用 argparse 管理所有可配置参数(如模型类型、窗口长度、特征维度、训练轮数等)。
  • 自动选择训练、测试、预测等功能。
  • 初始化模型并根据配置执行各个模块。

需要我帮你出一版注释清晰的 Markdown 说明文档版,或者生成 PDF 吗?


🧠 总结一句话:

构建了一个基于 TCN 的时间序列预测系统,支持端到端训练 + 测试 + 预测流程,结构清晰,功能完整,适合科研或者生产中的序列数据预测任务(比如风速、电量、温度、流量等)。


代码解析

✅ 1. 导入依赖包

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)

这段代码定义了一个自己实现的 标准化类 StandardScaler,模仿的是 sklearn 里的 StandardScaler,用于对数据进行标准化(去均值、除标准差)和反标准化。它是为了在 PyTorch 中使用而设计的。

下面我给你详细解释每一部分代码的意思:


🔄 2. 数据预处理(StandardScaler 类)

🔧 类初始化:__init__

def __init__(self):
    self.mean = 0.
    self.std = 1.
  • 初始化两个变量:meanstd,分别表示均值和标准差。
  • 初始值设为 0 和 1,之后会在 fit 中被赋值。

📊 拟合数据:fit

def fit(self, data):
    self.mean = data.mean(0)
    self.std = data.std(0)
  • 对数据按列计算均值和标准差。
  • mean(0) 表示在 第 0 维(也就是样本维度) 上计算均值,即每列的均值。
  • 结果存到 self.meanself.std,后面标准化要用。

🔁 数据标准化:transform

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
  • 把数据标准化为:(data - mean) / std
  • 如果数据是 torch.Tensor 类型(不是 numpy),就把 meanstd 转为 tensor,放到同样的 device 上(如 GPU)。
  • 这是为了让它和 PyTorch 模型兼容,不报错。

🔄 反标准化:inverse_transform

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
  • 用于把标准化后的数据恢复到原始数值。
  • 执行的是:data * std + mean
  • 如果数据维度对不上(比如只预测了最后一个特征),就只取 mean[-1:]std[-1:] 这一个特征的均值和标准差。

    这在你做多维输入但只预测一维输出时非常有用,比如你用过去 3 个特征预测未来一个目标变量。


✅ 总结一句话

这个 StandardScaler 类:

  • fit(data):记录数据均值和标准差;
  • transform(data):把数据标准化;
  • inverse_transform(data):把数据反标准化(还原)。

📉 3. 绘制损失函数

函数 plot_loss_data(data) 是用来绘制损失(loss)曲线的,整体写得已经没问题了,逻辑很清晰👌。


✅ 优化建议版:

def plot_loss_data(data):
    plt.figure(figsize=(8, 5))  # 设置画布大小

    plt.plot(data, marker='o', color='blue', linewidth=2, label='Loss')

    plt.title("Loss Curve", fontsize=14)
    plt.xlabel("Epoch", fontsize=12)
    plt.ylabel("Loss", fontsize=12)

    plt.grid(True, linestyle='--', alpha=0.5)  # 添加网格线
    plt.legend()
    plt.tight_layout()
    plt.show()

📦 4. 时间序列数据集定义(TimeSeriesDataset 类)

这个 TimeSeriesDataset 类是基于 torch.utils.data.Dataset 封装的自定义时间序列数据集,非常标准,适合用于训练 LSTM、GRU、Transformer 等时间序列模型。下面我给你详细解读一下它的每一部分逻辑:


🔍 代码


class TimeSeriesDataset(Dataset):
    def __init__(self, sequences):
        self.sequences = sequences
  • sequences 是传入的数据,通常格式是一个列表,里面每个元素是 (sequence, label) 的二元组。
    • sequence 是模型的输入(比如过去几天的数据)
    • label 是要预测的目标(比如未来1天或多天的值)
    def __len__(self):
        return len(self.sequences)
  • 返回数据集的长度:样本的数量。
    def __getitem__(self, index):
        sequence, label = self.sequences[index]
        return torch.Tensor(sequence), torch.Tensor(label)
  • 获取某个索引位置的数据,返回时把 numpy 或列表数据转成 torch.Tensor
  • 这样可以直接用于 DataLoader

函数 create_inout_sequences

函数 create_inout_sequences 是用于生成时间序列数据的输入输出对(滑动窗口样本),整体结构已经是实战中非常常用的方式,适合构造训练用的 (X, y) 数据。


🧠 函数目的

把一个完整的 input_data(比如 shape 是 [时间步数, 特征数])划分为:

  • train_seq:输入序列,长度为 tw(time window)
  • train_label:预测目标,长度为 pre_len(预测步数)

最终形成一个 (train_seq, train_label) 的列表,供 Dataset 使用。


📄 参数说明

def create_inout_sequences(input_data, tw, pre_len, config):
  • input_data:原始的时间序列数据,通常是 numpy 数组或张量,shape 类似于 [时间, 特征数]
  • tw:输入时间窗口长度(模型看到多长的历史)
  • pre_len:要预测的未来步数
  • config.feature:控制预测方式:
    • "MS":Multivariate → Single:用多个特征预测目标值(通常只预测最后一个特征)
    • "S":Single → Single 或 Full → Full,直接预测未来所有特征

🔁 核心逻辑详解

inout_seq = []
L = len(input_data)
  • 初始化结果列表,L 是时间序列的总长度。
for i in range(L - tw):
    train_seq = input_data[i:i + tw]
  • 构造一个长度为 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]
  • MS 模式只取最后一列(假设是目标特征)作为 label
  • 否则保留所有特征作为预测目标(多特征输出)
    inout_seq.append((train_seq, train_label))
  • 添加到输出列表中

calculate_mae 函数

calculate_mae 函数计算的是 平均绝对误差(MAE),写法标准,逻辑清晰。


📌 函数解析

def calculate_mae(y_true, y_pred):
    # 平均绝对误差
    mae = np.mean(np.abs(y_true - y_pred))
    return mae
  • y_true:真实值,通常是 numpy 数组或 tensor
  • y_pred:预测值,类型和 shape 应该与 y_true 一致
  • np.abs(...):计算每个位置的绝对误差
  • np.mean(...):取平均,得出 MAE

🔁 5. 数据加载器函数(create_dataloader

create_dataloader 函数涵盖了数据加载、标准化、窗口构造、Tensor 转换、Dataset 封装、DataLoader 创建的整个流程,逻辑很清晰👍。下面我来帮你详细解读一下代码的各个部分,并指出几点小建议和优化点:


🔍 函数功能总结

该函数作用是:

  1. 从 CSV 文件中加载时间序列数据;
  2. 选定预测目标列并移动到最后;
  3. 按比例划分训练、验证、测试集;
  4. 使用 StandardScaler 进行标准化;
  5. 利用滑动窗口创建 (X, y) 输入输出对;
  6. 构建 PyTorch 的 DataLoader

🧩 代码解析

🔹 1. 数据读取和目标列设置

df = pd.read_csv(config.data_path)
target_data = df[[config.target]]
df = df.drop(config.target, axis=1)
df = pd.concat((df, target_data), axis=1)

✔️ 将目标列移到最后,方便预测和切片操作。

🔹 2. 切除第一列

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

⚠️ 注意:你这里默认切掉了 df.columns[0],可能是时间戳之类的。确保你想切的列确实不是特征。

如果你想保留所有特征,可以改成:

df_data = df

或者在 config 中加一个参数 drop_col = 'date' 控制是否丢掉特定列。


🔹 3. 划分数据集比例

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))]

✅ 这个划分方式是“先 test,再 valid,再 train”,适合时间序列回溯验证的顺序结构。


🔹 4. 标准化处理

scaler = StandardScaler()
scaler.fit(true_data)

你是对 全部数据 fit,然后在各自子集上 transform。这个做法没错,但如果你更注重实验严谨性,可以考虑只用训练集做 fit:

scaler.fit(train_data)

🔹 5. Tensor 转换 & 滑动窗口

# 转化为深度学习模型需要的类型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)

⚠️ 注意:你先把数据 .to(device),然后 create_inout_sequences()。但你函数里可能还会再转成 Tensor。

建议:

  • create_inout_sequences() 里面再转 Tensor;
  • 或者直接传 numpy 数据进去,再统一转换。

否则你可能在 StandardScaler.transform() 里面会遇到 device 的类型不匹配问题。


🔹 6. 创建 DataLoader

# 创建 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)

✅ 最终返回

return train_loader, test_loader, valid_loader, scaler

这里把 scaler 返回也非常好,后续可以用于 inverse_transform() 还原真实值做评估。


🧱 6. TCN 构建模块

🔹 6.1 模块( Chomp1d

你这个 Chomp1d 类是 时间卷积网络(TCN) 中的一个关键组件,用来“裁剪”卷积输出的末尾,确保 因果性(causality)。下面我来帮你讲清楚它在干什么、为什么这么做,以及如何用 👇


🔍 什么是 Chomp1d

在 TCN 中,我们会用 膨胀因果卷积(dilated causal conv) 来建模序列。但是使用 padding='same' 或手动填充后,卷积输出中会包含 未来的信息(尤其是在末尾)。

为了避免模型“偷看未来”,我们就需要在输出后 砍掉多余的未来步。这就是 Chomp1d 的作用。


📦 源码解析

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()
参数说明:
  • chomp_size你想要砍掉的时间步数量,通常等于你卷积所使用的 padding
  • x 的形状通常是 [batch_size, channels, sequence_length]
举个例子:

假设你做了一次 Conv1d 卷积,为了保持时间维度长度不变你用了 padding,比如:

nn.Conv1d(in_channels=16, out_channels=32, kernel_size=3, padding=2, dilation=2)

为了保持时序一致性,你就得:

model = nn.Sequential(
    nn.Conv1d(...),
    Chomp1d(chomp_size=padding)
)

这样就会把多出来的步数砍掉,避免模型获取未来信息。


✅ 总结一句话:

Chomp1d 是用在因果卷积后的裁剪操作,保证模型不会“提前知道未来”,从而保留时间序列的因果结构。


🔹 6.2 残差块模块(TemporalBlock

TemporalBlock 类是典型的 TCN 中的残差模块(Residual Block),写得非常标准,结构也清晰!下面我来帮你详细解析一下这个模块干了什么,以及为什么这么设计👇


🧱 模块功能简介

TemporalBlock时间卷积网络 TCN 的核心构件。它通过:

  • 两个带有膨胀卷积的层(conv + ReLU + Dropout)
  • Chomp1d 保证因果性
  • Residual Connection 实现残差连接
  • WeightNorm 稳定训练

组合成一个稳定、强大的序列建模单元。


🔍 逐步解析结构

1️⃣ 第一层卷积块
self.conv1 = weight_norm(nn.Conv1d(...))
self.chomp1 = Chomp1d(padding)
self.relu1 = nn.ReLU()
self.dropout1 = nn.Dropout(dropout)
  • weight_norm:加速训练、避免梯度消失(类似 BatchNorm)
  • Chomp1d(padding):裁掉 padding 带来的“未来信息”
  • Dropout:防止过拟合

2️⃣ 第二层卷积块(结构相同)
self.conv2 = weight_norm(nn.Conv1d(...))
self.chomp2 = Chomp1d(padding)
self.relu2 = nn.ReLU()
self.dropout2 = nn.Dropout(dropout)

输出同样保证了时序一致性,并加了非线性和正则。


3️⃣ 残差连接(跳跃连接)
self.downsample = nn.Conv1d(n_inputs, n_outputs, 1) if n_inputs != n_outputs else None
  • 如果输入维度 ≠ 输出维度,需要对输入做一个 1x1 conv 投影匹配维度
  • 残差连接提高了训练稳定性(类似 ResNet)

4️⃣ 参数初始化
self.conv1.weight.data.normal_(0, 0.01)

给权重初始化为正态分布,有助于模型早期收敛。


5️⃣ 前向传播逻辑
out = self.net(x)                   # 卷积堆叠部分
res = x if self.downsample is None else self.downsample(x)  # 残差支路
return self.relu(out + res)        # 相加 + 非线性激活

✅ 输入输出格式要求

  • 输入张量形状:[batch_size, channels, sequence_length]
  • 确保时间维度是在 最后一个维度(TCN 用的是 Conv1d

🔹 6.3 全部TCN网络结构(TemporalConvNet

这个 TemporalConvNet 结构设计得非常清晰、完整,已经是一个可用于时间序列预测任务的完整 TCN 模型了 💯。我来帮你详细解读一下它的每个部分,并指出几个关键点,确保你完全掌握 ✅


🧠 模型结构概览

这个类是 基于 Temporal Convolutional Network (TCN) 的深度学习模型,用于多步时间序列预测,输出未来 pre_len 步的值。


📦 初始化部分解析

class TemporalConvNet(nn.Module):
    def __init__(self, num_inputs, outputs, pre_len, num_channels, kernel_size=2, dropout=0.2):
  • num_inputs: 每个时间步的特征数
  • outputs: 输出维度(通常为特征维度 = 1)
  • pre_len: 预测多少个时间步(多步预测)
  • num_channels: 列表,例如 [32, 64, 128],表示每一层的输出通道数

构建多层 TCN 结构

for i in range(num_levels):
    dilation_size = 2 ** i
    ...
    layers += [TemporalBlock(...)]
  • 指数膨胀(dilation):让模型能看到更大的时间感受野,解决长期依赖问题。
  • 每一层都是一个 TemporalBlock,这个你已经提前定义好了。
  • kernel_size=2 时,叠 6 层即可看到 2^6 = 64 个时间步。

线性层用于多步预测

self.linear = nn.Linear(num_channels[-1], outputs)
  • 把 TCN 最后一层的每个时间步的通道数压缩为输出维度(一般是 1)

🔁 前向传播流程

def forward(self, x):
    x = x.permute(0, 2, 1)          # 转成 [B, C, L] 给 Conv1d
    x = self.network(x)             # 经过 TCN 层
    x = x.permute(0, 2, 1)          # 转回 [B, L, C]
    x = self.linear(x)              # 每个时间步做线性映射
    return x[:, -self.pre_len:, :]  # 截取最后 pre_len 步

这个非常关键:

  • 截取最后 pre_len 步做为预测输出,适配多步预测需求。
  • 如果你输入序列长度是 window_size,输出就是 [B, pre_len, outputs]

✅ 输入输出格式举例

假设:

window_size = 24
pre_len = 6
num_inputs = 5   # 5个特征
outputs = 1      # 预测目标只有1个值

则输入 x 形状为:

[B, 24, 5]  # Batch × 时间步 × 特征数

最终输出为:

[B, 6, 1]  # 预测未来6个时间步的目标变量

🎯 7. 训练模块(train

train() 函数已经可以直接用于 TCN 模型训练了 💪
我来帮你梳理代码逻辑、指出一些 可优化点,并告诉你如何完善它👇


✅ 现有代码结构说明

🔁 训练主循环

for i in tqdm(range(epochs)):
    losss = []
    for seq, labels in train_loader:
        ...
  • 使用 tqdm 做进度条,非常友好 👍
  • train_loader 在循环内迭代批次
  • y_pred = model(seq),这里可能缺了 seq = seq.to(device)labels = labels.to(device)
  • 使用 MSELoss,没问题,也可以 later 加 MAE 一起记录

📈 记录 Loss 曲线

results_loss.append(sum(losss) / len(losss))
plot_loss_data(results_loss)
  • 保存每轮的 loss 供画图用,OK
  • plot_loss_data() 在最后调用也不错,不过你也可以加 savefig() 存下来

💾 模型保存频率

torch.save(model.state_dict(), 'save_model.pth')

⚠️ 目前每一轮都会覆盖保存!你可以改成:

torch.save(model.state_dict(), f'model_epoch_{i+1}.pth')

或只在最后一轮保存,节省空间:

if i == epochs - 1:
    torch.save(model.state_dict(), 'final_model.pth')

🧪 8. 验证模块(valid

你的 valid() 函数已经非常清晰地实现了对验证集的评估,其中包括加载模型、切换评估模式、计算 MAE 并返回平均误差。这里有一些优化和建议可以使代码更加健壮和高效,特别是在使用 torchnumpy 时。


🛠️ 优化建议

1️⃣ 使用 torch.Tensor 而非 numpy

在 PyTorch 中,我们应该尽量避免在计算中频繁转换 Tensornumpy 之间的格式。可以考虑直接使用 torch 来计算 MAE,而不需要转到 numpy

mae = torch.mean(torch.abs(pred - labels))

2️⃣ 加载模型的部分

如果每次调用 valid() 都加载模型,你可能希望避免每次都加载。可以考虑在 train() 中做保存操作,或者使用检查点保存当前的最佳模型,而不每次都加载:

# 在训练时保存最佳模型(例如:每10轮检查验证集效果)
torch.save(model.state_dict(), f"best_model_epoch_{epoch}.pth")

然后在验证时:

lstm_model.load_state_dict(torch.load('best_model.pth'))

3️⃣ 使用 torch.no_grad() 来禁用梯度计算

在评估阶段,可以禁用梯度计算,这样可以节省内存和计算,提升效率:

with torch.no_grad():
    for seq, labels in valid_loader:
        pred = lstm_model(seq)
        mae = torch.mean(torch.abs(pred - labels))
        losss.append(mae.item())  # 使用 .item() 获取标量值

✨ 优化后的 valid() 函数

def valid(model, args, scaler, valid_loader):
    model.eval()  # 切换为评估模式
    losss = []
    
    # 禁用梯度计算
    with torch.no_grad():
        for seq, labels in valid_loader:
            seq = seq.to(device)
            labels = labels.to(device)

            pred = model(seq)
            mae = torch.mean(torch.abs(pred - labels))  # 使用torch直接计算MAE
            losss.append(mae.item())  # .item() 获取标量值

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

🧾 9. 测试模块(test

你的 test() 函数看起来功能已经完整,包含了加载模型、评估、计算 MAE、反标准化预测结果、绘图等流程。不过,还是有一些优化空间可以提升效率和可维护性。以下是我给出的一些建议:


🛠️ 优化建议

1️⃣ 计算 MAE 时直接使用 torch

类似于 valid() 函数,计算 MAE 时,最好尽量避免将 Tensor 转换为 numpy,直接使用 torch 进行计算会更高效:

mae = torch.mean(torch.abs(pred - label))

2️⃣ 使用 torch.no_grad() 禁用梯度计算

在评估阶段,我们不需要计算梯度,所以应该用 torch.no_grad() 来禁用梯度计算,避免占用不必要的内存:

with torch.no_grad():
    for seq, label in test_loader:
        # 预测步骤

3️⃣ 存储预测结果时不需要 detach().cpu().numpy()

在做反标准化时,你可以避免使用 detach().cpu().numpy(),而直接使用 predlabel。这样能减少不必要的内存转换,代码更加简洁。

4️⃣ MAE 汇总方式改进

你在每个 batch 后都使用了 mae,但最后 losss 是用来输出的。如果你希望计算的是平均 MAE,你应该直接累积 MAE 值,或者使用 torch 计算整体的 MAE。


✨ 优化后的 test() 函数

def test(model, args, test_loader, scaler, device):
    model.eval()  # 切换为评估模式
    losss = []
    results = []
    labels = []

    with torch.no_grad():  # 禁用梯度计算
        for seq, label in test_loader:
            seq = seq.to(device)
            label = label.to(device)

            pred = model(seq)
            mae = torch.mean(torch.abs(pred - label))  # 计算MAE
            losss.append(mae.item())  # .item() 获取标量值

            pred = pred[:, 0, :]
            label = label[:, 0, :]

            # 反标准化
            pred = scaler.inverse_transform(pred.cpu().numpy())
            label = scaler.inverse_transform(label.cpu().numpy())

            results.extend(pred[:, -1])  # 保存最后一个预测值
            labels.extend(label[:, -1])  # 保存最后一个真实值

    avg_mae = sum(losss) / len(losss)
    print(f"测试集误差 MAE: {avg_mae}")

    # 绘制真实值和预测值对比图
    plt.plot(labels, label='True Value')
    plt.plot(results, label='Prediction')
    plt.title("Test Results")
    plt.legend()
    plt.show()

    return avg_mae

✅ 关键优化:

  1. 使用 torch.no_grad() 来禁用梯度计算,减少内存占用。
  2. 计算 MAE 时使用 torch.mean(torch.abs())
  3. 使用 .item() 提取标量值,保持一致性。
  4. 反标准化部分简化了 detach().cpu().numpy() 调用。

💡 后续优化:

  • 可以考虑添加学习率调度器(scheduler)来优化模型。
  • 如果测试集比较大,可以加入批量处理进度提示。

这样调整后,代码的执行效率会更高,同时也会让代码逻辑更加简洁清晰。如果你需要进一步的帮助,或者想要优化其他部分,随时告诉我!

🔍 10. 拟合情况检查(inspect_model_fit

inspect_model_fit() 函数已经很好地展示了模型的拟合情况,通过反标准化并绘制历史数据与预测数据的对比。但可以进行一些优化和改进:


🛠️ 优化建议

1️⃣ torch.no_grad() 禁用梯度计算

在推断阶段,我们不需要梯度计算,因此应该使用 torch.no_grad() 来减少内存消耗并加速计算。

2️⃣ 使用 torch.mean() 计算 MAE(可选)

虽然在这个函数中没有计算 MAE,但如果想要进一步评估模型的表现,可以直接用 torch.mean() 来计算 MAE。

3️⃣ 改善代码结构

你可以将 predlabel 的反标准化步骤移到一个单独的函数中,提升代码可读性。


✨ 优化后的 inspect_model_fit() 函数

def inspect_model_fit(model, args, train_loader, scaler, device):
    model.eval()  # 切换为评估模式
    model.load_state_dict(torch.load('save_model.pth'))  # 加载模型

    results = []
    labels = []

    with torch.no_grad():  # 禁用梯度计算
        for seq, label in train_loader:
            seq = seq.to(device)
            label = label.to(device)

            pred = model(seq)[:, 0, :]
            label = label[:, 0, :]

            # 反标准化
            pred = scaler.inverse_transform(pred.cpu().numpy())
            label = scaler.inverse_transform(label.cpu().numpy())

            results.extend(pred[:, -1])  # 保存预测结果
            labels.extend(label[:, -1])  # 保存真实值

    # 绘制真实值和预测值的对比
    plt.plot(labels, label='History', linestyle='-', color='b')
    plt.plot(results, label='Prediction', linestyle='--', color='r')
    plt.title("Model Fit Inspection")
    plt.legend()
    plt.show()

✅ 关键优化:

  1. torch.no_grad():禁用梯度计算,节省内存,提升性能。
  2. 反标准化:将 predlabel 的反标准化步骤放在了循环内,确保它们都是在每个批次计算时被正确处理。
  3. 简洁的代码结构:整体结构保持简单,便于理解和调试。

📊 可视化结果

通过这个函数,你可以直接比较历史数据与预测结果的拟合情况,观察模型的预测能力。这对模型评估和调优很有帮助。如果你想进一步优化模型或评估其他指标,随时告诉我!

🔮 11. 预测未来数据(predict

你的 predict() 函数已经很好地实现了对未知数据的预测,并且绘制了历史数据与预测数据的对比图。但是,函数可以进行一些优化,提升性能和可读性。以下是我的建议:


🛠️ 优化建议

1️⃣ 使用 torch.no_grad() 禁用梯度计算

在推断阶段,我们不需要梯度计算,所以可以用 torch.no_grad() 来减少内存消耗。

2️⃣ model 重复赋值冗余

在函数内部,你两次为 model 赋值 (model = modelmodel.load_state_dict()),这并没有实质性的作用,移除这部分可以简化代码。

3️⃣ 可视化部分优化

你可以在绘制预测值时增加一点样式变化,例如预测数据使用不同的线型或颜色,提升可视化效果。

4️⃣ 滚动预测功能(可选)

如你所说,滚动预测功能未实现,但可以提前做好设计,未来方便扩展。


✨ 优化后的 predict() 函数

def predict(model, args, device, scaler):
    # 预测未知数据的功能
    df = pd.read_csv(args.data_path)
    df = df.iloc[:, 1:][-args.window_size:].values  # 转换为numpy array
    pre_data = scaler.transform(df)  # 标准化
    tensor_pred = torch.FloatTensor(pre_data).to(device)
    tensor_pred = tensor_pred.unsqueeze(0)  # 单次预测

    model.load_state_dict(torch.load('save_model.pth'))
    model.eval()  # 切换为评估模式

    # 禁用梯度计算
    with torch.no_grad():
        pred = model(tensor_pred)[0]

    # 反标准化
    pred = scaler.inverse_transform(pred.detach().cpu().numpy())

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

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

    # 为预测数据生成x轴坐标
    prediction_x = range(history_length - 1, history_length + len(pred[:, -1]) - 1)

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

    # 绘制预测数据
    plt.plot(prediction_x, pred[:, -1], marker='o', linestyle='--', color='red', label='Prediction')

    # 添加红色竖线,分割历史数据和预测数据
    plt.axvline(history_length - 1, color='red', linestyle=':')

    # 添加标题和图例
    plt.title("History and Prediction")
    plt.legend()
    plt.show()

✅ 关键优化:

  1. 禁用梯度计算:使用 torch.no_grad() 来禁用梯度计算,节省内存和加速推理。
  2. 去除冗余赋值:直接加载模型参数而不重复赋值 model = model
  3. 改进可视化效果:使用不同的颜色和线型来区分历史数据和预测数据,使得图像更加清晰。

🧰 12. 主函数入口(__main__

  • 使用 argparse 管理所有可配置参数(如模型类型、窗口长度、特征维度、训练轮数等)。
  • 自动选择训练、测试、预测等功能。
  • 初始化模型并根据配置执行各个模块。



if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Time Series forecast')
    parser.add_argument('-model', type=str, default='TCN', 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.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()

完整代码

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))]

    total_len = len(true_data)
    test_data = true_data[:int(0.15 * total_len)]
    valid_data = true_data[int(0.15 * total_len):int(0.30 * total_len)]
    train_data = true_data[int(0.30 * total_len):]

    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, kernel_size=2, dropout=0.2):
        super(TemporalConvNet, self).__init__()
        layers = []
        self.pre_len = pre_len

        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[-1], outputs)

    def forward(self, x):
        x = x.permute(0, 2, 1)
        x = self.network(x)
        x = x.permute(0, 2, 1)
        x = self.linear(x)
        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', help="模型持续更新")

    parser.add_argument('-window_size', type=int, default=32, help="时间窗口大小, window_size > pre_len")
    parser.add_argument('-pre_len', type=int, default=1, 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('-data_path', type=str, default='../data/S1_2_results.csv', help="你的数据数据地址")
    parser.add_argument('-target', type=str, default='S1', help='你需要预测的特征列,这个值会最后保存在csv文件里')
    parser.add_argument('-input_size', type=int, default=4, 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)

    if args.use_gpu and torch.cuda.is_available():
        device = torch.device(f"cuda:{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.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()


参考
时间序列预测实战(二十一)PyTorch实现TCN时间卷积网络进行时间序列预测(专为新手编写的自研架构)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值