山上有鲸 2024-12-06 12:00 采纳率: 0%
浏览 56
已结题

时间序列LSTM模型归回预测代码问题

1.问题:我借用AI工具写了一个LSTM模型时间序列回归预测的Python代码脚本,但是我想知道这个代码脚本到底是否完整、正确,有没有实用价值,因不善言表,我会在下面的叙述中详细说明我的具体问题和诉求是什么;
2.我的数据集:我下载了网上的公开数据集北京PM2.5质量,我把除PM2.5数据列之外的其他数据列作为特征列,PM2.5数值列作为目标列,以此作为数据集示例检验我的下记代码脚本;
数据集截图请查看附图1,由于此公开数据集有4万多个样本,我暂时只截取了前面5千个左右的样本作为我的脚本模型分析样本;

img

3.脚本代码:tag_lstm_sl001.py详细如下

import pandas as pd
import numpy as np
from sklearn.preprocessing import MinMaxScaler, LabelEncoder
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, LSTM, Dense, Bidirectional, Embedding, Flatten, concatenate, Reshape, Lambda, ReLU 
from tensorflow.keras.optimizers import Adam
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from datetime import timedelta  
import matplotlib.pyplot as plt
import math

# 读取数据
file_path = 'C:\\python01\\test008.xlsx'  
sheet_name = 'Sheet1'
df = pd.read_excel(file_path, sheet_name=sheet_name)
df = df.dropna(subset=['目标列1'])
df['日期'] = pd.to_datetime(df['日期'])  # 将'日期'列转换为datetime类型

# 自动识别和分离特征列和目标列
feature_columns = [col for col in df.columns if col.startswith('特征列')]
target_columns = [col for col in df.columns if col.startswith('目标列')]

# 分离出包含字符串的特征列和数值型特征列
string_feature_columns = [col for col in feature_columns if df[col].dtype == 'object']
numeric_feature_columns = [col for col in feature_columns if df[col].dtype != 'object']

# 对字符串特征进行嵌入处理
embeddings = {}
encoded_features = []
for col in string_feature_columns:
    le = LabelEncoder()
    df[col] = le.fit_transform(df[col])
    embeddings[col] = (len(le.classes_), 50)  # 假设每个字符串特征嵌入维度为50
    encoded_features.append(df[col])

# 数值型特征归一化
scaler = MinMaxScaler()
df[numeric_feature_columns] = scaler.fit_transform(df[numeric_feature_columns])
numeric_features = df[numeric_feature_columns].values

# 合并特征列
features = np.hstack([numeric_features] + [np.array(f).reshape(-1, 1) for f in encoded_features])

# 准备输入数据
def prepare_data(features, targets, look_back):
    X, y = [], []
    for i in range(len(features) - look_back):
        X.append(features[i:(i + look_back), :])
        y.append(targets[i + look_back, :])
    return np.array(X), np.array(y)

look_back = 24  # 时间步长
features = features
targets = df[target_columns].values
X, y = prepare_data(features, targets, look_back)

# 划分训练集、验证集和测试集
train_ratio = 0.7
total_size = len(X)
train_size = int(total_size * train_ratio)
val_test_split = (1 - train_ratio) / 2
val_size = int(total_size * val_test_split)
test_size = total_size - train_size - val_size

X_train, X_rest = X[:train_size], X[train_size:]
y_train, y_rest = y[:train_size], y[train_size:]

X_val, X_test = X_rest[:val_size], X_rest[val_size:]
y_val, y_test = y_rest[:val_size], y_rest[val_size:]

# 重塑输入数据 [samples, time steps, features]
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], X_train.shape[2]))
X_val = X_val.reshape((X_val.shape[0], X_val.shape[1], X_val.shape[2]))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], X_test.shape[2]))
print("X_train shape--", X_train.shape)  
print("y_train shape--", y_train.shape)  
print("X_val shape--", X_val.shape)  
print("y_val shape--", y_val.shape)  
print("X_test shape--", X_test.shape)  
print("y_test shape--", y_test.shape)  

