文章目录
前言
书接上回,基于分类算法的学习失败预警(上),我们到底选择训练集上效果好的模型,还是测试集上效果好的模型呢?如何判断它是否过拟合了呢?本篇博客将给出答案,同时介绍一些将结果进行可视化的方法。
解答
在机器学习中,选择模型时应优先考虑测试集
上效果更好的模型,而判断模型是否过拟合需要结合训练集和测试集的表现进行分析。这里我们简单回顾一下什么是训练集,什么是测试集
训练集:用于模型参数学习,目标是让模型尽可能拟合训练数据。
测试集:用于评估模型的泛化能力(即对新数据的适应能力),反映模型在真实场景中的表现。
因此可以得出一下结论:
- 若模型在训练集和测试集上表现均优异,说明模型既充分学习了训练数据的规律,又具备良好的泛化能力。
- 若模型在训练集上表现好、测试集上表现差,则可能存在过拟合。
综上所述,我们应该选择在训练集上表现更好的模型,而不是通过网格搜索得出的在验证集
上效果最好的模型。
验证集(Validation Set)是机器学习流程中重要的中间数据集,其核心功能是辅助模型选择和超参数调优,确保模型在最终测试前具备最佳泛化能力。
注:这里的验证集我们并没有显著划分表明,而是在训练集上通过多折交叉验证得出的
1.程序的恢复
1.1数据的保存
因为上次程序没有写完,同时从头开始运行时间又太长了,因此我们选择将预处理完成的数据进行保存(即重新运行到该步骤即可),加入如下代码将数据保存:
python
train=pd.concat([pd.DataFrame(x_train,columns=df_downsample.columns[:-1]),pd.DataFrame(y_train,columns=df_downsample.columns[-1:])],axis=1)
train.to_csv("train.csv",index=False)
test=pd.concat([pd.DataFrame(x_test,columns=df_downsample.columns[:-1]),pd.DataFrame(y_test,columns=df_downsample.columns[-1:])],axis=1)
test.to_csv("test.csv",index=True)
后面就基于上述文件展开操作(建议新建一个文件操作),这里index忘记改了,后面会把这列数据删除的。
1.2数据加载
将保存的数据加载进来,代码如下:
python
import pandas as pd
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')
train_x=train.drop(['SState'],axis=1)
train_y=train['SState']
test_x=test.drop(['SState'],axis=1)
test_x=test_x.iloc[:,1:]
test_y=test['SState']
train_x.shape,test_x.shape
运行结果:
这里在测试集时加了一个切片操作,主要因为之前存储的时候改了index参数
1.3模型搭建
这里采用之前选择的参数,构建随机森林,代码如下:
python
from sklearn.metrics import recall_score
from sklearn.ensemble import RandomForestClassifier
clasifier = RandomForestClassifier(criterion = 'entropy', random_state = 1, n_estimators = 50,max_depth=20,min_samples_leaf=4,max_features=5)
clasifier.fit(train_x, train_y)
y_pred = clasifier.predict(test_x)
recall_score(test_y, y_pred)
运行结果:

