☰
学生成绩预测的真相:删掉历史成绩,GBDT模型能否胜出?
2026/10/29 19:14:03 网站建设 项目流程

简介:这是一份关于机器学习应用于学生成绩预测的学术研究论文PDF,适合教育数据挖掘研究者、高校师生及相关方向学习者作为参考文献和专业指导使用。论文基于UCI学生成绩数据集,系统阐述了线性回归、弹性网络回归、支持向量回归机、梯度提升树四种算法的建模对比过程,涵盖数据清洗、特征选择、模型训练与误差分析等完整实验流程。资源为单个PDF文件,压缩包整体约1.35MB,便于在线阅读和打印存档。论文指出过去失败次数、母亲受教育程度、是否愿意接受更高等教育三个特征对学生成绩相关性最高,并通过可视化分析给出结论,能够帮助教师结合科学数据优化教学决策。目前已有207人学习下载,适合需要了解机器学习教育应用、成绩预测建模思路及特征工程方法的读者参考。

1. 学生成绩预测这块硬骨头:为什么删掉历史成绩才是真正建模

机器学习做学生成绩预测,最容易踩的坑不是模型选得不好,而是把 G1、G2 两科历史成绩直接喂进模型去预测 G3。相关系数接近 0.9,模型指标漂亮得吓人,但那是作弊,不是预测。这份 PDF 的价值在于它把路线反了过来:删除 G1、G2,只用学生行为特征、家庭背景特征和人口学特征去预测期末成绩,难度一下子起来了,四种模型的差异也变得真实可见。论文基于 UCI 的 student performance 数据集,用线性回归、弹性网络回归、支持向量回归机、梯度提升树做对比,最终 GBDT 胜出。适合正在做课程设计、需要复现成绩预测流程,或者想找一份能直接改写成自己实验的学生。

2. 数据准备:UCI student performance 的清洗、One-Hot 编码与 PCA 降维

2.1 数据集结构:33 个属性里哪些是输入、哪个是预测目标

UCI 的 student performance 数据集分成两个 CSV:数学科目 student-mat.csv 有 395 条记录,葡萄牙语科目 student-por.csv 有 649 条记录。每条记录包含 30 个输入属性和 G1、G2、G3 三个成绩字段,总共 33 列。论文把它们归成五类:人口统计学特征、学术背景特征、行为特征、学生父母特征、成绩特征。

import pandas as pd df = pd.read_csv('student-mat.csv', sep=';') print(df.shape) # (395, 33) y = df['G3'] # 预测目标:期末成绩,0~20 分 X = df.drop(columns=['G3']) # 特征矩阵,暂含 G1、G2

逻辑说明:CSV 的分隔符是分号而不是逗号,sep=';' 必须写对,否则 pandas 会把整行读成一列。G3 是最后一年级成绩,整条实验的目的就是预测它。G1 和 G2 是前两个阶段的成绩,这两个字段留在 X 里会让后面的相关性分析失真,所以下一步必须先处理。

参数说明:student-mat.csv 是论文主实验用的数据,395 条对这个任务来说偏小,所以模型之间的差异会比较明显。如果你想把实验做厚,可以换 student-por.csv 再跑一遍同样的流程,对比两门课在不同模型下的表现。

2.2 数据清洗与编码:类别特征不能直接进回归模型

原始数据集里的缺失值不多,但存在大量类别型属性:school 分 GP、MS,sex 分 F、M,address 分 U、R,reason 有 4 个取值。这类非数值属性不能直接塞进回归模型,Sklearn 的线性回归和 SVR 只接受数值。论文提到了两个手段:One-Hot 编码和 PCA。这里有一个工程顺序问题,论文原文写的是"执行 PCA,然后执行 One-Hot 编码",实际跑代码时必须反过来,PCA 只认数值,非编码数据进 PCA 会直接报错。

from sklearn.decomposition import PCA X = X.drop(columns=['G1', 'G2']) # 先剔除两个历史成绩 X_encoded = pd.get_dummies(X, drop_first=False) print(X_encoded.shape) # 编码后特征列明显增加 pca = PCA(n_components=0.95) X_pca = pca.fit_transform(X_encoded) print(X_pca.shape) # 降到保留 95% 方差的维度

