风险大脑支付风险识别初赛经验分享【谋杀电冰箱-凤凰还未涅槃】

作者:凤凰还未涅槃  |  更新于:2018-08-28 09:32:14     

 

该分享主要包括EDA,以及基本的lgb模型

 

import pandas as pd
import gc
from dateutil.parser import parse
import numpy as np
import os
import matplotlib.pyplot as plt

 

 

一、加载数据

 

初赛的数据集对我已经用了N年的笔记本来说还是挺大的,初始数据也比较占内存,对我来说也是个挑战,受来自呵呵大佬的指导,学会了如何去更好的控制和管理内存。

 

直接读取数据集

 

train_data = pd.read_csv('atec_anti_fraud_train.csv')
train_data.info(memory_usage='deep')

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 994731 entries, 0 to 994730
Columns: 300 entries, id to f297
dtypes: float64(279), int64(20), object(1)
memory usage: 2.3 GB

 

 

可见会占用比较大的内存,因为数据存的格式比较不友好,详参考https://www.jiqizhixin.com/articles/2018-03-07-3

 

def creatDtype():
    dtype = {'id':'object',
             'label':'int8',
             'date':'int64',
             'f1':'uint8',
             'f2': 'uint8',
             'f3': 'uint8',
             'f4': 'uint8',
             'f5': 'float32',
             'ndays':'uint8'
             }
    for i in range(20,298):
        dtype['f'+str(i)] = 'float32'
    for i in range(6,20):
        dtype['f'+str(i)] = 'uint8'
    return dtype


train_data = pd.read_csv('atec_anti_fraud_train.csv',dtype=creatDtype())
train_data.info(memory_usage='deep')


train_data.info(memory_usage='deep')
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 994731 entries, 0 to 994730
Columns: 300 entries, id to f297
dtypes: float32(279), int64(1), int8(1), object(1), uint8(18)
memory usage: 1.2 GB

 

此一个表节约了近一半内存

 

test_A = pd.read_csv('atec_anti_fraud_test_a.csv',dtype=creatDtype())
test_B = pd.read_csv('atec_anti_fraud_test_b.csv',dtype=creatDtype())
data  = train_data.append(test_A).append(test_B)
data['ndays'] = data['date'].apply(lambda x:(parse(str(x))-parse(str(20170905))).days)

 

 

二、EDA

 

我在风控行业里做了比较久的年头,做风控有两大重要的事情,第一是特征稳定不稳定,第二是训练集学习到的风险是不是真实存在与稳定的。

 

1.这题我通过两个方面来做特征的稳定性分析。

1).特征的缺失情况随着时间推移的变化

 

p = 'f24'
a = pd.DataFrame(data.groupby('ndays')[p].apply(lambda x: sum(pd.isnull(x))) / data.groupby('ndays')['ndays'].count()).reset_index()
a.columns = ['ndays', p]
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
plt.plot(a['ndays'], a[p])
plt.axvline(61, color='r')
plt.axvline(122, color='r')
plt.axvline(153, color='r')
plt.xlabel('ndays')
plt.ylabel('miss_rate_' + p)
plt.title('miss_rate_' + p)

2).特征取值随着时间推移的变化

 

a = pd.DataFrame(data.groupby('ndays')[p].mean()).reset_index()
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
plt.plot(a['ndays'], a[p])
plt.axvline(61, color='r')
plt.axvline(122, color='r')
plt.axvline(153, color='r')

plt.xlabel('ndays')
plt.ylabel('mean_of_' + p)
plt.title('distribution of ' + p)

 

对于每个特征都可以生成类似的两张图,通过特征的缺失情况图来看,初赛的原始297个特征是来自6-7张不同的表,大部分特征训练期两个月的数据集分布比较类似,可以猜测特征是每个月分开提取的,这种想法在复赛的时候一直沿用。

 

 

2.观察特征的风险分布的稳定性。

这里由于一些原因就不给出原始代码了,实现该图也是比较简单的,最主要是要先生成右上方的表,然后根据这个表去画这个图,红线刻画的是不同分组的风险是平均风险倍数。

f2440天的风险分布

 

 

 

20

 

 

