3Idiots-2014-Kaggle比赛源码走读
最近在研究ffm,自然要找回3Idiots在2014年kaggle比赛的解决方案。主要是研究他们的特征提取方案,写下此文记录走读大佬代码的过程,目的是方便以后查询,免得忘记。
3Idiots代码下载
git clone https://github.com/guestwalk/kaggle-2014-criteo.git
特征取值统计
utils/count.py
对训练集tr.csv
26个离散型特征(C1~C26)各自不同的取值进行了统计,包括每个值出现次数(Total)、每个值对应的label为0和1的次数(Neg和Pos),写入fc.trva.t10.txt
列名如下
Field,Value,Neg,Pos,Total,Ratio
GBDT特征准备
converters/pre-a.py
将训练集tr.csv
转换成gbdt需要的数据格式。分成两部分,数值类特征(I1~I13)被转到tr.gbdt.dense
,离散型特征(C1~C26)作者设置了26个特殊的取值,然后遍历csv的每一行,找出每一行中包含的特殊取值,把他们的序号(0~25)写到tr.gbdt.sparse
.
数值类特征(I1~I13)处理部分如下,可见仅做了null值补全而已。
...
for j in range(1, 14):
val = row['I{0}'.format(j)]
if val == '':
val = -10
feats.append('{0}'.format(val))
f_d.write(row['Label'] + ' ' + ' '.join(feats) + '\n')
...
离散型特征(C1~C26)处理如下,首先各行全部处理成field-value
,例如C9-a73ee510
这样,然后逐行找出target_cat_feats
中包含的field-value
,将他们的index写入tr.gbdt.sparse
#These features are dense enough (they appear in the dataset more than 4 million times), so we include them in GBDT
target_cat_feats = ['C9-a73ee510', 'C22-', 'C17-e5ba7672',
'C26-', 'C23-32c7478e', 'C6-7e0ccccf',
'C14-b28479f6', 'C19-21ddcdc9', 'C14-07d13a8f',
'C10-3b08e48b', 'C6-fbad5c96', 'C23-3a171ecb',
'C20-b1252a9d', 'C20-5840adea', 'C6-fe6b92e5',
'C20-a458ea53', 'C14-1adce6ef', 'C25-001f3601',
'C22-ad3062eb', 'C17-07c540c4', 'C6-',
'C23-423fab69', 'C17-d4bb7bd8', 'C2-38a947a1',
'C25-e8b83407', 'C9-7cc72ec2']
...
cat_feats = set()
for j in range(1, 27):
field = 'C{0}'.format(j)
key = field + '-' + row[field]
cat_feats.add(key)
feats = []
for j, feat in enumerate(target_cat_feats, start=1):
if feat in cat_feats:
feats.append(str(j))
f_s.write(row['Label'] + ' ' + ' '.join(feats) + '\n')
...
GBDT训练
solvers/gbdt/gbdt
接收tr.gbdt.dense
、tr.gbdt.sparse
、te.gbdt.dense
和te.gbdt.sparse
,训练好模型,然后输出叶子结点编号到te.gbdt.out
和tr.gbdt.out
,都取30棵树,最深层数是7,因此一棵树叶子编号最多是
27
2
7
。
ffm特征准备
ffm用到的特征包括三个方面:数值类特征(I1~I13)、离散型特征(C1~C26)和GBDT输出的叶子结点。
- 数值类特征 numeric features (13)
数值类特征(I1~I13),大于2的数值进行转化:
v←⌊log(v)2⌋ v ← ⌊ log ( v ) 2 ⌋
common.py/gen_feats
函数对数值型特征(I1~I13)的处理:
for j in range(1, 14):
field = 'I{0}'.format(j)
value = row[field]
if value != '':
value = int(value)
if value > 2:
value = int(math.log(float(value))**2)
else:
value = 'SP'+str(value)
key = field + '-' + str(value)
feats.append(key)
- 离散型特征 categorical features (26)
首先read_freqent_feats
函数读取fc.trva.t10.txt
存储出现次数大于10次的离散型特征取值(以C3-xxxx
这样的形式)
离散型特征(C1~C26),出现次数少于10次的,编码成同一个特殊值{field}less
,否则保持原值。
converters/pre-b.py
对离散值特征的处理:
frequent_feats = read_freqent_feats(args['threshold']) # 10
...
for feat in gen_feats(row):
field = feat.split('-')[0]
type, field = field[0], int(field[1:])
# 离散特征,出现次数少于10次的
if type == 'C' and feat not in frequent_feats:
feat = feat.split('-')[0]+'less' # 可见将编码成同一值
if type == 'C':
field += 13
feats.append((field, feat))
- GBDT特征 (30)
直接使用.
for i, feat in enumerate(line_gbdt.strip().split()[1:], start=1):
field = i + 39
feats.append((field, str(i)+":"+feat)) # e.g. 40:127
最终ffm格式的value总是取1,feature采用hash并取模的方式生成。
converters/pre-b.py
中gen_hashed_fm_feats
的处理方式:
def gen_hashed_fm_feats(feats, nr_bins):
# 可见value总是1,即使特征中有连续值
feats = ['{0}:{1}:1'.format(field-1, hashstr(feat, nr_bins)) for (field, feat) in feats]
return feats
参考资料
github代码 https://github.com/guestwalk/kaggle-2014-criteo/
3 Idiots’ solution PDF
libffm PPT
深入FFM原理与实践