前段时间组队参加了DataFountain上"系统认证风险预测"挑战赛, 最后排名A榜第2, B榜第14, 遗憾未能进入决赛, 在这里分享下比赛过程, 最后有代码供各位参考.
比赛链接
比赛任务
参赛团队将基于用户认证行为数据及风险异常标记结构,构建用户认证行为特征模型和风险异常评估模型,利用风险评估模型去判断当前用户认证行为是否存在风险。
比赛为二分类预测问题, 对于竞赛初学者, 比较适合从这样的结构化比赛入手.
方案
最后提交的方案为lightgbm + catboost 融合, A榜得分为 0.53016672.
大家的得分都不高, 而且从数据上看, 所有分类特征, 不同类之间风险均值都很接近, 可能和这个数据由人工标记的不大准有关. 本方案上分思路大致如下:
- 最开始, 在baseline基础上细化特征, 主要是时间特征,得分在0.51397588932
- 调参数: 在构造了一些特征后, 发现再增加特征, 得分基本都下降, 就开始调参方向. 主要对num_leaves, max_depth, n_estimators, learning_rate进行调整, 因为本赛题数据计算不大, 时间也充足, 调参比较方便, 这时得分在0.52059315975
- 细调参数: 调参过程中发现, 参数有一点点变化, 结果差异都很大. 接着对n_splits, random_state, verbose等等参数都进行了调整, 最终分数在0.52587843799
- 模型融合: 比赛最后阶段(暂列第5), 大家开始组队, 第1, 第2, 第4三个组成了一队后, 分数有了明显提升. 我也开始考虑组队, 找到队友后(catboost模型), 将两个人的模型最好成绩做了简单平均, 得分居然到0.52975581963(惊喜), 之后修改模型权重, 将预测值改为rank排序值得分都有稍微提升, 最终得分0.53016671554. 再之后尝试用更多模型(树模型), 都没有好的效果
- 没想到: 切换B榜后, 我们的得分一下子掉到了十几名(意外), 而有的队在A榜是十几名, 在B榜一下子到了前三.
思考:
- 我们模型不够稳定, 参数调的太细, 感觉是B榜掉分的原因之一, 尤其是模型和k折的2个随机种子, 最好和最差的得分能有2%, 虽然上分, 但也是坑
- 可能有好的特征,没有发现. 本题风险预测+时序数据, 每条数据和它最近的几条数据应该有特征可以挖掘
- NN模型没有尝试
- 运气: A榜运气不错, B榜就…
比赛快结束了, 等大佬分享他们的思路, 最后附上代码(lightgbm部分, 线上得分0.52587843799):
导入数据与特征工程
# -*- coding: utf-8 -*-
import warnings
warnings.simplefilter('ignore')
import os
import re
import gc
import json
import numpy as np
import pandas as pd
pd.set_option('max_columns', None)
pd.set_option('max_rows', 200)
pd.set_option('float_format', lambda x: '%.3f' % x)
from tqdm.notebook import tqdm
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import KFold, StratifiedKFold
from sklearn.metrics import roc_auc_score
import lightgbm as lgb
import os
os.chdir('C:/Users/yyz/Desktop/系统认证风险预测/')
# 读取数据
train = pd.read_csv('data/train_dataset.csv', sep='\t')
test = pd.read_csv('data/test_dataset.csv', sep='\t')
data = pd.concat([train,test],axis=0)
# location列转成多列
data['location_first_lvl'] = data['location'].astype(str).apply(lambda x: json.loads(x)['first_lvl'])
data['location_sec_lvl'] = data['location'].astype(str).apply(lambda x: json.loads(x)['sec_lvl'])
data['location_third_lvl'] = data['location'].astype(str).apply(lambda x: json.loads(x)['third_lvl'])
# 删除值相同的列
data.drop(['client_type', 'browser_source'], axis=1, inplace=True)
data['auth_type'].fillna('__NaN__', inplace=True)
# 日期数据处理
data['op_date'] = pd.to_datetime(data['op_date'])
data['op_ts'] = data["op_date"].values.astype(np.int64) // 10 ** 9
data = data.sort_values(by=['user_name', 'op_ts']).reset_index(drop=True)
data['last_ts'] = data.groupby(['user_name'])['op_ts'].shift(1)
data['ts_diff1'] = data['op_ts'] - data['last_ts']
data['op_date_month'] = data['op_date'].dt.month # 月份放到年份的前面居然上分
data['op_date_year'] = data['op_date'].dt.year
data['op_date_day'] = data['op_date'].dt.day
data['op_date_dayofweek'] = data['op_date'].dt.dayofweek
data['op_date_ymd'] = data['op_date'].dt.year*100 + data['op_date'].dt.month
data['op_date_hour'] = data['op_date'].dt.hour
period_dict ={
23: 0, 0: 0, 1: 0,
2: 1, 3: 1, 4: 1,
5: 1, 6: 1, 7: 2,
8: 2, 9: 2, 10: 2, 11: 2,
12: 2, 13: 3,
14: 3, 15: 3, 16: 3, 17: 3,
18: 3,
19: 4, 20:4, 21: 4, 22: 4,
}
data['hour_cut']=data['op_date_hour'].map(period_dict)
# 一年中的哪个季度
season_dict = {
1: 1, 2: 1, 3: 1,
4: 2, 5: 2, 6: 2,
7: 3, 8: 3, 9: 3,
10: 4, 11: 4, 12: 4,
}
data['month_cut']=data['op_date_month'].map(season_dict)
data['dayofyear']=data['op_date'].apply(lambda x: x.dayofyear) # 一年中的第几天
data['weekofyear']=data['op_date'].apply(lambda x: x.week) # 一年中的第几周
data['是否周末'] = data['op_date'].apply(lambda x: True if x.dayofweek in [4,5, 6] else False) # 是否周末
data.loc[((data['op_date_hour'] >= 7) & (data['op_date_hour'] < 22)), 'isworktime'] = 1
# 特征编码
data['ip_risk_level'] = data['ip_risk_level'].map({'1级':1,'2级':2,'3级':3})
for f in ['ip', 'location', 'device_model', 'os_version', 'browser_version']:
data[f'user_{f}_nunique'] = data.groupby(['user_name'])[f].transform('nunique')
for method in ['mean', 'max', 'min', 'std']:
data[f'ts_diff1_{method}'] = data.groupby('user_name')['ts_diff1'].transform(method)
for i in[ 'os_type']:
data[i+'_n'] = data.groupby(['user_name','op_date_ymd','op_date_hour'])[i].transform('nunique')
lis = ['user_name', 'action',
'auth_type',
'ip',
'ip_location_type_keyword', 'device_model',
'os_type', 'os_version', 'browser_type', 'browser_version',
'bus_system_code', 'op_target', 'location_first_lvl', 'location_sec_lvl',
'location_third_lvl']
# one_hot
data_re = data[lis]
df_processed = pd.get_dummies(data_re, prefix_sep="_", columns=data_re.columns)
lis_sx = [i for i in data.columns if i not in lis]
data = pd.concat([data[lis_sx],df_processed],axis=1)
train = data[data['risk_label'].notna()]
test = data[data['risk_label'].isna()]
建模
ycol = 'risk_label'
feature_names = list(
filter(lambda x: x not in [ycol, 'session_id', 'op_date', 'location','last_ts'], train.columns))
model = lgb.LGBMClassifier(objective='binary',
boosting_type='gbdt',
tree_learner='serial',
num_leaves=29,
max_depth=7,
learning_rate=0.07,
n_estimators=1590,
subsample=0.7,
feature_fraction=0.95,
reg_alpha=0.,
reg_lambda=0.,
random_state=1973,
is_unbalance=True,
metric='auc')
oof = []
prediction = test[['session_id']]
prediction[ycol] = 0
df_importance_list = []
kfold = StratifiedKFold(n_splits=10, shuffle=True, random_state=1950)
for fold_id, (trn_idx, val_idx) in enumerate(kfold.split(train[feature_names], train[ycol])):
X_train = train.iloc[trn_idx][feature_names]
Y_train = train.iloc[trn_idx][ycol]
X_val = train.iloc[val_idx][feature_names]
Y_val = train.iloc[val_idx][ycol]
print('\nFold_{} Training ================================\n'.format(fold_id+1))
lgb_model = model.fit(X_train,
Y_train,
eval_names=['train', 'valid'],
eval_set=[(X_train, Y_train), (X_val, Y_val)],
verbose=250,
eval_metric='auc',
early_stopping_rounds=400)
pred_val = lgb_model.predict_proba(
X_val, num_iteration=lgb_model.best_iteration_)
df_oof = train.iloc[val_idx][['session_id', ycol]].copy()
df_oof['pred'] = pred_val[:, 1]
oof.append(df_oof)
pred_test = lgb_model.predict_proba(
test[feature_names], num_iteration=lgb_model.best_iteration_)
prediction[ycol] += pred_test[:, 1] / kfold.n_splits
df_importance = pd.DataFrame({
'column': feature_names,
'importance': lgb_model.feature_importances_,
})
df_importance_list.append(df_importance)
del lgb_model, pred_val, pred_test, X_train, Y_train, X_val, Y_val
gc.collect()
df_importance = pd.concat(df_importance_list)
df_importance = df_importance.groupby(['column'])['importance'].agg(
'mean').sort_values(ascending=False).reset_index()
df_importance
df_oof = pd.concat(oof)
print('roc_auc_score', roc_auc_score(df_oof[ycol], df_oof['pred']))
prediction['id'] = range(len(prediction))
prediction['id'] = prediction['id'] + 1
prediction = prediction[['id', 'risk_label']].copy()
prediction.columns = ['id', 'ret']
prediction.to_csv('result/sub40_lgb.csv', index=False)