当前位置: 首页 > news >正文

基于分类算法的学习失败预警(上)

文章目录

  • 前言
  • 1.数据预处理
    • 1.1数据探索
    • 1.2数值化处理
    • 1.3空值填充
    • 1.4添加标签
    • 1.5特征筛选
    • 1.6数据集类别平衡
    • 1.7划分数据集
    • 1.8训练集类平衡
    • 1.9标准化
  • 2.模型选择
    • 2.1建立模型
    • 2.2模型调参
      • 2.2.1遍历调参
      • 2.2.2网格搜索
  • 结语

前言

本次案例通过对现有数据分析,采用如下图所示的学习失败风险预测流程,从整体上分为生成模型和使用模型两个层次:

  • 生成模型:将原始数据经过特征工程进行统计加工生成新的样本特征,对所有特征进行验证和清洗,剔除缺失值较多的特征。在分类算法中,不平衡的样本对算法影响较大(上次实验已用实验证明,使用不平衡的训练集进行训练,容易出现过拟合,导致模型实际性能较差),所以在模型训练前先对样本进行平衡,通过子采样的方法使两类标签的样本基本相同,同时将数据集划分成训练集和测试集,用于模型训练和验证,经过多轮训练,验证过后最终确认风险预测模型。
  • 使用模型:模型的输入数据也需要经过和训练阶段相同的处理流程,即原始数据经过特征工程和数据清洗之后,将最终选择的特征输入至模型中,经过预测之后的结果作为测试样本的风险值。、
    在这里插入图片描述

1.数据预处理

1.1数据探索

首先,拿到数据第一件事就是数据探索,旨在理清样本数量、数据类型等信息。这里我们借用panda库进行操作,将用户相关特征读取进来转化为DataFrame对象。具体操作如下:

import pandas as pd

df=pd.read_csv("uwide.csv")
df.info()

运行结果为:
在这里插入图片描述
在这里插入图片描述
通过结果可以看出,该数据集共计2006条数据,共计52列(特征数),部分列含有缺失值,后续可能需要某种算法进行填充。同时52个特征中有29个特征的特征值类型为float、13个特征的特征值类型为int型数据,10个特征的特征值是object(未知),该变量共计占815.1+KB内存存储。
其次,我们也可以通过如下代码,统计数据的分布情况。

df.describe()

运行结果:
在这里插入图片描述
其中count字段数据凡是小于2006的都含有缺失值,只是程度不同,后续需要将缺失程度较大的特征剔除。

1.2数值化处理

通过整体观察阶段,我们发现有10个特征的特征值是object(未知),因此需要将object类型字段处理为数字类型变量(这里根据后续特征需要,只对SEX字段进行了数值化处理,其他的object类型字段均被剔除比如USERID等)
代码如下:

factor=pd.factorize(df['SEX'])
df.SEX=factor[0]
definitions=factor[1]
print(df.SEX.head())
print(definitions)

运行结果:
在这里插入图片描述
通过结果发现,0指代了‘女’,1指代了‘男’,达到了数字化的效果。

1.3空值填充

接着,我们需要对空值进行处理,首先,查看哪些列具有空值:

null_columns=df.columns[df.isnull().any()]
df[null_columns].isnull().sum()

运行结果:
在这里插入图片描述
通过结果发现,不同列出现了不同程度的缺失,共计2006条数据,有的缺失值都达到了1778个,对于缺失值严重的,我们后续要对该特征进行去除。

对于列含有缺失值的,我们需要进行统计并填充,这里选用了较简单的方式,直接填充0,填充完成后,我们再次统计含有空值的列,具体代码如下:

df=df.fillna(0)
df.columns[df.isnull().any()]

在这里插入图片描述
通过结果可以看到,目前和我们预计的效果一致,无缺失值了

1.4添加标签

因为该问题是一个二分类问题,但数据集中并没有相应的标签,因此我们需要添加一列表示标签(label)。
这里标签的值可以通过TOTALSCORE字段的值是否大于60进行计算判断。因为本实验的目的是预测出学业失败的,所以我们将失败的表示为1(即低于60分的),然后我们统计特征数。具体代码如下:

