kaggle实战案例——学生成绩预测


一、项目概述

二、数据集

2.1 数据文件夹分析


上述图片是kaggle官网上下载解压时候的数据集,分为train.csv(训练数据集)、test.csv(测试数据集)、sample_submission.csv(提交样本文件),点开观察之后我们会发现,test.csv文件中没有’exam_score(考试成绩)'这列数据,这是我们需要通过构建模型训练train.csv,再对test.csv文件中的学生进行预测,之后将预测结果的分数与sample_submission.csv格式匹配,上传到kaggle上,之后会给你进行打分。

2.2 数据集分析

基于kaggle的’预测学生考试成绩数据集’,共有20000条学生数据,13个特征字段,考试成绩范围是0-100分。

2.3 数据集字段分析

字段 类型 描述 示例
id 数值型 - 整型 学生id 1,2,3…
age 数值型 - 整型 学生年龄 17,18,20…
gender 类别型 学生性别 female,male,other
course 类别型 课程 b.sc,diploma,bca…
study_hours 数值型-float64 学生的平均学习时长,每日 7.91,4.95
class_attendance 数值型-float64 课堂出勤率 98.8,94.8
internet_access 类别型 是否拥有互联网接入 yes,no
sleep_hours 数值型-float64 平均睡眠时长 4.9,4.7
sleep_quality 类别型 睡眠质量 poor,average,good
study_method 类别型 学习方式 online videos,self-study,coaching,group study
facility_rating 类别型 设施评分 low,medium,high
exam_difficulty 类别型 考试的难度等级 easy,moderate,hard
exam_score 数值型-float64 考试分数 78.3,46.7

数值型特征 (6个):

​ id,age,study_hours,class_attendance,sleep_hours,exam_score

类别型特征 (7个):

​ gender, course,internet_access,sleep_quality, study_method,facility_rating,exam_difficulty

三 、数据分析及可视化

3.1 数据分析

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')
submission = pd.read_csv('sample_submission.csv')

print(f"训练集大小: {train.shape}")
print(f"测试集大小: {test.shape}")
train.head()

在这里插入图片描述

train.info()

在这里插入图片描述

test.info()

在这里插入图片描述

train.isnull().sum()

在这里插入图片描述

test.isnull().sum()

在这里插入图片描述

train.describe()

在这里插入图片描述

3.2 特征可视化

3.2.1 数值型特征可视化
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

fig, axes = plt.subplots(2, 3, figsize=(18, 10))
axes = axes.flatten()
features = ['exam_score', 'class_attendance', 'study_hours', 'sleep_hours', 'age']
for i, col in enumerate(features):
    sns.histplot(
        data=train, 
        x=col, 
        bins=20, 
        kde=True, 
        color='red',          
        facecolor='skyblue',  
        edgecolor='black',    
        ax=axes[i]
    )
    axes[i].set_title(f'Distribution of {col}')
    axes[i].set_xlabel(col)
    axes[i].set_ylabel('Count')
fig.delaxes(axes[5])
plt.tight_layout()
plt.show()

在这里插入图片描述

3.2.2 类别型特征可视化
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

sns.set(style="whitegrid")
fig, axes = plt.subplots(2, 4, figsize=(20, 12))
axes = axes.flatten() 

categorical_features = [
    'gender', 'course', 'internet_access', 'sleep_quality', 
    'study_method', 'facility_rating', 'exam_difficulty'
]

# 4. 循环绘图
for i, col in enumerate(categorical_features):
    sns.countplot(
        data=train_df, 
        x=col, 
        color='skyblue', 
        edgecolor='black', 
        ax=axes[i]
    )

    axes[i].set_title(f'Distribution of {col}', fontsize=14)
    axes[i].set_xlabel(col, fontsize=12)
    axes[i].set_ylabel('Count', fontsize=12)
    
fig.delaxes(axes[7])
plt.tight_layout()
plt.show()

在这里插入图片描述#### 3.2.3 绘出数值型特征相关热力图

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

