通过训练集训练和测试集测试来生成多个线性模型,从而预测学生成绩,本文所有代码请点击Github
1. 实验数据背景
1.1 数据来源
本项目的数据来源于kaggle.com,数据集的名称为Student Grade Prediction,Paulo Cortez,Minho大学,葡萄牙吉马良斯,http://www3.dsi.uminho.pt/pcortez
1.2 数据简介
该数据接近了两所葡萄牙学校的中学学生的学习成绩。数据属性包括学生成绩,人口统计学,社会和与学校相关的特征),并通过使用学校报告和调查表进行收集。提供了两个关于两个不同学科表现的数据集:数学(mat)和葡萄牙语(por)
该数据集共有396条,每列33个属性,属性简介如下:
1.学校-学生学校(二进制:“ GP”-加布里埃尔·佩雷拉(Gabriel Pereira)或“ MS”-Mousinho da Silveira)
2.性别-学生的性别(二进制:“ F”-女性或“ M”-男性)
3.年龄-学生的年龄(数字:15至22)
4.地址-学生的家庭住址类型(二进制:“ U”-城市或“ R”-农村)
5.famsize-家庭大小(二进制:“ LE3”-小于或等于3或“ GT3”-大于3)
6.Pstatus-父母的同居状态(二进制:“ T”-同居或“ A”-分开)
7.Medu-母亲的教育(数字:0-无,1-初等教育(四年级),2 – 5至9年级,3 –中等教育或4 –高等教育)
8.Fedu-父亲的教育(数字:0-无,1-初等教育(四年级),2 – 5至9年级,3 –中等教育或4 –高等教育)
9.Mjob-母亲的工作(名义:“教师”,“与健康”有关的,民事“服务”(例如行政或警察),“在家”或“其他”)
10.Fjob-父亲的工作(名义:“教师”,“与健康”相关的,民事“服务”(例如行政或警察),“在家”或“其他”)
11.理由-选择这所学校的理由(名义:接近“家”,学校“声誉”,“课程”偏好或“其他”)
12.监护人-学生的监护人(名词:“母亲”,“父亲”或“其他”)
13.traveltime-学校到学校的旅行时间(数字:1-<15分钟,2-15至30分钟,3-30分钟至1小时或4-> 1小时)
14.学习时间-每周学习时间(数字:1-<2小时,2-2至5小时,3-5至10小时或4-> 10小时)
15.失败-过去类失败的次数(数字:如果1 <= n ❤️,则为n,否则为4)
16.schoolup-额外的教育支持(二进制:是或否)
17.famsup-家庭教育支持(二进制:是或否)
18.付费-课程主题内的额外付费课程(数学或葡萄牙语)(二进制:是或否)
19.活动-课外活动(二进制:是或否)
20.托儿所-上托儿所(二进制:是或否)
21.更高-想要接受高等教育(二进制:是或否)
22.互联网-在家上网(二进制:是或否)
23.浪漫-具有浪漫关系(二进制:是或否)
24.家族-家庭关系的质量(数字:从1-非常差到5-极好)
25.空闲时间-放学后的空闲时间(数字:从1-非常低到5-非常高)
26.外出-与朋友外出(数字:从1-非常低到5-非常高)
27.Dalc-工作日酒精消耗(数字:从1-非常低到5-非常高)
28.Walc-周末酒精消耗(数字:从1-非常低至5-非常高)
29.健康-当前的健康状况(数字:从1-非常差到5-非常好)
30.缺勤-缺勤人数(数字:0到93)
这些成绩与课程主题(数学或葡萄牙语)相关:
31.G1-第一期成绩(数字:0至20)
32.G2-第二学期成绩(数字:0至20)
33.G3-最终成绩(数字:0到20,输出目标)
2. 研究思路
数据集中存在不少非数值型数据,所以在进行建模之前要进行数据清洗,主要是抛弃掉一些非相关性数据以及缺失值处理,对数据进行分析的时候要注意其中是否有缺失值。
一些机器学习算法能够处理缺失值,比如神经网络,一些则不能。对于缺失值,一般有以下几种处理方法:
(1)如果数据集很多,但有很少的缺失值,可以删掉带缺失值的行;
(2)如果该属性相对学习来说不是很重要,可以对缺失值赋均值或者众数。
(3)对于标称属性,可以赋一个代表缺失的值,比如‘U0’。因为缺失本身也可能代表着一些隐含信息。
(4)使用回归 随机森林等模型来预测缺失属性的值。因为Age在该数据集里是一个相当重要的特征(先对Age进行分析即可得知),所以保证一定的缺失值填充准确率是非常重要的,对结果也会产生较大影响。一般情况下,会使用数据完整的条目作为模型的训练集,以此来预测缺失值。对于当前的这个数据,可以使用随机森林来预测也可以使用线性回归预测。这里使用随机森林预测模型,选取数据集中的数值属性作为特征(因为sklearn的模型只能处理数值属性,所以这里先仅选取数值特征,但在实际的应用中需要将非数值特征转换为数值特征)
数据规范化后方可进行数据分析,本项目将首先拿出一些特定属性,通过绘制图标的形式单独分析该属性反映出来的信息,试图分析该属性对结果的影响。
最后将对全部数据建立线性模型,并把数据集分为训练集和测试集,以测试模型的准确性。
3. 具体步骤
3.1 数据总体分析
利用padans库的read_csv函数,可以读取数据集:
# 初始化数据
plt.rcParams['font.sans-serif'] = ['SimHei'] # 中文字体设置-黑体
plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题
sns.set(font='SimHei') # 解决Seaborn中文显示问题
student = pd.read_csv('student-mat.csv')
格式化后展示如图3-1-1
3.2 利用图表分析属性
3.2.1各分数段学生计数
对学生期末成绩直接分析,可以得出成绩的大致分布,那么后文在分析其他因素对成绩的影响时也会更有针对性
# 根据人数多少统计各分数段的学生人数
grade_counts = student['G3'].value_counts().sort_values().plot.barh(width=.9,color=sns.color_palette('inferno',40))
grade_counts.axes.set_title('各分数值的学生分布',fontsize=30)
grade_counts.set_xlabel('学生数量', fontsize=30)
grade_counts.set_ylabel('最终成绩', fontsize=30)
plt.show()
绘制出的图像如图3-2-1所示:
3.2.2 学生成绩分布直方图
3.2.1节中的图像并没有告诉我们有价值的信息,也许我们真正需要的是成绩分布直方图,即把成绩作为一个坐标轴,代码如下:
# 从低到高展示成绩分布图
grade_distribution = sns.countplot(student['G3'])
grade_distribution.set_title('成绩分布图', fontsize=30)
grade_distribution.set_xlabel('期末成绩', fontsize=20)
grade_distribution.set_ylabel('人数统计', fontsize=20)
plt.show()
绘制出的图像如图3-2-2所示:
可以看出得10分和11分的学生数量很多,虽然这只是一个中等成绩,大部分学生的成绩分布在8-15分之间。更值得注意的是,居然有接近40个人得了0分,不由得让我怀疑0其实是null值,但是在检查之后发现,确实是0值,外国学生这么菜?
3.2.3 年龄因素分析
中学生正处在身体机能快速发育的时期,大脑亦是如此,不同智力水平的学生势必会得到不同的分数,因此有必要分析一下年龄对成绩的影响,并且考虑到性别因素。
以下代码可以得出性别的相关数据:
# 分析性别比例
male_studs = len(student[student['sex'] == 'M'])
female_studs = len(student[student['sex'] == 'F'])
print('男同学数量:',male_studs)
print('女同学数量:',female_studs)
结果为:男同学数量: 187,女同学数量: 208。男女比例并不悬殊。
分析完性别因素之后,我们先来看看年龄比例的曲线图,代码如下:
# 分析年龄分布比例(曲线图)
age_distribution = sns.kdeplot(student['age'], shade=True)
age_distribution.axes.set_title('学生年龄分布图', fontsize=30)
age_distribution.set_xlabel('年龄', fontsize=20)
age_distribution.set_ylabel('比例', fontsize=20)
plt.show()
生成的图像如图3-2-3所示:
# 分性别年龄分布图(柱状图)
age_distribution_sex = sns.countplot('age', hue='sex', data=student)
age_distribution_sex.axes.set_title('不同年龄段的学生人数', fontsize=30)
age_distribution_sex.set_xlabel('年龄', fontsize=30)
age_distribution_sex.set_ylabel('人数', fontsize=30)
plt.show()
绘制的图像如图3-2-4所示:
# 各年龄段的成绩箱型图
age_grade_boxplot = sns.boxplot(x='age', y='G3', data=student)
age_grade_boxplot.axes.set_title('年龄与分数', fontsize = 30)
age_grade_boxplot.set_xlabel('年龄', fontsize = 20)
age_grade_boxplot.set_ylabel('分数', fontsize = 20)
plt.show()
得到的图像如图3-2-5所示:
# 各年龄段的成绩分布图
age_grade_swarmplot = sns.swarmplot(x='age', y='G3', data=student)
age_grade_swarmplot.axes.set_title('年龄与分数', fontsize = 30)
age_grade_swarmplot.set_xlabel('年龄', fontsize = 20)
age_grade_swarmplot.set_ylabel('分数', fontsize = 20)
plt.show()
得到的图像如图3-2-6所示:
3.2.4 学生城乡因素分析
既然年龄对成绩的影响不如预期那么明显,我们不妨再来分析一下学生地区因素对成绩的影响,在大部分地区,城乡教育资源都有不小的差距,教育质量也是如此,部分来自于乡下小学的孩子在步入城市中学后会不会落后于从小就在城市接受教育的孩子呢?
首先分析城乡学生的比例,代码如下:
# 城乡学生计数
areas_countplot = sns.countplot(student['address'])
areas_countplot.axes.set_title('城乡学生', fontsize = 30)
areas_countplot.set_xlabel('家庭住址', fontsize = 20)
areas_countplot.set_ylabel('计数', fontsize = 20)
plt.show()
绘制的图像如图3-2-7所示:
sns.kdeplot(student.loc[student['address'] == 'U', 'G3'], label='Urban', shade = True)
sns.kdeplot(student.loc[student['address'] == 'R', 'G3'], label='Rural', shade = True)
plt.title('城市学生获得了更好的成绩吗?', fontsize = 20)
plt.xlabel('分数', fontsize = 20)
plt.ylabel('占比', fontsize = 20)
plt.show()
绘制出的图像如图3-2-8所示:
3.3 各属性与成绩的相关性分析
至此我们已经分析了年龄和地区这两个因素,然而并没有得到预期的结果——找出对成绩有影响的因素并分析原因,所以接下来我们综合分析各个因素与成绩之间的关系,希望会有好的结果。(注:仅限于数值属性)
3.3.1相关性初步计算
首先计算出各个属性与最终成绩G3的相关性,只需一行代码:
student.corr()[‘G3’].sort_values()
结果如下:
\1. failures -0.360415
\2. age -0.161579
\3. goout -0.132791
\4. traveltime -0.117142
\5. health -0.061335
\6. Dalc -0.054660
\7. Walc -0.051939
\8. freetime 0.011307
\9. absences 0.034247
\10. famrel 0.051363
\11. studytime 0.097820
\12. Fedu 0.152457
\13. Medu 0.217147
\14. G1 0.801468
\15. G2 0.904868
\16. G3 1.000000
\17. Name: G3, dtype: float64
绝大部分机器学习算法无法处理非数值数据,因此在进行机器学习之前,我们要对部分属性进行编码。
首先介绍一下编码方式,第一种是标签编码:
此方法会为每个类别分配一个标签(label)
Occupation | Label |
---|---|
Programmer | 0 |
Data Scientist | 1 |
Engineer | 2 |
标签编码的问题是整数的分配是随机的,并且每次运行函数时都会改变。 此外,模型可能会为更大的标签赋予更高的优先级。当我们只有2个唯一值时,可以使用标签编码。
第二种是独热编码:
它为每个类别创建一个新列,并且仅使用二进制值。独热编码的缺点是,如果分类变量具有许多类别,则特征数量可能会爆炸。为了解决这个问题,我们可以执行PCA(或其他降维方法),然后执行独热编码。
Occupation | Occupation_prog | Occupation_ds | Occupation_eng |
---|---|---|---|
Programmer | 1 | 0 | 0 |
Data Scientist | 0 | 1 | 0 |
Enginner | 0 | 0 | 1 |
3.3.2 编码后的相关性计算
利用独热编码处理我们的数据集,然后再次寻找相关性,尽管G1和G2是学生的期末成绩,并且与期末成绩G3高度相关,但我们将其删除。没有G2和G1的情况下预测G3更加困难,但是这种预测更加有用,因为我们希望找到影响成绩的其他因素。代码如下:
# 选取G3属性值
labels = student['G3']
# 删除school,G1和G2属性
student = student.drop(['school', 'G1', 'G2'], axis='columns')
# 对离散变量进行独热编码
student = pd.get_dummies(student)
# 选取相关性最强的6个
most_correlated = student.corr().abs()['G3'].sort_values(ascending=False)
most_correlated = most_correlated[:6]
print(most_correlated)
输出的结果如下:
\1. G3 1.000000
\2. failures 0.360415
\3. Medu 0.217147
\4. higher_yes 0.182465
\5. higher_no 0.182465
\6. age 0.161579
\7. Fedu 0.152457
\8. Name: G3, dtype: float64
3.4 高相关度属性对成绩的影响
3.4.1 失败次数对成绩的影响
代码如下:
# 失败次数成绩分布图
failures_swarmplot = sns.swarmplot(x=student['failures'],y=student['G3'])
failures_swarmplot.axes.set_title('失败次数少的学生分数更高吗?', fontsize = 30)
failures_swarmplot.set_xlabel('失败次数', fontsize = 20)
failures_swarmplot.set_ylabel('最终成绩', fontsize = 20)
plt.show()
生成的图像如图3-4-1所示:
3.4.2 双亲受教育水平对成绩的影响
代码如下:
# 双亲受教育水平的影响
family_ed = student['Fedu'] + student['Medu']
family_ed_boxplot = sns.boxplot(x=family_ed,y=student['G3'])
family_ed_boxplot.axes.set_title('双亲受教育水平的影响', fontsize = 30)
family_ed_boxplot.set_xlabel('家庭教育水平(Mother + Father)', fontsize = 20)
family_ed_boxplot.set_ylabel('最终成绩', fontsize = 20)
plt.show()
生成的图像如图3-4-2所示:
3.4.3 学生升学意愿对成绩的影响
高等教育是绝对变量,其值为是和否。 由于我们使用了独热编码,因此已将其转换为2个变量。 因此,我们可以消除其中之一(因为这些值是互补的)。 我们消除Higher_no,因为Higher_yes更直观。
代码如下:
# 学生自己的升学意志对成绩的影响
personal_wish = sns.boxplot(x = student['higher_yes'], y=student['G3'])
personal_wish.axes.set_title('学生升学意愿对成绩的影响', fontsize = 30)
personal_wish.set_xlabel('更高级的教育 (1 = 是)', fontsize = 20)
personal_wish.set_ylabel('最终成绩', fontsize = 20)
plt.show()
生成的图像如图3-4-3所示:
图3-4-3 学生自己的升学意志对成绩的影响
显而易见,如果学生本人渴望受到更高的教育,其成绩也会更高一些,毕竟学习是自己的事情。
3.5 大数据模型
3.5.1 数据分割
在大数据通过机器学习建立模型的过程中,不仅要使用大量的数据来训练模型,还需要一定数量的测试集验证模型的准确性,从而判断模型是否可用。因此,我们将数据集分割成训练集和测试集,比例分别为75%和25%。只需一行代码:
X_train, X_test, y_train, y_test = train_test_split(student, labels, test_size = 0.25, random_state=42)
分割后我们便可以利用数据集进行线性回归模型的训练。
3.5.2 训练之前的准备工作
代码中的注释已经足够详细,不做解释,直接贴代码:
# 计算平均绝对误差和均方根误差
# MAE-平均绝对误差
# RMSE-均方根误差
def evaluate_predictions(predictions, true):
mae = np.mean(abs(predictions - true))
rmse = np.sqrt(np.mean((predictions - true) ** 2))
return mae, rmse
# 求中位数
median_pred = X_train['G3'].median()
# 所有中位数的列表
median_preds = [median_pred **for** _ **in** range(len(X_test))]
# 存储真实的G3值以传递给函数
true = X_test['G3']
# 展示基准的原始指标
mb_mae, mb_rmse = evaluate_predictions(median_preds, true)
print('Median Baseline MAE: {:.4f}'.format(mb_mae))
print('Median Baseline RMSE: {:.4f}'.format(mb_rmse))
最后两句代码的运行结果为:
Median Baseline MAE: 3.7879
Median Baseline RMSE: 4.8252
3.5.3 预测模型训练
本项目将试图训练出六种模型,分别为:线性回归,ElasticNet回归,随机森林,极端随机数,支持向量机(SVM),梯度提升树。
代码如下:
# 通过训练集训练和测试集测试来生成多个线性模型
def evaluate(X_train, X_test, y_train, y_test):
# 模型名称
model_name_list = ['Linear Regression', 'ElasticNet Regression',
'Random Forest', 'Extra Trees', 'SVM',
'Gradient Boosted', 'Baseline']
X_train = X_train.drop('G3', axis='columns')
X_test = X_test.drop('G3', axis='columns')
# 实例化模型
model1 = LinearRegression()
model2 = ElasticNet(alpha=1.0, l1_ratio=0.5)
model3 = RandomForestRegressor(n_estimators=100)
model4 = ExtraTreesRegressor(n_estimators=100)
model5 = SVR(kernel='rbf', degree=3, C=1.0, gamma='auto')
model6 = GradientBoostingRegressor(n_estimators=50)
# 结果数据框
results = pd.DataFrame(columns=['mae', 'rmse'], index = model_name_list)
# 每种模型的训练和预测
for i, model in enumerate([model1, model2, model3, model4, model5, model6]):
model.fit(X_train, y_train)
predictions = model.predict(X_test)
# 误差标准
mae = np.mean(abs(predictions - y_test))
rmse = np.sqrt(np.mean((predictions - y_test) ** 2))
# 将结果插入结果框
model_name = model_name_list[i]
results.loc[model_name, :] = [mae, rmse]
# 中值基准度量
baseline = np.median(y_train)
baseline_mae = np.mean(abs(baseline - y_test))
baseline_rmse = np.sqrt(np.mean((baseline - y_test) ** 2))
results.loc['Baseline', :] = [baseline_mae, baseline_rmse]
return results
results = evaluate(X_train, X_test, y_train, y_test)
print(results)
结果如下表所示:
模型名称 | MAE-平均绝对误差 | RMSE-均方根误差 |
---|---|---|
Linear Regression | 3.41954 | 4.27557 |
ElasticNet Regression | 3.62976 | 4.58258 |
Random Forest | 3.04111 | 3.77742 |
Extra Trees | 3.14586 | 4.05109 |
SVM | 3.50899 | 4.38375 |
Gradient Boosted | 3.19128 | 4.00822 |
Baseline | 3.78788 | 4.82523 |
至此模型的训练已经完成,后续的分析将在第五节中呈现。
4. 分析与总结
模型训练完成之后,并非全部工作完成了,我们在第四节的最后训练出了多个模型,但是模型也有准确率之别,我们下一步就是要找出最合适的模型,代码如下:
# 找出最合适的模型
plt.figure(figsize=(12, 8))
# 平均绝对误差
ax = plt.subplot(1, 2, 1)
results.sort_values('mae', ascending = True).plot.bar(y = 'mae', color = 'b', ax = ax, fontsize=20)
plt.title('平均绝对误差', fontsize=20)
plt.ylabel('MAE', fontsize=20)
# 均方根误差
ax = plt.subplot(1, 2, 2)
results.sort_values('rmse', ascending = True).plot.bar(y = 'rmse', color = 'r', ax = ax, fontsize=20)
plt.title('均方根误差', fontsize=20)
plt.ylabel('RMSE',fontsize=20)
plt.tight_layout()
plt.show()
生成的图像如图4-1所示:
# 保存线性回归模型*
model = LinearRegression()
model.fit(X_train, y_train)
filename = 'LR_Model'
pickle.dump(model, open(filename, 'wb'))
至此,建立大数据模型的工作已经全部完毕,并且保存了我们训练好的模型,以后如果想预测某个学生的成绩,只要将相关属性输入模型即可得出预测结果。
回过头来再想一下,大数据学习的初衷是数据分析,因此在建立模型之前,有必要对单个属性先进行一些分析,毕竟在某些数据集中可能会存在一些影响性大甚至是决定性的属性。但对属性分析不意味着随便拿属性分析,除了理性选择之外,我们最好提前把数据进行编码,然后分析各个属性的相关性,选取一些相关性较高的属性分析。比如本项目中,失败次数,学生个人升学意愿对学生的成绩影响非常明显。
se’, color = ‘r’, ax = ax, fontsize=20)
plt.title(‘均方根误差’, fontsize=20)
plt.ylabel(‘RMSE’,fontsize=20)
plt.tight_layout()
plt.show()
生成的图像如图4-1所示:
[外链图片转存中...(img-737XOIuU-1576926060105)]
<center>图4-1 各模型误差柱状图</center>
显然,无论是平均绝对误差还是均方根误差,线性回归模型都是最小的。因此我们对它进行保存,以便于以后调用,代码很简单:
*
```python
# 保存线性回归模型*
model = LinearRegression()
model.fit(X_train, y_train)
filename = 'LR_Model'
pickle.dump(model, open(filename, 'wb'))
至此,建立大数据模型的工作已经全部完毕,并且保存了我们训练好的模型,以后如果想预测某个学生的成绩,只要将相关属性输入模型即可得出预测结果。
回过头来再想一下,大数据学习的初衷是数据分析,因此在建立模型之前,有必要对单个属性先进行一些分析,毕竟在某些数据集中可能会存在一些影响性大甚至是决定性的属性。但对属性分析不意味着随便拿属性分析,除了理性选择之外,我们最好提前把数据进行编码,然后分析各个属性的相关性,选取一些相关性较高的属性分析。比如本项目中,失败次数,学生个人升学意愿对学生的成绩影响非常明显。
需要强调的是,基于大数据的预测并不是绝对准确的,很多时候由于各种原因可能会造成离谱的误差,因此它只是我们预测结果的参考值。
来源:CSDN
作者:Wave-Cao
链接:https://blog.csdn.net/qq_41108186/article/details/103647371