LSTM多变量时间序列预测实战:用Keras搭建学生成绩预测模型
2026/9/8 9:58:13 网站建设 项目流程

简介:面向机器学习初学者与时间序列分析人员的LSTM多变量预测Python资源包,聚焦长短期记忆网络处理多输入特征预测场景,适用于股票走势、天气变化、销量预估等连续数据预测任务。压缩包共33个文件,包含19个CSV数据集与14个Python脚本,整体仅3.88MB,轻量易下载。资源按LSTM单变量、多变量、多步预测等模块拆分子目录,覆盖数据预处理、时间序列转监督学习、观测值缩放、稳定化转换、模型定义与训练、效果评估等关键环节,并配有完整的香皂销量预测案例及更健壮的LSTM模型示例。通过阅读和调试代码,可系统掌握从原始数据清洗到LSTM模型调优的完整流程,也能理解MSE、MAE、R²等预测成绩指标的实际含义与计算方法。已有3419人学习使用,适合希望借助Python代码快速上手LSTM时间序列预测、并自行扩展到真实业务场景的读者。 做学生成绩预测这件事,如果只把历次考试分数扔进模型做单变量时间序列,那充其量是个趋势外推器。真正影响成绩的因素太多——出勤率、作业完成度、每天有效学习时长、阶段小测波动,这些变量会互相影响,最终共同决定期末成绩。用LSTM做多变量预测,就是把这些特征全部喂给模型,让它在时间维度上自己学出这些因素和最终结果之间的关系。这篇我用Python + Keras把完整流程走一遍,从数据怎么整理、窗口怎么切分,到模型怎么搭、参数怎么调、坑在哪里,全程用一套学生成绩数据集串起来。想真正落地LSTM预测项目、或者刚接触序列模型的读者,可以直接照着抄。

1. 为什么成绩预测值得用LSTM:从普通回归到序列模型的思路转变

1.1 成绩预测的本质是带依赖关系的数据

我刚接触这类问题时,第一反应是拿线性回归试一试:把出勤率、作业完成率、学习时长作为自变量,期末成绩作为因变量,跑一个回归,完事。但试过几次之后发现,成绩数据根本不满足普通回归的前提假设。回归模型假设样本之间是相互独立的,可成绩不是这样——你第5周的状态会影响第6周的学习效果,第6周又影响第7周,前几周的出勤和作业情况天然地跟后面的成绩串成了一条时间线。

这个特性就决定了,只有能捕捉序列依赖关系的模型才更合适。LSTM(长短期记忆网络)作为循环神经网络(RNN)的一种变体,核心优势就是它内部有门控机制,可以选择性地记住或遗忘历史信息。放在成绩预测这个场景里,就是模型会自己判断:"前两周的学习时长到底对期末成绩有没有影响?第3周的小测崩了,这个信息要不要一直记到最后?"这些判断在训练过程中自动完成,不需要你手动设计特征去捕捉。

1.2 多变量比单变量更能还原真实情况

单变量序列预测,比如只用历次考试成绩预测下一次考试,模型能学到的东西非常有限。它本质上只能看到一条曲线的走势,对"为什么这周成绩会下跌"这类原因完全无感。而多变量预测把多个相关特征同时作为输入,让模型在每一个时间步上都拿到一组完整的状态向量。

我习惯用一个类比来解释这个差异:单变量预测就像只看一科的考试分数来猜测学生下一次考得好不好,相当于只看体温来判断一个人生没生病,信息量太单薄。多变量预测则像是同时看到了体温、咳嗽频率、血常规指标、睡眠质量,综合判断病情自然更可靠。在成绩预测项目里,我把每周的出勤率、作业完成率、日均学习时长、阶段小测成绩全部作为输入特征,模型在每个时间步上理解的是学生这周的"综合状态",而不是孤零零的一个分数。

1.3 和传统方法相比到底强在哪

为了讲清楚为什么最终选择LSTM,我把几种常见方案放在一起对比过:

方法适用场景对多变量的支持对序列依赖的建模能力缺点
线性回归静态数据、特征间线性关系明显支持忽略时间顺序
ARIMA单变量时间序列不直接支持通过差分和自回归项建模多变量场景扩展麻烦
随机森林/XGBoost表格型特征支持需要手工构造滞后特征无法天然处理时序关系
LSTM多变量时间序列天然支持需要较大数据量、调参成本高

