理论部分请参考以下内容。 推荐系统遇到了深度学习(三)–深度调频模型理论与实践)简单的书。 这里主要是解读源代码,填补一些漏洞。

1.DeepFM可以简单地看作是从FM派生的算法。 将Deep与FM相组合,以FM为特征间的高阶组合,以Deep NN部分为特征间的高阶组合,并行组合两种方法,使最终体系结构具有以下特点:

(1)不需要对FM进行预训练得到隐藏矢量;(2)不需要人工特征步骤;(3)可以同时学习低阶和高阶组合的特征; )4)调频模块和深度模块共享Feature Embedding部分,可以实现更快的训练和更准确的训练学习。 2 .总体体系结构如下。 左侧为调频的结构层,右侧为深度部分的结构层。 两者共用同一特征输入。 其实我刚看了下图也有点无知。 两部分分离的体系结构很容易理解,那些红线和黑线表示什么? 为什么会有场标识,从输入层网络传播为什么会有嵌入式层,而且分成不同的区块? 这是我刚看到下面这张照片的疑问,看了源代码后,才比较清楚地理解,然后再一一解答。

体系结构说明1 .让我们往下看。 首先在最下面的部分,可以看到field这个名词。 这是因为在处理特征时,需要对离散型数据进行one-hot变换,经过one-hot后一列变成多列,特征矩阵变得非常稀疏。

为了处理这个问题,我们将进行one-hot之前的每个特征看作一个field。 这是因为在进行矩阵保存时,可以将对一个大稀疏矩阵的保存转换为对两个小矩阵和一个词典的保存。 (这是deepFM最难理解的地方)

*小词典的形状如下。 为离散特征的每个特征值添加一个索引标记,并对连续数据只赋予一个特征索引。 这样的作用是不保存离散特征的onehot矩阵,而只需要保存与字典中注册号码对应的离散特征值即可,使用号码作为特征标记。

{‘missing_feat’: 0,’ PS _ car _ 01 _ cat ‘ : { 1033601、11:73360、6:4、93360、53:6 } 23333:

*第一个子矩阵是特征索引矩阵,长度是样本的长度,各列表示与样本的特征量对应的词典中的索引值。 以下打印了其中两个样本的特征索引,可以看出这种差异在于离散的特征位置。 举个例子,180可能表示“性别是男”,181表示“性别是女”的意思。 在现有方式中,以下各行的特征索引值一致(其中,性别one-hot用2列表示),但这里主要考虑基于one-hot的稀疏性,使用下一行来代替多行

[180、186、200、202、205、213、215、217、219、221、223、225、225、248、250、251、253、254和255 ] 6172 225、227、229, 229] 19第19、31、33、50、54、55、61、79、66、172、173、175、176、0、174]*2第二子矩阵是特征量矩阵,这里特征量被分配给上述特征量索引矩阵下面是两个样本的特征的特征量,因为我们仅记录了对应于样本的离散特征的索引,所以必须考虑是1并且不为零,这是理解当前矩阵分解表示的关键。

[1.0、1.0、1.0、1.0、1.0、1.0、1.0、1.0、1.0、1.0、1.0、1.0、1.0、0.5、0.610 3.4641016150999997、2.0、0.408648773474527 1.0、1.0、1.0、1.0、1.0、1.0、0.9、0.5、0.7713624

309999999, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.316227766, 0.6063200202000001, 0.3583294573, 2.8284271247, 1.0, 0.4676924847454411]

     经过以上过程,我们可以将原样本都转换成上面的 两个小矩阵,并保留了特征索引字典,这样相对于对原始特征做了one-hot的转换,只不过用了一种特征抗稀疏化的方式,原来特征的长度是field_size个,现在one-hot后的特征长度也是field_size个,只不过可以假定为 feature_size个(应该的one-hot过的长度),因为我们之后会对每个离散特征值 跟 连续特征一起做处理。

      对应看图中,每个连续型特征都可以对应为一个点,而每个离散特征(成为一个field),因为要做one-hot话,则每个field根据 不同值的数量n可以对应n个点。

