资讯详情

LSTM多变量预测实战:用Python搭建成绩预测模型

📅 2026/10/11 14:04:00 | 华诺云谱 👁 阅读
LSTM多变量预测实战:用Python搭建成绩预测模型
简介面向希望掌握LSTM多变量预测的Python开发者这份资源以成绩预测等场景为主线系统覆盖时间序列转监督数据、数据预处理、模型定义与训练、评估优化等关键环节同时按单变量、多变量和多步预测拆分为多个模块并配置shampoo-sales等数据集便于上手实践。压缩包共33个文件含19个csv数据文件和14个py脚本整体3.88MB脚本从预处理、缩放、稳定化到模型开发与预测完整串联适合初学者循序渐进阅读。已有3419人浏览学习资源代码结构清晰、模块划分明确可直接复用于风电、股票、天气等序列预测任务也可作为入门LSTM的实操参考资料。1. LSTM 多变量预测是什么拿一片成绩单能算出下学期的线「LSTM 多变量预测」翻译成人话你手里有一张按周排开的成绩明细每行不是孤零零一个分数而是一组互相影响的指标——出勤率、作业提交率、周测得分、自习时长。要做的是拿过去 4 到 6 周这些多列数据预测下一次考试能考多少。很多人一上来就把历次分数排成一列丢给 LSTM结果模型输出一条接近水平的线真正能跑通的是把它建模成多变量时间序列让网络自学“出勤下滑之后两周成绩才开始掉”这类延迟关联。这篇适合手里有历次测验明细、想用 Python 跑通一版预测脚本的从业者按数据处理、模型搭建、训练验证和踩坑排查的顺序展开照抄能复现。2. 为什么成绩预测要选 LSTM从梯度消失到多变量输入输出设计2.1 RNN 记不住长序列的根源梯度消失与 LSTM 的闸门普通 RNN 在每个时间步把上一刻的隐藏状态和当前输入一起算得到新的隐藏状态。序列一长反向传播时梯度沿时间轴连乘权重矩阵的特征值只要小于 1梯度就会指数级衰减十几步之前的信息对后续更新几乎没贡献。成绩预测里“第 3 周开始刷题量加大、第 5 周进入疲态”这类信号经常要隔 6 到 10 周才在大考上兑现普通 RNN 根本传不到那么远。LSTM 的解决办法是引入细胞状态和三个门控遗忘门决定上一时刻的细胞状态保留多少输入门决定当前新信息写进多少输出门决定细胞状态对外放出多少。梯度在细胞状态这条“传送带”上可以近似无损地流过关键信息能传几十步。这也是为什么时间序列预测里LSTM 比普通 RNN 更常用。LSTM 还有个更节俭的亲戚 GRU把三个门简化成重置门和更新门参数更少后面如果遇到小样本过拟合把两层 LSTM 换成 GRU 是性价比最高的第一招。2.2 多变量预测的成绩建模特征表、时间步与预测目标多变量预测指的是输入不是单列分数而是多个随时间变化的特征列预测目标可以是其中的某一列也可以是一个单独的标签。成绩预测最常见的建模方式每个学生每周生成一条记录取过去 n_steps 周的所有特征作为输入 x预测下一个时间点的考试分数作为 y。我一般先这样定义特征表以教务系统能导出的字段为例特征列含义取值范围attendance本周出勤率0–100homework作业提交率0–100quiz周测平均分0–100study_hours本周自习总时长小时rank上周大考班级排名正整数预测目标 target 是下一次考试分数。注意 rank 这类“数值越小越好”的特征归一化后语义不会颠倒但解读反归一化结果时要心里有数。特征也不是越多越好成绩预测尤其忌讳把排名和成绩同时塞进去——两者高度共线模型会把注意力全放在 rank 上一旦排名波动预测就跟着抖。既然要走序列建模先解释一个绕不开的问题为什么不用随机森林或 XGBoost因为它们默认把每行样本当独立个体不利用顺序信息。同样是“上一周出勤低”发生在连续三周下滑和偶尔一周波动树模型看到的特征向量完全一样LSTM 通过时间步结构能区分这两种模式成绩预测的关键恰恰在趋势和延迟效应。当然这不代表 LSTM 一定更好第 6 章会给验证方法。预测目标的频率也要先想清楚。如果直接预测一学期只有三四次的大考样本量会小到没法训练常见做法是先用周测成绩当近端目标把模型训稳再在学期末的大考上做一次远端校验。另一种做法是每 4 周一个窗口预测第 5 周周测最后看期末大考的残差。这就是“先搞清楚 y 的频率”的重要性它决定了你的样本量级。2.3 动手前的 Python 环境numpy、pandas、tensorflow 怎么配跑 LSTM 之前先确认环境这一步翻车的人最多。我一般建议 python 3.8 以上配合 tensorflow 2.x、numpy、pandas、scikit-learn。Windows 上最容易出问题的是 python 环境变量配置命令行敲 python 没反应十有八九是安装时没勾选“Add Python to PATH”或者 PATH 里同时存在多个 python 入口。# 先升级 pip避免装包时出现依赖冲突 python -m pip install --upgrade pip # 安装本次要用到的核心库numpy 和 pandas 会一起装好 python -m pip install numpy pandas scikit-learn tensorflow装完用一个最小脚本验证环境别直接跑模型import numpy as np import pandas as pd import tensorflow as tf print(np.__version__) print(pd.__version__) print(tf.__version__)如果 import tensorflow 报 DLL 加载失败常见原因是用 32 位 python或者 python 版本太新而 tf 还没跟上这种情况建议换 64 位 python 3.8–3.11或者直接用 tf 的 CPU 版本。成绩预测这种小数据量任务 CPU 完全够用不必为 GPU 再折腾 CUDA。VSCode 里跑的话右下角确认解释器选的是同一个 python别让 Anaconda 的 base 环境混进来。环境一次配好后面代码报错才找得到根因。3. 把多变量成绩数据切成 LSTM 能吃的形状窗口化、归一化与切分3.1 按学生滑动窗口n_steps 取值与切窗口代码LSTM 的输入要求是三维数组样本数 × 时间步 × 特征数。成绩表通常是长表每行是一个学生某一周的一条记录。第一步是把每个学生的记录按时间排序再各自切窗口。手工用 for 循环切没问题数据量大就用 pandas groupby。关键决策是 n_steps 取多少。预测周测成绩我一般取 4 到 6 周预测学期末大考至少取 8 周以上让模型能看到一个完整的“状态变化周期”。n_steps 太大有两个副作用样本量成倍缩小训练变慢n_steps 太小则学不到趋势。常见做法是先画出几个学生的成绩曲线人工看看成绩从下滑到回升大概几周再定窗口。import numpy as np import pandas as pd def make_sequences(df, n_steps6, feature_colsNone, target_colscore): 按学生切滑动窗口返回 (X, y)。 X 形状: (样本数, n_steps, len(feature_cols)) y 形状: (样本数,) X, y [], [] for sid, group in df.groupby(student_id): group group.sort_values(week) # 每个学生必须按周升序 vals group[feature_cols].values target group[target_col].values for i in range(len(vals) - n_steps): X.append(vals[i:i n_steps, :]) y.append(target[i n_steps]) return np.array(X), np.array(y)这里有两个新手常踩的坑。一个是 groupby 之后忘记 sort_values导致同一学生的不同周样本乱序模型等于在看随机时间顺序训练 loss 能降但验证一塌糊涂。另一个是把不同学生拼成一条长序列直接滑窗窗口会跨过学生边界把学生 A 的尾巴和学生 B 的开头当成连续事件。上面代码按 student_id 分组就是为了避开这两个问题。3.2 切分训练/验证/测试集按时间切还是按学生切时间序列不能随机打乱再切分。成绩有明显学期趋势期初、期中、期末分布不同随机切会让训练集混进“未来”样本模型等于提前看到考题。最直接的做法是按时间顺序切前 70% 周次训练中间 15% 验证最后 15% 测试。n_total X.shape[0] train_end int(n_total * 0.7) val_end int(n_total * 0.85) X_train, X_val, X_test X[:train_end], X[train_end:val_end], X[val_end:] y_train, y_val, y_test y[:train_end], y[train_end:val_end], y[val_end:]但这里有个容易被忽略的问题按时间切分时训练集里某个学生的最后几周和测试集里同一学生的前几周是连续的模型在训练时见过该学生的状态测试时其实是“继续预测同一个人的下一个点”这和真实场景里要预测一个新学生不一样。如果目标是跨学生泛化我一般改成按学生 id 切分train_sids df[student_id].unique()[:70] train_mask df[student_id].isin(train_sids)按周切分测的是“这个学生未来怎么样”按学生切分测的是“没见过的人能不能预测”两者面对的问题不同脚本里要二选一并写清楚注释。3.3 归一化顺序scaler 必须在训练集上 fit成绩和自习时长的量纲差很多直接喂给 LSTM 会让数值大的特征主导梯度。常见做法是用 MinMaxScaler 把所有特征压到 0–1。顺序有讲究先在训练集上 fit再 transform 训练集、验证集和测试集测试集绝不能参与 fit。from sklearn.preprocessing import MinMaxScaler n_samples, n_steps, n_features X_train.shape scaler_X MinMaxScaler() X_train_2d X_train.reshape(-1, n_features) X_train_scaled scaler_X.fit_transform(X_train_2d).reshape(n_samples, n_steps, n_features) X_val_scaled scaler_X.transform(X_val.reshape(-1, n_features)).reshape(X_val.shape[0], n_steps, n_features) X_test_scaled scaler_X.transform(X_test.reshape(-1, n_features)).reshape(X_test.shape[0], n_steps, n_features) scaler_y MinMaxScaler() y_train_scaled scaler_y.fit_transform(y_train.reshape(-1, 1)).ravel() y_val_scaled scaler_y.transform(y_val.reshape(-1, 1)).ravel() y_test_scaled scaler_y.transform(y_test.reshape(-1, 1)).ravel()上面的 reshape 是在三维和二维之间切换经常有人在这里把形状搞乱报错先记下 X_train 的原始三维形状transform 完再 reshape 回去。fit/transform 顺序混乱是成绩预测里最典型的玄学问题如果在整份数据上 fit scaler等于让模型在训练时接触了测试集的数据分布线下指标好看换一批真实数据立刻现原形。测试集不仅不能进 fit连 min/max 都不该看见。4. 用 Python 搭建 LSTM 成绩预测模型结构、训练与画图4.1 两层 LSTM Dropout 的模型结构怎么定数据形状确定后模型结构我一般从两层 LSTM 起步。第一层用 return_sequencesTrue让每个时间步都输出隐藏状态给第二层第二层 return_sequencesFalse只输出最后一步的编码接一个带 relu 的中间层再压到 1 个输出节点。成绩预测输出是连续分数所以最后一层不加激活函数等价于线性输出用 MSE 作为损失。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.optimizers import Adam n_steps X_train_scaled.shape[1] n_features X_train_scaled.shape[2] model Sequential() model.add(LSTM(64, return_sequencesTrue, input_shape(n_steps, n_features))) model.add(Dropout(0.2)) model.add(LSTM(32, return_sequencesFalse)) model.add(Dropout(0.2)) model.add(Dense(16, activationrelu)) model.add(Dense(1))LSTM 单元数在这个样本量级不需要堆太大。6432 是稳妥起点第一层 64 负责捕捉每个特征各自的时间模式第二层 32 负责把多个时间步的隐状态融合成整体表达。Dropout 放在 LSTM 层之间而不是 LSTM 内部配合后面的早停基本能压住过拟合。如果训练集很小把结构缩到 3216 更稳参数少了不容易硬背训练样本。4.2 编译与训练参数adam、mse、early stopping编译时优化器用 Adam学习率默认 0.001 对这种任务通常够。损失用 mse同时监控 mae因为 MSE 对离群成绩比如某次大考整体偏难导致全班低分过于敏感MAE 更能反映平均偏差。EarlyStopping 是 LSTM 训练里最值得设的参数成绩预测数据量少训练到过拟合是常态它的价值是停在验证集最好的位置并回滚权重。from tensorflow.keras.callbacks import EarlyStopping model.compile(optimizerAdam(learning_rate0.001), lossmse, metrics[mae]) early_stop EarlyStopping(monitorval_loss, patience15, restore_best_weightsTrue) history model.fit( X_train_scaled, y_train_scaled, validation_data(X_val_scaled, y_val_scaled), epochs100, batch_size16, callbacks[early_stop], verbose1 )这里几个参数按经验给参考参数建议值说明LSTM 第一层 units64时间模式提取LSTM 第二层 units32多特征融合Dropout0.2防止过拟合batch_size16小样本下梯度更稳learning_rate0.001Adam 默认值patience15早停耐心越小越保守batch_size 对成绩预测这种小样本很敏感设 128 会让每个 batch 里学生多样性不够梯度方向不稳设 4 则统计噪声太大收敛慢。epochs 给到 100 甚至 200 都行反正早停兜底实际通常在 30 到 50 个 epoch 内就触发。训练完看 historyval_loss 先降后升说明早停生效如果从头到尾都在升先查数据顺序和归一化再调结构。4.3 预测结果还原与曲线绘制横坐标太密集怎么处理模型输出的 y 是 0–1 之间的归一化值要还原成真实分数才能拿去解读。预测与反归一化如下y_pred_scaled model.predict(X_test_scaled) y_pred scaler_y.inverse_transform(y_pred_scaled).ravel() y_test_real scaler_y.inverse_transform(y_test_scaled.reshape(-1, 1)).ravel()inverse_transform 时经常有人传了形状不对的数组把 y 先 reshape(-1, 1) 再传即可这也是最常见的 python 类型转换坑。画出真实与预测曲线时样本一多 x 轴刻度就会挤成一团我把刻度旋转一下再显示import matplotlib.pyplot as plt plt.figure(figsize(12, 5)) plt.plot(y_test_real, label真实成绩, linewidth2) plt.plot(y_pred, label预测成绩, linewidth2) plt.title(LSTM 多变量预测成绩测试集对比) plt.legend() plt.xticks(rotation45) # 横坐标太密集时旋转避免叠成一团 plt.grid(alpha0.3) plt.show()画图只做直观确认数值指标用 MAE 和 RMSE 才有说服力。成绩 0–100 分制下MAE 在 5 分以内算是能落地8 分以上基本等于白预测回去检查特征和窗口。5. LSTM 预测成绩的五条踩坑记录现象、原因、解决5.1 loss 不降或直接变 NaN现象训练一开始 loss 就是 nan或者几百步都不降。原因多数是数据里带 NaN 或无穷大MinMaxScaler 处理不了其次是学习率太大梯度爆炸。成绩表里某个学生缺考、周测缺勤填充方式不对就会留下空值。解决清洗时先 df.isna().sum() 查空值用前向填充或班级均值填充再把学习率从 0.001 降到 0.0001 试。若仍然 NaN检查是否有一个特征的量纲异常大比如排名填了 999scaler 之前先做去极值。5.2 训练集表现好、测试集一塌糊涂现象训练 loss 很低验证 loss 也不差测试 MAE 突然飙高。原因最常见的是数据泄漏——归一化在整份数据上 fit或者切分时把未来数据混进了训练集其次是按时间切分但学生重叠模型只是在“续写”见过的学生。解决严格先切分再 fit scaler测试数据连 min/max 都不许碰如果要预测新学生按 student_id 切分并验证跨学生效果。这个坑最隐蔽也是很多人觉得 LSTM 是黑匣子的主要原因。5.3 预测曲线整体滞后一拍现象预测曲线和真实曲线形状几乎一样但整体向右平移了一个点。原因模型学到的其实是“把上一周的分数抄下来”而不是“预测下一周的变化”。当目标序列高度自相关、输入特征变化不充分时LSTM 的最小化损失策略就是输出接近上一步的值。解决加入更有预测力的特征比如作业完成率的变化量、复习时长增量把目标从原始分数改成差分让模型预测变化而不是绝对值或者把预测目标改为未来两周的均值降低单次噪声。5.4 报错 expected ndim3, found ndim2现象第一次跑 fit 时直接报维度错误说期望三维输入但拿到了两维。原因LSTM 输入要求三维而 X_train 在切分后可能是二维忘了把时间步维度保留。常见于用 for 循环组装特征时把时间维压掉了。解决检查 X_train.shape期望是 (样本数, n_steps, n_features)。如果只有两维说明 n_steps1 或者窗口切片写错回到 make_sequences 重新切并在模型前面加一句 assert len(X_train_scaled.shape) 3 防呆。5.5 小样本下每生只有十几次记录怎么做都过拟合现象训练 loss 降到接近 0Dropout 加了也没用。原因LSTM 参数动辄几千上万而一个学生可能只有 12 周记录去掉窗口后只剩几个样本模型直接把训练样本背下来。解决一是把多个班、多学期数据合并扩大样本二是缩小模型到单层 LSTM 16 个单元三是用 GRU 替代 LSTM它参数更少在短序列上往往更抗过拟合。先跑通再谈精度小数据硬上大模型是成绩预测最常见的翻车现场。6. 让成绩预测真正上考场滚动验证与朴素基线对照6.1 滚动多步验证别只测一步单步预测容易给人“模型很准”的错觉每次都给真实历史窗口模型只猜下一个点误差自然小。我现在的习惯是训练好后做滚动验证——给定前 n_steps 周数据预测下一周再把预测值当真值拼进窗口预测再往后一周直到把整个测试期推完。这样暴露出来的累积误差才是真实使用场景里的误差。def rolling_predict(model, X_start, n_predict, scaler_X, scaler_y): window X_start[0].copy() # 第一个窗口 preds [] for _ in range(n_predict): next_pred model.predict(window[np.newaxis, :, :], verbose0) pred scaler_y.inverse_transform(next_pred)[0, 0] preds.append(pred) new_step np.concatenate([window[1:], next_pred], axis0) window new_step return preds滚动预测里每步都会把误差带进去累积几步之后曲线往往就飘了。如果你的模型滚动三步误差已经不可接受说明它只适合做短周期预警不适合做期末长周期预测。6.2 和“用上次成绩当预测”的基线比一比判断 LSTM 值不值得用最硬的标准是和一个朴素基线对照直接用该学生上一次考试分数作为本次预测算 MAE。LSTM 的 MAE 如果压不过这个基线说明多变量特征没有提供增量信息再调参也是白费。我踩过这个坑之后每次建模第一步都是先跑基线再做 LSTM。成绩预测方向最终能不能投入教学管理看的是滚动误差和基线差值而不是训练集那条漂亮的下降曲线。先跑通最小脚本再用滚动验证和基线对照去质问结果只有当多变量输入真的带来增益时LSTM 才算在成绩预测里站住了脚。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