# 构建双向LSTM模型
lstm_units01 = 224
lstm_units02 = 488
dropout01 = 0.0
dropout02 = 0.5
learning_rate = 0.002


# 创建模型
input_layer = Input(shape=(look_back, len(feature_columns)))

# 字符串特征嵌入层
embedding_layers = []
for i, col in enumerate(string_feature_columns):
    vocab_size, embedding_dim = embeddings[col]
    embedding_layer = Embedding(vocab_size, embedding_dim, input_length=look_back)(
        Lambda(lambda x: x[:, :, i])(input_layer)
    )
    embedding_layer = Flatten()(embedding_layer)
    embedding_layer = Reshape((look_back, -1))(embedding_layer)
    embedding_layers.append(embedding_layer)

# 合并所有嵌入层和数值型特征层
merged_embedding = concatenate(embedding_layers)
numeric_features = Lambda(lambda x: x[:, :, -len(numeric_feature_columns):])(input_layer)
merged_layer = concatenate([merged_embedding, numeric_features])
 
# 直接将merged_layer传递给Sequential模型的第一层
model = Sequential()
model.add(Bidirectional(LSTM(units=lstm_units01, return_sequences=True, dropout=dropout01, input_shape=(look_back, merged_layer.shape[-1]))))
model.add(ReLU())  # 添加ReLU激活函数
model.add(Bidirectional(LSTM(units=lstm_units02, dropout=dropout02)))
model.add(ReLU())  # 添加ReLU激活函数
model.add(Flatten())
model.add(Dense(len(target_columns)))
model.compile(loss='mean_squared_error', optimizer=Adam(learning_rate=learning_rate))


# 训练及验证模型
batch_size = 176
epochs = 250
history = model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size, verbose=1, validation_data=(X_val, y_val), shuffle=False)

# 测试评估
test_loss = model.evaluate(X_test, y_test, verbose=0)
print('Test Loss:', test_loss)

# 模型预测
train_predicted = model.predict(X_train)
val_predicted = model.predict(X_val)
test_predicted = model.predict(X_test)

# 模型评估指标
trainScore = math.sqrt(mean_squared_error(y_train, train_predicted))
print('Train Score(训练集均方根误差): %.4f RMSE' % (trainScore))
testScore = math.sqrt(mean_squared_error(y_test, test_predicted))
print('Test Score(测试集均方根误差): %.4f RMSE' % (testScore))
mse = mean_squared_error(y_test, test_predicted)
mae = mean_absolute_error(y_test, test_predicted)
r2 = r2_score(y_test, test_predicted)
print('MSE(均方误差): %.4f' % mse)
print('MAE(平均绝对误差): %.4f' % mae)
print('R^2 拟合值: %.4f' % r2)
if np.any(y_test == 0):
    print("警告:数据中存在0值,MAPE可能无法准确计算或不稳定。")
else:
    mape = np.mean(np.abs((y_test - test_predicted) / y_test)) * 100
    print('MAPE(平均绝对百分比误差): %.4f%%' % mape)



# 可视化结果  
# 可视化训练过程
plt.plot(history.history['loss'], label='train')
plt.plot(history.history['val_loss'], label='val')
plt.title('Loss Curve')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(loc='upper right')
plt.show()


# 可视化预测值
fig, axes = plt.subplots(len(target_columns), 1, figsize=(14, 10 * len(target_columns)), sharex=True, gridspec_kw={'hspace': 0})
# 获取日期列表
dates = df['日期'].tolist()
# 原始数据用于绘图
data = targets

ax = axes
target_name = target_columns[0]
ax.plot(dates, data[:, 0], label='原始序列实际值', marker='o', markersize=2)  # 使用 data[:, 0] 而不是 data[:, i]

# 训练集、验证集和测试集的日期索引
train_indices = dates[look_back:len(X_train) + look_back]
val_indices = dates[len(X_train) + look_back:len(X_train) + len(X_val) + look_back]
test_indices = dates[len(X_train) + len(X_val) + look_back:]