可以看出该特征风险关系比较稳定,但是这个风险是取值大于1带来的,这个值大于1的风险是平均风险的10倍左右,但是这个特征在样本的外推期(测试A和测试B)取值都是小于等于1了,这很可能是蚂蚁内部用了此类特征做了规则,所以在所有有表现的样本里不会出现大于等于1的情况了,这种特征不适合再应用到模型里去识别风险,再实际应用里可能需要直接用规则来识别这种风险,所以建模的时候把这类特征删除会有比较不错外推效果(A榜和B榜此类特征蚂蚁应用规则时间的应该不同)。

 

 

三、特征工程

    初赛的特征工程基本都在根据eda的结果去筛选特征上,所以初赛我做了很多的"人工"智能,但是考虑到一个某种组合新出现的时候风险较大这一个点上,还是发现了一些特征。

比如以下特征就是比较好用,计算的是f6---f20组合是第几次出现类特征。

train_data['acc1'] = train_data.groupby(['f'+str(k) for k in range(6,20)]).cumcount()

 

这个特征风险趋势非常明显,这类特征直接启发了复赛阶段的大部分特征衍生。

 

这个特征说明这种f6—f20组合出现的次数越早,风险越高,是一类单调且会比较有业务含义的好特征。

 

.rank_average_kflod_lgb baseline

最后给出一个lightgbmk_flod的一个rank融合的base

import pandas as pd
import gc
import xgboost as xgb
from dateutil.parser import parse
import numpy as np
from sklearn.model_selection import KFold
import lightgbm as lgb
def creatDtype():
    dtype = {'id':'object',
             'label':'int8',
             'date':'int64',
             'f1':'uint8',
             'f2': 'uint8',
             'f3': 'uint8',
             'f4': 'uint8',
             'f5': 'float32',
             'ndays':'uint8'
             }
    for i in range(20,298):
        dtype['f'+str(i)] = 'float32'
    for i in range(6,20):
        dtype['f'+str(i)] = 'uint8'
    return dtype

#测评函数 def tpr_weight_funtion(y_true,y_predict):
    d = pd.DataFrame()
    d['prob'] = list(y_predict)
    d['y'] = list(y_true)
    d = d.sort_values(['prob'], ascending=[0])
    y = d.y
    PosAll = pd.Series(y).value_counts()[1]
    NegAll = pd.Series(y).value_counts()[0]
    pCumsum = d['y'].cumsum()
    nCumsum = np.arange(len(y)) - pCumsum + 1
    pCumsumPer = pCumsum / PosAll
    nCumsumPer = nCumsum / NegAll
    a = pCumsumPer[abs(nCumsumPer-0.001).idxmin()]
    b = pCumsumPer[abs(nCumsumPer-0.005).idxmin()]
    c = pCumsumPer[abs(nCumsumPer-0.01).idxmin()]
    return 0.4 * a + 0.3 * b + 0.3 * c

train_data = pd.read_csv('atec_anti_fraud_train.csv',dtype=creatDtype())
train_data['ndays'] = train_data['date'].apply(lambda x:(parse(str(x))-parse(str(20170905))).days)
train_data = train_data[train_data.ndays<=40]
gc.collect()

test_data = pd.read_csv('atec_anti_fraud_test_b.csv',dtype=creatDtype())
data = train_data.append(test_data).reset_index(drop=True)
del train_data,test_data
gc.collect()
data = data.sort_values(by = 'date')
data['acc1'] = data.groupby(['f'+str(k) for k in range(6,20)]).cumcount()

train_data = data[data.label.notnull()]
test_data = data[data.label.isnull()]



pre = [k for k in train_data.columns if k not in ['id', 'label', 'date', 'bs', 'ndays']]
need_del = ['f64','f65','f66','f67','f68','f69','f70','f71','f111','f112','f113','f114','f115','f116','f117','f118','f119','f120','f121','f122','f123','f124','f125','f126','f127','f128','f129','f130','f131','f132','f133','f134','f135','f136','f137','f138','f139','f140','f141','f142','f143','f144','f145','f146','f147','f148','f149','f150','f151','f152','f153','f154','f161','f162','f163','f164','f165','f211','f212','f213','f214','f215','f216','f217','f218','f219','f220','f221','f222','f223','f224','f225','f226','f227','f228','f229','f230','f231','f232','f233']
for i in range(20,54):
    need_del.append('f'+str(i))