cols_to_plot = ['exam_score', 'class_attendance', 'study_hours', 'sleep_hours', 'age']
corr_matrix = df[cols_to_plot].corr()

plt.figure(figsize=(10, 8))
sns.heatmap(
    corr_matrix, 
    annot=True,      
    cmap='coolwarm',  
    fmt=".2f",       
    linewidths=0.5,  
    square=True     
)
plt.title('Correlation Heatmap', fontsize=16)
plt.tight_layout()
plt.show()

在这里插入图片描述
通过观察上述热力图,我们可知,

study_hours(学生的平均学习时长,每日)有显著的相关性;class_attendance(课堂出勤率)、sleep_hours(平均睡眠时长)也有较显著的相关性。

3.2.4 绘出类别型特征箱线图
import seaborn as sns
import matplotlib.pyplot as plt

# 假设成绩列名为 'exam_score'
features = ['gender', 'course', 'internet_access', 'sleep_quality', 'study_method', 'facility_rating', 'exam_difficulty']

plt.figure(figsize=(16, 12))
for i, col in enumerate(features, 1):
    plt.subplot(3, 3, i)
    sns.boxplot(x=col, y='exam_score', data=df)
    plt.title(f'Score vs {col}')
    plt.xticks(rotation=45)

plt.tight_layout()
plt.show()

在这里插入图片描述
通过观察上述箱线图,我们可知,

sleep_quality(睡眠质量)这是图中差异最明显的特征。poor组的中位数显著低于 good 组,且 good组的整体得分区间(上下四分位数)明显上移。说明良好的睡眠与高分有强关联;

study_method (学习方法):可以看到 coaching(辅导)方法的中位数最高,而 self-study(自学)相对较低。这表明不同的学习策略对分数分布有实质性改变;

facility_rating (设施评价):随着设施评价从 lowmedium 再到 high,箱体呈现明显的阶梯式上升。这反映了外部硬件资源对成绩的积极支撑作用。

四、模型构建

我们通过上述的数据分析可以得出,数值型特征:study_hours(学生的平均学习时长,每日)、class_attendance(课堂出勤率)、sleep_hours(平均睡眠时长)有相关性;类别型特征:sleep_quality(睡眠质量)、study_method (学习方法)、facility_rating (设施评价)有相关性。

4.1 多元线性回归模型(Linear)的构建

下面构建模型,首先构建多元线性回归模型(Linear),参数即为上述分析有相关性的参数(且需要将类别型特征参数转换为数值型类别参数)。

数学公式如下(多元线性回归 Linear/ 岭回归Ridge):
y = β 0 + β 1 x 1 + β 2 x 2 + ⋯ + β n x n y = \beta_0 + \beta_1x_1 + \beta_2x_2 + \dots + \beta_nx_n y=β0+β1x1+β2x2++βnxn

y : 预测的考试分数 y: 预测的考试分数 y:预测的考试分数

