江西省开放数据创新应用大赛vte赛道

  大家好,我是小k,好久没写博客了,从五月份开始接触机器学习,经过了一段时间沉淀,在11月份第一次参加了意义上的大数据比赛,下面想对这次比赛学习到的东西做个总结QAQ。

比赛地址传送门:

江西开放数据创新应用大赛 (jiangxi.gov.cn)

1:导入相应包与数据

import seaborn as sns
import lightgbm as lgb
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.preprocessing import LabelEncoder,OneHotEncoder
from sklearn.model_selection import KFold
from catboost import CatBoostClassifier, Pool
from sklearn.metrics import mean_squared_error, f1_score, accuracy_score
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import train_test_split

train = pd.read_excel(r'C:UserswzkDesktoptrain.xlsx')
test = pd.read_excel(r'C:UserswzkDesktoppredict.xlsx')
df_features = train.append(test)

2:观察数据集

#检查数据各列的缺失占比情况
df1 = (train.shape[0] - train.count())/train.shape[0]
df1[0:20]
唯一标识        0.000000
flag        0.000000
总评分         0.000000
住院号         0.000000
住院次数        0.000000
性别          0.000000
民族          0.000000
身高          0.000000
体重          0.004243
心率          0.000542
呼吸          0.002167
收缩压         0.001083
舒张压         0.001806
诊断          0.000000
3P试验        0.981943
C反应蛋白       0.939689
D-二聚体       0.213344
α-淀粉酶       0.972824
α-羟丁酸脱氢酶    0.740339
α羟丁酸脱氢酶     0.803720
dtype: float64

#检查数据类型 int/float/object

df_features.select_dtypes(include=['int']).columns
Index(['住院次数'], dtype='object')

df_features.select_dtypes(include=['float']).columns
Index(['flag', '总评分', '心率', '呼吸', '收缩压', '舒张压', 'D-二聚体', 'α-淀粉酶', 'α-羟丁酸脱氢酶',
       'β2-微球蛋白', '不饱和铁结合力', '凝血酶原时间比率', '凝血酶原时间活动度', '国际标准化比率', '尿酸', '总胆红素',
       '总蛋白', '总铁结合力', '果糖胺', '氯', '球蛋白', '白球比', '白蛋白', '直接胆红素', '磷', '糖化血清蛋白',
       '肌酐', '胰淀粉酶', '葡萄糖', '血清铁', '转铁蛋白', '钾', '铁', '镁', '高密度脂蛋白胆固醇'],
      dtype='object')

obj_columns = []
for i, item in enumerate(df_features.dtypes):
    if item == object:
        obj_columns.append(df_features.columns[i])
print(obj_columns)
['唯一标识', '住院号', '性别', '民族', '身高', '体重', '诊断', '3P试验', 'C反应蛋白', 'α羟丁酸脱氢酶', 'γ-谷氨酰基转移酶', 'γ谷氨酰转移酶', '丙型肝炎病毒IgG抗体', '丙氨酸氨基转移酶', '丙肝核心抗原', '乙型肝炎病毒e抗体', '乙型肝炎病毒e抗原', '乙型肝炎病毒核心抗体', '乙型肝炎病毒表面抗体', '乙型肝炎病毒表面抗原', '乙肝病毒大蛋白', '乳酸脱氢酶', '人类免疫缺陷病毒抗体', '低密度脂蛋白胆固醇', '凝血酶原时间', '凝血酶时间', '前白蛋白', '同型半胱氨酸', '天门冬氨酸氨基转移酶', '尿素', '总胆固醇', '抗RA33抗体IgG', '抗环瓜氨酸多肽抗体', '抗环瓜氨酸肽抗体抗体', '抗链球菌溶血素O', '无机磷', '梅毒螺旋体抗体', '活化部分凝血活酶时间', '淀粉酶', '甘油三酯', '碱性磷酸酶', '类风湿因子', '糖类抗原CA242', '纤维蛋白原(Clauss法)', '肌酸激酶', '肌酸激酶MB同功酶', '肌酸激酶同工酶', '脂肪酶', '脂蛋白a', '谷胱甘肽还原酶', '载脂蛋白A1', '载脂蛋白B', '钙', '钠']


