由于上传了很多代码运行结果截图,所以。。。首先。。。多图预警!!!
本文主要侧重于代码实现,关于DeepFM模型结构理论可以查看这篇:
训练集:
测试集
在做one-hot encoding 之前,需要先将训练集和测试集合并:
代码:
df = pd.concat([dfTrain, dfTest], axis=0)
代码运行结果:
然后进行one-hot encoding,离散特征和连续特征分开编码处理,其中每个离散特征中每一类作为一维特征,而每个连续特征则作为一维特征:
for col in df.columns: # 连续特征只有一个编号 if col in NUMERIC_FEATURES: feat_dict[col] = total_cnt total_cnt += 1 continue
# 离散特征,有多少个取值就有多少个编号 unique_vals = df[col].unique() #[2,3,1,0] unique_cnt = df[col].nunique() #4 feat_dict[col] = dict(zip(unique_vals, range(total_cnt, total_cnt + unique_cnt))) total_cnt += unique_cnt
feat_size = total_cnt
特征维度总共为9维,其中离散特征featcat_1 :3维,featcat_2 :4维,并对其中每一类对应编码为0-6维特征;连续特征feat_num_1和feat_num_2分别对应编码为7、8维特征。
一般用于CTR预估的数据中,存在著大量的类别特征,而类别特征经过one-hot encoding 则会急剧增加特征维度,并且特别的稀疏。这样的特征直接用于训练模型的话,会导致训练模型的参数特别多,从而影响模型训练效率。DeepFM 论文中采用embedding 操作,将稀疏的特征转化为稠密特征。
在TensorFlow中,有一个非常好用的embedding操作API:tf.nn.embedding_lookup(weights, ids),只需要输入初始权重weights和特征对应的ids,根据特征对应的索引值,获得各个特征对应的权重。再与特征值做位乘,即可得到embedding结果。
因此,首先要先获得输入特征对应的索引值以及对离散特征全部赋值为1的全新特征值:
for col in dfi.columns:
if col in NUMERIC_FEATURES: # 连续特征1个维度,对应1个编号,这个编号是一个定值 dfi[col] = feat_dict[col] else: # 离散特征。不同取值对应不同的特征维度,编号也是不同的。 dfi[col] = dfi[col].map(feat_dict[col]) dfv[col] = 1.0
代码结果:
# Sparse Features -> Dense Embedding embeddings_origin = tf.nn.embedding_lookup(weights[feature_embedding], ids=feat_index) # [None, field_size, embedding_size]
embeddings = tf.multiply(embeddings_origin, feat_value_reshape) # [None, field_size, embedding_size] 注意:multiply不是矩阵相乘,而是矩阵对应位置相乘。这里应用了broadcast机制。
embeddings 的维度为5??4??8
FM 的作用就是用来特征组合,其公式如下:
从公式中可以看出,FM 包含两部分,一个是前面的 ,另一个则是 ,论文中将第一部分称为Addition 单元,第二部分称为内积单元。Addition 单元反映的是一阶特征,内积单元反映的是二阶组合特征。
一阶特征就是将输入的特征值与权重相乘得到。
# --------- 一维特征 ----------- y_first_order = tf.nn.embedding_lookup(weights[feature_bias], ids=feat_index) # [None, field_size, 1] w_mul_x = tf.multiply(y_first_order, feat_value_reshape) # [None, field_size, 1] Wi * Xi 维度结果如何计算得到???是对应点乘->位乘 y_first_order = tf.reduce_sum(input_tensor=w_mul_x, axis=2) # [None, field_size]缩减维度,得到FM_1维的输出
对于计算二阶组合特征,上述FM内积单元计算过于复杂,可以通过简化得到:
# --------- 二维组合特征 ---------- embeddings = tf.multiply(embeddings_origin, feat_value_reshape) # [None, field_size, embedding_size] 注意:multiply不是矩阵相乘,而是矩阵对应位置相乘。这里应用了broadcast机制。
# sum_square part 先sum,再square summed_features_emb = tf.reduce_sum(input_tensor=embeddings, axis=1) # [None, embedding_size] summed_features_emb_square = tf.square(summed_features_emb) # [None, embedding_size]
# square_sum part squared_features_emb = tf.square(embeddings)# [None, field_size, embedding_size] squared_features_emb_summed = tf.reduce_sum(input_tensor=squared_features_emb, axis=1) # [None, embedding_size]
# second order FM_2维组合特征输出结果,维度为embedding_size y_second_order = 0.5 * tf.subtract(summed_features_emb_square, squared_features_emb_summed) # [None, embedding_size]#对应FM 论文的化简公式
Deep 单元主要用于对特征进行高阶特征组合,论文中Deep 单元为普通的全连接网路,网路输入层为embedding之后的结果,其结构如下图所示:
# ----------- Deep Component ------------ y_deep = tf.reshape(embeddings_origin, shape=[-1, config.field_size * config.embedding_size]) # [None, field_size * embedding_size] for i in range(0, len(deep_layers)): y_deep = tf.add(tf.matmul(y_deep, weights[layer_%d % i]), weights[bias_%d % i]) y_deep = config.deep_layers_activation(y_deep)#激励层
在分别得到FM单元和Deep单元的输出后,将两部分的输出concat合并之后,经一层全连接层,再经sigmoid激励函数,便得到最终预测的概率值。
# ----------- output ----------- concat_input = tf.concat([y_first_order, y_second_order, y_deep], axis=1) out = tf.add(tf.matmul(concat_input, weights[concat_projection]), weights[concat_bias]) out = tf.nn.sigmoid(out)
以上就是DeepFM代码实现的简单结果…
欢迎大家一起讨论鸭~