这里发现召回率和之前并不一样,即使将random_state=1
设置和之前一样。
这里我们再次用之前的遍历方式寻找最优参数:
python
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(train_x,train_y)
y_pred=clf.predict(test_x)
recall=recall_score(test_y,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
运行结果:
从结果来看,,和之前的参数相比,参数发生了很大的变化,甚至基决策树的类型也发生了变化。我们将相关参数设置成最优的参数,运行的结果却仍不相同。
这是什么原因呢?
因为随机森林是基于Bagging算法实现的,基学习器的数据集之间的差异以及选择特征不同导致结果可能不同。
1.4网格搜索
这里我们使用网格搜索寻找最优参数组合,代码如下:
python
from sklearn.metrics import make_scorer
from sklearn.model_selection import StratifiedKFold, GridSearchCV
clf=RandomForestClassifier(oob_score=True,random_state=1)
refit_score=make_scorer(recall_score)
skf=StratifiedKFold(n_splits=3)
grid_search=GridSearchCV(clf,param_grid=params,cv=skf,scoring=refit_score,n_jobs=-1)
grid_search.fit(train_x,train_y)
print(grid_search.best_params_)
print(grid_search.best_score_)
运行结果:
这里对模型进行评估:
python
from sklearn.metrics import confusion_matrix, accuracy_score, roc_auc_score, f1_score
y_pred=grid_search.predict(test_x)
# recall_score(test_y,y_pred)
print(pd.DataFrame(confusion_matrix(test_y,y_pred),columns=['pred_0','pred_1'],index=['real_0','real_1']))
print("accuracy",accuracy_score(test_y,y_pred))
print("recall",recall_score(test_y,y_pred))
print("auc",roc_auc_score(test_y,y_pred))
print("f1",f1_score(test_y,y_pred))
运行结果:
对比之前的模型:
从测试集的结果来看,两者差异并不是太大
2.结果可视化
首先,定义绘图参数、对图表标题、坐标标签字体大小等进行设置以控制输出较好的显示效果,并将参数更新到matplotlib
中,具体代码如下:
python
from matplotlib import pyplot as plt
parms={
'legend.fontsize': 'x-large',
'figure.figsize': (12, 9),
'axes.labelsize': 'x-large',
'axes.titlesize':'x-large',
'xtick.labelsize':'x-large',
'ytick.labelsize':'x-large'
}
plt.rcParams.update(parms)
2.1ROC曲线
这里我们将测试集分成三份,,分别绘制三分样本的曲线,并将曲线用不同形式和色彩进行绘制。
具体代码如下:
python
import numpy as np
tprs=[]
aucs=[]
mean_fpr=np.linspace(0,1,100)
skf=StratifiedKFold(n_splits=3)
linetypes=['--',':','-.','-']
i=0
for train,test in skf.split(x_test,y_test):
probas_=grid_search.predict_proba(x_test[test])
fpr,tpr,thresholds=roc_curve(y_test[test],probas_[:,1])
# tprs.append(interp1d(fpr,tpr)(mean_fpr))
interp_tpr = interp1d(fpr, tpr)(mean_fpr)
tprs.append(interp_tpr)
# tprs.append(interp1d(mean_fpr,fpr,tpr))
tprs[-1][0]=0.0
roc_auc=auc(fpr,tpr)
aucs.append(roc_auc)
plt.plot(fpr,tpr,lw=1.5,alpha=0.8,label='ROC fold %d (AUC=%0.3f)'%(i,roc_auc),linestyle=linetypes[i])
i+=1
plt.plot([0,1],[0,1],linestyle='--',lw=1,color='r',label='Chance',alpha=.6)
mean_tpr=np.mean(tprs,axis=0)
mean_tpr[-1]=1.0
mean_auc=auc(mean_fpr,mean_tpr)
std_auc=np.std(aucs)
plt.plot(mean_fpr,mean_tpr,color='b',label=r'Mean ROC (AUC=%0.3f $\pm$ %0.3f)'%(mean_auc,std_auc),lw=2,alpha=.8)
std_tpr=np.std(tprs,axis=0)
tprs_upper=np.minimum(mean_tpr+std_tpr,1)
tprs_lower=np.maximum(mean_tpr-std_tpr,0)
plt.fill_between(mean_fpr,tprs_lower,tprs_upper,color='grey',alpha=.15,label=r'$\pm$ 1 std. dev.')
plt.xlim([-0.02,1.02])
plt.ylim([-0.02,1.02])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend(loc='lower right')
plt.show()
运行结果:
实线部分为平均ROC曲线,其下的面积用AUC的值表示,随机森林算法的AUC均值可达到0.967,可见,平均ROC曲线效果较好。
为了迭代计算随机森林评估器的准确率和查全率,将3份数据的平均准确率和平均查全率变化趋势可视化。
2.2准确率
代码如下:
python
results = grid_search.cv_results_
plt.plot(results['mean_train_accuracy_score'])
plt.plot(results['mean_test_accuracy_score'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['mean_train_accuracy_score','mean_test_accuracy_score'], loc='lower right')
plt.show()
运行结果:
随着迭代次数的变多,训练集准确率不断提高,测试集准确率也不断提高,但低于训练集的准确率
2.3查全率
代码部分:
python
results = grid_search.cv_results_
plt.plot(results['mean_train_recall_score'])
plt.plot(results['mean_test_recall_score'])
plt.title('model recall')
plt.ylabel('recall')
plt.xlabel('epoch')
plt.legend(['mean_train_recall_score','mean_test_recall_score'], loc='lower right')
plt.show()
运行结果:
通过结果发现,随着迭代次数的升高,训练集和测试集的召回率都在升高,测试集的召回率波动较大
- 训练准确率和查全率均不断提升,在达到较高值之后提升较慢,测试准确率和查全率整体趋势与之相近,但均低于训练准确率。
2.4 OOB曲线
OOB 曲线(Out-of-Bag Curve)是随机森林模型中用于评估模型性能的一种工具,通过袋外数据(未参与单棵树训练的样本)计算误差率,并绘制误差率随决策树数量增加的变化趋势。
因为随机森林基于Bagging算法实现,每个基学习器的训练样本是从原数据集有放回采样,因此有些数据就没有被抽取到,称为袋外数据(OOB数据)。每个基学习器训练完成后,用其对应的OOB数据进行预测,累计所有基学习器的预测结果得到 OOB 误差率。
代码如下:
python
min_estimators= 1
max_estimators= 149
clf= grid_search.best_estimator_
errs=[]
for i in range(min_estimators,max_estimators+1):
clf.set_params(n_estimators=i)
clf.fit(x_train,y_train)
oob_error=1-clf.oob_score_
# y_pred=clf.predict(x_test)
errs.append(oob_error)
plt.plot(errs,label='RandomForestClassifier')
plt.xlim(min_estimators, max_estimators)
plt.xlabel("n_estimators")
plt.ylabel("OOB error rate")
plt.legend(loc="upper right")
plt.show()
运行结果:
通过结果可以发现,随着评估器的增加,模型的误差逐渐下降,在超过80个评估器之后,基本达到最低值,其后下降趋势放缓,并趋于稳定。
2.5参数变化可视化
网格搜索过程中,使用sklearn中model_selection的validation_curve方法实现对节点再划分所需最小样本数(min_samples_split)的参数的变化情况进行分析。
代码如下:
python
from sklearn.model_selection import validation_curve
param_range= range(2,10)
train_scores, test_scores= validation_curve(grid_search.best_estimator_, x_train, y_train,param_name='min_samples_split',param_range=param_range,cv=3,scoring="recall", n_jobs=-1)
train_scores_mean= np.mean(train_scores, axis=1)
train_scores_std= np.std(train_scores, axis=1)
test_scores_mean= np.mean(test_scores, axis=1)
test_scores_std= np.std(test_scores, axis=1)
train_scores_mean.shape
# 设置字体为黑体
plt.rcParams['font.sans-serif'] = ['SimHei'] # 使用黑体字体
plt.title("验证曲线")
plt.xlabel("min_samples_split")
plt.ylabel("Score")
lw= 2
plt.semilogx(param_range, train_scores_mean, label="Training score",color="darkorange", lw=lw)
plt.fill_between(param_range, train_scores_mean-train_scores_std,train_scores_mean+ train_scores_std, alpha=0.2,
color="darkorange", lw=lw)
plt.semilogx(param_range, test_scores_mean, label="Cross-validation score",color="navy", lw=lw)
plt.fill_between(param_range, test_scores_mean-test_scores_std,test_scores_mean+ test_scores_std, alpha=0.2,
color="navy", lw=lw)
plt.legend(loc="best")
plt.show()
运行结果:
当 min_samples_split 较小时,模型在训练集和验证集上表现较均衡;但随着 min_samples_split 增大,训练集分数仍高,验证集分数下降,可能暗示模型泛化能力变差,存在过拟合倾向。
同理我们对最大深度进行统计。代码如下:
python
param_range= [5,10,15,20]
train_scores, test_scores= validation_curve(grid_search.best_estimator_, x_train, y_train,param_name='max_depth',param_range=param_range,cv=3,scoring="recall", n_jobs=-1)
train_scores_mean= np.mean(train_scores, axis=1)
train_scores_std= np.std(train_scores, axis=1)
test_scores_mean= np.mean(test_scores, axis=1)
test_scores_std= np.std(test_scores, axis=1)
plt.title("验证曲线")
plt.xlabel("max_depth")
plt.ylabel("Score")
lw= 2
plt.semilogx(param_range, train_scores_mean, label="Training score",color="darkorange", lw=lw)
plt.fill_between(param_range, train_scores_mean-train_scores_std,train_scores_mean+ train_scores_std, alpha=0.2,
color="darkorange", lw=lw)
plt.semilogx(param_range, test_scores_mean, label="Cross-validation score",color="navy", lw=lw)
plt.fill_between(param_range, test_scores_mean-test_scores_std,test_scores_mean+ test_scores_std, alpha=0.2,
color="navy", lw=lw)
plt.legend(loc="best")
plt.show()
运行结果:
随着最大深度的增加,训练集和验证集上的召回率不断增加,当max_depth=10时两者性能趋于稳定。
3.特征重要性分析
3.1特征重要性统计
对模型的输入特征进行重要性分析,并按照重要程度进行排序代码如下:
python
classifier = grid_search.best_estimator_
importances= classifier.feature_importances_
indices = np.argsort(importances)[::-1]
for f in range(x.shape[1]):
print("%d. feature %d (%f)" % (f + 1, indices[f],
importances[indices[f]]))
运行结果:
3.2可视化
为了更加详细和具体的展示重要性,采用柱状图的形式将其可视化出来,代码如下:
python
plt.figure()
plt.title("Feature importances")
std = np.std([tree.feature_importances_ for tree in classifier.estimators_], axis=0)
plt.bar(range(x.shape[1]),importances[indices],color="r",yerr=std[indices],align='center')
plt.xticks(range(x.shape[1]), indices)
plt.xlim([-1, x.shape[1]])
plt.show()
运行结果:
3.3列名匹配
为了方便直观阅读,采用如下代码将特征的序号与列名进行对应,按照从低到高的顺序进行排序,结果存于result_importances字段中。
python
results_importances=list(zip(df.columns[0:len(df.columns.tolist())-1],classifier.feature_importances_))
results_importances.sort(key=lambda x:x[1])
results_importances
运行结果:
通过结果可以看出,模型中最重要的前五个特征分别是EXAM_AH_SCORE、COURSE_WORKCOUNT、EXAM_HOMEWORK、BROWSER_COUNT、EXAM_MIDDLE_SCORE,可见,形考(EXAM_AH_SCORE)成绩对于学习成败非常关键,而剩余的其他几项均与学生的努力程度相关。
备注
如果感兴趣,可以使用其他的分类器实现类别分类,对比一下性能。下述是我对比的结果。
通过结果发现,还是随机森林性能综合较好。
结语
本案例的基于分类算法的学习失败预警,也是一个二分类问题,因此有相当多的模型可供选择,不限于本篇博客所举例的,本篇博客主要带你了解机器学习实践的过程,相对之前案例,增加了数据集的划分,网格搜索、数据可视化等内容。因为本人所学有限,也遇到了一些问题,欢迎交流!!!
本案例参考教材:《Python机器学习实战案例(第二版)》赵卫东、董亮