# 绘制训练集和测试集的实际值和预测值
ax.plot(train_indices, train_predicted[:, 0], label='训练集拟合值', linestyle='--', marker='o', markersize=2)
ax.plot(val_indices, val_predicted[:, 0], label='验证集预测值', linestyle='--', marker='o', markersize=2)
ax.plot(test_indices, test_predicted[:, 0], label='测试集预测值', linestyle='--', marker='o', markersize=2)

# 设置图例
ax.legend(bbox_to_anchor=(0.01, 1), loc='upper left')
ax.set_ylabel(f'{target_name} 值')
ax.grid(True)

# 设置x轴标签和刻度
plt.xlabel('日期')
plt.xticks(rotation=45)

# 设置整个图形的标题
fig.suptitle('时间序列LSTM模型预测', fontsize=16)

# 调整子图的间距
plt.subplots_adjust(bottom=0.05, top=0.95, left=0.05, right=0.98)

# 设置字体
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.rcParams['axes.unicode_minus'] = False

# 显示图表
plt.show()

4.代码结果:我是直接在cmd命令窗口运行Python脚本的,部分截取结果如下

C:\python01>tag_lstm_sl001.py
X_train shape-- (3318, 24, 7)
y_train shape-- (3318, 1)
X_val shape-- (711, 24, 7)
y_val shape-- (711, 1)
X_test shape-- (711, 24, 7)
y_test shape-- (711, 1)
2024-12-04 16:49:31.443699: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX AVX2
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2024-12-04 16:49:32.229197: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1510] Created device /job:localhost/replica:0/task:0/device:GPU:0 with 23725 MB memory:  -> device: 0, name: Tesla P40, pci bus id: 0000:09:00.0, compute capability: 6.1
2024-12-04 16:49:32.775421: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:185] None of the MLIR Optimization Passes are enabled (registered 2)
Epoch 1/250
2024-12-04 16:49:41.244985: I tensorflow/stream_executor/cuda/cuda_dnn.cc:369] Loaded cuDNN version 8100
19/19 [==============================] - 12s 121ms/step - loss: 11641.4404 - val_loss: 8977.6484
Epoch 2/250
19/19 [==============================] - 1s 34ms/step - loss: 9597.6934 - val_loss: 7096.3208
Epoch 3/250
19/19 [==============================] - 1s 34ms/step - loss: 8376.0811 - val_loss: 5746.4658
Epoch 4/250
19/19 [==============================] - 1s 34ms/step - loss: 7580.8970 - val_loss: 4828.5317
Epoch 5/250
19/19 [==============================] - 1s 35ms/step - loss: 7095.6514 - val_loss: 4222.6196
Epoch 6/250
19/19 [==============================] - 1s 34ms/step - loss: 6816.3838 - val_loss: 3834.0535
Epoch 7/250
19/19 [==============================] - 1s 34ms/step - loss: 6664.5483 - val_loss: 3587.9658
Epoch 8/250
19/19 [==============================] - 1s 34ms/step - loss: 6585.8389 - val_loss: 3432.9216
Epoch 9/250
19/19 [==============================] - 1s 34ms/step - loss: 6546.7559 - val_loss: 3335.2754
Epoch 10/250
19/19 [==============================] - 1s 34ms/step - loss: 6528.0566 - val_loss: 3273.6768
Epoch 11/250
19/19 [==============================] - 1s 34ms/step - loss: 6519.3701 - val_loss: 3234.7705
Epoch 12/250
19/19 [==============================] - 1s 34ms/step - loss: 6515.4194 - val_loss: 3210.2166
Epoch 13/250
19/19 [==============================] - 1s 34ms/step - loss: 6513.6431 - val_loss: 3194.7717
Epoch 14/250
19/19 [==============================] - 1s 34ms/step - loss: 6512.8481 - val_loss: 3185.1116
Epoch 15/250
19/19 [==============================] - 1s 35ms/step - loss: 6512.4956 - val_loss: 3179.1143
Epoch 16/250
19/19 [==============================] - 1s 34ms/step - loss: 6512.3413 - val_loss: 3175.4211
Epoch 17/250
19/19 [==============================] - 1s 34ms/step - loss: 6512.2822 - val_loss: 3173.1655
………
Epoch 245/250
19/19 [==============================] - 1s 33ms/step - loss: 3742.5806 - val_loss: 2817.4897
Epoch 246/250
19/19 [==============================] - 1s 33ms/step - loss: 3641.6494 - val_loss: 2745.9536
Epoch 247/250
19/19 [==============================] - 1s 34ms/step - loss: 4579.7510 - val_loss: 2880.9116
Epoch 248/250
19/19 [==============================] - 1s 33ms/step - loss: 5230.1187 - val_loss: 2759.8218
Epoch 249/250
19/19 [==============================] - 1s 33ms/step - loss: 4787.3862 - val_loss: 2869.9761
Epoch 250/250
19/19 [==============================] - 1s 34ms/step - loss: 4398.8384 - val_loss: 2638.9834
Test Loss: 4404.56494140625
Train Score(训练集均方根误差): 63.6965 RMSE
Test Score(测试集均方根误差): 66.3669 RMSE
MSE(均方误差): 4404.5649
MAE(平均绝对误差): 55.2028
R^2 拟合值: 0.1112
MAPE(平均绝对百分比误差): 79.4055%