pred = [k for k in pre if  k not in need_del]
train = train_data[(((train_data.f28<=1)|(train_data.f28.isnull()))&((train_data.f29<=1)|(train_data.f29.isnull()))&((train_data.f30<=1)|(train_data.f30.isnull()))&((train_data.f31<=1)|(train_data.f31.isnull()))&(train_data['ndays']<=40))]
train['label'] = train['label'].apply(lambda x: 0 if x == 0 else 1)

test_data['score'] = 0
del train_data
gc.collect()


res = []

##5折训练,并考虑rank融合 from sklearn.model_selection import StratifiedKFold
skf = StratifiedKFold(n_splits=5,random_state=999)
X = np.array(train[pred])
y = np.array(train['label'])

i = 0
for trn, tst in skf.split(X,y):
    i += 1
    print(i)
    trn_X, tst_X = X[trn], X[tst]
    trn_Y, tst_Y = y[trn], y[tst]
    dtrain = lgb.Dataset(trn_X, label=trn_Y)
    dtest = lgb.Dataset(tst_X, label=tst_Y)

    params = {
        'boosting_type': 'gbdt',
        'objective': 'binary',
        'metric': 'auc',
        'min_child_weight': 1.5,
        'num_leaves': 2 ** 5,
        'lambda_l2': 10,
        'subsample': 0.85,
        'learning_rate': 0.1,
        'seed': 2018,
        'colsample_bytree': 0.5,
        'nthread': 12
    }
    model = lgb.train(params, dtrain, num_boost_round=50000, valid_sets=dtest, early_stopping_rounds=50)
    res.append(tpr_weight_funtion(tst_Y, model.predict(tst_X)))
    test_data['res_s'] = model.predict(np.array(test_data[pred]))
    test_data['score'] = test_data['score'] + test_data['res_s'].rank()

##标准化最后的得分

max_v = test_data['score'].max()
min_v = test_data['score'].min()
test_data['score'] = test_data['score'].apply(lambda x: (x - min_v) / (max_v - min_v))
test_data[['id', 'score']].to_csv('res_lgb_0707_1.csv', index=False)

 

 

.小结

初赛在做的事情实际上是有穿越性质的事情,就是挑选稳定特征上,这种做法是值得商榷的,但是最终的模型的稳定性上一定是体现在特征的稳定性上,希望此次分享可以给大家带来一点帮助和启发。

做以下工作可以进一步提升模型效果:

1)部分不稳定特征的平滑

2)多模型融合

3)规则辅助预测

 

目若 | 2018-08-29 09:07:12 #1
<p>原来凤凰兄复赛就在冰箱组。从初赛到复赛,一路过来始终保持领先,实在佩服</p>
LeonHardt | 2018-08-29 09:59:00 #2
<p>膜拜凤凰哥</p>
ATEC小哥 | 2018-08-29 10:10:57 #3
<p>打call,打call</p>
ATEC小哥 | 2018-08-29 10:16:05 #4
Hayes:<p>打call,打call</p>回到原帖
<p>测试楼</p>
凤凰还未涅槃 | 2018-08-29 10:19:23 #5
目若:<p>原来凤凰兄复赛就在冰箱组。从初赛到复赛,一路过来始终保持领先,实在佩服</p>回到原帖
<p>运气实在是好,有没有感觉,,,也没有一直领先,队友给力而已,单撸的人才是牛</p>
Stary | 2018-08-29 16:10:02 #6
<p>一楼!写的很好。启发很大。T
Jiangcq | 2018-08-30 20:15:26 #7
<p>降内存真是666,我还傻傻的用excel打开然后再保存</p>
leon_yang | 2018-09-01 16:55:37 #8
<p>冰箱组卧虎藏龙,大佬的考察变量的思路在工作中很有借鉴意义哇</p>
我的回复
回复数
 8
阅读数
 3026

返回顶部