提示:该Blog仅用于作业汇报展示,大佬请绕路
该项目依托于某医院处理好之后的体检数据,首先进行了一些简单的数据分析,然后使用多重线性分析法、递归特征消除法、正则化法三种方法对数据进行特征选择,选择出了预测相关度最高的几个属性,以此来分别训练Logistic、SVM、XGBoost三种算法模型,观察其训练效果,最后再进行模型调参,观察调参前后的训练效果。
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sn
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression
from sklearn import preprocessing
from sklearn.svm import SVC, LinearSVC
from sklearn.feature_selection import SelectFromModel
from sklearn.metrics import confusion_matrix
from sklearn.metrics import f1_score
from sklearn.metrics import roc_curve,auc
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
from sklearn.model_selection import train_test_split
from xgboost import XGBClassifier
from sklearn.model_selection import GridSearchCV
import warnings
# 查看数据的前五行数据
dataConcat.head()
# 查看数据总共有多少行,多少个特征值
dataConcat.shape
结果:(1006, 16)
# 查看数据详细的详细
dataConcat.info()
# 为了后续更好的将数据输入机器学习模型,有必要将某些数据列进行分桶,也就是划分区间
# 选取年龄这个特征值
age = dataConcat['年龄']
# 开始对数据进行分桶
# 将年龄分为四个区间,(0,30),(30,45)(45,60)(60,100),并将四个区间的标签设置为0,1,2,3
age_binary = pd.cut(age,[0,30,45,60,100],labels=[0,1,2,3],right=False)
dataConcat['年龄_cut'] = age_binary
# 因为添加减去的数据类型为category,所有要做一次数据转换
dataConcat['年龄_cut'] = dataConcat['年龄_cut'].astype('float')
# 接下来对脉搏和舒展压进行与年龄相同的数据处理
pulse = dataConcat['脉搏']
pulse_binary = pd.cut(pulse,[0,60,99,200],labels=[0,1,2],right=False)
dataConcat['脉搏'] = pulse_binary
dataConcat['脉搏'] = dataConcat['脉搏'].astype('float')
pressure = dataConcat['舒张压']
pressure_binary = pd.cut(pressure,[0,60,89,200],labels=[0,1,2],right=False)
dataConcat['舒张压'] = pressure_binary
dataConcat['舒张压'] = dataConcat['舒张压'].astype('float')
# 设置画布大小
fig=plt.figure(figsize=(12,30))
# 调整子图内部间距
plt.subplots_adjust(wspace=0.4,hspace=0.4)
# subplot2gird() 函数以非等分的形式对画布进行切分,并按照绘图区域的大小来展示最终绘图结果,这里设置为5行2列的画布,并开始第一个子图绘制
a1 = plt.subplot2grid((5,2),(0,0),colspan=2)
# 统计没有患糖尿病的人的体重分布数量
diabetes_0_1 = dataConcat.体重检查结果[dataConcat.是否糖尿病 == 0].value_counts()
# 统计患糖尿病的人的体重分布数量
diabetes_1_1 = dataConcat.体重检查结果[dataConcat.是否糖尿病 == 1].value_counts()
# 将diabetes_0_1和iabetes_1_1组合成一个Dataframe
df1 = pd.DataFrame({'糖尿病患者':diabetes_1_1,'正常人':diabetes_0_1})
# 由于患糖尿病的人没有偏瘦的人的存在,所以在将数据组合成DataFrame时糖尿病患者那一列存在空值,不利于之后画图,所以再次处理一下空值
df1['糖尿病患者'].fillna(0,inplace=True)
# 进行值的映射,源数据的0,1,2,3分别表示较瘦,正常,偏重,肥胖
weight_map={0:'较瘦',1:'正常',2:'偏重',3:'肥胖'}
df1.index = df1.index.map(weight_map)
# 绘制直方图
index = ['较瘦','正常','偏重','肥胖']
plt.bar(df1.index,df1['正常人']+df1['糖尿病患者'])
plt.bar(df1.index,df1['糖尿病患者'])
# plt.bar(['较瘦','正常','偏重','肥胖'],df1['正常人'])
plt.title(u"BMI不同的人患糖尿病情况")
plt.xlabel(u"体重检查结果")
plt.ylabel(u"人数")
plt.legend((u'正常人', u'糖尿病'),loc='best')
df1
# 在男女患病情况进行查看,步骤与上基本一致
diabetes_0 = dataConcat.性别[dataConcat.是否糖尿病 == 0].value_counts()
diabetes_1 = dataConcat.性别[dataConcat.是否糖尿病 == 1].value_counts()
df2 = pd.DataFrame({'糖尿病患者':diabetes_1,'正常人':diabetes_0})
sex_map={1:'男',0:'女'}
df2.index = df2.index.map(sex_map)
plt.bar(['男','女'],df2['糖尿病患者'])
plt.bar(['男','女'],df2['正常人'],bottom=list(df2.loc[:,'糖尿病患者']))
plt.title(u"男女患糖尿病情况")
plt.xlabel(u"性别")
plt.ylabel(u"人数")
plt.legend((u'糖尿病', u'正常人'),loc='best')
dataConcat.年龄[dataConcat.是否糖尿病 == 1].plot(kind='kde')
dataConcat.年龄[dataConcat.是否糖尿病 == 0].plot(kind='kde')
plt.xlabel(u"年龄")# plots an axis lable
plt.ylabel(u"密度")
plt.title(u"糖尿病和正常人年龄分布")
plt.legend((u'糖尿病', u'正常人'),loc='best')
# 绘制低密度脂蛋白胆固醇密度曲线
dataConcat.低密度脂蛋白胆固醇[dataConcat.是否糖尿病 == 1].plot(kind='kde')
dataConcat.低密度脂蛋白胆固醇[dataConcat.是否糖尿病 == 0].plot(kind='kde')
plt.xlabel(u"低密度脂蛋白胆固醇")# plots an axis lable
plt.ylabel(u"密度")
plt.title(u"低密度脂蛋白胆固醇分布")
plt.legend((u'糖尿病', u'正常人'),loc='best')
# 绘制高密度脂蛋白胆固醇密度曲线
dataConcat.高密度脂蛋白胆固醇[dataConcat.是否糖尿病==1].plot(kind='kde')
dataConcat.高密度脂蛋白胆固醇[dataConcat.是否糖尿病 == 0].plot(kind='kde')
plt.xlabel(u'高密度脂蛋白固醇')
plt.ylabel(u'密度')
plt.title(u'高密度脂蛋白固醇分布')
plt.legend((u'糖尿病',u'正常人'),loc='best')
#绘制尿素氮曲线
dataConcat.尿素氮[dataConcat.是否糖尿病 == 1].plot(kind='kde')
dataConcat.尿素氮[dataConcat.是否糖尿病 == 0].plot(kind='kde')
plt.xlabel(u"尿素氮")# plots an axis lable
plt.ylabel(u"密度")
plt.title(u"尿素氮分布")
plt.legend((u'糖尿病', u'正常人'),loc='best')
从上述的基本统计分析,我们可以大致观测出糖尿病人与正常人的一些差异,比如体重指数较高,高密度脂蛋白胆固醇较低,尿素氮和尿酸较高等
plt.figure(figsize=(10,8))
df_cor=dataConcat.iloc[:,1:-1]
df_cor.corr()
data_cor=df_cor.corr()
mask = np.triu(np.ones_like(data_cor, dtype=np.bool))
mask = mask[1:, :-1]
corr = data_cor.iloc[1:, :-1].copy()
cmap = sns.diverging_palette(0, 230, 90, 60, as_cmap=True)
sns.heatmap(corr,mask=mask, annot=True, fmt=".2f", cmap=cmap,linewidth=3,
vmin=-1, vmax=1, cbar_kws={"shrink": .8},square=True)
plt.title('属性热力图')
plt.tight_layout()#调整布局防止显示不全
当任何两个特征存在相关性时,就会出现多重线性,而在机器学习中,期望每个特征都应该是独立于其他特征,即每个特征之间没有共线性。
这里采用方差膨胀因子VIF来衡量多重线性,根据以往经验,VIF大于10,则代表特征有较严重的多重共线性。
如果模型仅用于预测,则只要拟合程度好,可不处理多重共线性问题,存在多重共线性的模型用于预测时,往往不影响预测结果
from statsmodels.stats.outliers_influence import variance_inflation_factor
feature_names = ['性别', '年龄_cut', '低密度脂蛋白胆固醇','高密度脂蛋白胆固醇',
'极低密度脂蛋白胆固醇','甘油三酯', '总胆固醇', '脉搏', '舒张压','高血压史','尿素氮','尿酸','肌酐','体重检查结果']
X=dataConcat[feature_names]
y=dataConcat.是否糖尿病
# 计算 VIF
vif = pd.Series([variance_inflation_factor(X.values, i) for i in range(X.shape[1])], index=X.columns)
# 展示VIF结果
index = X.columns.tolist()
vif_df = pd.DataFrame(vif, index = index, columns = ['vif']).sort_values(by = 'vif', ascending=False)
vif_df
#递归特征消除法,返回特征选择后的数据
#参数estimator为基模型
#参数n_features_to_select为选择的特征个数
min_max_scaler1 = preprocessing.MinMaxScaler()
# 进行归一化处理
X_train_minmax1 = min_max_scaler1.fit_transform(dataConcat[feature_names])#特征归一化处理
svc = SVC(kernel="linear")
selector=RFE(estimator=svc, n_features_to_select=8)
Xt=selector.fit_transform(X_train_minmax1,dataConcat.是否糖尿病)
print("N_features %s" % selector.n_features_) # 保留的特征数
print("Support is %s" % selector.support_) # 是否保留
print("Ranking %s" % selector.ranking_) # 重要程度排名
for i in zip(selector.ranking_,feature_names,selector.support_):
print(i)
model = LinearSVC(penalty= 'l1', C = 0.1,dual=False)
model.fit(X,y)
# 特征选择
# L1惩罚项的SVC作为基模型的特征选择,也可以使用threshold(权值系数之差的阈值)控制选择特征的个数
selector = SelectFromModel(estimator = model, prefit=True,max_features=8)
X_new = selector.transform(X)
feature_names = np.array(X.columns)
feature_names[selector.get_support()]#获取选择的变量
这里我才用混淆矩阵中的精确率(TP/(TP+FP))和召回率(TP/(TP+FN))、精确率和召回率的加权平均数(F1-score)、以及ROC曲线中的AUC(曲线下方面积占整个图的面积的比例)来对模型进行评估。其中ROC曲线是以假正率(FP/(FP+TN))为X轴,召回率为Y轴。
5.1 准备工作
select_features=[ '年龄_cut','高密度脂蛋白胆固醇','舒张压','脉搏','尿素氮','体重检查结果',"性别","甘油三酯"]
X1 = dataConcat[select_features]#变量筛选后的特征
y1 = dataConcat.是否糖尿病
# 切分数据集
train_X, val_X, train_y, val_y = train_test_split(X1, y1, random_state=1)
5.2 逻辑回归
select_features=[ '年龄_cut','高密度脂蛋白胆固醇','舒张压','脉搏','尿素氮','体重检查结果',"性别","甘油三酯"]
X1 = dataConcat[select_features]#变量筛选后的特征
y1 = dataConcat.是否糖尿病
# 切分数据集
train_X, val_X, train_y, val_y = train_test_split(X1, y1, random_state=1)
model_logistics = LogisticRegression()
model_logistics.fit(train_X,train_y)
y_pred = model_logistics.predict(val_X)
scores_logistics=[]
scores_logistics.append(precision_score(val_y, y_pred))
scores_logistics.append(recall_score(val_y, y_pred))
confusion_matrix_logistics=confusion_matrix(val_y,y_pred)
f1_score_logistics=f1_score(val_y, y_pred,labels=None, pos_label=1, average='binary', sample_weight=None)
precision_logistics = precision_score(val_y, y_pred, average='binary')# 精确率指模型预测为正的样本中实际也为正的样本占被预测为正的样本的比例。
importance=pd.DataFrame({"columns":list(val_X.columns), "coef":list(model_logistics.coef_.T)})
predictions_log=model_logistics.predict_proba(val_X)#每一类的概率
FPR_log, recall_log, thresholds = roc_curve(val_y, predictions_log[:,1],pos_label=1)
area_log=auc(FPR_log,recall_log)
print('logistics模型结果:\n')
print(pd.DataFrame(columns=['预测值=1','预测值=0'],index=['真实值=1','真实值=0'],data=confusion_matrix_logistics))#混淆矩阵
print("f1值:"+str(f1_score_logistics))
print("准确率和召回率为:"+str(scores_logistics))
print('模型系数:\n'+str(importance))
5.3 SVM
min_max_scaler = preprocessing.MinMaxScaler()#注意要归一化
X_train_minmax = min_max_scaler.fit_transform(train_X)
X_test_minmax=min_max_scaler.transform(val_X)
model_svm=SVC(probability=True)
model_svm.fit(X_train_minmax,train_y)
y_pred = model_svm.predict(X_test_minmax)
scores_svm=[]
scores_svm.append(precision_score(val_y, y_pred))
scores_svm.append(recall_score(val_y, y_pred))
confusion_matrix_svm=confusion_matrix(val_y,y_pred)
f1_score_svm=f1_score(val_y, y_pred,labels=None, pos_label=1, average='binary', sample_weight=None)
predictions_svm=model_svm.predict_proba(X_test_minmax)#每一类的概率
FPR_svm, recall_svm, thresholds = roc_curve(val_y,predictions_svm[:,1], pos_label=1)
area_svm = auc(FPR_svm,recall_svm)
print('svm模型结果:\n')
print(pd.DataFrame(columns=['预测值=1','预测值=0'],index=['真实值=1','真实值=0'],data=confusion_matrix_svm))#混淆矩阵
print("f1值:"+str(f1_score_svm))
print("准确率和召回率为:"+str(scores_svm))
model_XGB = XGBClassifier()
eval_set = [(val_X, val_y)]
model_XGB.fit(train_X, train_y, early_stopping_rounds=500, eval_metric="logloss", eval_set=eval_set, verbose=False)
# verbose改为True就能可视化loss
y_pred = model_XGB.predict(val_X)
scores_XGB=[]
scores_XGB.append(precision_score(val_y, y_pred))
scores_XGB.append(recall_score(val_y, y_pred))
confusion_matrix_XGB=confusion_matrix(val_y,y_pred)
f1_score_XGB=f1_score(val_y, y_pred,labels=None, pos_label=0, average="binary", sample_weight=None)
predictions_xgb=model_XGB.predict_proba(val_X)#每一类的概率
FPR_xgb, recall_xgb, thresholds = roc_curve(val_y,predictions_xgb[:,1], pos_label=1)
area_xgb = auc(FPR_xgb,recall_xgb)
print('xgboost模型结果:\n')
print(pd.DataFrame(columns=['预测值=1','预测值=0'],index=['真实值=1','真实值=0'],data=confusion_matrix_XGB))#混淆矩阵
print("f1值:"+str(f1_score_XGB))
print("精确度和召回率:"+str(scores_XGB))
plt.figure(figsize=(10,8))
plt.plot(FPR_xgb, recall_xgb, 'b', label='XGBoost_AUC = %0.3f' % area_xgb)
plt.plot(FPR_svm, recall_svm,label='SVM_AUC = %0.3f' % area_svm)
plt.plot(FPR_log, recall_log,label='Logistic_AUC = %0.3f' % area_log)
plt.legend(loc='lower right')
plt.plot([0,1],[0,1],'r--')
plt.xlim([0.0,1.0])
plt.ylim([0.0,1.0])
plt.ylabel('Recall')
plt.xlabel('FPR')
plt.title('ROC_before_GridSearchCV')
plt.show()
以下都采用网格搜索交叉验证(GridSearchCV)的方式来寻找最佳参数
parameters_Logr = {'C':[0.05,0.1,0.5,1]}
grid_search_log = GridSearchCV(model_logistics,parameters_Logr, cv=5,scoring='roc_auc')
grid_search_log.fit(train_X, train_y)
print('Log:\n')
print(grid_search_log.best_params_,grid_search_log.best_score_)
parameters_svm = {'C':[0.05,0.1,1,5,10,20,50,100],'gamma':[0.01,0.05,0.1,0.2,0.3]}
grid_search_svm = GridSearchCV(model_svm,parameters_svm, cv=5,scoring='roc_auc')
grid_search_svm.fit(X_train_minmax, train_y)
print('\nSVM:\n')
print(grid_search_svm.best_params_,grid_search_svm.best_score_)
#第一步:先调max_depth、min_child_weight
param_test1 = {
'max_depth':range(3,10,2),
'min_child_weight':range(1,6,2)
}
gsearch1 = GridSearchCV(estimator = XGBClassifier(),
param_grid = param_test1,scoring='roc_auc')
gsearch1.fit(train_X,train_y,early_stopping_rounds=500, eval_metric="logloss", eval_set=eval_set, verbose=False)
gsearch1.best_params_,gsearch1.best_score_
在#第二步:调gamma
param_test2 = {
'gamma':[0.01,0.05,0.1,0.2,0.3,0.5,1]
}
gsearch2 = GridSearchCV(estimator = XGBClassifier(max_depth=3,min_child_weight=3),
param_grid = param_test2, scoring='roc_auc', cv=5)
gsearch2.fit(train_X,train_y,early_stopping_rounds=500, eval_metric="logloss", eval_set=eval_set, verbose=False)
gsearch2.best_params_, gsearch2.best_score_
model_logistics_after = LogisticRegression(C=0.5)
model_logistics_after.fit(train_X,train_y)
y_pred_after = model_logistics_after.predict(val_X)
scores_logistics_after=[]
scores_logistics_after.append(precision_score(val_y, y_pred_after))
scores_logistics_after.append(recall_score(val_y, y_pred_after))
confusion_matrix_logistics_after=confusion_matrix(val_y,y_pred_after)
f1_score_logistics_after=f1_score(val_y, y_pred_after,labels=None, pos_label=1, average='binary', sample_weight=None)
precision_logistics_after = precision_score(val_y, y_pred_after, average='binary')# 精确率指模型预测为正的样本中实际也为正的样本占被预测为正的样本的比例。
importance_after=pd.DataFrame({"columns":list(val_X.columns), "coef":list(model_logistics_after.coef_.T)})
predictions_log_after=model_logistics_after.predict_proba(val_X)#每一类的概率
FPR_log_after, recall_log_after, thresholds_after = roc_curve(val_y, predictions_log_after[:,1],pos_label=1)
area_log_after=auc(FPR_log_after,recall_log_after)
print('调参后logistics模型结果:\n')
print(pd.DataFrame(columns=['预测值=1','预测值=0'],index=['真实值=1','真实值=0'],data=confusion_matrix_logistics_after))#混淆矩阵
print("f1值:"+str(f1_score_logistics_after))
print("准确率和召回率为:"+str(scores_logistics_after))
print('模型系数:\n'+str(importance_after))
#SVM模型建立
min_max_scaler = preprocessing.MinMaxScaler()#注意要归一化
X_train_minmax = min_max_scaler.fit_transform(train_X)
X_test_minmax=min_max_scaler.transform(val_X)
model_svm=SVC(probability=True)
model_svm.fit(X_train_minmax,train_y)
y_pred = model_svm.predict(X_test_minmax)
scores_svm=[]
scores_svm.append(precision_score(val_y, y_pred))
scores_svm.append(recall_score(val_y, y_pred))
confusion_matrix_svm=confusion_matrix(val_y,y_pred)
f1_score_svm=f1_score(val_y, y_pred,labels=None, pos_label=1, average='binary', sample_weight=None)
predictions_svm=model_svm.predict_proba(X_test_minmax)#每一类的概率
FPR_svm, recall_svm, thresholds = roc_curve(val_y,predictions_svm[:,1], pos_label=1)
area_svm = auc(FPR_svm,recall_svm)
print('svm模型结果:\n')
print(pd.DataFrame(columns=['预测值=1','预测值=0'],index=['真实值=1','真实值=0'],data=confusion_matrix_svm))#混淆矩阵
print("f1值:"+str(f1_score_svm))
print("准确率和召回率为:"+str(scores_svm))
model_XGB_after = XGBClassifier(max_depth= 3, min_child_weight= 3,gamma=0.5)
eval_set = [(val_X, val_y)]
model_XGB_after.fit(train_X, train_y, early_stopping_rounds=500, eval_metric="logloss", eval_set=eval_set, verbose=False)
# verbose改为True就能可视化loss
y_pred_after = model_XGB_after.predict(val_X)
scores_XGB_after=[]
scores_XGB_after.append(precision_score(val_y, y_pred_after))
scores_XGB_after.append(recall_score(val_y, y_pred_after))
confusion_matrix_XGB_after=confusion_matrix(val_y,y_pred_after)
f1_score_XGB_after=f1_score(val_y, y_pred_after,labels=None, pos_label=0, average="binary", sample_weight=None)
predictions_xgb_after=model_XGB_after.predict_proba(val_X)#每一类的概率
FPR_xgb_after, recall_xgb_after, thresholds_after = roc_curve(val_y,predictions_xgb_after[:,1], pos_label=1)
area_xgb_after = auc(FPR_xgb_after,recall_xgb_after)
print('调参后xgboost模型结果:\n')
print(pd.DataFrame(columns=['预测值=1','预测值=0'],index=['真实值=1','真实值=0'],data=confusion_matrix_XGB_after))#混淆矩阵
print("f1值:"+str(f1_score_XGB_after))
print("精确度和召回率:"+str(scores_XGB_after))
plt.figure(figsize=(10,8))
plt.plot(FPR_xgb_after, recall_xgb_after, 'b', label='XGBoost_AUC = %0.3f' % area_xgb_after)
plt.plot(FPR_svm_after, recall_svm_after,label='SVM_AUC = %0.3f' % area_svm_after)
plt.plot(FPR_log_after, recall_log_after,label='Logistic_AUC = %0.3f' % area_log_after)
plt.legend(loc='lower right')
plt.plot([0,1],[0,1],'r--')
plt.xlim([0.0,1.0])
plt.ylim([0.0,1.0])
plt.ylabel('Recall')
plt.xlabel('FPR')
plt.title('ROC_after_GridSearchCV')
plt.show()
此次作业以体检数据集为样本进行了机器学习的预测,但是需要注意几个问题:
体检数据量太少,仅有1006条可分析数据,这对于糖尿病预测来说是远远不足的,所分析的结果代表性不强。 这里的数据糖尿病和正常人基本相当,而真实的数据具有很强的不平衡性。也就是说,糖尿病患者要远少于正常人,这种不平衡的数据集给真实情况下糖尿病的预测带来了很大困难,当然可以通过上采样、下采样或者其他方法进行不平衡数据集的预测。 缺少时序数据使得预测变得不准确,时序数据更能够体现一个人的身体状态。 总而言之,机器学习预测疾病道阻且长,尽信机器学习不如无机器学习,经验加技术才是医学预测未来发展的大方向。