脚本生成的附图2和3如下:

img

img

5.结论:可以看到上述脚本运行的结果是不太理想的,不管是训练集、验证集还是测试集,其拟合预测值与原序列走势图都不是我想要的预期效果,须要补充说明一下,上述代码脚本中的相关参数除了look_back时间步长和train_ratio数据集划分系数是依据经验自行设置的,其他的参数我都是通过贝叶斯超参数寻优找到最佳超参数组合,我的贝叶斯优化主要代码部分截取如下:

# 构建LSTM模型
def build_model(lstm_units01, lstm_units02, dropout01, dropout02, learning_rate):  
    input_layer = Input(shape=(look_back, len(feature_columns)))

    # 字符串特征嵌入层
    embedding_layers = []
    for i, col in enumerate(string_feature_columns):
        vocab_size, embedding_dim = embeddings[col]
        # 对每个时间步的特征应用嵌入
        embedding_layer = Embedding(vocab_size, embedding_dim, input_length=look_back)(
            Lambda(lambda x: x[:, :, i])(input_layer)
        )
        embedding_layer = Flatten()(embedding_layer)
        embedding_layer = Reshape((look_back, -1))(embedding_layer)
        embedding_layers.append(embedding_layer)

    # 合并所有嵌入层和数值型特征层
    merged_embedding = concatenate(embedding_layers)
    numeric_features = Lambda(lambda x: x[:, :, -len(numeric_feature_columns):])(input_layer)
    merged_layer = concatenate([merged_embedding, numeric_features])
 
    # 直接将merged_layer传递给Sequential模型的第一层
    model = Sequential()
    model.add(Bidirectional(LSTM(units=lstm_units01, return_sequences=True, dropout=dropout01, input_shape=(look_back, merged_layer.shape[-1]))))
    model.add(ReLU())  # 添加ReLU激活函数
    model.add(Bidirectional(LSTM(units=lstm_units02, dropout=dropout02)))
    model.add(ReLU())  # 添加ReLU激活函数
    model.add(Flatten())
    model.add(Dense(len(target_columns)))
    model.compile(loss='mean_squared_error', optimizer=Adam(learning_rate=learning_rate))
    return model  

# 在objective函数中使用新的build_model函数来创建模型
def objective(params):  
    print("Params:", params)  
    lstm_units01 = params['lstm_units01']  
    lstm_units02 = params['lstm_units02']  
    dropout01 = params['dropout01']  
    dropout02 = params['dropout02']  
    learning_rate = params['learning_rate']     
    batch_size = params['batch_size']  
    epochs = params['epochs']  
    # 构建LSTM模型  
    model = build_model(lstm_units01, lstm_units02, dropout01, dropout02, learning_rate)  
    # 训练模型
    history = model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size, verbose=0, validation_data=(X_val, y_val), shuffle=False)
    # 验证模型
    val_loss = model.evaluate(X_val, y_val, verbose=0)     
    return {'loss': val_loss, 'status': STATUS_OK, 'history': history}


