DeepFM(2) 数据集处理
一、数据集
Kaggle的一个竞赛数据集:
Preprocess Criteo dataset. This dataset was used for the Display Advertising
Challenge (https://www.kaggle.com/c/criteo-display-ad-challenge).
二、数据集描述
第1列就是标签(点击与否用0/1表示,1为点击,0为没点击)。后面的2-40列数据(共39列)是我们的特征变量(指标),也就是原文paper里面提到的“n-field”的概念,这里的n是39。
然后数据内又分了前13列是连续型变量,后26列是类别型变量。
三、训练集生成
1、连续变量和类别变量特征:[1,13],[14,39]。
continous_features = range(1, 14) # [1,...13]
categorial_features = range(14, 40) # [14,...39]
将39列的每一列做为独立的输入之后再合并,所以根据每一列建立索引字典。建立好之后根据索引字典将原始数据(上面的数据视图呈现那样)映射到数字空间,即每个值都代表着索引字典里面的索引,可以根据索引找到原来的值。
2、feature_sizes:每个特征的长度,用于构建索引。
首先要知道每个类别特征的特征个数。连续特征的特征个数为1。因为在特征索引中,不同的连续值对应一个特征位,类别特征每种情况对应一个特征位。
1、类别特征维度统计
categorial_features = [14,...39]
num_feature = len(categorial_features)
dicts = []
for i in range(0, num_feature):
dicts.append(collections.defaultdict(int)) # defaultdict(int) 如果key不存在的时候,则赋值为int类型,值0。
with open('train.txt', 'r') as f: # 读取文件,遍历行,针对所有类别特征,如果有值。则以该值为key的字典的value加1。
for line in f:
features = line.rstrip('\n').split('\t')
for i in range(0, num_feature):
if features[categorial_features[i]] != '':
dicts[i][features[categorial_features[i]]] += 1
得到每个类别特征的个数统计之后,进行后续处理。dicts[i]
表示第i个类别特征的统计字典。
for i in range(0, num_feature):
dicts[i] = filter(lambda x: x[1] >= 2,
dicts[i].items()) # 过滤掉只出现过一次的类别特征。
dicts[i] = sorted(dicts[i], key=lambda x: (-x[1], x[0])) ## 第二个元素的负数, 如果第二个元素相等,则基于第1个元素。皆为正序
vocabs, _ = list(zip(*dicts[i])) # vocabs:tuple. 第i种类别特征的所有情况组成的元组。
dicts[i] = dict(zip(vocabs, range(1, len(vocabs) + 1))) # 得到 {特征:索引}的字典。索引从1开始。
dicts[i]['<unk>'] = 0 # 赋值未知特征为0,即出现次数少于2次的当做0处理。
dict_size = [len(self.dicts[idx]) for idx in range(0, self.num_feature)] # 26个类别特征,每个特征包含多少个不同的类别。
sizes = [1] * len(continous_features) + dict_sizes
sizes = [str(i) for i in sizes] # sizes 保存了所有特征的维度。
feature_sizes.write(','.join(sizes)) # 保存为feature_sizes.txt。
四、构建feature_index 和 feature_value
前面得到了特征长度,可以构建最后的特征索引和特征值。根据每一列建立索引字典。
with open('train.txt', 'r') as f:
for line in f:
features = line.rstrip('\n').split('\t') # 同样是首先得到原始数据的特征列表。
continous_vals = [] # 连续特征转换之后的值
for i in range(0, len(continous_features)):
val = features[continous_features[i]] # 第i个连续特征的原始值
if val == '': # 原始数据是有存在一些缺少值的,对缺失值采取的手段是填0处理
value= 0.0
else:
val = float(val)
continous_vals.append("{0:.6f}".format(val).rstrip('0')
.rstrip('.'))
categorial_vals = [] # 类别特征转换之后的值,其值为索引。
for i in range(0, len(categorial_features)):
key = features[categorial_features[i]] # 第i个类别特征的原始值
if key not in dicts[i]:
res = dicts[i]['<unk>']
else:
res = dicts[i][key]
categorial_vals.append(str(res)) # 类别特征转换后的值为其每个类别中的索引。
continous_vals = ','.join(continous_vals)
categorial_vals = ','.join(categorial_vals)
label = features[0]
out_train.write(','.join([continous_vals, categorial_vals, label]) + '\n') # 保存起来。
总结
最终得到了两个文件
1、feature_sizes.txt
每个特征的维度:连续特征都为1,类别特征为其类别数,相当于每个field的特征数,用于初始化deepfm模型。
1,1,1,1,1,1,1,1,1,1,1,1,1,10,22,7,6,7,7,3,9,3,5,6,7,7,8,5,7,10,14,4,4,7,3,9,8,11,5
2、train.txt
每行长度为\(13+26+1=40\),13个连续特征为其原始值,26个类别特征,为其类别索引。最后一位为label。
1,1,5,0,1382,4,15,2,181,1,2,0,2,2,3,0,0,1,1,0,3,1,0,0,0,0,3,0,0,1,5,1,3,0,0,2,0,1,0,0
2,0,44,1,102,8,2,2,4,1,1,0,4,2,14,0,0,1,3,0,1,1,0,0,0,0,1,0,0,2,0,1,2,0,0,2,0,1,0,1