复现github链接:https://github.com/JulesBelveze/time-series-autoencoder.git
论文:A Dual-Stage Attention-Based Recurrent Neural Network for Time Series Prediction:https://arxiv.org/abs/1704.02971
python版本:python3.8.20
cuda版本:cuda111
包版本环境参考:
Package Version Editable project location
-------------------- ------------ ---------------------------
build 1.2.2.post1
CacheControl 0.14.2
certifi 2025.1.31
charset-normalizer 3.4.1
cleo 2.1.0
colorama 0.4.6
contourpy 1.1.1
crashtest 0.4.1
cycler 0.10.0
distlib 0.3.9
dulwich 0.21.7
fastjsonschema 2.21.1
filelock 3.16.1
fonttools 4.56.0
future 0.18.2
idna 3.10
importlib_metadata 8.5.0
importlib_resources 6.4.5
installer 0.7.0
jaraco.classes 3.4.0
joblib 0.15.1
keyring 24.3.1
kiwisolver 1.2.0
matplotlib 3.2.1
more-itertools 10.5.0
msgpack 1.1.0
numpy 1.21.0
packaging 24.2
pandas 1.1.5
pexpect 4.9.0
pillow 10.4.0
pip 24.3.1
pkginfo 1.12.1.2
platformdirs 4.3.6
poetry 1.8.5
poetry-core 1.9.1
poetry-plugin-export 1.8.0
protobuf 5.29.3
ptyprocess 0.7.0
pyparsing 2.4.7
pyproject_hooks 1.2.0
python-dateutil 2.8.1
pytz 2025.1
pywin32-ctypes 0.2.3
rapidfuzz 3.9.7
requests 2.32.3
requests-toolbelt 1.0.0
scikit-learn 0.23.1
scipy 1.4.1
setuptools 75.3.0
shellingham 1.5.4
six 1.15.0
sklearn 0.0
tensorboardX 2.6.2.2
threadpoolctl 2.1.0
tomli 2.2.1
tomlkit 0.13.2
torch 1.9.1+cu111
torchaudio 0.9.1
torchvision 0.10.1+cu111
tqdm 4.46.1
trove-classifiers 2025.2.18.16
tsa 0.1.0 D:\temp\Pytorch双注意LSTM自动编码器
typing_extensions 4.12.2
urllib3 2.2.3
virtualenv 20.29.2
wheel 0.45.1
zipp 3.20.2
注:
vscode配置:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Current File with Arguments",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"cwd":"${fileDirname}",
"console": "integratedTerminal",
// "args": [
// "--ckpt", "output/checkpoint-5000.ckpt" // 添加 --ckpt 参数及其值
// ]
}
]
}
源代码所用数据字段:
| 列名 | 含义 | 单位 |
| ------------- | ------------------ | -------- |
| Date_Time | 日期和时间 | - |
| CO(GT) | 一氧化碳浓度 | mg/m³ |
| PT08.S1(CO) | 一氧化碳传感器响应值 | 无量纲 |
| NMHC(GT) | 非甲烷烃浓度 | µg/m³ |
| C6H6(GT) | 苯浓度 | µg/m³ |
| PT08.S2(NMHC) | 非甲烷烃传感器响应值 | 无量纲 |
| NOx(GT) | 氮氧化物浓度 | µg/m³ |
| PT08.S3(NOx) | 氮氧化物传感器响应值 | 无量纲 |
| NO2(GT) | 二氧化氮浓度 | µg/m³ |
| PT08.S4(NO2) | 二氧化氮传感器响应值 | 无量纲 |
| PT08.S5(O3) | 臭氧传感器响应值 | 无量纲 |
| T | 温度 | °C |
| RH | 相对湿度 | % |
| AH | 绝对湿度 | g/m³ |
将时间序列数据转换为适合时间序列预测的格式,具体来说,它通过滑动窗口的方式从输入数据 X
和标签 y
中提取特征和标签,并生成一个 TensorDataset
。提取特征和标签,预测的是后面的预测窗口长度的标签。下面我将详细解释 X
、y
和 target
的取数逻辑,并指出 y
取数可能存在的问题。
X
的取数X
是输入特征数据,形状为 (nb_obs, nb_features)
,其中 nb_obs
是样本数量,nb_features
是特征数量。X
中提取长度为 seq_length
的序列:features.append(torch.FloatTensor(X[i:i + self.seq_length, :]).unsqueeze(0))
seq_length = 10
,则每次提取 X[i:i+10, :]
,即从第 i
个时间步开始的 10 个时间步的特征数据。unsqueeze(0)
是为了增加一个批次维度。y
的取数y
是目标值(标签),通常是与 X
对应的输出值。y
中提取的是滞后一期的历史值(y[i-1:i+self.seq_length-1]
):y_hist.append(torch.FloatTensor(y[i - 1:i + self.seq_length - 1]).unsqueeze(0))
seq_length = 10
,则提取的是 y[i-1:i+9]
,即从第 i-1
个时间步开始的 10 个时间步的标签值。y[i-1]
的使用可能有问题,因为 y[i-1]
是前一个时间步的值,而不是当前时间步的值。如果 y
是当前时间步的标签,那么这里应该直接使用 y[i:i+self.seq_length]
。target
的取数target
是预测的目标值,即未来 prediction_window
个时间步的标签值:target.append(torch.FloatTensor(y[i + self.seq_length:i + self.seq_length + self.prediction_window]))
seq_length = 10
且 prediction_window = 5
,则提取的是 y[i+10:i+15]
,即从第 i+10
个时间步开始的 5 个时间步的标签值。假设有以下数据:
X
和 y
的长度为 20。seq_length = 3
,prediction_window = 2
。X
的取数i = 1
时,提取 X[1:4, :]
。i = 2
时,提取 X[2:5, :]
。y
的取数i = 1
时,提取 y[0:3]
(即 y[i-1:i+seq_length-1]
)。i = 2
时,提取 y[1:4]
。target
的取数i = 1
时,提取 y[4:6]
(即 y[i+seq_length:i+seq_length+prediction_window]
)。i = 2
时,提取 y[5:7]
。y
取数的问题在代码中,y
的取数逻辑是:
y_hist.append(torch.FloatTensor(y[i - 1:i + self.seq_length - 1]).unsqueeze(0))
这里使用了 y[i-1]
,即前一个时间步的值。如果 y
是当前时间步的标签,那么这里应该直接使用 y[i:i+self.seq_length]
,而不是 y[i-1:i+self.seq_length-1]
。修正后的代码应该是:
y_hist.append(torch.FloatTensor(y[i:i + self.seq_length]).unsqueeze(0))
def frame_series(self, X, y=None):
'''
Function used to prepare the data for time series prediction
:param X: set of features
:param y: targeted value to predict
:return: TensorDataset
'''
nb_obs, nb_features = X.shape
features, target, y_hist = [], [], []
for i in range(1, nb_obs - self.seq_length - self.prediction_window):
features.append(torch.FloatTensor(X[i:i + self.seq_length, :]).unsqueeze(0))
# 修正后的 y 取数逻辑
y_hist.append(torch.FloatTensor(y[i:i + self.seq_length]).unsqueeze(0))
features_var, y_hist_var = torch.cat(features), torch.cat(y_hist)
if y is not None:
for i in range(1, nb_obs - self.seq_length - self.prediction_window):
target.append(
torch.FloatTensor(y[i + self.seq_length:i + self.seq_length + self.prediction_window]))
target_var = torch.cat(target)
return TensorDataset(features_var, y_hist_var, target_var)
return TensorDataset(features_var)
X
的取数是滑动窗口提取特征序列。y
的取数逻辑存在问题,不应使用 y[i-1]
,而应直接使用 y[i:i+self.seq_length]
。target
的取数是提取未来 prediction_window
个时间步的标签值。这段代码的数据流可以分为以下几个步骤:
数据预处理:
self.preprocess_data()
方法,生成训练集和测试集的特征和标签:X_train
, X_test
, y_train
, y_test
。X_train
中获取特征的数量 nb_features
。数据集封装:
self.frame_series(X_train, y_train)
方法,将训练集的特征和标签封装成一个 train_dataset
对象。self.frame_series(X_test, y_test)
方法,将测试集的特征和标签封装成一个 test_dataset
对象。DataLoader 创建:
DataLoader
类创建 train_iter
,用于加载训练数据集。参数包括 batch_size
(批次大小)、shuffle=False
(不打乱数据)、drop_last=True
(丢弃最后一个不完整的批次)。DataLoader
类创建 test_iter
,用于加载测试数据集。参数与 train_iter
相同。返回结果:
train_iter
(训练数据加载器)、test_iter
(测试数据加载器)和 nb_features
(特征数量)。self.preprocess_data()
进行预处理,生成特征和标签。Dataset
对象,然后通过 DataLoader
进行批次加载。DataLoader
对象,以及特征数量。原始数据 → preprocess_data() → (X_train, X_test, y_train, y_test) → frame_series() → (train_dataset, test_dataset) → DataLoader() → (train_iter, test_iter)
DataLoader
是 PyTorch 中用于批量加载数据的工具,支持多线程加载、数据打乱等功能。Dataset
是 PyTorch 中用于封装数据集的基类,通常需要实现 __len__
和 __getitem__
方法。为了更好地理解数据维度的变化情况,我们可以通过一个具体的例子来逐步分析代码中的数据维度变化。假设我们有一个时间序列数据集,包含以下列:
date
: 时间戳feature1
: 数值特征feature2
: 数值特征category
: 类别特征target
: 目标值原始数据 (data
):
date
, feature1
, feature2
, category
, target
)。(1000, 5)
预处理 (preprocess_data
):
X = data.drop('target', axis=1)
:去掉目标列,剩下 4 列。
(1000, 4)
y = data['target']
:目标列。
(1000,)
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.8, shuffle=False)
:
X_train
维度:(800, 4)
X_test
维度:(200, 4)
y_train
维度:(800,)
y_test
维度:(200,)
X_train = preprocessor.fit_transform(X_train)
:经过 ColumnTransformer
处理,假设 category
列被编码为 3 个新列。
(800, 5)
(feature1
, feature2
, category_encoded_1
, category_encoded_2
, category_encoded_3
)X_test = preprocessor.transform(X_test)
:
(200, 5)
时间序列帧化 (frame_series
):
seq_length = 10
,prediction_window = 1
。nb_obs, nb_features = X_train.shape
:
nb_obs = 800
, nb_features = 5
features
和 y_hist
的生成:
i
从 1 到 800 - 10 - 1 = 789
,每次取 10 个时间步的数据。features
维度:(789, 10, 5)
y_hist
维度:(789, 10)
target
的生成:
i
从 1 到 789
,每次取 1 个时间步的目标值。target
维度:(789, 1)
TensorDataset
的生成:
features_var
维度:(789, 10, 5)
y_hist_var
维度:(789, 10)
target_var
维度:(789, 1)
DataLoader (get_loaders
):
train_iter = DataLoader(train_dataset, batch_size=32, shuffle=False, drop_last=True)
:
(32, 10, 5)
(特征),(32, 10)
(历史目标),(32, 1)
(目标)test_iter = DataLoader(test_dataset, batch_size=32, shuffle=False, drop_last=True)
:
(32, 10, 5)
(特征),(32, 10)
(历史目标),(32, 1)
(目标)原始数据 (1000, 5)
|
v
预处理 (X_train: 800, 5, y_train: 800)
|
v
时间序列帧化 (features: 789, 10, 5, y_hist: 789, 10, target: 789, 1)
|
v
DataLoader (batch_size=32, features: 32, 10, 5, y_hist: 32, 10, target: 32, 1)
通过上述步骤,可以看到数据从原始形式逐步转换为适合时间序列模型训练的格式。每个步骤中的数据维度变化如下:
(1000, 5)
(800, 5)
(训练集特征),(800,)
(训练集目标)(789, 10, 5)
(特征),(789, 10)
(历史目标),(789, 1)
(目标)(32, 10, 5)
(特征),(32, 10)
(历史目标),(32, 1)
(目标)