这不是说LSTM在所有情况下都优于其他方法,而是在"多变量 + 长序列 + 目标变量受多个因素影响"这个组合条件下,LSTM能把特征之间的时序关系利用得更充分。成绩预测恰恰符合这个画像:输入特征多、数据跨周数、最终结果依赖一系列历史状态的累积。

2. 数据预处理:把学习行为变成模型能读懂的输入张量

2.1 原始数据表长什么样

我用的模拟数据集结构大概是这样的:

学号周次出勤率作业完成率日均学习时长阶段小测成绩期末成绩
100110.850.902.578NaN
100120.900.853.082NaN
100130.750.702.070NaN
100140.950.953.588NaN

这里期末成绩那一列只在最后一周有值,但为了构造训练样本,我在预处理阶段把每个学生每个非最后周记录的"期末成绩"用他最终的真实成绩进行填充,作为标签。也就是说,第1周的学习行为组合对应着这个学期最终的期末成绩,第2周的同样如此。这样每个时间点都有一组输入特征和一个明确的预测目标。

注意一个小细节:原始数据里不同学生的周数必须对齐。如果学生A有12周记录、学生B只有8周,那滑动窗口处理的时候要以每个学生为单位分别处理,不能跨学生混着切,否则会引入完全虚假的连续性。

2.2 滑动窗口和时间步长的构建

LSTM的输入是三维张量:(样本数, 时间步长, 特征数)。这里的"时间步长"就是模型一次能往回看多少步。我通常用n_steps = 4,也就是用连续4周的综合状态去预测这个学生的期末成绩。为什么是4?因为前几周的出勤、作业、学习时长积累到第4周,形成了一段稳定且有意义的学习轨迹,窗口太短信息不够,太长在数据量有限的情况下会显著减少样本数。

构建窗口的代码不复杂:

import numpy as np import pandas as pd from sklearn.preprocessing import MinMaxScaler from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout def create_sequences(data, n_steps): """ data: 二维数组,每一行是一个时间点的全部特征 n_steps: 时间步长 返回 X, y """ X, y = [], [] for i in range(len(data) - n_steps): X.append(data[i:i + n_steps, :]) # 取连续 n_steps 行 y.append(data[i + n_steps, 0]) # 预测目标列,这里是期末成绩所在列 return np.array(X), np.array(y)

用这份数据跑下来,data的二维数组形状如果是(12, 5),在n_steps=4的情况下,会生成12 - 4 = 8个样本,每个样本的形状是(4, 5)。一个12周的学生数据只能贡献8个训练样本,所以数据量小是LSTM这类模型绕不开的痛点,后面我会专门讲怎么应对。

2.3 归一化和数据切分最容易翻车

LSTM对特征的绝对数值范围非常敏感。出勤率是0到1之间的小数,日均学习时长可能是2到5的小数,阶段小测成绩是0到100的整数,如果不做归一化直接丢进模型,数值大的特征会在梯度计算中占据绝对主导,小数值特征几乎学不到任何东西。所以必须把全部特征缩放到同一个范围,我一般用MinMaxScaler缩放到(0, 1)区间。

但这里有一个特别容易踩的坑:scaler必须在训练集上拟合,再用训练集拟合出的minmax去转换验证集和测试集。绝对不能在整个数据集上先归一化再切分,否则测试集的信息会通过归一化参数泄漏到训练过程中,评估结果会虚高,上线后一测真实数据就原形毕露。

切分方式也要慎重。时序数据不能随机shuffle,打乱后训练集中的未来信息会跑到前面,模型等于提前偷看了答案。我按时间顺序切分,比如前80%的周数作为训练集,后20%作为测试集。如果数据集包含多个学生,还要保证同一个学生的数据不会同时出现在训练集和测试集里。

3. Keras搭建LSTM多变量预测模型:核心结构与参数选择

3.1 一个能跑通的基础网络结构

以Keras的Sequential模型为例,一个我实测稳定的基础结构是这样的:

n_steps = 4 n_features = 4 # 出勤率、作业完成率、学习时长、阶段小测成绩 n_units = 64 model = Sequential() model.add(LSTM(n_units, return_sequences=True, input_shape=(n_steps, n_features))) model.add(Dropout(0.2)) model.add(LSTM(n_units // 2)) model.add(Dropout(0.2)) model.add(Dense(1)) model.compile(optimizer='adam', loss='mse', metrics=['mae'])

第一层LSTM设置return_sequences=True,是为了让这一层在每个时间步都输出状态,给第二层LSTM提供完整的时间序列信息。如果只有一层LSTM,这个参数设不设都无所谓。最后一层接一个Dense(1)来输出预测值,因为这是回归任务,不需要加激活函数。

