资讯中心

学生成绩预测实战:线性回归、支持向量回归与神经网络对比与避坑指南

📅 2026/9/26 8:48:48
学生成绩预测实战:线性回归、支持向量回归与神经网络对比与避坑指南
简介面向数据分析与教育数据挖掘学习者压缩包内含基于 Python 的完整成绩预测项目覆盖线性回归、SVM 与神经网络三种算法可用于课程设计、实验对比与入门实战。包内共14个文件包括4个 py 脚本、5个 npy 数据文件与5个 txt 说明文档整体大小12.25MBpy 脚本负责建模与预测npy 存放特征和标签数据txt 记录数据说明与结果输出结构便于对照学习。内容从数据预处理、特征构造到模型训练与评估均有完整代码实现并附有数据转换与辅助工具脚本可清晰理解训练集/测试集划分、标准化及交叉验证流程通过比较 MSE、R² 等指标能直观感受三种算法在不同数据条件下的表现为后续优化提供依据。已有360人学习下载适合希望快速上手 Python 机器学习建模的读者。1. 学生成绩预测不是玄学一次对比把三个模型的路数摸清期末考试前辅导机构想根据半学期的签到、作业和测验数据预判哪个学生需要补课学校也想在补考名单出来前提早介入。学生成绩预测这个经典入门项目目标就是用历史学习行为特征回归出期末分数常见路线是线性回归、SVM、神经网络三选一。这篇笔记把三条路都用Python完整跑一遍数据怎么清洗、线性回归和SVM基线怎么做、前馈神经网络怎么调、五个高频坑在哪最后给你一套可以直接复现的对比结果。适合刚学完机器学习基础想找项目练手的人也适合需要把成绩预测落到课程设计或业务报告里的从业者——看完能照着复现并判断这个方向值不值得投入。2. 成绩预测先看数据特征选择与归一化是第一个隐形坑2.1 选特征31列不能全塞进模型最常被拿去讲成绩预测的公开数据是UCI的Student Performancestudent-mat.csv里395行记录、31个字段。新手容易犯的毛病是把31列一股脑喂给模型想着「特征越多模型越强」。实际结果是维度膨胀、训练变慢线性回归的系数也变得难以解释。真正跟期末分强相关的其实是一小撮studytime每周学习时间、failures过往挂科次数、absences缺勤次数、G1和G2前两次阶段考成绩、goout社交频率、health健康状况。首次做模型先从这7个数值型行为特征入手最稳。性别、学校、住址这些类别列不是没用而是需要先看分布再决定是否做哑变量编码。在小样本上哑变量一多就容易把自由度吃光模型方差变大。我在实际项目里的原则是第一版模型只用容易获取、含义清楚、缺失率低的数值特征跑通之后再考虑加类别特征做对比实验。相关性的判断也值得提前做。G1、G2与期末成绩G3的相关系数通常能到0.8上下它们是预测期末分最有力的信号studytime、failures、absences属于过程性行为特征真实业务里老师手上正好有这些记录。goout和health的相关性很弱但先保留不删因为在SVM的rbf核和神经网络里弱特征也可能通过交互效应对预测产生贡献。只有在特征数量超过几十个、模型明显过拟合时才需要正式做一轮特征筛选那时可以用Lasso或者随机森林重要性排序。2.2 归一化与数据集划分fit_transform只能用于训练集新手必踩的坑在这一步先写一句scaler.fit_transform(X)把全量数据标准化再划分训练测试集然后SVM跑出个漂亮得离谱的分数。问题在于测试集的均值和方差参与了训练数据的归一化计算这就是数据泄漏。在成绩预测这类小样本项目里泄漏会让模型在测试集上的指标虚高但一上真实新数据就露馅。正确的顺序是先用train_test_split切分再只对训练集做fit测试集和以后所有新样本都只调transform使用训练集统计出来的同一组均值方差。import pandas as pd from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler df pd.read_csv(student-mat.csv, sep;) features [studytime, failures, absences, G1, G2, goout, health] X df[features] y df[G3] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)注意这两行关键调用fit_transform只在训练集上执行transform在测试集上执行。fit意味着计算并保存这组特征的均值和标准差transform是拿这组统计量对数据进行标准化。如果测试集自己fit测试集的统计信息会流入特征等于让模型在考试时偷看了答案这是SVM测试指标虚高最常见的来源。StandardScaler把每个特征变成均值0、方差1的量纲这是SVM和神经网络的基本要求。线性回归不做归一化也能跑但因为回归目标G3的分数范围是0到20而absences可以到75以上量纲差异会让系数大小失去可比性归一化之后再看coef_才有意义。另一个选项MinMaxScaler把数据压到0到1适合特征分布均匀的场景当特征里有明显离群值比如某个学生缺勤次数爆表StandardScaler更抗干扰。两种都能用关键是训练集fit、测试集transform这条铁律不能破。2.3 相关性探查与缺失值处理别让0分和空值带歪模型进入模型之前我会先跑一个相关性汇总确认手里没有明显的废牌。corr df[features [G3]].corr() print(corr[G3].sort_values(ascendingFalse))输出中G2、G1对G3的相关性最高goout通常是负相关health接近0。这组数字帮我们建立对数据的直觉也能在调参时快速定位问题。假如某个特征显示NaN说明csv读取时发生了类型解析问题需要显式转成数值类型。成绩数据里还有两类脏数据要处理一是G3列存在0分代表挂科或缺考二是缺勤次数出现极端值。0分不是异常它是真实业务信号不能随便删。缺勤的极端值同样保留因为SVR的epsilon管带机制本身对离群点不敏感这正是选它的理由。缺失值处理上成绩数据的缺失基本集中在某几次测验没参加导致的空值常见做法是用中位数而不是均值去填。G1、G2这类偏态分布的字段均值容易被极端值拉偏中位数更贴近典型水平。df[features] df[features].apply(pd.to_numeric, errorscoerce) df[features] df[features].fillna(df[features].median()) print(df[features].isna().sum().sum())apply(pd.to_numeric, errorscoerce)把读取阶段混入的字符串统一转成数值转不动的变成NaNfillna(med)用每列中位数填充。如果原始数据本身是干净的这两行不会改变任何值如果存在脏数据这两行能避免后续模型直接报错或静默吞掉整行。到这里X和y已经是干净的数值矩阵且训练测试划分和归一化都妥了。接下来三个模型共用同一份X_train_scaled、X_test_scaled对比结果才公平。3. 两个快速基线线性回归和SVM先把分数预测到1附近3.1 线性回归用系数看懂每个特征的作用方向线性回归是成绩预测项目里最直观的模型。它假设期末成绩是特征加权和权重就是coef_。把这个模型跑通的意义不在于精度而在于建立一条可解释的基线你能直接跟老师说G2的系数最大说明期中成绩是期末分的最强信号failures系数为负说明挂科次数越多期末分趋势往下走。这一条结论业务方一听就懂。from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score lr LinearRegression() lr.fit(X_train_scaled, y_train) y_pred_lr lr.predict(X_test_scaled) mae_lr mean_absolute_error(y_test, y_pred_lr) rmse_lr mean_squared_error(y_test, y_pred_lr, squaredFalse) r2_lr r2_score(y_test, y_pred_lr) print(fMAE{mae_lr:.3f}, RMSE{rmse_lr:.3f}, R²{r2_lr:.3f})这段代码就是标准的sklearn三步fit建模、predict预测、三个指标评估。MAE是预测分与真实分绝对差之和的平均可以直接理解成「平均猜偏多少分」。RMSE先平方再开方对误差大的个别样本更敏感如果RMSE明显大于MAE说明有个别学生被预测得特别离谱。R²衡量模型解释了多少比例的目标方差越接近1越强。在这个7特征、395行的数据上线性回归的典型水平是R²约0.80、MAE在1.2分上下。记牢这个数字后面SVM和神经网络如果打不过它说明数据或者模型设置出了问题而不是模型本身「不行」。如果想把31个原始字段都用上又不想要一堆无用特征常见做法是把线性回归换成Lasso。Lasso是带L1正则的线性回归训练时自动把不重要的特征系数压到0本质上就是边建模边做特征筛选。它的alpha参数控制压缩强度alpha越大系数越稀疏。当字段数超过几十个时Lasso比手写特征选择更省事。3.2 支持向量回归SVR用rbf核抓非线性关系SVM的分类版本大家熟但成绩预测要的是连续分数对应的是支持向量回归也就是SVR。SVR的思路和线性回归不同它不追求每个训练点都尽量贴近回归线而是允许样本落在以预测线为中心的「管带」内不罚超出的部分才计入误差。所以SVR对离群值不敏感这正好适合成绩数据里那些缺勤爆表、G3得0分的边缘样本。from sklearn.svm import SVR svr SVR(kernelrbf, C10.0, epsilon0.5, gammascale) svr.fit(X_train_scaled, y_train) y_pred_svr svr.predict(X_test_scaled) mae_svr mean_absolute_error(y_test, y_pred_svr) rmse_svr mean_squared_error(y_test, y_pred_svr, squaredFalse) r2_svr r2_score(y_test, y_pred_svr) print(fMAE{mae_svr:.3f}, RMSE{rmse_svr:.3f}, R²{r2_svr:.3f})三个核心参数的直觉要建立起来。C是「对误差的容忍度」C越大模型越努力拟合每个训练点C越小越宽容给离群值留余地默认值1.0在这个数据上偏保守。epsilon是管带宽度它越小要求的拟合精度越高但太小会贴着噪声走造成过拟合0.5对0到20的成绩范围是个合理起点。gammascale表示让sklearn根据特征数量自动估算gamma它决定单一样本的影响半径gamma越大每个样本的影响范围越小决策边界越曲折反之越平滑。运行时的一个重点SVR对归一化的敏感度远高于线性回归。rbf核在计算距离时如果特征量纲不一致量纲大的特征会完全统治结果所以前面2.2节的scaler处理是SVR起效的前提。另外SVR的训练走的是SMO算法不是神经网络的梯度下降路线网上偶尔能看到「SVM梯度下降」的说法那通常是用hinge loss加梯度优化去近似SVM正规的sklearn实现并不这么干做对比实验时别被这个概念带偏。样本量方面SVR在几千条数据内训练很快这里395条更是一两秒的事。但数据量上了五位数后它的训练时间会明显拉长这也是SVR在大规模场景里逐渐被神经网络替代的原因之一。3.3 评估指标怎么读先看MAE再看R²三个模型对比时指标口径必须一致。我的习惯是先看MAE因为「预测成绩平均差1.0分」比「R²0.82」更容易跟业务方解释。RMSE用于排查极端误差RMSE比MAE大出一截说明某个低分或高分学生被预测得很离谱需要回头查脏数据。R²用于横向比较模型在整体解释能力上的差异在学术报告和课程设计中更常用。在student-mat这种样本量下SVR用rbf核通常比线性回归MAE低0.1到0.2分R²高个0.03左右。这个幅度已经能说明成绩数据里有非线性结构但不算夸张。如果SVR的指标跟线性回归一模一样甚至更差先检查是不是没有归一化就直接跑或者C、epsilon用了默认值没有调。顺便说一个选择倾向当MAE差距很小、R²也接近时我会优先选可解释性好的模型。线性回归虽然精度略低但你能把coef_直接列成一张表去解释业务逻辑SVM精度高一点却是个黑匣子需要额外做解释工具。这也是为什么教学场景里通常先用线性回归立住基线再上SVM和神经网络。4. 前馈神经网络用PyTorch把预测精度再压一档4.1 网络结构设计三层前馈为什么够用神经网络在成绩预测里用得最多的是前馈神经网络也叫多层感知机BP反向传播就是从它身上出来的。输入层吃特征向量隐藏层做非线性变换输出层吐一个连续分数。对这种几百行的小表格数据不需要上卷积神经网络或者Transformer一个两隐藏层的前馈网络已经能逼近绝大部分非线性关系。层数再深在395条样本上只会加速过拟合。隐藏层宽度从64到32递减符合信息逐层压缩的直觉。第一层64个神经元足够把7个特征映射到高维组合空间第二层32个神经元做进一步抽象输出层1个神经元输出没有接激活函数因为回归任务需要任意实数。这个结构是我在类似表格回归任务上的起手式不是最优解但稳定、好调、容易解释。为什么不用sklearn自带的MLPRegressor它能跑但封装太完整学习率、激活函数、权重初始化、随机种子这些关键行为被隐藏了出了问题只能换一组参数重新猜。用PyTorch写训练循环每个环节都能看到改结构、加Dropout、做早停都顺手。如果只是追求最快跑通MLPRegressor也是合法选择但想搞清楚网络在干什么、方便排错PyTorch更透明。4.2 训练循环把梯度下降的每一步摊开看import torch import torch.nn as nn from torch.utils.data import TensorDataset, DataLoader X_tr torch.FloatTensor(X_train_scaled) y_tr torch.FloatTensor(y_train.values) X_te torch.FloatTensor(X_test_scaled) y_te torch.FloatTensor(y_test.values) class ScoreNet(nn.Module): def __init__(self, n_features): super().__init__() self.net nn.Sequential( nn.Linear(n_features, 64), nn.ReLU(), nn.Linear(64, 32), nn.ReLU(), nn.Linear(32, 1) ) def forward(self, x): return self.net(x).squeeze(-1) model ScoreNet(X_train_scaled.shape[1]) optimizer torch.optim.Adam(model.parameters(), lr0.01) criterion nn.MSELoss() train_ds TensorDataset(X_tr, y_tr) train_loader DataLoader(train_ds, batch_size32, shuffleTrue) for epoch in range(200): model.train() epoch_loss 0.0 for xb, yb in train_loader: optimizer.zero_grad() pred model(xb) loss criterion(pred, yb) loss.backward() optimizer.step() epoch_loss loss.item() * len(xb) if (epoch 1) % 20 0: print(fepoch {epoch1}, loss {epoch_loss / len(train_ds):.4f})这段训练循环值得逐行讲。optimizer.zero_grad()清空上一步累积的梯度不清空的话梯度会在不同batch之间叠加导致更新方向错误model(xb)前向传播计算预测值criterion用MSE计算预测与真实的差距loss.backward()反向传播为每个参数算出梯度optimizer.step()让Adam沿梯度方向更新一次权重。batch_size32的意思是每32条样本计算一次梯度、更新一次权重。在整个395条训练数据上一个epoch约等于12个batch的更新。小batch带来的梯度噪声在表格数据上通常是好事能帮模型跳出局部最优点。lr0.01是Adam配小表格数据的常见起手值数据量小的时候它够用如果发现loss前几个epoch就冲到几十甚至nan第一件事把lr降到0.001。激活函数选ReLU而不是sigmoid或tanh是因为ReLU在反向传播时对正区间的梯度恒为1能有效缓解梯度消失网络深到三层以上时优势更明显。输出层不加激活函数是回归任务的关键一加就会把预测范围限制住比如套个sigmoid就只能输出0到1没法表示0到20的成绩。4.3 留一个验证集监控过拟合直接拿整个训练集硬训200轮最容易出现的不是不收敛而是过拟合epoch 50之后训练loss还在降测试MAE反而上升。解决办法是从训练数据里再切出20%当验证集每个epoch末尾在验证集上算一次loss验证loss连续上升而训练loss下降就是明确的过拟合信号。切分验证集没有泄漏风险因为它完全来自训练部分且norm参数用的是训练部分的统计量X_train_part, X_val, y_train_part, y_val train_test_split( X_train_scaled, y_train, test_size0.2, random_state1 )随后在训练循环中每个epoch结束用model.eval()切换到推理模式配合torch.no_grad()关闭梯度计算在验证集上算loss并记录当前最小的验证loss和对应权重。训练结束后取这一份权重作为最终模型而不是最后一步迭代的权重。这是早期停止的简化实现也是小样本神经网络最常见的「后悔药」——即使当时没设早停至少能把最好的那次权重捞回来。4.4 神经网络的真实上限赢多少取决于数据量说句实话在395条样本上神经网络赢SVR的幅度通常有限MAE大概从1.05压到0.95R²从0.84涨到0.86。它的优势要等数据量变大才明显。如果你手里有几千条包含签到、作业、在线学习行为的完整记录神经网络可以学到特征之间的高层交互这时候SVM和线性回归开始吃力反之只有几百条记录时神经网络参数多容易把训练集背下来泛化反而不如线性回归。遇到神经网络在测试集上表现反而低于SVR时先别急着加层数。回去看训练loss曲线如果训练loss低到0.05但测试MAE很高是过拟合加Dropout或者把隐藏层从64/32减到32/16如果训练loss一直降不下去才是学习率或网络结构的问题。方向上小样本先做减法大样本再做加法这是我调这类项目的一贯顺序。5. 三模型对比与避坑排查5个把结果带歪的常见翻车点5.1 归一化泄漏到全量数据SVM测试指标虚高现象SVM在测试集上的R²高得不像话甚至逼近0.98而线性回归只有0.80。原因scaler的fit_transform直接作用在X全量上划分后的X_train和X_test共享同一套均值方差。测试集的统计信息混进了训练特征SVM在预测时等于提前见过测试集分布。解决回到2.2节的铁律scaler只fit训练集测试和验证只用transform。做交叉验证时更规范的做法是sklearn的Pipeline把StandardScaler和模型包在一起让每一折都在自己的训练部分完成fit。这条排在翻车榜第一位因为它最隐蔽指标又最漂亮容易让人误以为自己调参水平很高。5.2 学习率太大loss直接变成NaN现象训练跑不了几个batchloss.item()输出nan后面所有epoch全是nan。原因lr0.01对Adam通常安全但特征没归一化或存在极端离群值时梯度可能瞬间爆炸。成绩数据里如果有人在某个特征上取值极端前向传播的中间激活值就可能溢出。解决先确认特征全部走完StandardScaler再把学习率降到0.001。如果降完还炸给训练循环加一行梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)把梯度的模长限制在1以内这是防止训练崩溃的保险丝属于基本功但不是默认选项。5.3 样本量几百条神经网络跑不过线性回归现象神经网络测试MAE是1.4线性回归是1.2训练越久效果越差。原因395条样本对三层前馈网络来说太少。模型容量大、样本少网络记住了训练数据里的噪声在测试集上泛化不住。解决先做减法隐藏层64/32减到32/16加Dropout(0.2)epoch砍到100。如果这样调整后测试指标仍然低于线性回归那就接受这个数据规模下非线性模型提升有限的结论。成绩预测项目里「模型复杂不等于结果更好」本身就是一条有价值的结果写在报告里比硬凑一个高分更有说服力。5.4 SVR的C和epsilon用默认值非线性能力被白白浪费现象SVR跑出来跟线性回归几乎一模一样甚至R²还更低。原因默认C1.0、epsilon0.1在rbf核下偏保守。epsilon0.1对0到20分的成绩范围太窄模型被迫去拟合噪声C1.0的容错又小两头一挤泛化能力打折。解决以C10.0、epsilon0.5、gammascale起步然后做一次小范围网格搜索。用GridSearchCV在C[1,10,100]、epsilon[0.1,0.5,1.0]上扫一遍再把搜索选出的参数放回到验证集上确认防止网格搜索结果只对测试集有效。成绩预测这种低维小样本场景SVR参数就这三个花几分钟扫一遍是值得的。5.5 不固定随机种子复现不出同一组数字现象同一份代码跑两次线性回归的指标完全一致神经网络的指标对不上换个机器差异更大。原因train_test_split的划分、网络权重初始化和DataLoader的shuffle都依赖随机数。不固定seed每次跑的训练集划分和初始权重都不同模型结果自然对不上后续调参也没法公平比较。解决在代码入口统一设置随机种子涉及numpy、torch和Python内置的random三个模块。import random import numpy as np import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) set_seed(42)set_seed函数在脚本开头调用一次配合前面train_test_split的random_state就能保证每次训练结果完全可复现。这条不只是为了论文里的「随机种子固定」表述更是为了你自己调参时不至于跑出来两个结果不知道怎么对比。5.6 三模型对比同一特征集上的典型表现模型MAERMSER²训练耗时可解释性LinearRegression1.201.550.80毫秒级系数直观SVR rbf1.051.400.84秒级黑匣子前馈神经网络0.951.250.8620-40秒需辅助解释这组数字是student-mat数据集、7个数值特征、固定随机种子下的典型水平。换成student-por葡萄牙语课或者换特征集绝对数值会变但相对关系基本一致神经网络略胜SVRSVR略胜线性回归。如果只留G1、G2、G3做「记忆式预测」三者差距会缩小到几乎可忽略因为期中成绩本身就携带了期末成绩的大部分信息。这张表的另一层含义是精度换解释的权衡。线性回归差0.25分MAE但coef_可以直接输出成业务报告神经网络好一点但调参和复现成本高。做课程设计或小规模业务预测时很多情况下线性回归已经够用只有当你有足够数据并且明确需要那零点几分的精度提升神经网络才值得上。6. 模型交付前最后一步可解释性分析与分段误差排查6.1 置换重要性给黑匣子补上解释缺口模型跑完业务方会问凭什么预测这个学生12分线性回归可以直接看coef_SVM和神经网络看不到直接的权重常见做法是用置换重要性也叫permutation importance。from sklearn.inspection import permutation_importance result permutation_importance(svr, X_test_scaled, y_test, n_repeats10, random_state42) for name, imp in zip(features, result.importances_mean): print(f{name}: {imp:.4f})置换重要性的含义把某一列特征随机打乱观察模型误差上升多少。误差上升越大说明该特征对预测越关键。在student-mat上跑出来的结论通常还是G2第一、G1第二studytime和failures排在第三梯队goout和health几乎不影响结果。这个结论的意义比R²0.86更容易写进报告。6.2 保存模型与分数段误差排查线性回归和SVR用joblib.dump保存神经网络用torch.save保存state_dict预测时分别加载对新数据只做transform然后predict不重走训练。我的固定习惯是最后把预测误差按分数段拆开看低分段学生是不是被系统性高估高分段学生是不是被低估。如果低分段预测普遍偏高说明训练集里挂科样本太少模型没见过足够的低分模式补救方式是给低分样本提高采样权重。这个分数段排查比任何调参动作都更能反应模型在业务里的真实可用性——成绩预测真正有说服力的交付物不是那个R²而是你能明确说出模型在哪个分数段可信、哪个分数段需要人工二次确认。这也是我做过几期这类项目后最想强调的一条经验希望帮到你。本文还有配套的精品资源点击获取

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案