2.接下来,从架构上进行观察,其实主要经过了以下步骤:

       (1)对 feature的embeding转换,包括两部分的embedding, 其中可以看到1是针对不同特征做的embedding【FM的二阶两两交互计算部分和 deep部分是共享这个embedding结果的】,2是FM的一阶计算部分【使用权重weights[‘feature_bias’] 、直接对原始特征做的一阶计算】

                      

       3是对应FM的二阶计算阶段,对经过weights[‘feature_embeddings’]权重embedding的结果做二阶交叉计算,4是deep部分的全连接计算部分,使用神经网络权重weights[“layer_n]和 weights[“bias_n]进行计算。

       (2)FM计算的过程,其中左部分FM阶段对应的公式如下,可以看到2部分对应着左侧一阶的公式,3部分对应右侧二阶的公式。

                                  

      (3)深度部分可以对应序号4,架构图是下面的形式。

   3.具体计算时,主要涉及四个权重(embedding_size是进行embeding转化时指定的大小)

            * weights[‘feature_embeddings’] :维度是[self.feature_size,self.embedding_size]。这里表示存储对 每个feature的embedng表示,这里embedding转换就相当于是一个全连接层、feature_size是总的特征数量(可以看出离散特征的每个离散值都有单独的embedding表示的)。

            * weights[‘feature_bias’]:维度是 [self.feature_size,1]。这里存储对特征的一维交叉计算,对应FM公式中的一维计算部分,对每个特征都会附加长度为1的权重。

            * weights[‘layer_0’]:维度是(上一层神经元树量,当前层神经元数量),对应deep部分神经元的权重部分

            * weights[‘bias_0’]:维度是(1,当前层神经元数量),对应深度计算的偏置部分。

核心的计算过程

          (1)首先进行 样本embedding结果的获取,针对每个样本,会根据其具有的特征索引列表feat_index,获取这些特征对应在weights[‘feature_embeddings’] 矩阵中所存储的embeding表达形式。  之后我们样本原值, 利用embeding表达形式【维度 是 field长度 * embeding长度】 * 样本原值【维度是field长度】 得到当前样本的 embedding转换结果 【维度是 field长度 * embeding长度】,这里样本的特征长度都是field长度,权重矩阵中的特征长度则是feature_size,这点要明白,有点小弯。

self.embeddings =tf.nn.embedding_lookup(self.weights[‘feature_embeddings’],self.feat_index) # N * F * Kfeat_value = tf.reshape(self.feat_value,shape=[-1,self.field_size,1])self.embeddings = tf.multiply(self.embeddings,feat_value)

        (2)进行FM部分的计算,FM部分可以分为一阶计算 和 二阶计算两部分。首先是一阶计算阶段, 其直接使用W *x计算结果即可,没做embeding.

          

self.y_first_order = tf.nn.embedding_lookup(self.weights[‘feature_bias’],self.feat_index)self.y_first_order = tf.reduce_sum(tf.multiply(self.y_first_order,feat_value),2)self.y_first_order = tf.nn.dropout(self.y_first_order,self.dropout_keep_fm[0])

        在二阶计算部分,对应如下的对FM转化的公式(优化后公式简单相当多),这里的u其实就是weights[‘feature_embeddings’]隐向量矩阵,u*x已经在第一部分做embedding时候做过了,剩余的就是 对两两内积结果的组内相加等操作

                                     

       对应代码如下,每个样本都是对应下面的过程,两部分的相减。

# second order term 这整体区间代表FM公式中的二次项计算# sum-square-part 在公式中 相减的前一部分。 先加后平方 这里的1表示维度内相加,对应公式中的,对所有的u*x的结果相加self.summed_features_emb = tf.reduce_sum(self.embeddings,1) # None * kself.summed_features_emb_square = tf.square(self.summed_features_emb) # None * K# squre-sum-part 在公式中 相减的后一部分。 先平方后加self.squared_features_emb = tf.square(self.embeddings)self.squared_sum_features_emb = tf.reduce_sum(self.squared_features_emb, 1) # None * K#second orderself.y_second_order =0.5*tf.subtract(self.summed_features_emb_square,self.squared_sum_features_emb)self.y_second_order = tf.nn.dropout(self.y_second_order,self.dropout_keep_fm[1])

      (3)最后是深度deep计算,这部分就是典型的DNN的方式,从embedidng结果开始【尺寸是field_size * embedding_size】,定义几层全连接计算即可。

# Deep component 将Embedding part的输出再经过几层全链接层self.y_deep = tf.reshape(self.embeddings,shape=[-1,self.field_size * self.embedding_size])self.y_deep = tf.nn.dropout(self.y_deep,self.dropout_keep_deep[0])for i in range(0,len(self.deep_layers)): self.y_deep = tf.add(tf.matmul(self.y_deep,self.weights[“layer_%d” %i]), self.weights[“bias_%d”%i]) self.y_deep = self.deep_layers_activation(self.y_deep) self.y_deep = tf.nn.dropout(self.y_deep,self.dropout_keep_deep[i+1])

至此已经完成了核心的计算过程。

5.以上是 主要的模型架构部分,整体的算法过程是这样的:

         (1)数据的加载,训练集和测试集的分装。

         (2)配置离散特征集、连续特征集,构建特征字典,构建每个样本的特征索引矩阵 和特征值矩阵, 完成对原特征矩阵 one-hot形式的转换。

         (3)搭建网络架构,初始化网络参数,对网络进行批次训练,最后进行效果验证。

源码

该链接是我自己添加注释后的代码,地址:https://github.com/isthegoal/recommendation_model_master/tree/master/Basic-DeepFM-model