《如何打一場(chǎng)數(shù)據(jù)挖掘賽事》進(jìn)階版
這個(gè)比賽是一個(gè)醫(yī)療領(lǐng)域的數(shù)據(jù)挖掘?qū)嵺`,賽事的任務(wù)是構(gòu)建一種模型,該模型能夠根據(jù)患者的測(cè)試數(shù)據(jù)來(lái)預(yù)測(cè)這個(gè)患者是否患有糖尿病。這種類型的任務(wù)是典型的二分類問(wèn)題(患有糖尿病 / 不患有糖尿?。1疚膶⒁匀蝿?wù)學(xué)習(xí)和啟發(fā)性思考的方式,幫助大家深入學(xué)習(xí)。

賽事背景:
本次比賽是一個(gè)醫(yī)療領(lǐng)域數(shù)據(jù)挖掘賽,需要選手通過(guò)訓(xùn)練集數(shù)據(jù)構(gòu)建模型,對(duì)驗(yàn)證集數(shù)據(jù)進(jìn)行預(yù)測(cè),并將預(yù)測(cè)的結(jié)果提交到科大訊飛數(shù)據(jù)競(jìng)賽平臺(tái)中,得到排名反饋。
報(bào)名地址:
https://challenge.xfyun.cn/topic/info?type=diabetes&ch=ds22-dw-gzh02
教程說(shuō)明:
本教程共有6個(gè)任務(wù),任務(wù)難度逐漸增加。每個(gè)任務(wù)中分為不同的模塊,具體要求如下:
主線任務(wù)需要學(xué)習(xí)者獨(dú)立完成 支線任務(wù)為學(xué)有余力的同學(xué)獨(dú)立完成 思考為學(xué)習(xí)者提供了可以思考的方向,可通過(guò)討論或搜索獲得結(jié)果

任務(wù)1:比賽報(bào)名與環(huán)境配置
主線任務(wù):
訪問(wèn)糖尿病遺傳風(fēng)險(xiǎn)檢測(cè)挑戰(zhàn)賽網(wǎng)頁(yè),并注冊(cè)相關(guān)賬號(hào) 點(diǎn)擊頁(yè)面中賽事概要,了解比賽的賽事背景、賽事任務(wù)、提交說(shuō)明、評(píng)估指標(biāo)等相關(guān)信息 安裝并配置好python的編程環(huán)境
思考:
為什么要了解比賽的相關(guān)信息? 比賽的評(píng)估指標(biāo)有哪幾種?本次比賽中為什么使用F1-score,相比其他評(píng)估指標(biāo)有什么優(yōu)勢(shì)?
任務(wù)2:數(shù)據(jù)的讀取與數(shù)據(jù)類型
主線任務(wù):
解壓比賽數(shù)據(jù),使用pandas讀取比賽數(shù)據(jù),并查看訓(xùn)練集和測(cè)試集數(shù)據(jù)大小 查看訓(xùn)練集和測(cè)試集的數(shù)據(jù)類型
思考:
為什么要查看訓(xùn)練集和測(cè)試集的大?。?/section> 為什么查看訓(xùn)練集和測(cè)試集的數(shù)據(jù)類型?
參考代碼:
import pandas as pd
train_df=pd.read_csv('比賽訓(xùn)練集.csv',encoding='gbk')
test_df=pd.read_csv('比賽測(cè)試集.csv',encoding='gbk')
print('訓(xùn)練集的數(shù)據(jù)大?。?,train_df.shape)
print('測(cè)試集的數(shù)據(jù)大?。?,test_df.shape)
print('-'*30)
print('訓(xùn)練集的數(shù)據(jù)類型:')
print(train_df.dtypes)
print('-'*30)
print(test_df.dtypes)
任務(wù)3:數(shù)據(jù)的分析與探索
主線任務(wù):
查看訓(xùn)練集和測(cè)試集的缺失值,并比訓(xùn)練集和測(cè)試集的缺失值分布是否一致 使用.corr()函數(shù)查看數(shù)據(jù)間的相關(guān)性 對(duì)訓(xùn)練集和測(cè)試集數(shù)據(jù)進(jìn)行可視化統(tǒng)計(jì)
思考:
數(shù)據(jù)中的缺失值產(chǎn)生的原因? 怎么查看數(shù)據(jù)間的相關(guān)性?如果相關(guān)性高說(shuō)明了什么?
參考代碼:
#----------------查數(shù)據(jù)的缺失值----------------
print(train_df.isnull().sum())
print('-'*30)
print(test_df.isnull().sum())
#可以看到 訓(xùn)練集和測(cè)試集中都是舒張壓有缺失值
#----------------查數(shù)據(jù)相關(guān)性----------------
print('-'*30)
print('查看訓(xùn)練集中數(shù)據(jù)的相關(guān)性')
print(train_df.corr())
print(test_df.corr())
#----------------數(shù)據(jù)的可視化統(tǒng)計(jì)----------------
import matplotlib.pyplot as plt
import seaborn as sns
train_df['性別'].value_counts().plot(kind='barh')
sns.set(font='SimHei',font_scale=1.1) # 解決Seaborn中文顯示問(wèn)題并調(diào)整字體大小
sns.countplot(x='患有糖尿病標(biāo)識(shí)', hue='性別', data=train_df)
sns.boxplot(y='出生年份', x='患有糖尿病標(biāo)識(shí)', hue='性別', data=train_df)
sns.violinplot(y='體重指數(shù)', x='患有糖尿病標(biāo)識(shí)', hue='性別', data=train_df)
plt.figure(figsize = [20,10],dpi=100)
sns.countplot(x='出生年份',data=train_df)
plt.tight_layout()
任務(wù)4:數(shù)據(jù)的特征工程
主線任務(wù):
將數(shù)據(jù)中的糖尿病家族史中的文本數(shù)據(jù)進(jìn)行編碼 將數(shù)據(jù)中的舒張壓的缺失值進(jìn)行填充 將出生年份的數(shù)據(jù)轉(zhuǎn)換成年齡數(shù)據(jù)并進(jìn)行分組 對(duì)體重和舒張壓的數(shù)據(jù)進(jìn)行分組 刪除數(shù)據(jù)中的編號(hào)這一列
支線任務(wù):
計(jì)算每個(gè)個(gè)體口服耐糖量測(cè)試、胰島素釋放實(shí)驗(yàn)、舒張壓這三個(gè)指標(biāo)對(duì)糖尿病家族史進(jìn)行分組求平均值后的差值 計(jì)算每個(gè)個(gè)體口服耐糖量測(cè)試、胰島素釋放實(shí)驗(yàn)、舒張壓這三個(gè)指標(biāo)對(duì)年齡進(jìn)行分組求平均值后的差值
思考:
文本數(shù)據(jù)為什么要進(jìn)行編碼?有沒(méi)有其他的處理方法?除了編碼為連續(xù)數(shù)字,有沒(méi)有其他形式? 為什么要填充缺失值?你覺(jué)得參考代碼中將所有的缺失值全部填充為0是否正確? 為什么要將出生年份轉(zhuǎn)換成年齡?為什么要對(duì)年齡分組? 為什么對(duì)體重和舒張壓進(jìn)行了分組?這么做是否正確? 為什么要?jiǎng)h除編號(hào)這一列?
參考代碼:
#這里將文本數(shù)據(jù)轉(zhuǎn)成數(shù)字?jǐn)?shù)據(jù)
dict_糖尿病家族史 = {
'無(wú)記錄': 0,
'叔叔或姑姑有一方患有糖尿病': 1,
'叔叔或者姑姑有一方患有糖尿病': 1,
'父母有一方患有糖尿病': 2
}
train_df['糖尿病家族史'] = train_df['糖尿病家族史'].map(dict_糖尿病家族史)
test_df['糖尿病家族史'] = test_df['糖尿病家族史'].map(dict_糖尿病家族史)
#考慮到舒張壓是一個(gè)較為重要的生理特征,并不能適用于填充平均值,這里采用填充為0的方法
train_df['舒張壓'].fillna(0, inplace=True)
test_df['舒張壓'].fillna(0, inplace=True)
#將數(shù)據(jù)中的出生年份換算成年齡
train_df['出生年份'] = 2022 - train_df['出生年份']
test_df['出生年份'] = 2022 - test_df['出生年份']
#將年齡進(jìn)行一個(gè)分類
"""
>50
<=18
19-30
31-50
"""
def resetAge(input):
if input<=18:
return 0
elif 19<=input<=30:
return 1
elif 31<=input<=50:
return 2
elif input>=51:
return 3
train_df['rAge']=train_df['出生年份'].apply(resetAge)
test_df['rAge']=test_df['出生年份'].apply(resetAge)
#將體重指數(shù)進(jìn)行一個(gè)分類
"""
人體的成人體重指數(shù)正常值是在18.5-24之間
低于18.5是體重指數(shù)過(guò)輕
在24-27之間是體重超重
27以上考慮是肥胖
高于32了就是非常的肥胖。
"""
def BMI(a):
if a<18.5:
return 0
elif 18.5<=a<=24:
return 1
elif 24<a<=27:
return 2
elif 27<a<=32:
return 3
else:
return 4
train_df['BMI']=train_df['體重指數(shù)'].apply(BMI)
test_df['BMI']=test_df['體重指數(shù)'].apply(BMI)
#將舒張壓進(jìn)行一個(gè)分組
"""
舒張壓范圍為60-90
"""
def DBP(a):
if a==0:#這里為數(shù)據(jù)缺失的情況
return 0
elif 0<a<60:
return 1
elif 60<=a<=90:
return 2
else:
return 3
train_df['DBP']=train_df['舒張壓'].apply(DBP)
test_df['DBP']=test_df['舒張壓'].apply(DBP)
#刪除編號(hào)
train_df=train_df.drop(['編號(hào)'],axis=1)
test_df=test_df.drop(['編號(hào)'],axis=1)
---
#以下是支線任務(wù)參考代碼
#這里計(jì)算口服耐糖量相對(duì)糖尿病家族史進(jìn)行分組求平均值后的差值
train_df['口服耐糖量測(cè)試_diff'] = abs(train_df['口服耐糖量測(cè)試'] - train_df.groupby('糖尿病家族史').transform('mean')['口服耐糖量測(cè)試'])
test_df['口服耐糖量測(cè)試_diff'] = abs(test_df['口服耐糖量測(cè)試'] - test_df.groupby('糖尿病家族史').transform('mean')['口服耐糖量測(cè)試'])
#這里計(jì)算口服耐糖量相對(duì)年齡進(jìn)行分組求平均值后的差值
train_df['口服耐糖量測(cè)試_diff'] = abs(train_df['口服耐糖量測(cè)試'] - train_df.groupby('rAge').transform('mean')['口服耐糖量測(cè)試'])
test_df['口服耐糖量測(cè)試_diff'] = abs(test_df['口服耐糖量測(cè)試'] - test_df.groupby('rAge').transform('mean')['口服耐糖量測(cè)試'])
任務(wù)5:模型的構(gòu)建與優(yōu)化
主線任務(wù):
構(gòu)建用于模型訓(xùn)練的訓(xùn)練集、訓(xùn)練標(biāo)簽以及測(cè)試集 從以下4個(gè)不同模型中選擇1個(gè)完成模型構(gòu)建,并提交分?jǐn)?shù)
思考:
能夠用于二分類的機(jī)器學(xué)習(xí)算法有哪些? 在邏輯回歸代碼中,為什么要進(jìn)行數(shù)據(jù)標(biāo)準(zhǔn)化? 本次比賽中邏輯回歸算法有較差的分?jǐn)?shù)可能有哪些原因?
參考代碼:
train_label=train_df['患有糖尿病標(biāo)識(shí)']
train=train_df.drop(['患有糖尿病標(biāo)識(shí)'],axis=1)
test=test_df
---
邏輯回歸(分?jǐn)?shù):0.74):
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import MinMaxScaler
from sklearn.pipeline import make_pipeline
# 構(gòu)建模型
model = make_pipeline(
MinMaxScaler(),
LogisticRegression()
)
model.fit(train,train_label)
pre_y=model.predict(test)
result=pd.read_csv('提交示例.csv')
result['label']=pre_y
result.to_csv('LR.csv',index=False)
決策樹(shù)(分?jǐn)?shù):0.93):
from sklearn.tree import DecisionTreeClassifier
# 構(gòu)建模型
model = DecisionTreeClassifier()
model.fit(train,train_label)
pre_y=model.predict(test)
result=pd.read_csv('提交示例.csv')
result['label']=pre_y
result.to_csv('CART.csv',index=False)
lightgbm版本(分?jǐn)?shù):0.95):
import lightgbm
def select_by_lgb(train_data,train_label,test_data,random_state=2022,metric='auc',num_round=300):
clf=lightgbm
train_matrix=clf.Dataset(train_data,label=train_label)
params={
'boosting_type': 'gbdt',
'objective': 'binary',
'learning_rate': 0.1,
'metric': metric,
'seed': 2020,
'nthread':-1 }
model=clf.train(params,train_matrix,num_round)
pre_y=model.predict(test_data)
return pre_y
#輸出預(yù)測(cè)值
test_data=select_by_lgb(train,train_label,test)
pre_y=pd.DataFrame(test_data)
pre_y['label']=pre_y[0].apply(lambda x:1 if x>0.5 else 0)
result=pd.read_csv('提交示例.csv')
result['label']=pre_y['label']
result.to_csv('lgb.csv',index=False)
lightgbm版本5折交叉驗(yàn)證(分?jǐn)?shù):0.96):
import lightgbm
from sklearn.model_selection import KFold
def select_by_lgb(train_data,train_label,test_data,random_state=2022,n_splits=5,metric='auc',num_round=10000,early_stopping_rounds=100):
kfold = KFold(n_splits=n_splits, shuffle=True, random_state=random_state)
fold=0
result=[]
for train_idx, val_idx in kfold.split(train_data):
random_state+=1
train_x = train_data.loc[train_idx]
train_y = train_label.loc[train_idx]
test_x = train_data.loc[val_idx]
test_y = train_label.loc[val_idx]
clf=lightgbm
train_matrix=clf.Dataset(train_x,label=train_y)
test_matrix=clf.Dataset(test_x,label=test_y)
params={
'boosting_type': 'gbdt',
'objective': 'binary',
'learning_rate': 0.1,
'metric': metric,
'seed': 2020,
'nthread':-1 }
model=clf.train(params,train_matrix,num_round,valid_sets=test_matrix,early_stopping_rounds=early_stopping_rounds)
pre_y=model.predict(test_data)
result.append(pre_y)
fold+=1
return result
test_data=select_by_lgb(train,train_label,test)
pre_y=pd.DataFrame(test_data).T
pre_y['averge']=pre_y[[i for i in range(5)]].mean(axis=1)
pre_y['label']=pre_y['averge'].apply(lambda x:1 if x>0.5 else 0)
result=pd.read_csv('提交示例.csv')
result['label']=pre_y['label']
result.to_csv('lgb.csv',index=False)
任務(wù)6:模型構(gòu)建的進(jìn)階:
主線任務(wù):
使用不同模型來(lái)評(píng)估預(yù)測(cè)準(zhǔn)確性 對(duì)3個(gè)預(yù)測(cè)準(zhǔn)確度最高的模型參數(shù)的搜索,并比較不同模型的預(yù)測(cè)準(zhǔn)確性
思考:
模型融合的優(yōu)點(diǎn)在哪里? 運(yùn)行主線任務(wù)1,思考這些算法為什么要較高的準(zhǔn)確度? 為什么可以通過(guò)搜索來(lái)調(diào)整模型的參數(shù)?模型參數(shù)的調(diào)整一定會(huì)讓預(yù)測(cè)更準(zhǔn)確嘛? 你覺(jué)得參考代碼中搜索的參數(shù)設(shè)置合理嘛?如果不合理應(yīng)該如何改進(jìn)?
參考代碼:
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier,GradientBoostingClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn import svm
from sklearn.linear_model import LogisticRegression
train_label=train_df['患有糖尿病標(biāo)識(shí)']
train=train_df.drop(['患有糖尿病標(biāo)識(shí)'],axis=1)
test=test_df
#分割訓(xùn)練集和驗(yàn)證集
train_x,val_x,train_y,val_y=train_test_split(train,train_label,test_size=0.25,random_state=2020)
model={}
model['rfc']=RandomForestClassifier()
model['gdbt']=GradientBoostingClassifier()
model['cart']=DecisionTreeClassifier()
model['knn']=KNeighborsClassifier()
model['svm']=svm.SVC()
model['lr']=LogisticRegression()
for i in model:
model[i].fit(train_x,train_y)
score=cross_val_score(model[i],val_x,val_y,cv=5,scoring='f1')
print('%s的f1為:%.3f'%(i,score.mean()))
"""
rfc的f1為:0.927
gdbt的f1為:0.925
cart的f1為:0.899
knn的f1為:0.811
svm的f1為:0.751
lr的f1為:0.718
"""
---
from sklearn.model_selection import GridSearchCV
model=['rfc','gbdt','cart']
temp=[]
rfc=RandomForestClassifier(random_state=0)
params={'n_estimators':[50,100,150,200,250],'max_depth':[1,3,5,7,9,11,13,15,17,19],'min_samples_leaf':[2,4,6]}
temp.append([rfc,params])
gbt=GradientBoostingClassifier(random_state=0)
params={'learning_rate':[0.01,0.05,0.1,0.15,0.2],'n_estimators':[100,300,500],'max_depth':[3,5,7]}
temp.append([gbt,params])
cart=DecisionTreeClassifier(random_state=0)
params={'max_depth':[1,3,5,7,9,11,13,15,17,19],'min_samples_leaf':[2,4,6]}
temp.append([cart,params])
for i in range(len(model)):
best_model=GridSearchCV(temp[i][0],param_grid=temp[i][1],refit=True,cv=5).fit(train,train_label)
print(model[i],':')
print('best parameters:',best_model.best_params_)
"""
rfc :
best parameters: {'max_depth': 17, 'min_samples_leaf': 2, 'n_estimators': 100}
gbdt :
best parameters: {'learning_rate': 0.01, 'max_depth': 7, 'n_estimators': 300}
cart :
best parameters: {'max_depth': 7, 'min_samples_leaf': 2}
"""
model={}
model['rfc']=RandomForestClassifier(max_depth=17,min_samples_leaf=2,n_estimators=100)
model['gdbt']=GradientBoostingClassifier(learning_rate=0.01,max_depth=7,n_estimators=300)
model['cart']=DecisionTreeClassifier(max_depth=7,min_samples_leaf=2)
for i in model:
model[i].fit(train_x,train_y)
score=cross_val_score(model[i],val_x,val_y,cv=5,scoring='f1')
print('%s的f1為:%.3f'%(i,score.mean()))
"""
rfc的f1為:0.931
gdbt的f1為:0.922
cart的f1為:0.920
"""
---
rfc版本(分?jǐn)?shù):0.965):
model=RandomForestClassifier(max_depth=17,min_samples_leaf=2,n_estimators=100)
model.fit(train,train_label)
pre_y=model.predict(test)
result=pd.read_csv('提交示例.csv')
result['label']=pre_y
result.to_csv('rfc.csv',index=False)
作者寄語(yǔ)
行文至此,數(shù)據(jù)挖掘比賽項(xiàng)目就告一段落了,經(jīng)過(guò)這2次教程的學(xué)習(xí),你應(yīng)該體驗(yàn)到了數(shù)據(jù)挖掘比賽從報(bào)名到模型構(gòu)建到優(yōu)化的全過(guò)程,這將是你打開(kāi)數(shù)據(jù)科學(xué)/算法工程/數(shù)據(jù)分析的第一步。正所謂“路漫漫其修遠(yuǎn)兮,吾將上下而求索”,這一步終究只是開(kāi)始,在距離你的成為AI大師還有漫長(zhǎng)的路要探索,但這也是一個(gè)美好的開(kāi)始。正所謂“千里之行,始于足下”,相信這個(gè)簡(jiǎn)短的數(shù)據(jù)挖掘比賽教程將打開(kāi)你數(shù)據(jù)挖掘的大門,若干年后,你將還會(huì)記得當(dāng)初那個(gè)跟著教程不斷嘗試的自己。也期待成長(zhǎng)后你加入幕后的貢獻(xiàn)者團(tuán)隊(duì),我們將一起堅(jiān)持初心,幫助更多學(xué)習(xí)者成長(zhǎng)。

推薦閱讀
歡迎長(zhǎng)按掃碼關(guān)注「數(shù)據(jù)管道」
