本项目结合《【时序预测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.
- 初始化两个变量:
mean
和std
,分别表示均值和标准差。 - 初始值设为 0 和 1,之后会在
fit
中被赋值。
📊 拟合数据:fit
def fit(self, data):
self.mean = data.mean(0)
self.std = data.std(0)
- 对数据按列计算均值和标准差。
mean(0)
表示在 第 0 维(也就是样本维度) 上计算均值,即每列的均值。- 结果存到
self.mean
和self.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),就把mean
和std
转为 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 数组或 tensory_pred
:预测值,类型和 shape 应该与y_true
一致np.abs(...)
:计算每个位置的绝对误差np.mean(...)
:取平均,得出 MAE
🔁 5. 数据加载器函数(create_dataloader
)
create_dataloader
函数涵盖了数据加载、标准化、窗口构造、Tensor 转换、Dataset 封装、DataLoader 创建的整个流程,逻辑很清晰👍。下面我来帮你详细解读一下代码的各个部分,并指出几点小建议和优化点:
🔍 函数功能总结
该函数作用是:
- 从 CSV 文件中加载时间序列数据;
- 选定预测目标列并移动到最后;
- 按比例划分训练、验证、测试集;
- 使用
StandardScaler
进行标准化; - 利用滑动窗口创建
(X, y)
输入输出对; - 构建 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 并返回平均误差。这里有一些优化和建议可以使代码更加健壮和高效,特别是在使用 torch
和 numpy
时。
🛠️ 优化建议
1️⃣ 使用 torch.Tensor
而非 numpy
在 PyTorch 中,我们应该尽量避免在计算中频繁转换 Tensor
和 numpy
之间的格式。可以考虑直接使用 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()
,而直接使用 pred
和 label
。这样能减少不必要的内存转换,代码更加简洁。
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
✅ 关键优化:
- 使用
torch.no_grad()
来禁用梯度计算,减少内存占用。 - 计算 MAE 时使用
torch.mean(torch.abs())
。 - 使用
.item()
提取标量值,保持一致性。 - 反标准化部分简化了
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️⃣ 改善代码结构
你可以将 pred
和 label
的反标准化步骤移到一个单独的函数中,提升代码可读性。
✨ 优化后的 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()
✅ 关键优化:
torch.no_grad()
:禁用梯度计算,节省内存,提升性能。- 反标准化:将
pred
和label
的反标准化步骤放在了循环内,确保它们都是在每个批次计算时被正确处理。 - 简洁的代码结构:整体结构保持简单,便于理解和调试。
📊 可视化结果
通过这个函数,你可以直接比较历史数据与预测结果的拟合情况,观察模型的预测能力。这对模型评估和调优很有帮助。如果你想进一步优化模型或评估其他指标,随时告诉我!
🔮 11. 预测未来数据(predict
)
你的 predict()
函数已经很好地实现了对未知数据的预测,并且绘制了历史数据与预测数据的对比图。但是,函数可以进行一些优化,提升性能和可读性。以下是我的建议:
🛠️ 优化建议
1️⃣ 使用 torch.no_grad()
禁用梯度计算
在推断阶段,我们不需要梯度计算,所以可以用 torch.no_grad()
来减少内存消耗。
2️⃣ model
重复赋值冗余
在函数内部,你两次为 model
赋值 (model = model
和 model.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()
✅ 关键优化:
- 禁用梯度计算:使用
torch.no_grad()
来禁用梯度计算,节省内存和加速推理。 - 去除冗余赋值:直接加载模型参数而不重复赋值
model = model
。 - 改进可视化效果:使用不同的颜色和线型来区分历史数据和预测数据,使得图像更加清晰。
🧰 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()