比赛介绍
本小节是比赛基本信息介绍,对相关信息比较熟悉的可以跳过,之后看后面的方案。
基本信息
MarTech Challenge in Baidu AI Studio
MarTech 技术已经被广泛应用于商业广告分析与挖掘中,在搜索广告,信息流广告,营销预测,反欺诈发现,商品购买预测,智能创意生成中有广泛的应用。
赛题中提供约50万次点击数据,请预测用户的点击行为是否为正常点击,还是作弊行为。
标签:反欺诈预测
比赛时间:2020/09/01 - 2023/01/01
举办方:
赛题赛制
- 即日起面向全社会公开报名,直到赛题下线。
- 飞桨常规赛不设初赛、复赛,以当月每位参赛选手提交的最优成绩排名。每月竞赛周期为本月1日至月末最后1日。
- 比赛期间选手每天可最多提交5次作品(预测结果+原始代码),系统自动选取最优成绩作为榜单记录。
参赛方式
本次竞赛面向全社会开放,不限年龄、身份、国籍,相关领域的个人、高等校、科研机构、企业单位、初创团队等人员均可报名参赛。
仅支持个人参赛。 不允许组队参赛。
比赛现状
目前榜首成绩:89.448
如果想进入比赛的每月前三名,必须要89.1以上。有些月份竞争激烈 89.1 不能进入前三名。
本文方案最高成绩: 89.102, 这个成绩在很多月份可以进入前三名。
赛题说明
任务说明
测试集中提供了会话sid及该会话的各维度特征值,基于训练集得出的模型进行预测,判断该会话sid是否为作弊行为。
数据集
选手报名后,可在【数据集】tab获取数据集,以及基线系统。
训练集: train.csv (50万条)
测试集: test1.csv(15万条)
字段说明
train.csv和test.csv字段说明
字段 | 类型 | 说明 |
---|---|---|
sid | string | 样本id/请求会话sid |
package | string | 媒体信息,包名(已加密) |
version | string | 媒体信息,app版本 |
android_id | string | 媒体信息,对外广告位ID(已加密) |
media_id | string | 媒体信息,对外媒体ID(已加密) |
apptype | int | 媒体信息,app所属分类 |
timestamp | bigint | 请求到达服务时间,单位ms |
location | int | 用户地理位置编码(精确到城市) |
fea_hash | int | 用户特征编码(具体物理含义略去) |
fea1_hash | int | 用户特征编码(具体物理含义略去) |
cus_type | int | 用户特征编码(具体物理含义略去) |
ntt | int | 网络类型 0-未知, 1-有线网, 2-WIFI, 3-蜂窝网络未知, 4-2G, 5-3G, 6–4G |
carrier | string | 设备使用的运营商 0-未知, 46000-移动, 46001-联通, 46003-电信 |
os | string | 操作系统,默认为android |
osv | string | 操作系统版本 |
lan | string | 设备采用的语言,默认为中文 |
dev_height | int | 设备高 |
dev_width | int | 设备宽 |
dev_ppi | int | 屏幕分辨率 |
评估指标 Accuracy
参赛选手结果提交时应提交submission.csv文件,格式如下:
sid | label |
---|---|
1440682 | 0 |
1606824 | 1 |
1774642 | 0 |
1742535 | 0 |
… | 0或1 |
submission.csv 字段说明
字段 | 说明 |
---|---|
sid | 请求会话sid |
label | 是否作弊,0为正常,1位作弊 |
提交注意事项
在线评估提交限制:每个选手每日最多提交5次系统结果进行在线评测,如果新提交结果好于之前提交结果,排行榜中的成绩将自动进行更新覆盖。
数据探索
train_data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 500000 entries, 0 to 499999
Data columns (total 21 columns):
Unnamed: 0 500000 non-null int64
android_id 500000 non-null int64
apptype 500000 non-null int64
carrier 500000 non-null float64
dev_height 500000 non-null float64
dev_ppi 500000 non-null float64
dev_width 500000 non-null float64
label 500000 non-null int64
lan 316720 non-null object
media_id 500000 non-null int64
ntt 500000 non-null float64
os 500000 non-null object
osv 493439 non-null object
package 500000 non-null int64
sid 500000 non-null int64
timestamp 500000 non-null float64
version 500000 non-null object
fea_hash 500000 non-null object
location 500000 non-null int64
fea1_hash 500000 non-null int64
cus_type 500000 non-null int64
dtypes: float64(6), int64(10), object(5)
memory usage: 80.1+ MB
样本分布分析
我们把训练集中的每一行成为一个样本。
所谓的样本分布,就是看训练集和测试集,样本是否分布均衡。在本题中,样本分布是均衡的。 经过简单分析,本赛题样本分布均衡,不做详细介绍。
缺失值处理
所谓缺失值,是指某些样本的某些值是空的,对此我们要进行缺失值的处理。 通常可采用的方案有填充和删除等。本文采用的填充。
异常值处理
本文中,字段 osv 的数据中有各种各样的格式,看起来不是那么规范的。通常可采用的方案有 删除、转换、填充、区别对待等。我们采用的是转换。
特征挖掘
由于题中给的特征之间,有些列之间有明显的关联关系,如果把它们当成单独的特征进行处理,就会丢失一些隐藏的关联性。
像本题中的 dev 相关的几个变量,是有关联关系的。
还有就是timestamp特征,其实是一个时间特征,我们可以把它转换为 年、月、日、星期、时分秒等特征。
还有一些特征值会明显影响结果,我们单独抽离一些特征来,提高该特征的影响程度。
标准化和归一化
本体采用的事xgboost模型进行分类。 Xgboost是在GBDT的基础上对boosting算法进行的改进,LightGBM (Light Gradient Boosting Machine)是一个实现 GBDT 算法的框架,GBDT的树是在上一棵树的基础上通过梯度下降求解最优解,归一化能收敛的更快,GBDT通过减少偏差来提高性能。
这两种算法并不像bagging集成算法那样随机森林那样,通过减少方差提高性能的,树之间建立关系是独立的,不需要标准化和归一化。
一共21列。
特征工程
'Unnamed: 0'
train_data['Unnamed: 0']
0 0
1 1
2 2
3 3
4 4
...
499995 499995
499996 499996
499997 499997
499998 499998
499999 499999
Name: Unnamed: 0, Length: 500000, dtype: int64
该列其实就是样本的序号,从0到5w,初步看没有任何参考价值,可以移除。
train = train_data.drop(['Unnamed: 0', 'sid'], axis=1)
'android_id'
train['android_id_0'] = train['android_id'].map(lambda x: 0 if x == 0 else 1)
test['android_id_0'] = test['android_id'].map(lambda x: 0 if x == 0 else 1)
'timestamp'
从timestamp 提炼相应的特征 ,包含 weekday/day, minute, time_diff 其中:
weekday 星期 day 日期 time_diff 从 最早样本的时间开始计算时间偏移量
单独写成文件如下:
process_timestamp.py
import time
import pandas as pd
from datetime import datetime
def process_timestamp(train, test):
def get_date(features):
if 'timestamp' not in features.columns:
return
features2 = features.copy()
# 除以1000 转化为日期格式
features2['timestamp'] = features2['timestamp'].apply(lambda x: datetime.fromtimestamp(x/1000))
# 创建时间索引
temp = pd.DatetimeIndex(features2['timestamp'])
features2['year'] = temp.year
features2['month'] = temp.month
features2['day'] = temp.day
features2['week_day'] = temp.weekday
features2['hour'] = temp.hour
features2['minute'] = temp.minute
start_time = features2['timestamp'].min()
features2['time_diff'] = features2['timestamp'] - start_time
features2['time_diff'] = features2['time_diff'].dt.days * 24 + features2['time_diff'].dt.seconds / 3600
features2.drop(['timestamp'], axis = 1, inplace=True)
features2.drop(['year', 'month'], axis = 1, inplace=True)
# features2.drop(['week_day', 'day', 'minute'], axis = 1, inplace=True)
return features2
train = get_date(train)
test = get_date(test)
return train, test
'osv'
pd.set_option("display.max_rows", None)
train_data['osv'].value_counts()
结果如下:
8.1.0 151419
9 71332
6.0.1 31714
7.1.1 26503
9.0.0 24385
6.0 23674
5.1 19602
8.1 19301
5.1.1 17387
8.0.0 15127
7.1.2 12068
4.4.4 10612
7.0 9346
Android_8.1.0 8043
Android_9 7085
5.0.2 5019
7.9.0 4348
7.9.2 4312
8.0 4000
7.1 3816
9.0 1882
3.8.6 1733
4.4.2 1681
6.0.0 1568
5.1.0 1421
4.1 1399
2.3 1328
5.0 1309
4.3 1220
7.0.0 1107
Android_8.0.0 1085
4.4 695
Android_7.1.1 656
3.9.0 620
Android_6.0.1 582
7.8.9 517
4.2.2 496
7.8.7 383
8.0.1 310
5.0.1 278
Android_5.1.1 252
Android_7.1.2 212
6.1 183
7.8.0 176
3.8.4 170
Android_5.1 163
8 155
Android_6.0 142
4.1.2 142
5.0.0 138
7.7.7 129
Android_7.0 117
7.8.2 104
3.8.0 103
21100 93
7.7.3 90
Android_4.4.4 85
7.2.1 82
4.3.0 82
7 74
4.2.1 72
7.7.5 70
6.1.0 64
5.2 60
7.8.5 59
7.8.4 59
7.7.2 57
21000 52
5 47
4.1.1 46
7.6.7 45
7.7.0 43
3.8.3 40
6.0_23 38
7.6.8 38
3.7.6 35
7.6.4 34
7.8.8 27
Android_5.0.2 26
9.0.5 26
3.7.8 25
7.1.3 22
7.7.4 22
7.1.0 21
4.0.3 20
7.6.9 20
4.2 20
4.0.4 20
Android_5.0 19
4.4.5 18
7.8.6 17
6.2.1 16
3.1.1 13
Android_4.4.2 13
4.4.3 12
Android_4.3 10
4.4.0 9
6.0.1_19 9
3.0.3 8
2.3.6 8
Android_4.1.2 7
2.1.2 7
6.1.2 7
7930 7
2.3.4 7
2.2.3 6
6 6
4.3.3 4
3.2 4
2.3.5 4
7.3.1 4
4.4W 4
5.3.0-FL2-20180717.9010 3
Android_4.2.2 3
5.1_22 3
4.4.2_19 3
5.3 2
4.0.2 2
7.6.6 2
71200 2
7910 2
4.3.1 2
5.2.0 2
3.2.0-FL2-20180726.9015 1
9.1.0 1
5.0.3 1
5.0_21 1
8.0.2 1
5.1.1_22 1
6.1.0-RS-20190305.1125 1
4.0 1
Android 4.2.9 1
6.0.2 1
Android_4.4.3 1
Android_4.2.1 1
9.1 1
7920 1
2.0.1 1
6.0 十核2.0G_HD 1
4 1
%E6%B1%9F%E7%81%B5OS+5.0 1
Android_5.0.1 1
2.3.7 1
2.2.2 1
f073b_changxiang_v01_b1b8_20180915 1
4.2.3.2 1
Android 7.1 1
10.3.3 1
2.9.2 1
5.3.0 1
11 1
5.1.1-F-20180719.9007 1
Android 5.12 1
Android 4.3.1 1
Name: osv, dtype: int64
我们发现大部分数据是 a.b.c这种类型。也有很多数据不是规范格式的。于是我们把他们转换a.bc 这种格式,后续方便当成小数来处理。
单独抽离process_osv.py文件:
import re
def process_osv(features, test1):
features['osv'] = features['osv'].apply(f)
test1['osv'] = test1['osv'].apply(f)
return features, test1
def f(x):
x = str(x)
x = x.replace('Android_', '')
x = x.replace('Android ', '')
if x == '%E6%B1%9F%E7%81%B5OS+5.0':
return 5.0
if x == '6.0 十核2.0G_HD':
return 6.0
if x == 'f073b_changxiang_v01_b1b8_20180915':
return 0
if x == '6.0.1_19':
return 6.01
if x == 'nan':
return 0
if x == 'GIONEE_YNGA':
return 0
if x == '4.4W':
return 4.4
if x.find('.') > 0:
index = x.find('.')
x = x[0:index] + '.' + re.sub(r'\D', "", x[index+1:])[0:2]
x = float(x)
if x > 10000:
return x / 10000
if x > 1000:
return x / 1000
return x
'os'
首先分析特征分布:
train_data['os'].value_counts()
结果:
android 303175
Android 196825
Name: os, dtype: int64
我们可以发现,所有数据都是安卓系统。
我们可以初步把该特征舍弃,因为所有特征都是android,没啥区别。
但是大家也可以保留试试,根据最后准确率看看效果。
我个人把它保留了。理由如下:
train_data[train_data['label'] == 0]['os'].value_counts()
结果:
android 180903
Android 76857
Name: os, dtype: int64
发现 lable == 0的样本里面,该特征的两个值,占比约 2.x:1。
train_data[train_data['label'] == 1]['os'].value_counts()
结果:
android 122272
Android 119968
Name: os, dtype: int64
发现 label == 1 的样本里,2个值的占比约 1:1。 所以可以认为该特征对最后分类结果有一定影响。不需要舍弃该样本。
编写函数为单独文件:process_os.py
import pandas as pd
def process_os(features, test):
def f(x):
if x == 'android':
return 0
return 1
features['os'] = features['os'].apply(f)
test['os'] = test['os'].apply(f)
return features, test
'fea_hash'
train_data['fea_hash'].apply(lambda x: len(x)).value_counts()
10 378925
9 108904
8 11235
7 740
6 93
38 37
39 28
37 16
5 11
36 3
33 2
32 2
1 2
31 1
30 1
我们发现该特征长度分布非常不均匀。 要么是 10以为的长度,要么是30几位的长度。我们来看看数据:
train_data[train_data['fea_hash'].apply(lambda x: len(x) > 10 )]['fea_hash']
结果:
4214 2408:84e4:554:54fd:b36:6770:1a58:927d
6767 2408:84e4:196:9d1f:a41f:deff:fef9:8c60
7742 2408:84ec:4008:ae53:fcc6:f9ff:fe08:44ba
10086 2409:893c:1e0a:1e18:cc0b:52a0:6104:4d40
19696 2408:84e4:103:21f5:4d40:76b9:47e5:930c
...
494813 2408:84e1:e9:e956:564:59e8:9f05:506b
494835 2409:8928:24d8:bb5a:c526:4398:37dd:f2c8
495653 2409:8a3c:5917:c670:7827:91e2:ed47:d29d
495873 2409:8a30:c452:3a70:ec8b:f096:6288:6986
499386 2408:84e4:516:974f:419b:32d3:d69f:443b
Name: fea_hash, Length: 90, dtype: object
也就是说,在长度 > 10 的情况下,该特征值都是类似某 设备物理地址。
再看看 长度 < 10的情况下,特征值的情况:
train_data[train_data['fea_hash'].apply(lambda x: len(x) < 10 )]['fea_hash']
结果:
6 369486452
15 738486495
28 596797663
29 276796533
31 634714332
...
499975 471301994
499976 406243511
499985 699472385
499987 364899549
499995 861755946
Name: fea_hash, Length: 120985, dtype: object
所以发现该特征不是单一特征,里面包含了2种不同的情况,需要分别处理。
处理结果:添加新特征 fea_hash_len
# fea_hash
all_df['fea_hash_len'] = all_df['fea_hash'].map(lambda x:len(str(x)))
train.loc[:,'fea_hash_len'] = all_df[all_df['label'].notnull()]['fea_hash_len']
test.loc[:, 'fea_hash_len'] = all_df[all_df['label'].isnull()]['fea_hash_len']
'fea1_hash'
同上 ,添加新特征 fea1_hash_len
# fea1_hash_len
all_df['fea1_hash_len'] = all_df['fea1_hash'].map(lambda x:len(str(x)))
train.loc[:,'fea1_hash_len'] = all_df[all_df['label'].notnull()]['fea1_hash_len']
test.loc[:, 'fea1_hash_len'] = all_df[all_df['label'].isnull()]['fea1_hash_len']
'dev_rate'
我们发现有 'dev_width', 'dev_height' 两个特征。我们构造宽高比 'dev_rate'
# 设别高宽比、设备面积 dev_height, dev_width
all_df['dev_rate'] = all_df['dev_height']/all_df['dev_width']
train.loc[:,'dev_rate'] = all_df[all_df['label'].notnull()]['dev_rate']
test.loc[:, 'dev_rate'] = all_df[all_df['label'].isnull()]['dev_rate']
'dev_area'
同理构造面积特征 'dev_area'
# 面积 dev_height, dev_width
all_df['dev_area'] = all_df['dev_height']*all_df['dev_width']
train.loc[:,'dev_area'] = all_df[all_df['label'].notnull()]['dev_area']
test.loc[:, 'dev_area'] = all_df[all_df['label'].isnull()]['dev_area']
'dev_area_ppi'
注,该特征从其他选手方案中抄过来的,此时编写该文章的时候发现不是很合理。大家可自行调整,调整过后也许会比本人分数更高。
个人认为合理的是:
all_df['dev_ppi'] * all_df['dev_area']
这个物理含义就是屏幕像素总个数。
某选手方案:
# 分辨率 dev_ppi
all_df['dev_area_ppi'] = all_df['dev_ppi']/all_df['dev_area']
all_df['dev_area_ppi'][np.isnan(all_df['dev_area_ppi'])] = 0
train.loc[:,'dev_area_ppi'] = all_df[all_df['label'].notnull()]['dev_area_ppi']
test.loc[:, 'dev_area_ppi'] = all_df[all_df['label'].isnull()]['dev_area_ppi']
其他特征处理
某些特征值对样本影响巨大,比例更影响分类权重。
用该方式,增加一些特征。 主要思路如下:
假设 在lan字段中。 lan == zh-CN 的样本中 label == 0 和label == 1的样本数量比例超过3 我们就增加一个新特征 lan0,取值 1.
import pandas as pd
# 数据探索,找到导致1的关键特征值
def find_positive_key_features_info(train, selected_cols):
features = train.drop(['Unnamed: 0', 'label'], axis=1)
positive_key_features_info = {}
for selected in selected_cols:
positive_key_features_info[selected] = find_positive_key_values(train, selected, 3)
return positive_key_features_info
# 数据探索,找到导致1的关键特征值
def find_negtive_key_features_info(train, selected_cols):
features = train.drop(['Unnamed: 0', 'label'], axis=1)
negtive_key_features_info = {}
for selected in selected_cols:
negtive_key_features_info[selected] = find_negtive_key_values(train, selected, 3)
return negtive_key_features_info
def find_key_values(train, selected_feature, ratio):
temp0 = train[train['label'] == 0]
temp = pd.DataFrame(columns=[0,1])
temp[0] = temp0[selected_feature].value_counts() / len(temp0)
temp1 = train[train['label'] == 1]
temp[1] = temp1[selected_feature].value_counts() / len(temp1)
temp[2] = temp[1] / temp[0]
result = temp[temp[2] > ratio].sort_values(2, ascending=False).index
return result
def find_negtive_key_values(train, selected_feature, ratio):
temp0 = train[train['label'] == 0]
temp = pd.DataFrame(columns=[0,1])
temp[0] = temp0[selected_feature].value_counts() / len(temp0)
temp1 = train[train['label'] == 1]
temp[1] = temp1[selected_feature].value_counts() / len(temp1)
temp[2] = temp[0] / temp[1]
result = temp[temp[2] > ratio].sort_values(2, ascending=False).index
# print('negtive', result)
return result
def find_positive_key_values(train, selected_feature, ratio):
temp0 = train[train['label'] == 0]
temp = pd.DataFrame(columns=[0,1])
temp[0] = temp0[selected_feature].value_counts() / len(temp0)
temp1 = train[train['label'] == 1]
temp[1] = temp1[selected_feature].value_counts() / len(temp1)
temp[2] = temp[1] / temp[0]
result = temp[temp[2] > ratio].sort_values(2, ascending=False).index
# print('positive', result)
return result
def add_key_features(train, features, test1, selected_cols):
# key_features_info = find_key_features_info(train, selected_cols)
# for feature in key_features_info:
# if len(key_features_info[feature]) > 0:
# features[feature + '1'] = features[feature].apply(f, args=(feature, key_features_info))
# test1[feature + '1'] = test1[feature].apply(f, args=(feature, key_features_info))
# key_features_info = find_key_features_info(train, selected_cols)
positive_key_features_info = find_positive_key_features_info(train, selected_cols)
for feature in positive_key_features_info:
if len(positive_key_features_info[feature]) > 0:
features[feature + '1'] = features[feature].apply(f, args=(feature, positive_key_features_info))
test1[feature + '1'] = test1[feature].apply(f, args=(feature, positive_key_features_info))
negtive_key_features_info = find_negtive_key_features_info(train, selected_cols)
for feature in negtive_key_features_info:
if len(negtive_key_features_info[feature]) > 0:
features[feature + '0'] = features[feature].apply(f, args=(feature, negtive_key_features_info))
test1[feature + '0'] = test1[feature].apply(f, args=(feature, negtive_key_features_info))
return features, test1
def f(x, feature, key_features_info):
if x in key_features_info[feature]:
return 1
else:
return 0
其他倍数特征
和上一小节思路相同,我们把所有特征导致分类结果的比例占比计算出来。
process_feature_times.py 每个特征都会新增一个 'feature'_times 的特征。
该方案明显增加了特征值个数,会导致计算变慢。
import pandas as pd
import numpy as np
def process_feature_times(train, test, feature):
len_label_0 = len(train[train['label'] == 0])
len_label_1 = len(train[train['label'] == 1])
all_df = pd.concat([train, test])
series_0 = train[train['label'] == 0][feature].value_counts()
series_1 = train[train['label'] == 1][feature].value_counts()
keys = all_df[feature].value_counts().keys()
temp = pd.DataFrame(columns=['feature',0, 1])
temp['feature'] = keys
temp[0] = temp['feature'].apply(lambda x: series_0[x] if x in series_0 else 0)
temp[1] = temp['feature'].apply(lambda x: series_1[x] if x in series_1 else 0)
import math
def f(x):
if x == 0:
return 0
elif x == np.inf:
return 0
elif x > 1:
return int(x)
elif x < 1:
return -int(1/x)
else:
return 0
temp[0] = temp['feature'].map(lambda x: series_0[x] / len_label_0 if x in series_0 else 0)
temp[1] = temp['feature'].map(lambda x: series_1[x] / len_label_1 if x in series_1 else 0)
temp[2] = temp[0] / temp[1]
temp[3] = temp[2].map(lambda x: f(x))
lan_2_times = temp[["feature", 3]].set_index("feature").to_dict(orient='dict')[3]
train[feature + '_times'] = train[feature].map(lambda x: 0 if type(x) == float else lan_2_times[x])
test[feature + '_times'] = test[feature].map(lambda x: 0 if type(x) == float else lan_2_times[x])
return train, test
模型训练
五折交叉验证
采用五折交叉验证,提高准确率:
from sklearn.model_selection import KFold, StratifiedKFold
from sklearn.metrics import accuracy_score
n_splits = 5
def ensemble_model(clf, train_x, train_y, test):
sk = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=2021)
prob = []
mean_acc = 0
for k, (train_index, val_index) in enumerate(sk.split(train_x, train_y)):
print('第{}个子模型, training'.format(k+1))
train_x_real = train_x.iloc[train_index]
train_y_real = train_y.iloc[train_index]
val_x = train_x.iloc[val_index]
val_y = train_y.iloc[val_index]
clf = clf.fit(train_x_real, train_y_real)
val_y_pred = clf.predict(val_x)
acc_val = accuracy_score(val_y, val_y_pred)
print('第{}个子模型, acc{}'.format(k+1, acc_val))
mean_acc += acc_val / n_splits
test_y_pred = clf.predict_proba(test)[:, -1]
prob.append(test_y_pred)
print(mean_acc)
mean_prob = sum(prob) / n_splits
return mean_prob
开始训练
# train = train.drop(['label'], axis=1)
import time
# 12, 5000, 0.9 0.86, 0.1, 0.025 = 89 =》 88.684
# 13, 2000, 0.889304 => 88.71
# 提炼 dev 特征后 89.0242 => 89.032
# 13 提炼 倍数关系 8914.01 => 89.0453
# 17, 3000 运行非常慢。
max_depth = 13
n_estimators = 2000
subsample=0.9
colsample_bytree=0.86
reg_lambda = 0.1
learning_rate = 0.001
clf = xgb.XGBClassifier(
max_depth = max_depth,
n_estimators = n_estimators,
subsample=subsample,
colsample_bytree=colsample_bytree,
learning_rate = learning_rate,
objective = 'binary:logistic',
tree_method = 'gpu_hist',
min_child_samples = 3,
eval_metric='auc',
reg_lambda = reg_lambda
)
result = ensemble_model(clf, train, labels, test)
print(result)
网格搜索
由于超参数特别多,需要耗费的时间很长,大家可以尝试网格搜索,也许会找到一组参数,相信最后结果,一定可以比本文方案,准确率提高。
模型选取
本文使用的xgboost模型。网上看到其他选手的catboost准确率会更高。大家也可以自行尝试。
还有一些深度神经网络算法,准确率也许会更高。大家可以尝试。
生成提交的 CSV文件
a = pd.DataFrame(test_data['sid'])
a['label'] = result
a['label'] = a['label'].apply(lambda x: 0 if x < 0.5 else 1)
import datetime
timestr = datetime.datetime.now().strftime('%y-%m-%d %H %M %S')
a.to_csv('result/v9' + 'max_depth=5subsample=0.72colsample_bytree=0.72reg_lambda = 0.7' + '.csv', index=False)
感悟
有时候每改动一点代码,就需要跑一次,比较费时间。大家可以做好每次代码的版本管理,方便代码和最后结果一一对应,方便改进。
本文方案最高成绩: 89.102, 这个成绩在很多月份可以进入前三名。
比赛是长期赛,每个月一个榜单。
文中也提到了很多优化方案,希望大家多多尝试,一定可以拿到更好成绩。
有任何疑问,想探讨代码细节的,可以留言或者联系我,大家一起交流。