# 定义超参数搜索空间
# 定义一个函数来生成离散的初始学习率列表  
def generate_learning_rates():  
    rates_interval1 = [round(x, 4) for x in np.arange(0.0001, 0.001, 0.0001)]      
    rates_interval2 = [round(x, 3) for x in np.arange(0.001, 0.01, 0.001)]     
    learning_rates = rates_interval1 + rates_interval2      
    return learning_rates  
learning_rates = generate_learning_rates()  

# dropout参数设定取值范围
def generate_dropouts():  
    dropouts = [round(x, 2) for x in np.arange(0.00, 0.61, 0.1)]      
    return dropouts  
dropouts = generate_dropouts()  
  
# 在空间定义中使用这个列表  
space = {    
    'lstm_units01': hp.choice('lstm_units01', range(64, 512, 8)),     
    'lstm_units02': hp.choice('lstm_units02', range(64, 512, 8)),    
    'dropout01': hp.choice('dropout01', dropouts),  
    'dropout02': hp.choice('dropout02', dropouts),  
    'learning_rate': hp.choice('learning_rate', learning_rates), 
    'batch_size': hp.choice('batch_size', range(16, 256, 8)),   
    'epochs': hp.choice('epochs', range(50, 600, 2)),    
}

# 定义贝叶斯优化参数优化函数
def param_hyperopt(max_evals):
    trials = Trials()
    # 提前停止条件
    early_stop_fn = no_progress_loss(20)
    # 优化模型
    best_params = fmin(fn=objective, space=space, algo=tpe.suggest, max_evals=max_evals,  
                       trials=trials, early_stop_fn=early_stop_fn)  
    print("最佳参数索引:", best_params)    # 找最佳索引后续再转换找到对应最佳超参数组合值(略)
    return best_params, trials

# 执行贝叶斯优化  
best_params, trials = param_hyperopt(max_evals=100)  
# 获取最佳参数对应的history  
best_trial = trials.best_trial  
history = best_trial['result']['history']

下记是我手动修改相关参数后模型训练过拟合生成的附图4(参考):

img

从图4可以看到,训练集数据拟合度还不错可接受,但验证集和测试集数据就明显拟合效果不理想了,当然这里是过拟合下生成的附图没有什么意义,我提供这个附图是想要表达,我要如何修改或调整我的数据或脚本代码才能使贝叶斯寻优超参数后找到的最佳超参数组合,通过脚本模型训练生成的LSTM模型归回预测图,其测试集和验证集数据拟合预测效果与上记过拟合时附图4中的训练集预测效果差不多,我目前调试了很多次脚本代码和参数,我有进行过下记尝试和调整:
1)改用全部数据集样本,将上述数据集改为使用全部4万多个样本数据集,而不是仅截取前面5千个样本,贝叶斯寻优超参数后,其模型训练及测试损失值和模型评估指标并没有得到明显改善;
2)改用其他公开数据集测试检验,例如某股票股价的数据集,其改善效果也不明显;
3)更改添加的激活函数,将激活函数ReLU改为其他LeakyReLU(alpha=0.01)或ELU或swish等激活函数,或者不使用添加的激活函数,或者只在LSTM模型某一层添加,其改善效果也不明显;
4)尝试修改相关参数,比如修改嵌入维度增大或减小、时间步长增大或减小、数据集划分系数等等,其改善效果也都不明显;
通过上记的调整修改方法其最终模型寻优的超参数组合训练后生成的模型预测图,其验证集和测试集(包括训练集在内)其拟合预测值和模型评估指标结果都不怎么理想,始终达不到上记过拟合时附图4中训练集数据那种拟合效果;
目前我猜测有以下两种可能情况是导致效果不明显的原因所在:
1)选择的数据集不适用于LSTM等神经网络这类模型;
2)上述模型创建和训练的代码逻辑存在一些漏洞或错误;
但是我不知道具体的问题症结所在,所以希望各位资深博主们能帮我找到问题的症结所在;如果是脚本代码有问题,麻烦提供一下修正方法或代码,我的初衷是希望这类模型分析预测脚本具有实用意义,例如在生活工作中收集到一些数据集(比如某个产品的销售数据等等)能够真正使用这类模型代码进行一定的预测性指导,否则这种模型脚本就是个花架子,没有什么实际应用意义.

  • 写回答