#热力图观察各特征间的相关度
_,ax = plt.subplots(figsize=(50,50))

sns.set_style('whitegrid', {'font.sans-serif': ['simhei','FangSong']})
sns.heatmap(df_features[features].corr(),annot=True,cmap='RdYlGn',ax=ax);

#图太大,无法放到这里

通过上面初步分析,我们可以发现,很多特征的缺失值特别多,我刚开始选择把缺失值70%以上的特征全部删了,但我发现删了之后训练得到的分数奇低,所以我先将他们全填-1,0处理。

对于各个特征的数据类型,刚开始我有点诧异,为什么object类型会这么多,正常来说医学上很多测量值应该都为float类型,仔细的观察数据后,我惊奇的发现有些列中某些行会出现>,=号,进而导致整列的数据类型被定义为object类型。所以在训练前我尝试将它们改成float型进行训练。

3:数据清洗

接上述问题,可以发现官方所给的训练集存在很大的问题,对于这些问题,我主要做了以下事情作为尝试。

#删除缺失值大于90%的列,其它列的缺失值先默认填值处理
df_features.drop(labels='住院次数',axis=1,inplace = True)
模板如此,多余行我就不展示了。

#将部分object类型特征转化为float类型
from numpy import *
a = []
for item in df_features['身高']:
    try:
        if float(item) >= 140 and float(item) <= 200:
            a.append(float(item))
        else:
            a.append(mean(a))
    except:
        a.append(mean(a))

df_features.drop(axis=1, columns=['身高'], inplace=True)
df_features['身高'] = a
同样模板如此。


当然,我这些只是最基本的一些尝试,对于数据集中的一些特征,官方给出了相应正常值的范围,在填值这个方面,我们可以认为将空缺值填正常值范围内的值,并进行相应的标注。

4:特征工程

因为业务能力编程能力的限制,在特征工程方面,我第一反应想做的就是通过matplotlib与seaborn包做出相应特征相关度的图,再进行一些简单的特征衍生。

#生活中最常见的BMI概念,查表观察体重和身高相关度也有0.4+
from numpy import *
a = []
for item in train['体重']:
    try:
        if float(item)>=30 and float(item)<=100:
            a.append(float(item))
        else:
            a.append(mean(a))
    except:a.append(mean(a))

from numpy import *
b = []
for item in train['身高']:
    try:
        if float(item) >= 140 and float(item) <= 200:
            b.append(float(item))
        else:
            b.append(mean(b))
    except:
        b.append(mean(b))
c = []
for i in range(len(b)):
    b[i] = b[i]/100
    c.append(a[i]/(b[i]*b[i]))

train['BMI'] = c

当然,主要的上分途径还是多亏了一个大佬开源的单特征方法,对于诊断这个特征,使用word2vec对文字生成词向量,再将词向量送入到模型中进行处理,实现过程如下。

import jieba
import jieba.analyse
import codecs # python封装的文件的工具包
import pandas as pd

train = pd.read_excel(r'C:UserswzkDesktoptrain.xlsx')
test = pd.read_excel(r'C:UserswzkDesktoppredict.xlsx')

df = pd.DataFrame()

a = []
for item in train['诊断'].values:
    a.append(item)

import gensim
from gensim.models import Word2Vec
import logging
from gensim.models import FastText

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

model = FastText(a, window=3, min_count=1, min_n = 3 , max_n = 6)

model.save('Word2Vec.model')

model = FastText.load('Word2Vec.model')
a = []
for item in test['诊断'].values:
    a.append(model.wv[item])


import numpy as np
a = np.array(a)

for i in range(100):
    df['word2vec_诊断_' + str(i+1)] = a[:,i]

df.to_csv(r'诊断1.csv')

单特征+单模就能直接干上0.5+。

5:模型选择

  工业类的比赛,catboost,xgboost,lightgbm三大巨头,对于二分类的比赛,在网上查阅资料,很多大佬都是用树模型,所以在选择模型这方面,我选用了五折lgb。

  在群里听那些大佬们分享,也有很多人也用nn来做,不过似乎不太稳定。