import numpy as np

df["SState"]=np.where(df["TOTALSCORE"]>60,0,1)
cols=df.columns.tolist()
cols,len(cols)

运行结果:
在这里插入图片描述
通过结果发现,此时特征数由52->53,多出的特征为新加的label。

1.5特征筛选

我们将数据质量较差的字段移除,同时去掉一些无意义的变量(主观判断),因为这类字段无助于分类算法的改进,故将此类字段全部进行过滤,过程如下:

df=df[[
    # 'USERID',
 'BROWSER_COUNT',
 'COURSE_COUNT',
 #'COURSE_LAST_ACCESS',
 'COURSE_SUM_VIEW',
 'COURSE_AVG_SCORE',
 'EXAM_AH_SCORE',
 'EXAM_WRITEN_SCORE',
 'EXAM_MIDDLE_SCORE',
 'EXAM_LAB',
 'EXAM_PROGRESS',
 'EXAM_GROUP_SCORE',
 'EXAM_FACE_SCORE',
 'EXAM_ONLINE_SCORE',
 #'NODEBB_LAST_POST',
 'NODEBB_CHANNEL_COUNT',
 'NODEBB_TOPIC_COUNT',
 'COURSE_SUM_VIDEO_LEN',
 'SEX',
 # 'MAJORID',
 # 'STATUS',
 # 'GRADE',
 # 'CLASSID',
 'EXAM_HOMEWORK',
 'EXAM_LABSCORE',
 'EXAM_OTHERSCORE',
 'NODEBB_PARTICIPATIONRATE',
 'COURSE_WORKTIME',
 'COURSE_WORKACCURACYRATE',
 'COURSE_WORKCOMPLETERATE',
 'NODEBB_POSTSCOUNT',
 'NODEBB_NORMALBBSPOSTSCOUONT',
 'NODEBB_REALBBSARCHIVECOUNT',
 'NORMALBBSARCHIVECOUNT',
 'COURSE_WORKCOUNT',
 # 'STUNO',
 # 'ID',
 # 'STUID',
 # 'COURSEOPENID',
 # 'HOMEWORKSCORE',
 # 'WRITTENASSIGNMENTSCORE',
 # 'MIDDLEASSIGNMENTSCORE',
 # 'LABSCORE',
 # 'OTHERSCORE',
 # 'TOTALSCORE',
 # 'STUDYTERM',
 # 'COURSEID',
 # 'EXAMNUM',
 # 'PROCESS',
 # 'GROUPSTUDYSCORE',
 # 'FACESTUDYSCORE',
 # 'ONLINESTUDYSCORE',
 'SState']]
len(df.columns.tolist()),df.columns.tolist()

运行结果为:

在这里插入图片描述

通过结果可以看出,列数由53->29
这里我们也可以查看前几行数据
在这里插入图片描述
通过结果可以看到,每行数据确实变成了28(特征)+1(标签)=29

1.6数据集类别平衡

我们可以通过如下代码,观察不同类别的数量。

df.SState.value_counts()

在这里插入图片描述
通过结果发现,类别存在不平衡现象,因此我们可以先做一个初步的平衡,直接下采样多的类别,具体代码如下:

df_major=df[df['SState']==0]
df_minor=df[df['SState']==1]
df_major_downsample=df_major.sample(8*len(df_minor))
df_downsample=pd.concat([df_major_downsample,df_minor])
df_downsample.SState.value_counts()

运行结果:
在这里插入图片描述
当然此处你也可以使用resample()函数。这里之所以平衡数据集,为了确保训练 / 测试集的类别分布合理,避免评估偏差,此处将数据集类别比例调整到8:1.

1.7划分数据集

因为本次数据只有一个文件即整个数据集,因此需要对数据集进行划分,此处选择8:2的比例进行划分,你也可以选择其他的比例,具体代码如下:

from sklearn.model_selection import train_test_split

x=df_downsample.iloc[:,:-1].values
y=df_downsample.iloc[:,-1].values
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.2,random_state=1)
x_train.shape,x_test.shape

运行结果:
在这里插入图片描述
通过结果可以发现,训练集样本数量为1476,测试集样本数量为369。

1.8训练集类平衡

此时我们再进行统计训练集的类别比例,发现类不平衡现象。(注:因为之前代码取的是DataFrame的value值,此时x_train,x_test,y_train,y_test都是numpy.ndarray类型数据,所以没有直接用value_counts()函数)
在这里插入图片描述

为了避免模型对类别的偏好,因此进行类平衡。这里我们采用SMOTE算法生成新的样本

SMOTE(Synthetic Minority Over-sampling Technique)是一种用于解决数据不平衡问题的过采样算法,主要通过生成少数类的合成样本来平衡数据集。

from imblearn.over_sampling import SMOTE

smote=SMOTE(random_state=1)
x_smote,y_smote=smote.fit_resample(x_train,y_train)
np.sum(y_smote==1),np.sum(y_smote==0)

运行结果:
在这里插入图片描述
用生成的样本覆盖之前的样本。

x_train=x_smote
y_train=y_smote

1.9标准化

由于特征中值差别较大,较分散,会影响模型的训练效果,因此采用变换将特征值映射到某一取值范围中,并记录变换方法方便进行还原。变换方式如下:
在这里插入图片描述
代码如下:

from sklearn.preprocessing import StandardScaler

scale=StandardScaler()
x_train=scale.fit_transform(x_train)
x_test=scale.transform(x_test)

备注:这里需要对测试集进行同样的处理,而不是使用fit_transform()方法,因为该方法会重新计算均值和方差,使测试集使用的均值和方差不是训练集的,导致变换方式不一致,后续会出现泛化性能较低的结果。

2.模型选择

根据上次案例的经验——机器学习实战案例——保险产品推荐(下),集成学习的效果比单个决策树效果更好,所以解决此类问题推荐使用随机森林或adaboosting。这里以随机森林为例。

2.1建立模型

因为随机森林可以直接通过RandomForestClassifier()函数直接调用,通过fit()函数实现训练。训练完成后通过一个指标进行衡量模型的好坏,因为我们希望能够将找到所有具有学习失败风险的学生,并及时给出预警。所以此处选择查全率(召回率)。具体代码如下:

from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import recall_score


clf=RandomForestClassifier(random_state=1)
clf.fit(x_train,y_train)
y_pred=clf.predict(x_test)
print(recall_score(y_test,y_pred))

运行结果:
在这里插入图片描述
此处我们发现模型性能并不是很优异,因此我们进行调参,进一步找到更好模型。

2.2模型调参

2.2.1遍历调参

这里我们按照上一篇博客的方式进行调参:机器学习实战案例——保险产品推荐(下)
具体代码如下:

params={
    'criterion':['gini','entropy'],
    'n_estimators':[50,100,150,200],
    'max_depth':[5,10,15,20],
   'min_samples_split':range(2,10,2),
    'max_features':[5,10,20]
}
results=pd.DataFrame(columns=['criterion','n_estimators','max_depth','min_samples_split','max_features','recall'])
for criterion in params['criterion']:
    for n_estimators in params['n_estimators']:
        for max_depth in params['max_depth']:
            for min_samples_split in params['min_samples_split']:
                for max_features in params['max_features']:
                    clf=RandomForestClassifier(criterion=criterion,n_estimators=n_estimators,max_depth=max_depth,min_samples_split=min_samples_split,max_features=max_features,random_state=1)
                    clf.fit(x_train,y_train)
                    y_pred=clf.predict(x_test)
                    recall=recall_score(y_test,y_pred)
                    row=pd.DataFrame({'criterion':criterion,'n_estimators':n_estimators,'max_depth':max_depth,'min_samples_split':min_samples_split,'max_features':max_features,'recall':recall},index=[0])
                    results=pd.concat([results,row],ignore_index=True)
