这里写目录标题
- 前言
- LSTM的输入组成
- 时间步
- 例子
- 实际代码解读
- 特征提取
- 处理成dataloader格式(用于输入到模型当中)
- 对应到lstm的模型创建代码
- 总结
前言
本文章将帮助理解如何将一个时间序列的各种特征(年月日的时间特征,滚动窗口滞后项等时变特征输入)输入到lstm模型(关键在于时间步)中,并给出使用pytorch进行特征输入的代码实例。
LSTM的输入组成
包含三个部分
- 样本数:输入样本数量
- 时间步:一共使用多少个时间节点的数据进行预测(重点部分)
- 特征数:某一个数据所具备的特征
时间步
时间步,具体指模型在一个样本中可以看到的时间序列数据的长度,即模型每次处理多少个连续的时间点
其实就是使用前(包括自己)n天的数据进行预测
由于时间步会使用前n天(类似于滞后的想法),所以使用n天时间步会导致样本缩小,所以实际输入(input_size)公式为
样本数量 − 时间步 + 1 样本数量 - 时间步 + 1 样本数量−时间步+1 (要记得把自己算上哦)
时间步与滞后比较像,通过这样的对比帮助更好理解时间步:
滞后:取过去n期的标志值作为当期特征
时间步:取算上自己的过去n期的所有特征作为当期特征
例子
假设一个时间序列[1, 2, 3],在不考虑时间的情况下
- 假设时间步为1,输入到lstm模型当中的_x应该为
一个三维列表,其大小为
(3, 1, 1),(样本数,时间步,特征值)
[
[[1]],
[[2]],
[[3]]
]
- 假设时间步为2,此时的输入大小则为(3, 2, 1)
[
[[1], [2]],
[[2], [3]]
]
实际代码解读
这里可以找gpt捏造一个数据,重点是理解过程中的数据处理
先简单对一个时间序列进行特征提取
特征提取
小贴士:LSTM对数据大小敏感,推荐优先进行
- 归一化(数值大小关系)
- 独热编码(没有大小和周期之分)
- 正余弦编码(有周期不存在数值大小的特征)
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import torch
from tensorflow.python.keras.backend import dtype
from torch import nn, optim
from sklearn.preprocessing import MinMaxScaler
from torch.utils.data import DataLoader, TensorDatasettorch.random.manual_seed(42)
pd.set_option('display.max_columns', 500)
data = pd.read_csv('real_temp.csv')
# 找gpt捏造的数据,其由两列组成,一列Date,一列Sales,时间范围为2023-01-01到2023-12-31
# 其中设置前十一个月用于训练,十二月用于测试
data["Date"] = pd.to_datetime(data["Date"]) # 转换为时间列
dt = data["Date"].dt # 设置时间接口
data["month"] = dt.month
data["day"] = dt.day
data["weekday"] = dt.weekdaydef SinCosScale(name, round):"""正余弦化处理,公式为sin(2Π * 值 / 周期),cos(2Π * 值 / 周期)"""data[f"sin_{name}"] = np.sin(2 * np.pi * data[name] / round) data[f"cos_{name}"] = np.cos(2 * np.pi * data[name] / round)
SinCosScale("weekday", 7)
SinCosScale("month", 12)values = ["Sales", "day", "shift", "windowmean", "windowstd"] # 将具有大小之分的数据进行归一化
# 为什么日期也包括在里面呢,因为不同月份对应的day其实不同,这里不太好处理
data["shift"] = data["Sales"].shift(7) # 滞后特征
rolling = data["Sales"].rolling(7) # 滚动窗口
data["windowmean"] = rolling.mean() # 窗口内的均值
data["windowstd"] = rolling.std() # 窗口内的标准差
scaler = MinMaxScaler()
scalar_test = MinMaxScaler() # 用于专门转换y值的归一化模型
scalar_test.fit(data["Sales"].values.reshape(-1, 1)) # 输入y值进行训练,方便后续inverse
data[values] = scaler.fit_transform(data[values])
data.dropna(inplace=True)
data.drop(["month", "weekday"], inplace=True, axis=1)
处理成dataloader格式(用于输入到模型当中)
通过TensorDataloader转换为张量数据,通过TensorDataset能够让模型训练的部分模板化,强烈推荐使用
tran_mask = (data["Date"] <= "2023-12-01") # 训练集的范围
test_mask = (data["Date"] >= "2023-12-01") & (data["Date"] <= "2025-12-31") # 测试集范围
train_data = data[tran_mask].values[:, 1:] # 取出训练集(不包括Date)
test_data = data[test_mask].values[:, 1:] # 取出测试集(不包括Date)
def create_sequences(data, time_steps):"""取出时间步数据"""x, y = [], [] # 这里的x其实是所有带时间步的汇总for i in range(len(data) - time_steps):x.append(data[i:i + time_steps]) # 选择从 i 开始的 time_steps 个时间步,这里已经是一个二维列表了,其中每一个元素都对应过去的一个时间节点的所有数据y.append(data[i + time_steps][0]) # 选择第 i + time_steps 个数据作为目标return np.array(x).astype("float32"), np.array(y).astype("float32").reshape(-1, 1)
train_x, train_y = create_sequences(train_data, 7)
test_x, test_y = create_sequences(test_data, 7)
train_inputs = TensorDataset(torch.tensor(train_x, dtype=torch.float32), torch.tensor(train_y, dtype=torch.float32)) # 转换为张量数据,通过TensorDataset能够让模型训练的部分完全模板化,强烈推荐使用
test_inputs = TensorDataset(torch.tensor(test_x, dtype=torch.float32), torch.tensor(test_y, dtype=torch.float32))
train_dataloader = DataLoader(dataset=train_inputs, batch_size=32, shuffle=True)
test_dataloader = DataLoader(dataset=test_inputs, batch_size=32)
对应到lstm的模型创建代码
注意lstm中的输入层,其大小应该为特征的数量
如果只设置了值为特征,那么input_size就为1
如果设置了8个特征,那么算上自己的值之后input_size就为9
class LSTM(nn.Module):def __init__(self, input_size, hidden_size, output_size):super(LSTM, self).__init__()self.lstm = nn.LSTM(input_size, hidden_size, batch_first=True) # lstm的返回有两个值,所以不能直接用sequentialself.fc = nn.Linear(hidden_size, output_size) # 线性def forward(self, x):# (样本大小, 时间步,特征数量)x, _ = self.lstm(x) # x代表输出的形状,_是用于下一步处理,简单lstm就不用这个了x = x[:, -1, :] # 只取最后一个时间步x = self.fc(x) # 将输入的x转换为output_size尺寸return x
# 设置接口
input_size, hidden_size = 9, 8 # 输入的为特征数量
lstm = LSTM(input_size, hidden_size, 1) # input_size为特征数量
其他代码这里就不放了,后面都是模板,问gpt,看其他文章都差不多,不浪费时间:
- 设置优化器损失函数,数据移入device
- for epoch 个训练,每个epoch中for data in dataloader:(这也是我觉得dataloader好的地方,也可以帮助我们将数据分批次与打乱,调整dataloader的batch_size即可)
- 前向传播,计算损失,清空梯度,反向传播,更新参数
- 测试集验证模型精度
总结
特征正常提取,和机器学习中的特征学习一样,假设n个特征
接下来对特征处理成时间步的形状,假设m个时间步,则每一个样本输入到模型中的大小为
(1, m, n):一个样本算上自己的前m个时间节点,包含每个时间节点的n个特征