26条回答 默认 最新

  • 专家-皆知 2024-12-06 12:23
    关注

    参考免费微信小程序:皆我百晓生 作答,经由专家-皆知整理回复,如问题解决,望采纳;如仍有疑问,请评论回复。

    针对您的问题,我会从几个关键点进行分析和建议,以帮助您提升模型的预测性能和实用性。

    数据预处理和模型结构优化

    1. 特征工程:确保您已经进行了充分的特征工程。除了基本的归一化,考虑对时间序列数据进行周期性特征提取,如一天中的时间、星期几等,特别是如果数据中存在季节性模式。

    2. 数据集大小与过拟合:使用全部数据集通常会提高模型的泛化能力,但如果模型复杂度过高,即使是大量数据也可能过拟合。检查您的数据集中是否存在重复或者非常相似的序列,这可能导致模型学习到噪声而非真实模式。

    3. 模型复杂度:您的模型结构较为复杂,包含嵌入层和双层LSTM,这可能导致训练困难或过拟合。尝试简化模型,比如减少LSTM单元的数量,或者去掉一个LSTM层,看看是否能改善验证集的表现。

    4. 正则化:加入正则化项(如kernel_regularizer=l2(0.01))到LSTM层,可以帮助减轻过拟合。

    5. 学习率调整:尽管您已经使用了贝叶斯优化来寻找学习率,有时候手动微调或使用学习率衰减策略也能带来改善。

    6. Dropout比例:您提到的dropout比例可能需要进一步调整。在LSTM中,使用适当的dropout可以防止过拟合,但太高会损害模型的学习能力。

    训练策略

    1. 早停法(Early Stopping):确保您的早停策略正确配置,不仅仅基于验证损失,也可以考虑结合验证准确率或其他指标,以避免过早停止训练。

    2. 数据增强:对于时间序列,数据增强可能不如图像处理中常见,但可以通过时间平移、缩放等方式尝试。

    3. 序列长度look_back):您当前的look_back=24假设了24小时的时间序列信息对预测很重要。根据数据特性,这个值可能需要调整,以捕捉到最相关的过去信息。

    评估与调参

    1. 多模型融合:单一模型的表现可能受限,尝试集成学习,比如训练多个模型然后融合它们的预测结果。

    2. 交叉验证:虽然在时间序列数据上直接使用传统的交叉验证可能会有问题,但可以通过时间序列交叉验证(TSCV)来更稳健地评估模型性能。

    3. 目标变量的尺度:确保目标列的尺度适合模型预测。如果预测值范围很大,考虑对其进行对数变换,然后再反变换预测结果。

    实验记录与分析

    • 系统性实验:记录每次调整后的结果,包括模型结构、参数变化以及性能指标,这样可以更清晰地看到哪些改变是有益的。

    • 理解数据:深入分析数据的统计特性,包括趋势、周期性和异常值,这些都能指导模型的设计。

    由于无法直接运行代码或查看数据,以上建议是基于一般性的指导。具体实施时,需要根据实际情况调整。如果模型仍然表现不佳,可能需要回到数据本身,检查是否有未被发现的模式或质量问题。

    评论

报告相同问题?

问题事件

  • 已结题 (查看结题原因) 12月13日
  • 创建了问题 12月6日

悬赏问题

  • ¥15 PADS Logic 原理图
  • ¥15 PADS Logic 图标
  • ¥15 电脑和power bi环境都是英文如何将日期层次结构转换成英文
  • ¥20 气象站点数据求取中~
  • ¥15 如何获取APP内弹出的网址链接
  • ¥15 wifi 图标不见了 不知道怎么办 上不了网 变成小地球了
  • ¥50 STM32单片机传感器读取错误
  • ¥50 power BI 从Mysql服务器导入数据,但连接进去后显示表无数据
  • ¥15 (关键词-阻抗匹配,HFSS,RFID标签天线)
  • ¥15 机器人轨迹规划相关问题