- 前言
- 一、报名比赛
- 二、比赛数据分析
- 1.统计字段的缺失值,计算缺失比例
- 2.分析字段的类型
- 3.计算字段相关性
- 4.可视化尝试
- 三、逻辑回归尝试
- 四、特征工程
- 五:特征筛选
- 1. 使用树模型完成模型的训练,通过特征重要性筛选出Top5的特征;
- 2. 使用筛选出的特征和逻辑回归进行训练
- 3. 提高原因分析
- 六:高阶树模型
- 1. 使用LightGBM进行训练
- 2. 线上得分验证
- 3. 调参及再次提交
- 七:多折训练与集成
前言
讯飞糖尿病检测,入门机器学习。
一、报名比赛
- 报名比赛http://challenge.xfyun.cn/topic/info?type=diabetes&ch=ds22-dw-zmt05
- 下载比赛数据(点击比赛页面的赛题数据)
- 解压比赛数据,并使用pandas进行读取;
- 查看训练集和测试集字段类型,代码如下:
import pandas as pd
import warnings
import seaborn as sns
sns.set_style({'font.sans-serif': ['SimHei', 'Arial']})
warnings.filterwarnings('ignore')
train = pd.read_csv('../比赛训练集.csv', encoding='gbk')
test = pd.read_csv('../比赛测试集.csv', encoding='gbk')
print(train.shape, test.shape)
print(train.dtypes, test.dtypes)
二、比赛数据分析
1.统计字段的缺失值,计算缺失比例
#统计缺失值
print(train.isnull().sum())
print(test.isnull().sum())
#通过输出发现仅有舒张压为空,计算舒张压的缺失比例
print('训练集缺失比例', train['舒张压'].isnull().sum() / len(train))
print('测试集缺失比例', test['舒张压'].isnull().sum() / len(test))
通过缺失比例计算,发现仅有舒张压缺失,且缺失率很小,仅为0.049
使用pandas的内置函数可以方便的查看数据类型。
print(train.info()) print(test.info())
具体类型如图所示,这里不再赘述。
使用pandas内置函数即可计算相关性
print(train.corr())
运行结果如下:
可以看出体重指数和肱三头肌皮邹厚度这两个特征与患有糖尿病的相关性较高,在后续特征工程可以注意这两个特征。
在后续特征工程中完成。
三、逻辑回归尝试总体代码如下,具体处理请看注释:
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import f1_score, roc_auc_score
import warnings
warnings.filterwarnings('ignore')
train = pd.read_csv('../比赛训练集.csv', encoding='gbk')
test = pd.read_csv('../比赛测试集.csv', encoding='gbk')
df = pd.concat([train, test], ignore_index=True)
df['出生年份'] = 2022 - df['出生年份'] # 换成年龄
# -------对值为0的4个特征值替换为np.nan
for i in ['口服耐糖量测试', '胰岛素释放实验', '肱三头肌皮褶厚度', '体重指数']:
df[i] = df[i].apply(lambda x: np.nan if x == 0 else x)
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
df['BMI'] = df['体重指数'].apply(BMI)
# 糖尿病家族史
"""
无记录
叔叔或者姑姑有一方患有糖尿病/叔叔或姑姑有一方患有糖尿病
父母有一方患有糖尿病
"""
def FHOD(a):
if a == '无记录':
return 0
elif a == '叔叔或者姑姑有一方患有糖尿病' or a == '叔叔或姑姑有一方患有糖尿病':
return 1
else:
return 2
df['糖尿病家族史'] = df['糖尿病家族史'].apply(FHOD)
"""
舒张压范围为60-90
"""
def DBP(a):
if a < 60:
return 0
elif 60 <= a <= 90:
return 1
elif a > 90:
return 2
else:
return a
features = [f for f in df.columns if f not in ['编号', '患有糖尿病标识']]
df['DBP'] = df['舒张压'].apply(DBP)
for col in features:
df[col].fillna(df[col].median(), inplace=True)
df_train = df[df['患有糖尿病标识'].notnull()].reset_index(drop=True)
df_test = df[df['患有糖尿病标识'].isnull()].reset_index(drop=True)
df_test.drop('患有糖尿病标识', inplace=True, axis=1)
x_train, x_test, y_train, y_test = train_test_split(df_train[features], df_train['患有糖尿病标识'], test_size=0.25,
random_state=42)
lr = LogisticRegression()
lr.fit(x_train, y_train)
pred = lr.predict(x_test)
print("logistic regression f1 score: ", f1_score(pred, y_test))
print("logistic regression auc score: ", roc_auc_score(pred, y_test))
运行结果如下:
可以看到逻辑回归的准确率还是不错,在测试集上取得了不错的成绩,再次采用决策树尝试,代码只需修改上面的一点,将模型切换即可完成,这里不再给出完整代码
model = DecisionTreeClassifier() model.fit(x_train, y_train) pred = model.predict(x_test)
得到分数如下:
决策树预测代码:
pred_y = model.predict(df_test[features])
submit = pd.read_csv('../提交示例.csv', encoding='gbk')
submit['label'] = pred_y
submit.to_csv('../submit/decision_tree.csv', encoding='gbk')
print('over')
决策树模型更适合这个题,将提交决策树的结果,线上得分如下:
可以看到,决策树得到了一个不错的分数。
- 步骤1:统计每个性别对应的【体重指数】、【舒张压】平均值
tmp = df.groupby('性别')['体重指数'].agg('mean')
print(tmp)
tmp = df.groupby('性别')['舒张压'].agg('mean')
print(tmp)
运行结果如下:
- 步骤2:计算每个患者与每个性别平均值的差异;
tmp1 = df.groupby('性别')['体重指数'].agg('mean')
tmp2 = df.groupby('性别')['舒张压'].agg('mean')
def op(x):
#女性
if x['性别'] == 0:
x['体重指数与平均值差异'] = x['体重指数'] - tmp1[0]
x['舒张压与平均值差异'] = x['舒张压'] - tmp2[0]
#男性
if x['性别'] == 1:
x['体重指数与平均值差异'] = x['体重指数'] - tmp1[1]
x['舒张压与平均值差异'] = x['舒张压'] - tmp2[1]
return x
df = df.apply(lambda x: op(x), axis=1)
print(df)
- 步骤3:在上述基础上将训练集20%划分为验证集,使用逻辑回归完成训练。
加入上述特征后,完整代码如下:
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import f1_score, roc_auc_score
from sklearn.tree import DecisionTreeClassifier
import warnings
warnings.filterwarnings('ignore')
train = pd.read_csv('../比赛训练集.csv', encoding='gbk')
test = pd.read_csv('../比赛测试集.csv', encoding='gbk')
df = pd.concat([train, test], ignore_index=True)
df['出生年份'] = 2022 - df['出生年份'] # 换成年龄
# -------对值为0的4个特征值替换为np.nan
for i in ['口服耐糖量测试', '胰岛素释放实验', '肱三头肌皮褶厚度', '体重指数']:
df[i] = df[i].apply(lambda x: np.nan if x == 0 else x)
tmp1 = df.groupby('性别')['体重指数'].agg('mean')
tmp2 = df.groupby('性别')['舒张压'].agg('mean')
def op(x):
# 女性
if x['性别'] == 0:
x['体重指数与平均值差异'] = x['体重指数'] - tmp1[0]
x['舒张压与平均值差异'] = x['舒张压'] - tmp2[0]
# 男性
if x['性别'] == 1:
x['体重指数与平均值差异'] = x['体重指数'] - tmp1[1]
x['舒张压与平均值差异'] = x['舒张压'] - tmp2[1]
return x
df = df.apply(lambda x: op(x), axis=1)
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
df['BMI'] = df['体重指数'].apply(BMI)
# 糖尿病家族史
"""
无记录
叔叔或者姑姑有一方患有糖尿病/叔叔或姑姑有一方患有糖尿病
父母有一方患有糖尿病
"""
def FHOD(a):
if a == '无记录':
return 0
elif a == '叔叔或者姑姑有一方患有糖尿病' or a == '叔叔或姑姑有一方患有糖尿病':
return 1
else:
return 2
df['糖尿病家族史'] = df['糖尿病家族史'].apply(FHOD)
"""
舒张压范围为60-90
"""
def DBP(a):
if a < 60:
return 0
elif 60 <= a <= 90:
return 1
elif a > 90:
return 2
else:
return a
features = [f for f in df.columns if f not in ['编号', '患有糖尿病标识']]
df['DBP'] = df['舒张压'].apply(DBP)
for col in features:
df[col].fillna(df[col].median(), inplace=True)
df_train = df[df['患有糖尿病标识'].notnull()].reset_index(drop=True)
df_test = df[df['患有糖尿病标识'].isnull()].reset_index(drop=True)
df_test.drop('患有糖尿病标识', inplace=True, axis=1)
x_train, x_test, y_train, y_test = train_test_split(df_train[features], df_train['患有糖尿病标识'], test_size=0.2,
random_state=42)
model = DecisionTreeClassifier()
model.fit(x_train, y_train)
pred = model.predict(x_test)
print("decision tree f1 score: ", f1_score(pred, y_test))
print("decision tree auc score: ", roc_auc_score(pred, y_test))
pred_y = model.predict(df_test[features])
submit = pd.read_csv('../提交示例.csv', encoding='gbk')
submit['label'] = pred_y
submit.to_csv('../submit/decision_tree.csv', encoding='gbk')
print('over')
最后的f1得分为:
可以看到得分进一步得到了提高。
- 步骤4:思考字段含义,尝试新的特征。
人的舒张压正常范围为60-90,其他范围为异常,可对舒张压进行分组,上述代码中以及构造此特征,这里不再赘述。
使用决策树模型完成训练,并进行特征筛选。
得到的特征重要性如下:
筛选出Top5的特征:
完整的训练代码不再给出,只需修改一行代码:
features = ['体重指数', '肱三头肌皮褶厚度', '口服耐糖量测试', '舒张压', '胰岛素释放实验']
验证集精度如下:
可以看到验证集精度得到了进一步提高。
重要的特征对模型准确度有着很大的影响,基于树模型的特征选择,选择重要的特征入模,不仅加快了训练速度,更提高了准确率。
六:高阶树模型 1. 使用LightGBM进行训练只需修改一行代码,完整代码不再给出:
model = LGBMClassifier()
训练结果如下:
可以看到lightgbm的准确度,明显高于逻辑回归,得分与决策树类似。
提交lightgbm的预测结果,得到分数如下:
可以看到lightgbm的得分明显高于决策树
使用网格搜索调参,过程不再赘述,最后确定参数如下:
params = {
'boosting_type': 'gbdt',
'objective': 'binary',
'tree_learner': 'serial',
'n_estimators': 1000,
'min_child_weight': 6,
'num_leaves': 2 ** 6,
'feature_fraction': 0.8,
'bagging_fraction': 0.8,
'bagging_freq': 4,
'learning_rate': 0.01,
'seed': 2021,
'nthread': 4,
'n_jobs': 4,
'verbose': -1,
}
完整训练代码如下:
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from lightgbm import LGBMClassifier
from sklearn.metrics import f1_score, roc_auc_score
import warnings
warnings.filterwarnings('ignore')
train = pd.read_csv('../比赛训练集.csv', encoding='gbk')
test = pd.read_csv('../比赛测试集.csv', encoding='gbk')
df = pd.concat([train, test], ignore_index=True)
df['出生年份'] = 2022 - df['出生年份'] # 换成年龄
# -------对值为0的4个特征值替换为np.nan
for i in ['口服耐糖量测试', '胰岛素释放实验', '肱三头肌皮褶厚度', '体重指数']:
df[i] = df[i].apply(lambda x: np.nan if x == 0 else x)
tmp1 = df.groupby('性别')['体重指数'].agg('mean')
tmp2 = df.groupby('性别')['舒张压'].agg('mean')
def op(x):
# 女性
if x['性别'] == 0:
x['体重指数与平均值差异'] = x['体重指数'] - tmp1[0]
x['舒张压与平均值差异'] = x['舒张压'] - tmp2[0]
# 男性
if x['性别'] == 1:
x['体重指数与平均值差异'] = x['体重指数'] - tmp1[1]
x['舒张压与平均值差异'] = x['舒张压'] - tmp2[1]
return x
df = df.apply(lambda x: op(x), axis=1)
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
df['BMI'] = df['体重指数'].apply(BMI)
# 糖尿病家族史
"""
无记录
叔叔或者姑姑有一方患有糖尿病/叔叔或姑姑有一方患有糖尿病
父母有一方患有糖尿病
"""
def FHOD(a):
if a == '无记录':
return 0
elif a == '叔叔或者姑姑有一方患有糖尿病' or a == '叔叔或姑姑有一方患有糖尿病':
return 1
else:
return 2
df['糖尿病家族史'] = df['糖尿病家族史'].apply(FHOD)
"""
舒张压范围为60-90
"""
def DBP(a):
if a < 60:
return 0
elif 60 <= a <= 90:
return 1
elif a > 90:
return 2
else:
return a
params = {
'boosting_type': 'gbdt',
'objective': 'binary',
'tree_learner': 'serial',
'n_estimators': 1000,
'min_child_weight': 6,
'num_leaves': 2 ** 6,
'feature_fraction': 0.8,
'bagging_fraction': 0.8,
'bagging_freq': 4,
'learning_rate': 0.01,
'seed': 2021,
'nthread': 4,
'n_jobs': 4,
'verbose': -1,
}
# features = [f for f in df.columns if f not in ['编号', '患有糖尿病标识']]
features = ['体重指数', '肱三头肌皮褶厚度', '口服耐糖量测试', '舒张压', '胰岛素释放实验']
df['DBP'] = df['舒张压'].apply(DBP)
for col in features:
df[col].fillna(df[col].median(), inplace=True)
df_train = df[df['患有糖尿病标识'].notnull()].reset_index(drop=True)
df_test = df[df['患有糖尿病标识'].isnull()].reset_index(drop=True)
df_test.drop('患有糖尿病标识', inplace=True, axis=1)
x_train, x_test, y_train, y_test = train_test_split(df_train[features], df_train['患有糖尿病标识'], test_size=0.2,
random_state=42)
model = LGBMClassifier(**params)
model.fit(x_train, y_train)
pred = model.predict(x_test)
print("lightgbm f1 score: ", f1_score(pred, y_test))
print("lightgbm auc score: ", roc_auc_score(pred, y_test))
pred_y = model.predict(df_test[features])
submit = pd.read_csv('../提交示例.csv', encoding='gbk')
submit['label'] = pred_y
submit.to_csv('../submit/lgb_pre.csv', encoding='gbk')
print('over')
再次提交结果:
可以看到分数得到了进一步提高。
直接使用StratifiedKFold对数据集进行划分,并完成训练,完整代码如下:
import pandas as pd
import numpy as np
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import f1_score, roc_auc_score, log_loss
import lightgbm as lgb
import warnings
warnings.filterwarnings('ignore')
train = pd.read_csv('../比赛训练集.csv', encoding='gbk')
test = pd.read_csv('../比赛测试集.csv', encoding='gbk')
df = pd.concat([train, test], ignore_index=True)
df['出生年份'] = 2022 - df['出生年份'] # 换成年龄
# -------对值为0的4个特征值替换为np.nan
for i in ['口服耐糖量测试', '胰岛素释放实验', '肱三头肌皮褶厚度', '体重指数']:
df[i] = df[i].apply(lambda x: np.nan if x == 0 else x)
tmp1 = df.groupby('性别')['体重指数'].agg('mean')
tmp2 = df.groupby('性别')['舒张压'].agg('mean')
def op(x):
# 女性
if x['性别'] == 0:
x['体重指数与平均值差异'] = x['体重指数'] - tmp1[0]
x['舒张压与平均值差异'] = x['舒张压'] - tmp2[0]
# 男性
if x['性别'] == 1:
x['体重指数与平均值差异'] = x['体重指数'] - tmp1[1]
x['舒张压与平均值差异'] = x['舒张压'] - tmp2[1]
return x
df = df.apply(lambda x: op(x), axis=1)
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
df['BMI'] = df['体重指数'].apply(BMI)
# 糖尿病家族史
"""
无记录
叔叔或者姑姑有一方患有糖尿病/叔叔或姑姑有一方患有糖尿病
父母有一方患有糖尿病
"""
def FHOD(a):
if a == '无记录':
return 0
elif a == '叔叔或者姑姑有一方患有糖尿病' or a == '叔叔或姑姑有一方患有糖尿病':
return 1
else:
return 2
df['糖尿病家族史'] = df['糖尿病家族史'].apply(FHOD)
"""
舒张压范围为60-90
"""
def DBP(a):
if a < 60:
return 0
elif 60 <= a <= 90:
return 1
elif a > 90:
return 2
else:
return a
features = [f for f in df.columns if f not in ['编号', '患有糖尿病标识']]
# features = ['性别', '出生年份', '体重指数', '糖尿病家族史', '舒张压', '口服耐糖量测试', '胰岛素释放实验', '肱三头肌皮褶厚度', 'BMI']
df_train = df[df['患有糖尿病标识'].notnull()].reset_index(drop=True)
df_test = df[df['患有糖尿病标识'].isnull()].reset_index(drop=True)
df_test.drop('患有糖尿病标识', inplace=True, axis=1)
fold_num = 7
seed = 2021
kf = StratifiedKFold(n_splits=fold_num, shuffle=True, random_state=seed)
params = {
'boosting_type': 'gbdt',
'objective': 'binary',
'tree_learner': 'serial',
'n_estimators': 1000,
'min_child_weight': 6,
'num_leaves': 2 ** 6,
'feature_fraction': 0.8,
'bagging_fraction': 0.8,
'bagging_freq': 4,
'learning_rate': 0.01,
'seed': 2021,
'nthread': 4,
'n_jobs': 4,
'verbose': -1,
}
# 自定义metric
def custom_f1(preds, train_data):
labels = train_data.get_label()
preds = list(map(lambda x: 1 if x >= 0.4 else 0, preds.tolist()))
return 'f1_score', f1_score(labels, preds, ), True
oof = np.zeros(len(df_train))
importance = 0
pred_y = pd.DataFrame()
LABEL = '患有糖尿病标识'
for fold, (train_idx, val_idx) in enumerate(kf.split(df_train[features], df_train[LABEL])):
print('-----------', fold)
train = lgb.Dataset(df_train.loc[train_idx, features],
df_train.loc[train_idx, LABEL])
val = lgb.Dataset(df_train.loc[val_idx, features],
df_train.loc[val_idx, LABEL])
model = lgb.train(params, train, valid_sets=val, num_boost_round=10000,
early_stopping_rounds=100, verbose_eval=1000, feval=custom_f1)
oof[val_idx] += model.predict(df_train.loc[val_idx, features], num_iteration=model.best_iteration)
pred_y['fold_%d_seed_%d' % (fold, seed)] = model.predict(df_test[features], num_iteration=model.best_iteration)
importance += model.feature_importance(importance_type='gain') / fold_num
thre = 0.4
tmp = oof.copy()
score = f1_score(df_train[LABEL], list(map(lambda x: 1 if x >= thre else 0, tmp)), average='macro')
print('nF1... ', score)
print('AUC %0.5f, LOGLOSS %0.5f' % (roc_auc_score(df_train[LABEL], oof), log_loss(df_train[LABEL], oof)))
pred = pred_y.copy(deep=True).mean(axis=1).map(lambda x: 1 if x >= thre else 0)
submit = pd.read_csv('../提交示例.csv', encoding='gbk')
submit['label'] = pred
submit.to_csv('../submit/lgb.csv', encoding='gbk')
pred.value_counts()
提交预测结果:
可以看到准确度得到了进一步得到。



