2017-CCF-BDCI-企业经营退出风险预测:9th/569 (Top 1.58%)
这是我的第一个数据挖掘比赛,CCF 大数据与计算智能大赛(BDCI)中的一题:企业经营退出风险预测。最终取得复赛 A 榜第 3,B 榜第 9 (Top 1.58%) 的成绩。
这个比赛 12 月中旬就结束了,硬是被我拖到现在才来总结,我这拖延症真的是……现在回忆起这个比赛,比赛时的那种郁闷感依然记忆犹新。我在复赛的第 5 天便达到了分数 6924,但之后一直无法提分,这种烦躁感当时给我带来了挺大的困扰(当然最后还是提升到了分数 6930)。等比赛结束之后,我回过头来看,其实当时我参赛的心态是不端正的,功利心太强,这样带来的问题就是比赛心态的爆炸,自己的眼界会被约束,提分方式的想象力也会被限制。最好的心态应该是抱着学习的心态参赛,只要能够学到一点点新的东西,就会感到惊喜。
另外一个想说的点是,我们团队在复赛 A 榜中排名第 3,但是切换 B 榜之后,便跌到第 9 了,这个现象直接导致我们团队没有进入决赛,因此我会在后文中谈一谈为什么会有这个现象。
我的另两位队友 JinjinLin 和 ZhenxianZheng 也开源了解决方案,详情请见 JinjinLin 的解决方案 和 ZhenxianZheng 的解决方案
CCF 举办的这次大赛中这么多比赛,为什么唯独选择这个呢?
为什么我想要说一下这个呢,因为我相信未来有很多的新人会尝试加入数据挖掘的阵营中,他们也会遇到相同的境遇,我希望能够将我当时的一些思考与选择作为他们的参考选项,以便于他们做出他们的最优选择。
第一次参赛,可以说连 Python 的语法都不熟悉,更何况 pandas 的各种操作。这时候师兄给的 baseline 就显得十分重要了。当中的各种基础操作,例如:文件读取、数据定义、分组聚集等等,对我来说都是新鲜的。其中最为关键的是传统的数据挖掘比赛中的代码框架。我们来看一下,这个极为经典的代码框架(非原始 baseline 框架,我做了一些修改)。
# 1. 导入库
import numpy as np
import pandas as pd
...
# 2. 读取数据文件
train = pd.read_csv('../data/input/train.csv')
test = pd.read_csv('../data/input/evaluation_public.csv')
...
# 3. 定义特征构建函数
def get_entbase_feature(df):
...
def get_alter_feature(df):
...
...
# 4. 调用函数,构建特征
entbase_feat = get_entbase_feature(entbase)
alter_feat = get_alter_feature(alter)
...
# 5. 拆分数据集的特征与标签
dataset = pd.merge(entbase_feat, alter_feat, on='EID', how='left')
...
trainset = pd.merge(train, dataset, on='EID', how='left')
testset = pd.merge(test, dataset, on='EID', how='left')
train_feature = trainset.drop(['TARGET', 'ENDDATE'], axis=1)
train_label = trainset.TARGET.values
test_feature = testset
test_index = testset.EID.values
# 6. 模型的交叉验证
...
iterations, best_score = xgb_cv(train_feature, train_label, params, config['folds'], config['rounds'])
...
# 7. 模型的训练与预测
...
model, pred = xgb_predict(train_feature, train_label, test_feature, iterations, params)
...
# 8. 结果文件的写出
res = store_result(test_index, pred, 0.18, '1207-xgb-%f(r%d)' % (best_score, iterations))
从上面给的样例代码中,我们可以观察到整个代码的框架如下:
使用这样一个代码框架,能够十分清晰的知道整个数据挖掘的流程,这一点对于第一次参赛的新人是尤为重要的。另外当我们想要提分时,我们只需要在特定的部分做出相应的修改就能够达到目的。例如:我希望构建新的特征,来提升我的分数,那么这时只需要新增框架中的第 3 和第 4 部分即可。
这个数据集中存在着不少的脏数据,这个阶段便是对这些脏数据进行处理,其中包括:
我将特征分为 5 个部分,分别是基础特征、偏离值特征、交叉特征和想象力特征。
基础特征是比赛中最容易想到的特征,其中包括:
uid
、ZCZB
、RGYEAR
、INUM
、ENUM
等偏离值特征指单个个体与分组之间的偏离距离。以下的代码所生成的特征便是这一类特征:
dataset['MPNUM_CLASS'] = dataset['INUM'].apply(lambda x : x if x <= 4 else 5)
dataset['FSTINUM_CLASS'] = dataset['FSTINUM'].apply(lambda x : x if x <= 6 else 7)
dataset.fillna(value={'alt_count': 0, 'rig_count': 0}, inplace=True)
for column in ['MPNUM', 'INUM', 'FINZB', 'FSTINUM', 'TZINUM', 'ENUM', 'ZCZB', 'allnum', 'RGYEAR', 'alt_count', 'rig_count']:
groupby_list = [['HY'], ['ETYPE'], ['HY', 'ETYPE'], ['HY', 'PROV'], ['ETYPE', 'PROV'], ['MPNUM_CLASS'], ['FSTINUM_CLASS']]
for groupby in groupby_list:
if 'MPNUM_CLASS' in groupby and column == 'MPNUM':
continue
if 'FSTINUM_CLASS' in groupby and column == 'FSTINUM':
continue
groupby_keylist = []
for key in groupby:
groupby_keylist.append(dataset[key])
tmp = dataset[column].groupby(groupby_keylist).agg([sum, min, max, np.mean]).reset_index()
tmp = pd.merge(dataset, tmp, on=groupby, how='left')
dataset['ent_' + column.lower() + '-mean_gb_' + '_'.join(groupby).lower()] = dataset[column] - tmp['mean']
dataset['ent_' + column.lower() + '-min_gb_' + '_'.join(groupby).lower()] = dataset[column] - tmp['min']
dataset['ent_' + column.lower() + '-max_gb_' + '_'.join(groupby).lower()] = dataset[column] - tmp['max']
dataset['ent_' + column.lower() + '/sum_gb_' + '_'.join(groupby).lower()] = dataset[column] / tmp['sum']
dataset.drop(['MPNUM_CLASS', 'FSTINUM_CLASS'], axis=1, inplace=True)
这段代码的意思是:
这类特征对于这个比赛十分有效,是我分数大幅上升的一个原因。
交叉特征指不单单从一个角度去构建特征,而从多个角度构建够特征,或者说将特征之间相互作用后生成新的特征。这类特征包括:
MPNUM+INUM
、FINZB/ZCZB
等HY
做独热编码后,乘以 ZCZB
、RGYEAR
等MPNUM^2+INUM
等(我没有做这类交叉特征)交叉特征的效果也十分明显,能显著的提升分数,其中独热交叉特征在这个比赛中最为有效。
想象力特征这个词是我自己构造的,指的是根据实际的业务场景,思考其中可能存在的一些隐晦的特征。例如:投资表中,就可以构建一个投资网络,然后基于这个网络提取相关的特征。这个思路来自我的师兄 @Kaho,这也是我赛后才了解到的特征构造方式,十分新颖。
模型部分包括:单模型的提分与多模型融合。
首先,谈谈单模型的提分。在这个比赛中,根据师兄的建议,我选择了 XGBoost,使用它的原因在于:
其次,是多模型融合。这部分是我的另一位队友做的,因此我没有过多的尝试多模型融合。在这个比赛中,我们团队的融合效果不是太好,加权融合之后分数仅提升 1 至 2 个千。
新人入赛不踩坑是不可能的,比赛中我是踩了无数个坑,其中比较有意思的,比较隐晦的有这么几个:
reset_index(drop=True)
,不然之后对这个 dataframe 的各种操作的是误操作。这个坑同样耗费了我不少的精力我们团队在复赛 A 榜中排名第 3,但是切换 B 榜之后,便跌到第 9 了,这个现象直接导致我们团队没有进入决赛,在赛后我进行了认真的分析与思考,并且与他人探讨,大致总结了几点原因:
感谢以下朋友,他们向我输送了一些新的观点:
如果您有任何的想法,例如:发现某处有 bug、觉得我对某个方法的讲解不正确或者不透彻、有更加有创意的见解,欢迎随时发 issue 或者 pull request 或者直接与我讨论!另外您若能 star 或者 fork 这个项目以激励刚刚踏入数据挖掘的我,我会感激不尽~