β 0 : 截距( m o d e l . i n t e r c e p t ) ,代表基础分。 \beta_0: 截距(model.intercept_),代表基础分。 β0:截距(model.intercept,代表基础分。

β i : 回归系数( m o d e l . c o e f ) ,代表每个特征对分数的边际贡献。 \beta_i: 回归系数(model.coef_),代表每个特征对分数的边际贡献。 βi:回归系数(model.coef,代表每个特征对分数的边际贡献。

x i : 输入特征(如 s t u d y h o u r s 或哑变量 s l e e p q u a l i t y g o o d )。 x_i: 输入特征(如 study_hours 或哑变量 sleep_quality_good)。 xi:输入特征(如studyhours或哑变量sleepqualitygood)。

代码如下:

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn import linear_model

# --- 1. 数据准备与统一编码 ---
features = ['study_hours', 'class_attendance', 'sleep_hours', 
            'sleep_quality', 'study_method', 'facility_rating']

# 合并编码以确保特征对齐
full_df = pd.concat([train[features], test[features]], axis=0)
full_df_encoded = pd.get_dummies(full_df, columns=['sleep_quality', 'study_method', 'facility_rating'], drop_first=True)

# 拆分出训练用的特征矩阵
X_all_train = full_df_encoded.iloc[:len(train), :]
y_all_train = train['exam_score']

print("--- 转换后的数值变量预览 ---")
print(X_all_train.head())

# --- 2. 划分训练集与验证集 (80/20) ---
x_train, x_val, y_train, y_val = train_test_split(X_all_train, y_all_train, test_size=0.2, random_state=0)

print(f"训练集特征数量: {x_train.shape}")
print(f"验证集特征数量: {x_val.shape}")

在这里插入图片描述
模型训练:

model = linear_model.LinearRegression()
model.fit(x_train, y_train)

print("模型在训练集上的得分:", model.score(x_train, y_train))
print("模型在验证集上的得分:", model.score(x_val, y_val))

在这里插入图片描述

打印方程:

np.set_printoptions(suppress=True)
print("\n回归系数与截距:", model.coef_, model.intercept_)

print("\n多元线性回归方程:\n考试分数 = ", end = '')
for i in range(x_train.shape[1]):
    print("%.2f*%s + " %(model.coef_[i], x_train.columns[i]), end = '')
print("%.2f" %model.intercept_)

在这里插入图片描述
通过构建上述模型,我们可以发现,上述模型得最终的测试准确度为:77.9%,接下来,我们通过对比其他的回归模型来确定最终用什么模型完成对test.csv文件数据的预测。

首先是与多元回归模型最相近的岭回归(Ridge):

from sklearn.linear_model import Ridge

# 创建岭回归模型,alpha 是正则化强度
ridge_model = Ridge(alpha=1.0)
ridge_model.fit(x_train, y_train)

print(f"岭回归训练集得分: {ridge_model.score(x_train, y_train):.4f}")
print(f"岭回归验证集得分: {ridge_model.score(x_val, y_val):.4f}")

在这里插入图片描述

上述结果可以看出,岭回归的测试集准确度与多元线性回归的准确度相似。这说明数据集中的线性特征非常稳健,模型在验证集上的表现甚至略优于训练集,反映了模型具有极强的泛化能力,不存在过拟合风险。

4.2 决策树(Random Forest)模型的构建

下面采用决策树(Random Forest)模型:

数学公式:
y = ∑ m = 1 M c m ⋅ I ( x ∈ R m ) y = \sum_{m=1}^{M} c_m \cdot I(x \in R_m) y=m=1McmI(xRm)

R m : 树的叶子节点(空间划分的区域) R_m: 树的叶子节点(空间划分的区域) Rm:树的叶子节点(空间划分的区域)

c m : 该叶子节点内所有样本分数的平均值(预测值) c_m: 该叶子节点内所有样本分数的平均值(预测值) cm:该叶子节点内所有样本分数的平均值(预测值)

I : 指示函数,如果特征 x 落在区域 R m 内则为 1 ,否则为 0 I: 指示函数,如果特征 x 落在区域 R_m 内则为 1,否则为 0 I:指示函数,如果特征x落在区域Rm内则为1,否则为0

import numpy as np
import pandas as pd
from sklearn.tree import DecisionTreeRegressor, plot_tree
import matplotlib.pyplot as plt

tree_model = DecisionTreeRegressor(max_depth=5, random_state=0)

# 模型训练
tree_model.fit(x_train, y_train)

print("【决策树回归评估结果】")
train_score_tree = tree_model.score(x_train, y_train)
val_score_tree = tree_model.score(x_val, y_val)

print(f"决策树 训练集得分 (R^2):{train_score_tree:.4f}")
print(f"决策树 验证集得分 (R^2):{val_score_tree:.4f}")

# --- 3. 可视化特征重要性 ---
importances_tree = tree_model.feature_importances_
feat_imp_df = pd.DataFrame({
    '特征': x_train.columns, 
    '重要性': importances_tree
}).sort_values(by='重要性', ascending=False)

print("\n--- 决策树特征重要性排名 ---")
print(feat_imp_df)

plt.rcParams['font.sans-serif'] = ['SimHei'] 
plt.rcParams['axes.unicode_minus'] = False

plt.figure(figsize=(20,10))
plot_tree(tree_model, 
          feature_names=x_train.columns, 
          max_depth=2,  
          filled=True, 
          fontsize=10)
plt.title("决策树结构预览 (前2层)")
plt.show()

在这里插入图片描述

上述运行结果可以看出,决策树模型的测试集准确度为67.3%,对比之前的模型准确度下降了不少;

4.3 随机森林模型的构建

下面,试试随机森林 (Random Forest)模型:
数学公式:
y = 1 B ∑ b = 1 B T b ( x ) y = \frac{1}{B} \sum_{b=1}^{B} T_b(x) y=B1b=1BTb(x)

B : 森林中树的数量(你的代码中是 n e s t i m a t o r s = 100 ) B: 森林中树的数量(你的代码中是 n_estimators=100) B:森林中树的数量(你的代码中是nestimators=100

T b ( x ) : 第 b 棵决策树对样本的预测值 T_b(x): 第 b 棵决策树对样本的预测值 Tb(x):b棵决策树对样本的预测值

import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import r2_score

rf_model = RandomForestRegressor(n_estimators=100, max_depth=None, random_state=0)

# 模型训练
rf_model.fit(x_train, y_train)

print("【随机森林模型评估结果】")
train_score_rf = rf_model.score(x_train, y_train)
val_score_rf = rf_model.score(x_val, y_val)

print(f"模型在训练集(80%)上的得分 (R^2):{train_score_rf:.4f}")
print(f"模型在验证集(20%)上的得分 (R^2):{val_score_rf:.4f}")

# --- 4. 查看特征重要性 ---
# 随机森林的一大优势是它可以量化每个特征对预测分数的贡献度
importances = rf_model.feature_importances_
feature_names = x_train.columns
feature_importance_df = pd.DataFrame({'特征': feature_names, '重要性': importances}).sort_values(by='重要性', ascending=False)

print("\n--- 随机森林视角下的特征重要性排名 ---")
print(feature_importance_df)

在这里插入图片描述
上述运行结果可以看出,随机森林模型的训练集准确度为97%左右,测试集的准确度为76%左右,对比多元线性回归的模型准确度降低了一点,但是训练集的准确度上涨了许多,这种巨大的分差在数学上反映了明显的过拟合 (Overfitting) 现象,即模型过度学习了训练集中的噪声。

下面,对随机森林模型进行调优:
代码如下:

import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import r2_score

rf_model_tuned = RandomForestRegressor(
    n_estimators=100, 
    max_depth=6,           # 限制树的高度,不让它钻牛角尖
    min_samples_leaf=5,    # 每个末梢节点至少保留5个样本
    random_state=0
)
rf_model_tuned.fit(x_train, y_train)

print(f"调优后训练集得分: {rf_model_tuned.score(x_train, y_train):.4f}")
print(f"调优后验证集得分: {rf_model_tuned.score(x_val, y_val):.4f}")

# --- 4. 查看特征重要性 ---
# 随机森林的一大优势是它可以量化每个特征对预测分数的贡献度
importances = rf_model.feature_importances_
feature_names = x_train.columns
feature_importance_df = pd.DataFrame({'特征': feature_names, '重要性': importances}).sort_values(by='重要性', ascending=False)

print("\n--- 随机森林视角下的特征重要性排名 ---")
print(feature_importance_df)

在这里插入图片描述

在对随机森林进行调优后,虽然训练集和验证集的得分趋于一致(约 0.70),但整体预测精度(R²)相较于线性回归有所下降,说明过度限制树的生长导致了信息的丢失。

4.4 梯度提升树 (GBDT)模型的构建

下面用梯度提升树 (GBDT)模型:
数学公式:
y = f 0 ( x ) + η ∑ t = 1 T h t ( x ) y = f_0(x) + \eta \sum_{t=1}^{T} h_t(x) y=f0(x)+ηt=1Tht(x)

f 0 ( x ) : 初始预测(通常是所有分数的平均值) f_0(x): 初始预测(通常是所有分数的平均值) f0(x):初始预测(通常是所有分数的平均值)

η : 学习率(你的代码中是 l e a r n i n g r a t e = 0.1 )。 \eta: 学习率(你的代码中是 learning_rate=0.1)。 η:学习率(你的代码中是learningrate=0.1)。

h t ( x ) : 第 t 次迭代学习到的回归树(负责拟合之前所有树留下的残差) h_t(x): 第 t 次迭代学习到的回归树(负责拟合之前所有树留下的残差) ht(x):t次迭代学习到的回归树(负责拟合之前所有树留下的残差)

代码如下:

import numpy as np
import pandas as pd
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.metrics import r2_score

# --- 1. 创建并训练 GBDT 模型 ---
# n_estimators: 迭代次数(树的数量)
# learning_rate: 学习率,步长越小,模型学习越细腻,但需要更多树
# max_depth: 限制单棵树的深度,防止过拟合
gbdt_model = GradientBoostingRegressor(
    n_estimators=100, 
    learning_rate=0.1, 
    max_depth=4, 
    random_state=0
)
# 模型训练
gbdt_model.fit(x_train, y_train)

# --- 2. 输出模型得分 (R^2) ---
print("【GBDT 梯度提升决策树评估结果】")
train_score_gbdt = gbdt_model.score(x_train, y_train)
val_score_gbdt = gbdt_model.score(x_val, y_val)

print(f"GBDT 训练集得分 (R^2):{train_score_gbdt:.4f}")
print(f"GBDT 验证集得分 (R^2):{val_score_gbdt:.4f}")

# --- 3. 查看 GBDT 视角的特征重要性 ---
importances_gbdt = gbdt_model.feature_importances_
feature_names = x_train.columns
feat_imp_df = pd.DataFrame({'特征': feature_names, '重要性': importances_gbdt}).sort_values(by='重要性', ascending=False)

print("\n--- GBDT 特征重要性排名 ---")
print(feat_imp_df)

在这里插入图片描述
上述运行结果可以看出,梯度提升树 (GBDT)模型的测试集的准确度为78%左右,对比之前的模型准确度是最高的;

4.5 XGBoost 模型的构建

from xgboost import XGBRegressor

# 创建 XGBoost 模型
xgb_model = XGBRegressor(n_estimators=100, learning_rate=0.1, max_depth=5, random_state=0)
xgb_model.fit(x_train, y_train)

print(f"XGBoost 训练集得分: {xgb_model.score(x_train, y_train):.4f}")
print(f"XGBoost 验证集得分: {xgb_model.score(x_val, y_val):.4f}")

在这里插入图片描述
上述运行结果可以看出,XGBoost 模型的准确度也在78%左右,我们可以得出结论,我们可以用梯度提升树 (GBDT)模型来做为最终预测test.csv文件的模型,代码如下:

final_test_predictions = gbdt_model.predict(x_test_final)
final_test_predictions = np.clip(final_test_predictions, 0, 100)
submission['exam_score'] = final_test_predictions

# 导出为 CSV 文件
submission.to_csv('gbdt_final_submission.csv', index=False)

print("【GBDT 最终预测完成】")
print(f"预测结果已保存至: 'gbdt_final_submission.csv'")
print("\n测试集前 5 行预测分数预览:")
print(submission.head())

#打印公式
print("\n" + "="*50)
print("GBDT 预测逻辑(数学表达):")
print("Final Score = F_0(x) + η * Σ [h_t(x)]")
print(f"其中:F_0 为初始均值,η(学习率) = 0.1,共叠加了 {gbdt_model.n_estimators} 棵残差修正树。")
print("="*50)

按照如下格式将预测数据填充:
在这里插入图片描述
结果如下:
在这里插入图片描述
下面,我们对模型进行从数值指标、可视化分布两个维度增加误差分析, 除了 R 2 R^2 R2 得分,我们需要引入平均绝对误差 ( M A E MAE MAE)、均方误差 ( M S E MSE MSE) 和均方根误差 ( R M S E RMSE RMSE) 来更直观地衡量分数偏差。

平均绝对误差 ( M A E MAE MAE): MAE = 1 n ∑ i = 1 n ∣ y i − y ^ i ∣ \text{MAE} = \frac{1}{n} \sum_{i=1}^{n} |y_i - \hat{y}_i| MAE=n1i=1nyiy^i。它表示预测分数与实际分数平均相差多少分。

均方根误差 ( R M S E RMSE RMSE): RMSE = 1 n ∑ i = 1 n ( y i − y ^ i ) 2 \text{RMSE} = \sqrt{\frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2} RMSE=n1i=1n(yiy^i)2 。由于进行了平方运算,它对大误差(极端错误)更加敏感。

代码如下:

from sklearn.metrics import mean_absolute_error, mean_squared_error

# 计算验证集预测值
y_val_pred = gbdt_model.predict(x_val)

mae = mean_absolute_error(y_val, y_val_pred)
mse = mean_squared_error(y_val, y_val_pred)
rmse = np.sqrt(mse)

print("--- GBDT 误差指标分析 ---")
print(f"平均绝对误差 (MAE): {mae:.4f} 分")
print(f"均方根误差 (RMSE): {rmse:.4f} 分")

在这里插入图片描述
观察 GBDT 误差分析结果可知:

平均绝对误差 (MAE)7.0580 分,意味着模型对每个学生分数的预测平均偏差仅在 7 分左右。

均方根误差 (RMSE)8.8405 分,数值略高于 MAE,反映出数据中存在少量误差较大的极端样本。

残差可视化分析 (Residual Visualization):

残差( R e s i d u a l Residual Residual)定义为 e i = y i − y ^ i e_i = y_i - \hat{y}_i ei=yiy^i 通过观察残差的分布,可以判断模型是否存在系统性偏差。

代码如下:

import matplotlib.pyplot as plt
import seaborn as sns

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

# 计算残差
residuals = y_val - y_val_pred

plt.figure(figsize=(15, 5))

# 图1:残差分布直方图(检查是否符合正态分布)
plt.subplot(1, 2, 1)
sns.histplot(residuals, kde=True, color='purple')
plt.title('残差分布直方图 (Residuals Distribution)')
plt.xlabel('误差分数')

# 图2:预测值 vs 实际值 散点图
plt.subplot(1, 2, 2)
plt.scatter(y_val, y_val_pred, alpha=0.5, color='orange')
plt.plot([y_val.min(), y_val.max()], [y_val.min(), y_val.max()], 'r--', lw=2)
plt.title('预测值 vs 实际值 (Prediction vs Actual)')
plt.xlabel('实际分数')
plt.ylabel('预测分数')

plt.tight_layout()
plt.show()

在这里插入图片描述
残差分布直方图:观察左侧图表可知,残差(实际值与预测值的差)呈现出非常完美的正态分布(钟形曲线),且中心紧密围绕在 0 附近。在数学上,这证明了模型的预测偏差是纯随机的,没有出现系统性的预测偏见。

预测值 vs 实际值散点图:观察右侧散点图,大量橙色数据点紧密聚集在红色虚线( y = x y=x y=x)两侧。

分析:数据点越贴近虚线,说明预测越精准。图表显示模型在 40-80 分的中段区间表现最为出色;而在极高分或极低分区域,散点略微发散,说明极端成绩的预测难度相对较高。
综上所述,虽然线性模型表现稳健,但 GBDT 模型 通过残差迭代学习,在保持高准确度的同时(R² 约 78%),其残差分布表现最符合统计学理想状态。因此,本案例最终选用 GBDT 模型作为 test.csv 的预测工具。

五、 总结性话语

本案例通过从基础的线性回归(建立基准)到决策树(探索逻辑),再到集成学习 GBDT(追求精度)的演进,完整展示了预测任务的优化路径。最终选择 GBDT 是因为其在处理非线性等级特征(如 facility_rating)时,能通过二阶导数信息更精准地定位梯度下降方向,从而实现了测试集上最高的准确度的表现。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