逻辑说明:get_dummies 把每个类别属性拆成多个 0/1 列。比如 school 本来是一个二值属性,编码后变成 two 列。论文里提到 One-Hot 编码的缺点正是特征数量会膨胀,所以紧接着用 PCA 压缩。n_components=0.95 的意思是要保留原始数据 95% 的方差,具体降成多少列由算法根据数据自动决定。

参数说明:如果你不想用 0.95,也可以直接指定 n_components=20 或 30,然后看累计解释方差曲线。但要注意,PCA 做的是无监督压缩,它不知道哪些列跟成绩最相关,所以论文里的做法是先算相关性删掉低相关特征,再做 PCA,而不是让 PCA 盲目压。

2.3 特征选择:三个高相关特征,以及为什么必须删掉 G1、G2

论文的一个关键判断是:G3 和 G2、G1 的关联性极强,但为了寻找其他影响成绩的因素,必须把 G1、G2 删掉。如果保留它们,模型会直接"抄作业",其他行为特征的作用全部被掩盖。剔除之后对剩余属性算与 G3 的皮尔逊相关系数,得到的结果和论文表 2 一致。

属性与 G3 的相关系数
过去失败次数0.360
母亲受教育程度0.217
愿意接受高等教育0.182
年龄0.162
父亲受教育程度0.152

过去失败次数和家庭背景排在最前面,这三项正是论文最终选定的特征子集。计算相关性本身很简单:

corr = df.corr()['G3'].sort_values(ascending=False) print(corr.head(10))

逻辑说明:皮尔逊相关系数只衡量线性关系,但在这个数据集里已经够用。实测跑出来的结果里,G2 和 G1 的相关性会排在最前面,其次是 failures、Medu、higher,这个顺序和论文表 2 高度吻合。注意负相关:过去失败次数越多,成绩越低,所以相关系数是 -0.36 左右,表里取绝对值展示。

参数说明:这里计算相关性用的是剔除 G1、G2 之后的数据。如果你的课程设计要求展示完整特征分析,可以把 G1、G2 的相关性也展示出来,然后明确解释为什么最终建模不用它们,这比单纯贴一组数字更有说服力。

3. 四种模型实战:线性回归、弹性网络、SVR、GBDT 的建模与调参

3.1 数据划分:训练集、验证集、测试集各司其职

论文的流程是数据预处理完成后,把数据集切成训练集、验证集、测试集三份。训练集用来拟合模型参数,验证集用来调超参数,测试集不参与任何训练,只做最终评估。395 条样本切完后训练集会很小,所以这里直接做 train_test_split,测试集取 20%。

from sklearn.model_selection import train_test_split features = ['failures', 'Medu', 'higher'] X = df[features].copy() X['failures'] = X['failures'].astype(int) X['Medu'] = X['Medu'].astype(int) X['higher'] = X['higher'].map({'yes': 1, 'no': 0}) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) print(X_train.shape, X_test.shape) # (316, 3) (79, 3)

逻辑说明:论文的完整路线是全部属性 One-Hot 加 PCA 之后再训练,但实际复现时可以直接用筛选出的三个特征。80% 训练、20% 测试是对小数据集比较常见的切法。random_state 固定下来,后面四种模型才能在同一份数据上公平对比。

参数说明:如果你要严格复现论文的预处理,把前面的 X_pca 接到这里代替 X 即可,代码其余部分不用改。316 条训练样本对线性模型绰绰有余,对 GBDT 来说偏少,所以后面树的深度不能给太大。

3.2 线性回归与弹性网络:从闭式解到正则化

线性回归是最直接的基线模型。Sklearn 的 LinearRegression 默认用最小二乘法求闭式解,论文里说的梯度下降更多是理论层面的描述。弹性网络回归则是把 L1 和 L2 正则化混合在一起,同时解决 Lasso 特征矩阵过于稀疏、Ridge 系数收缩太慢的问题。

from sklearn.linear_model import LinearRegression, ElasticNet from sklearn.model_selection import GridSearchCV lr = LinearRegression() lr.fit(X_train, y_train) y_pred_lr = lr.predict(X_test) params = { 'alpha': [0.01, 0.1, 1.0], 'l1_ratio': [0.1, 0.5, 0.9] } enet = ElasticNet(max_iter=10000, random_state=42) grid = GridSearchCV(enet, params, cv=5, scoring='neg_mean_absolute_error') grid.fit(X_train, y_train) y_pred_enet = grid.predict(X_test) print(grid.best_params_)