# results.sort_values(by='recall',ascending=False)
results



运行结果:
在这里插入图片描述

这个运行时间可能有点长,这个警告可以不用管暂时。通过结果发现最优参数为前两个,即entropy,50,20,2,5,0.800000entropy,50,20,4,5,0.800000

2.2.2网格搜索

这里我们介绍另一种方式进行选择参数——网格搜索。

网格搜索(Grid Search)是机器学习中用于超参数调优的经典方法,通过系统性地遍历指定参数的所有可能组合,找到最优参数配置。

代码如下:

from sklearn.metrics import make_scorer
from sklearn.model_selection import  GridSearchCV

classifier=RandomForestClassifier(oob_score=True,random_state=1)
refit_score=make_scorer(recall_score)
grid_search=GridSearchCV(classifier,param_grid=params,scoring=refit_score,n_jobs=-1)
grid_search.fit(x_train,y_train)
print(grid_search.best_params_)
print(grid_search.best_score_)

这里加了n_jobs=-1不然默认使用单线程、会运行很长时间,譬如
在这里插入图片描述
在这里插入图片描述
此处结果为:{'criterion': 'entropy', 'max_depth': 20, 'max_features': 5, 'min_samples_split': 4, 'n_estimators': 200}召回率更是达到了0.98,貌似比之前的结果好了不止一星半点儿。真是如此吗?显然不是,这里都没有用到测试集,所以这里的评分基于训练集的,因此我们查看它真实的召回率(即在测试集上的)代码如下:

y_pred=grid_search.predict(x_test)
print(recall_score(y_test,y_pred))

在这里插入图片描述
这时我们再查看之前我们逐个遍历的结果,结果保持一致(因为random_state=1设置的一致)在这里插入图片描述

结语

这里我们抛出一个疑问,到底选择训练集上效果好的模型,还是测试集上效果好的模型呢?如何判断它是否过拟合了呢?(注:其实我怕回答不恰当,容我去问问,哈哈)
未完待续······

相关文章:

  • 力扣热题100(方便自己复习,自用)
  • 利用ffmpeg库实现音频Opus编解码
  • 车载以太网网络测试-18【传输层-DOIP协议-1】
  • PyTorch模型转ONNX例子
  • 深入探究 JVM 堆的垃圾回收机制(一)— 判活
  • python3 -m http.sever 8080加载不了解决办法
  • 6个常见的Python设计模式及应用场景
  • Python实战:开发经典猜拳游戏(石头剪刀布)
  • MySQL事务全解析:从概念到实战
  • 【CXX-Qt】2.1.1 为 WebAssembly 构建
  • 汽车免拆诊断案例 | 2024 款路虎发现运动版车无法正常识别智能钥匙
  • Java EE 进阶:MyBatis
  • 【NLP】 11. 神经网络,线性模型,非线性模型,激活函数,感知器优化,正则化学习方法
  • SpringBoot配置文件加载优先级
  • 最大公约数(GCD)和最小公倍数(LCM)专题练习 ——基于罗勇军老师的《蓝桥杯算法入门C/C++》
  • 蓝桥杯2023年第十四届省赛真题-接龙数列
  • Linux后门程序工作原理的详细解释,以及相应的防御措施
  • c语言数据结构 双循环链表设计(完整代码)
  • Ubuntu版免翻墙搭建BatteryHistorian
  • freeswitch(开启抓包信息)
  • 车主质疑零跑汽车撞车后AEB未触发、气囊未弹出,4S店:其把油门当刹车
  • 4月新增社融1.16万亿,还原地方债务置换影响后信贷增速超过8%
  • 最新研究:新型合成小分子可“精准杀伤”癌细胞
  • 沙县小吃中东首店在沙特首都利雅得开业,首天营业额超5万元
  • 牛市早报|中美日内瓦经贸会谈联合声明公布
  • 科创板年内第3家!健信超导IPO获受理,拟募资8.65亿