6:模型训练

  在训练中,会发现一个问题,就是打印出结果发现全是0,对于这个问题,经过查阅相应资料后,发现是训练集数据不平衡造成的,意思就是训练集中0和1数量差距悬殊而非常容易过拟合。对于这类问题,使用SMOTE算法后得到了解决。

from imblearn.over_sampling import SMOTE

params = {
    "objective": "binary",
    "num_leaves": 58,
    "max_depth": -1,
    "learning_rate": 0.10584835945584467,
    "bagging_fraction": 0.9,  # subsample
    "feature_fraction": 0.9,  # colsample_bytree
    "bagging_freq": 5,  # subsample_freq
    "bagging_seed": 2018,
    "verbosity": -1,
    'num_threads': 8,  # 进程数 根据机器资源调整

from sklearn.model_selection import StratifiedKFold
folds = StratifiedKFold(n_splits=5, shuffle=True, random_state=2019)

oof = np.zeros([len(X), ])
predictions = np.zeros([len(X_test), ])

X,y = SMOTE(random_state=42).fit_resample(X,y) 

for fold_, (trn_idx, val_idx) in enumerate(folds.split(X, y)):
    print("fold n°{}".format(fold_ + 1))
    X_train, X_valid = X[features].iloc[trn_idx], X[features].iloc[val_idx]
    y_train, y_valid = y[trn_idx], y[val_idx]
    
    X_train, y_train = SMOTE(random_state=42).fit_resample(X_train, y_train)
    
    trn_data = lgb.Dataset(X_train, y_train)
    val_data = lgb.Dataset(X_valid, y_valid)
    
    oof = np.zeros([len(X_valid )+1])
    num_round = 100
    clf = lgb.train(params,
                    trn_data,
                    num_round,
                    valid_sets=[trn_data, val_data],
                    verbose_eval=1000,
                    early_stopping_rounds=500)
    print(y_valid)
    oof = clf.predict(X_valid, num_iteration=clf.best_iteration)
    print(oof)
    for i in range(len(oof)):
        if oof[i]>=0.5:
            oof[i] = 1
        else:
            oof[i] = 0
    F2(y_valid,oof)        
    predictions += clf.predict(X_test, num_iteration=clf.best_iteration)
    print(predictions)
predictions/=fold_

7:模型评估

参考官网的评估指标

我们可以自己定义一个损失函数,以便于能够在线下自我对训练结果的好坏进行评估。

def F2(true_labels, pre_labels):
    a = [i for i in range(true_labels.shape[0]) if (true_labels.values[i] == 1 and pre_labels[i] == 1)]
    b = [i for i in range(true_labels.shape[0]) if (true_labels.values[i] == 0 and pre_labels[i] == 1)]
    c = [i for i in range(true_labels.shape[0]) if (true_labels.values[i] == 1 and pre_labels[i] == 0)]
    TP = len(a)
    TN = len(b)
    FP = len(c)
    precision = TP / (TP + TN + FP)
    recall = TP / (TP + FP)
    F2 = (5 * precision * recall) / (4 * precision + recall)
    print("precision= %f, recall= %f, F2= %f"%(precision, recall, F2))

当然,线下线上还是有点差别的,有时候在验证集上表现得好不一定在测试集就能表现得好,所以更多时候还是要通过提交来进行评估。 

8:取巧上分

通过一次一次的训练,我们发现在4000个样本,预测的1越多分数越高,我想这应该是跟评估函数有关系更多的关注你预测的成功的情况。然后我们还发现score这个特征和flag相关性特别高,甚至可以说是决定了flag,在训练集中只要score>5,flag就会是1。所以我们进行了既对score进行了训练也对flag进行了训练,最后再将两者预测的1并起来,发现在b榜能干上0.6。

9:总结

大数据类的比赛,考验的是对数据的敏感度和业务能力,也渐渐明白为什么一些好的公司会对面试者的业务能力有要求。

在比赛中学习是提升业务能力比较好的一个方法,第一次正式参加比较正式的比赛,虽然没能够进决赛,但确实从中学到了挺多。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>