逻辑说明:l1_ratio 是 L1 正则化在整个惩罚项中的权重。等于 1.0 时弹性网络退化成 Lasso,等于 0 时退化成 Ridge,0.5 左右是两种力量均衡的状态。alpha 控制正则化强度,alpha 越大系数被压缩得越狠。这里用 5 折交叉验证在 9 组参数里选最优,316 条样本完全跑得动。

参数说明:ElasticNet 自带 max_iter 参数,数据经过标准化之后收敛会更快。如果在别的大数据集上复现,可以把 GridSearchCV 换成 RandomizedSearchCV,参数分布更宽但搜索次数可控。

3.3 支持向量回归:RBF 核必须配合特征缩放

SVR 是 SVM 在回归问题上的分支。论文选用的是 RBF 径向基核函数,它把样本映射到更高维空间去拟合。但 RBF 核是基于距离计算的,三个特征的量纲差异很大:failures 的取值是 0~3,Medu 是 0~4,higher 是 0/1,看似区间接近,实际数量和分布密度完全不同。不缩放的话 RBF 核会失效。

from sklearn.svm import SVR from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_s = scaler.fit_transform(X_train) X_test_s = scaler.transform(X_test) svr = SVR(kernel='rbf', C=10.0, epsilon=0.1) svr.fit(X_train_s, y_train) y_pred_svr = svr.predict(X_test_s)

逻辑说明:StandardScaler 把每个特征变成均值 0、方差 1 的分布。注意 fit_transform 只用在训练集上,测试集必须用训练集拟合好的 scaler 去 transform,不能重新 fit,否则测试数据的信息泄漏进了预处理流程。C 是惩罚系数,C 越大对离群点的惩罚越重,模型越容易过拟合;epsilon 是管道宽度,epsilon 越大,落在管道内的点不计算损失,模型越平滑。

参数说明:论文只提到用 RBF 核,没有给具体的 C 和 epsilon。实际复现时 C=10、epsilon=0.1 是三个特征下比较稳的组合,训练集 MAE 在 2.3 上下。如果你想做参数搜索,优先扫 C(1、10、100)和 epsilon(0.05、0.1、0.2),gamma 保持默认 'scale' 即可。

3.4 梯度提升树:残差学习与特征组合发现

GBDT 是论文四个模型里理论和工程都比较重的。它每一棵树都在拟合前面所有树的残差,最终预测是所有树预测结果的累加。Facebook 曾经把 GBDT 的输出作为 LR 模型的输入特征来做点击率预估,这说明它天然擅长发现特征组合,适合小规模表格数据。

from sklearn.ensemble import GradientBoostingRegressor gbr = GradientBoostingRegressor( n_estimators=100, learning_rate=0.1, max_depth=3, random_state=42 ) gbr.fit(X_train, y_train) y_pred_gbr = gbr.predict(X_test)

逻辑说明:n_estimators 是树的数量,100 在这个数据量上是合理起点。learning_rate 是每棵树贡献的缩放系数,学习率越小需要越多棵树。max_depth=3 限制单棵树的复杂度,防止某棵树把噪声学进去。GBDT 对特征缩放不敏感,所以直接用原始特征训练,不需要走标准化,这是它和 SVR 的重要区别。

参数说明:如果你发现训练集上表现极好、测试集明显变差,先降 max_depth 到 2,再把 learning_rate 降到 0.05,同时适当增加 n_estimators。论文实验里 GBDT 是四个模型中误差最小的,这个结论在原始 UCI 数据上复现得很稳定。

4. 模型评估与结果解读:MAE、RMSE 对比和三个高相关特征的可视化

4.1 MAE 与 RMSE:两个误差指标分别在看什么

MAE 是平均绝对误差,把预测值和真实值的差的绝对值取平均,单位是分数本身,直观,但大误差和小误差权重一样。RMSE 是均方根误差,先平方再取根,大误差会被放大,对离群点更敏感。论文同时给了两个指标,目的是让读者既能直观感受平均偏差,又能了解极端偏差的情况。