为什么用MSE作为损失函数?因为成绩预测本质上是回归,MSE对预测偏差大的样本会给出更大的惩罚,迫使模型把那些"预测成绩和实际成绩差得很远"的样本学好。如果某些场景下你更关注预测误差的整体水平而不是极端偏差,也可以换成MAE损失。

3.2 关键超参数怎么定

超参数这一块是最容易让人困惑的,我根据自己的实践把优先级排了一下:

  • LSTM单元数(units):数据量小的时候,64或32就是合理起步值。单元数越多,模型的记忆容量越大,但过拟合风险也越高。我试过128个单元的模型,在几百条训练样本上很快就过拟合了。
  • 时间步长(n_steps):这是影响样本量的关键参数。n_steps越大,有效训练样本越少。数据量少时优先用3或4,数据量充足时再考虑拉到8以上。
  • batch_size:我用的是16,数据量更小的时候用8。batch太小会导致梯度更新震荡,太大在数据集规模不够时反而容易陷入局部最优。
  • epochs:一般不设死。我配合早停机制,patience设为30,意思是验证集损失连续30轮不降低就停止训练。
  • 学习率:Adam优化器默认的0.001在多数场景下都能用。如果发现loss不降,优先调整学习率而不是换优化器。

3.3 训练过程的监控手段

训练LSTM不能直接拍脑袋定一个epoch数然后就完事了,我强烈建议用EarlyStopping配合ModelCheckpoint

from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint early_stop = EarlyStopping( monitor='val_loss', patience=30, restore_best_weights=True ) checkpoint = ModelCheckpoint( 'best_model.h5', monitor='val_loss', save_best_only=True ) history = model.fit( X_train, y_train, validation_data=(X_val, y_val), epochs=200, batch_size=16, callbacks=[early_stop, checkpoint], verbose=1 )

restore_best_weights=True意味着训练结束后模型自动回滚到验证集损失最低的那一轮权重,而不是最后一轮的权重。这一点很关键,因为训练后期模型往往已经过拟合了,直接拿最后一轮权重去预测,效果会很差。

4. 训练与结果评估:判断模型是真会还是假会

4.1 损失曲线是最直观的诊断工具

训练结束后,我第一件事永远是画loss曲线:

import matplotlib.pyplot as plt plt.plot(history.history['loss'], label='train_loss') plt.plot(history.history['val_loss'], label='val_loss') plt.xlabel('Epoch') plt.ylabel('Loss') plt.legend() plt.show()

如果训练损失持续下降,但验证损失在第20轮附近触底后反弹,这是标准的过拟合信号。看到这种曲线时不要急着加数据或加正则化,先看是不是epoch太多导致的——EarlyStopping其实已经能拦截这个问题。如果训练损失和验证损失都居高不下,那就要检查数据预处理、模型结构和学习率了。

还有一种情况比较隐蔽:训练损失降得很低,验证损失也降了一点,但两者之间有一个稳定的差距。这说明模型对训练集学得很透,泛化却一般,此时Dropout和模型简化比加数据更有效。

4.2 反归一化与误差指标计算

模型预测出来的值区间是(0,1),这是归一化之后的结果。要算出和原始分数同一量纲的预测成绩,必须做反归一化。这里同样要注意:只有目标列参与过归一化,所以只用目标列的scaler来做inverse_transform

# y_test和y_pred是模型直接输出的归一化预测值 y_pred = model.predict(X_test) # 构造相同形状的数组用于反归一化 scaler_y = scaler # 如果全部特征合并归一化,需要单独保存目标列的 scaler # 简便做法:单独归一化目标列 from sklearn.preprocessing import MinMaxScaler scaler_target = MinMaxScaler() # 在预处理阶段单独对 y 做 scaler_target.fit_transform(y.reshape(-1, 1)) y_pred_actual = scaler_target.inverse_transform(y_pred.reshape(-1, 1)) y_test_actual = scaler_target.inverse_transform(y_test.reshape(-1, 1))

反归一化之后,计算几个核心指标:

from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score mae = mean_absolute_error(y_test_actual, y_pred_actual) rmse = np.sqrt(mean_squared_error(y_test_actual, y_pred_actual)) r2 = r2_score(y_test_actual, y_pred_actual) print(f'MAE: {mae:.2f}') print(f'RMSE: {rmse:.2f}') print(f'R2: {r2:.4f}')