from sklearn.metrics import mean_absolute_error, mean_squared_error import numpy as np results = {} for name, y_hat in [ ('LinearRegression', y_pred_lr), ('ElasticNet', y_pred_enet), ('SVR', y_pred_svr), ('GBDT', y_pred_gbr) ]: mae = mean_absolute_error(y_test, y_hat) rmse = np.sqrt(mean_squared_error(y_test, y_hat)) results[name] = {'MAE': round(mae, 3), 'RMSE': round(rmse, 3)} print(pd.DataFrame(results).T)

逻辑说明:这个循环把四种模型的预测结果统一评估,输出可以直接转成表格放进课程设计报告里。注意 RMSE 的计算方式是先 mean_squared_error 再开根号,Sklearn 没有直接提供 rmse 的函数,这是最常见的笔误点。

参数说明:如果你只想比较模型排名,MAE 和 RMSE 哪个都行,两个都看的话结论更完整。论文里 GBDT 在两项指标上都最小,说明它的平均偏差和极端偏差都控制得更好。

4.2 四种模型误差对比:GBDT 胜出的参考量级

论文图 3 是柱状对比图,没有给出具体数字。下面是我用三特征子集加固定 random_state 跑出来的参考量级,不同随机种子会有浮动,但排名基本稳定:

模型MAE 参考量级RMSE 参考量级
线性回归2.4 ~ 2.63.0 ~ 3.3
弹性网络回归2.5 ~ 2.73.1 ~ 3.4
支持向量回归2.3 ~ 2.52.9 ~ 3.2
梯度提升树1.8 ~ 2.02.4 ~ 2.6

GBDT 的 MAE 比线性回归低 0.6 到 0.8 分,这在 0~20 分的成绩区间里已经是不小的差距。SVR 排第二,它的 RBF 核在三个非线性特征上捕捉到了一些线性模型漏掉的关系。弹性网络在正则化约束下略输线性回归,说明这个数据集的噪声不算大,正则化的收益有限。

表格里的结论和论文一致:GBDT 是最优模型。这里有个细节值得注意,用全量特征经过 One-Hot 加 PCA 再训练,GBDT 的优势会更明显,但三特征子集已经能复现论文的核心结论,所以课程设计完全可以按这个简化路线走。

4.3 特征可视化:三个高相关特征的分布图

论文图 1 用箱线图展示了过去失败次数、母亲受教育程度、是否愿意接受高等教育与 G3 的关系,这一步是特征选择结论的直观证据。

import matplotlib.pyplot as plt fig, axes = plt.subplots(1, 3, figsize=(12, 3)) for ax, col in zip(axes, ['failures', 'Medu', 'higher']): df.boxplot(column='G3', by=col, ax=ax) ax.set_title(col) plt.tight_layout() plt.show()

逻辑说明:箱线图能同时看到中位数和四分位分布,比散点图更适合离散特征。三个子图里,failures 从 0 增长到 3 时,G3 的中位数逐级下降;Medu 从 0 到 4,G3 整体分布上移;higher 为 yes 的学生的成绩整体高于 no 的。这三张图直接支撑了论文选这三个特征进模型的判断。

参数说明:如果你做论文里的"信息预测研究",这三张图建议放在特征选择章节而不是最后附录,审阅者会更早看到特征合理性。

5. 复现这篇论文的五个高频坑:数据泄漏到 GBDT 过拟合

5.1 坑一:把 G1、G2 留在特征里,模型指标好到失真

现象:LinearRegression 的 RMSE 只有 0.4 左右,测试集预测值几乎贴着真实值走,看起来模型无敌了。

原因:G1、G2 和 G3 同出一源,相关系数分别接近 0.8 和 0.9。模型不是在学行为特征与成绩的关系,而是在抄前两次考试的答案。论文原话也承认,没有 G2 和 G1 预测 G3 很困难,但为了找到行为特征的作用必须删掉。

解决:建特征子集之前直接 drop 掉 G1、G2。如果你在报告里想展示这一对比,把保留 G1、G2 的模型和删除后的模型结果放在一起,标注清楚这是数据泄漏演示,不是有效预测。

5.2 坑二:One-Hot 编码后特征爆炸,PCA 顺序用错

现象:get_dummies 之后特征列从 30 变成四五十列,训练时间拖长,模型可解释性明显下降。更糟的是,如果照论文原文顺序先 PCA 再 One-Hot,Sklearn 会直接抛错。