我自己跑完一组数据后,MAE在5分左右,R2在0.82上下,这个水平已经具备参考价值了。需要注意的是,成绩预测的误差不是均匀分布的——成绩越高、越接近满分的样本,预测误差往往越小;而成绩位于60分边缘的学生,波动会更大,模型也更容易在这些样本上翻车。

4.3 预测结果可视化

把实际成绩和预测成绩画在同一张图里,是最直接的验证方式:

plt.figure(figsize=(12, 5)) plt.plot(y_test_actual, label='Actual Score', marker='o') plt.plot(y_pred_actual, label='Predicted Score', marker='x') plt.xlabel('Test Sample') plt.ylabel('Score') plt.title('LSTM Multi-variable Prediction: Actual vs Predicted') plt.legend() plt.show()

如果两条折线整体趋势一致、局部偏差不大,说明模型学到了核心规律。如果出现"实际成绩高、预测成绩低"或者反过来,可能是某些突变因素(比如学生某周生病缺考导致小测分数骤降)模型无法从历史数据中提前感知。这类偏差不能完全靠模型解决,需要结合业务规则做修正。

5. 多变量LSTM实测中踩过的坑与优化思路

5.1 数据泄漏:最隐蔽又最致命的问题

这个问题我反复提,因为真的是踩过最狠的坑。最典型的一种泄漏就是把整个数据集先做归一化、再切分训练集和测试集。看起来流程没毛病,但scaler在计算整个数据集的minmax时,已经偷看了测试集的数据范围。测试集再拿去评估,指标自然好看,真实上线之后立刻现出原形。

另一种泄漏更隐蔽:构造滑动窗口时使用了未来信息。比如用第1到4周的特征去预测第5周的成绩,这没问题;但如果在第1到4周的特征里不小心包含了第5周之后才产生的变量(比如最终的平时成绩总分),模型等于提前看到了答案。特征工程做完后,一定要逐个字段核对该字段在时间轴上是否早于或等于预测时刻。

5.2 数据量太小怎么办

LSTM是出了名的数据饕餮,成绩预测场景恰恰数据量不可能太大。一个班40个学生、每人16周记录,按n_steps=4切出来也就480个样本,这还只是理论上限。实际切分后训练集可能只有300多条。

这种情况我有几个实测有效的应对手段:

  • 降低模型复杂度:把LSTM单元数从128降到32,甚至用单层LSTM。模型参数少了,过拟合风险自然降低。
  • 适当减小时间步长n_steps=3对比n_steps=4,样本量会增加20%左右。
  • 用验证集而不是单独的测试集:数据量少时,单独留出20%测试集太奢侈。我倾向于把数据切成训练集和验证集,早停时用验证集监控,最后给出验证集上的误差即可。
  • 扩充数据来源:如果有多个班级、多个学期、多个科目,只要特征口径一致,都可以合并使用,而不是局限在一个班。

数据增强这类在图像领域普遍适用的方法,在时间序列上要谨慎,搞不好会破坏数据的时间一致性,反而带来噪声。

5.3 从基础模型往进阶方向扩展

跑通一遍基础LSTM之后,你会发现这个框架是完全可以迁移的。换一套数据、保留同样的处理流程,基本能复用到大部分多变量时序预测场景。如果还想往深走,有几个方向我亲测值得尝试。

数据层面,可以加入更多维度的特征,比如课堂参与度、历次考试排名变化、心理测评结果等,特征越多,模型还原学生状态的能力越强,但对特征质量的要求也越高。

模型层面,在LSTM后面接一个注意力机制层,可以让模型在多个历史时间步中有侧重地取信息。成绩预测里,有些学生的成绩崩溃是突然发生的,注意力机制可以让模型学会在预测时更多关注前两周的异常值,而不是平均对待所有历史信息。

输出层面,如果目标不只是预测最终的期末成绩,而是预测未来3周每一周的成绩,那就需要改成多步预测结构。常见做法是把Dense(1)换成Dense(n_output_steps),或者在训练时使用Teacher Forcing策略。后者要复杂一些,但预测结果在时间维度上会更连贯。

回到成绩预测这个场景本身,我觉得LSTM最大价值不在于它一定能比传统方法准多少,而在于它逼着你把数据结构化、把特征之间的关系想清楚。当你把每周的出勤率、作业完成率、学习时长组合成一段序列喂给模型时,你实际上是在用工程手段还原"这个学生这个学期是怎么学过来的"——这个过程本身,就已经比单纯跑一个黑箱模型有意义得多。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询