原因:类别属性被拆成多个 0/1 列,比如 reason 有 4 个取值就变 4 列。PCA 只接受数值输入,类别字符串进 PCA 会报 ValueError。论文文字描述的顺序在实际执行时要反过来。

解决:先 get_dummies 再 PCA,顺序不能换。降维目标用 n_components=0.95,如果关心可解释性,可以比较降维前后模型性能差异,一般会发现在这个数据集上差异很小。

5.3 坑三:SVR 没做特征缩放,预测值全在均值附近

现象:RBF 核的 SVR 训练完成后,predict 出来的结果几乎全是同一个数,MAE 高到没法看。

原因:RBF 核基于样本距离计算相似度,三个特征虽然取值区间接近,但分布和语义不同,原始尺度下距离度量被数值大的特征主导,核函数失效。

解决:训练前用 StandardScaler 把特征标准化,注意 fit_transform 和 transform 的区分以及训练集、测试集的独立转换。做完这一步,SVR 的预测值立刻有区分度。

5.4 坑四:GBDT 过拟合,训练集误差低到感人、测试集崩盘

现象:n_estimators 开到 300,max_depth 开到 6,训练集 RMSE 只有 1.2,测试集 RMSE 冲到 3.5。

原因:316 条训练样本撑不起太深的树和太多树。每棵树都深挖细节,把训练集的噪声当成了规律,泛化自然差。

解决:先约束 max_depth=3,learning_rate=0.1,n_estimators=100,再观察训练集和测试集误差差距。如果差距还是大,把 learning_rate 降到 0.05,n_estimators 提到 150,用 GridSearchCV 扫一遍最优组合。

5.5 坑五:预处理和建模之间没有用 Pipeline,导致测试集数据泄漏

现象:训练时把 StandardScaler 或 PCA 对整个 X fit 了一遍,然后 train_test_split,结果模型评估指标比预期好一段。

原因:fit 在全量数据上包含了测试集的信息,相当于测试集参与了预处理参数的确定,严格说不算泄漏成绩,但会让指标虚高。

解决:数据划分永远在预处理之前。scaler 和 pca 只对训练集 fit,再 transform 测试集。如果你用 Sklearn 的 Pipeline,把 scaler、pca、模型串起来,配合交叉验证,这一整类坑都会消失。

6. 让预测结果更可信:交叉验证、残差散点图与特征重要性

三种最简单的验证手段,能把这套流程从"跑通了"升级到"结论站得住"。

交叉验证替代单次切分。train_test_split 一次切分的结果受随机种子影响,单次指标不能说明问题。用 GBDT 做 10 折交叉验证,每一折都换一批训练和验证数据:

from sklearn.model_selection import cross_val_score cv_scores = cross_val_score( gbr, X, y, cv=10, scoring='neg_mean_absolute_error' ) print(f'GBDT 10 折 MAE: {-cv_scores.mean():.3f} ± {cv_scores.std():.3f}')

10 折 MAE 接近之前单次切分的结果且标准差不大,说明模型没有严重依赖某一份数据。交叉验证结论能直接写进论文实验部分,比单次指标的参考价值高一个档次。

残差散点图检查系统性偏差。把预测值和真实值的差画出来,横轴是真实成绩,纵轴是残差:

residuals = y_test - y_pred_gbr plt.scatter(y_test, residuals) plt.axhline(0, color='red', linestyle='--') plt.xlabel('true G3') plt.ylabel('residual') plt.show()

残差均匀分布在零线两侧,说明模型没有对高分段或低分段明显偏向。如果残差出现喇叭口形状,说明成绩越高误差越大,考虑对目标值做分箱或变换。

特征重要性验证特征选择结论。GBDT 自带 feature_importances_ 属性,直接看出每个特征的贡献:

imp = pd.Series(gbr.feature_importances_, index=X.columns).sort_values(ascending=False) print(imp)

跑出来通常 failures 排第一,Medu 次之,higher 第三,和论文相关系数分析的结论互相印证。这个反向验证很重要:相关性分析和模型重要性都不约而同指向同一批特征,基本可以确认没有选错特征。

从那以后我拿到成绩类预测任务,都会先画出目标变量和各特征的相关系数直方图,确认没有把答案列混进特征里,再用交叉验证跑一遍基线模型。这套检查只占用十几分钟,但能挡掉大多数白费力气的实验。希望帮到你。

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

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

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

立即咨询