推荐系统 - DeepFM架构详解

说明

理论部分请参照:推荐系统遇上深度学习(三)--DeepFM模型理论和实践 - 简书,这里主要针对源码进行解读,填一些坑。

简单介绍

1.DeepFM可以看做是从FM基础上衍生的算法,将Deep与FM相结合,用FM做特征间低阶组合,用Deep NN部分做特征间高阶组合,通过并行的方式组合两种方法,使得最终的架构具有以下特点。

   (1) 不需要预训练 FM 得到隐向量;
   (2) 不需要人工特征工程;
   (3)能同时学习低阶和高阶的组合特征;
   (4)FM 模块和 Deep 模块共享 Feature Embedding 部分,可以更快的训练,以及更精确的训练学习。

2.整体的架构体系如下,左侧为FM的结构层,右侧为Deep部分的结构层,两者公用相同的特征输入,其实我刚看到下面的图也有点懵逼,两部分分离的架构都好理解,但是那些红线和黑线都表示什么?为什么有Field的标识,从输入层网上传播时候为什么有embedding层,而且还分成不同的块?  这都是我刚看到如下图产生的疑问,经过阅读源码后,才有了较为清晰的理解,接下来进行一一的解答。

                              

架构说明

 1.咱们从下往上开始看吧,首先是最底下这一部分,可以看到field这个名词,这是因为在处理特征时候,我们需要对离散型数据进行one-hot转化,经过one-hot之后一列会变成多列,这样会导致特征矩阵变得非常稀疏。

                           

为了应对这一问题,我们把进行one-hot之前的每个特征都看作一个field,这样做的原因是为了在进行矩阵存储时候,我们可以把对一个大稀疏矩阵的存储,转换成对两个小矩阵 和一个字典的存储。  (这个是deepFM最难理解的地方)

          *小字典的形式如下,为离散型特征每个特征值都添加索引标识,对连续数据只给予一个特征索引,这样的作用用于我们不必存储离散特征的onehot矩阵,而只需要存储 离散特征值对应在 字典中的 编号就行了,用编号作为特征标识。

         {'missing_feat': 0, 'ps_car_01_cat': {10: 1, 11: 2, 7: 3, 6: 4, 9: 5, 5: 6, 4: 7, 8: 8, 3: 9, 0: 10, 2: 11, 1: 12, -1: 13}, 'ps_car_02_cat': {1: 14, 0: 15}

         *第一个小矩阵是特征索引矩阵,长度是 样本长度,每个列表示样本特征值所对应 在字典中的索引值,如下是对其中两样本特征索引的打印,可以发现这样的区别在于 离散特征的位置,举个例子,180可能表示‘性别为男’、181表示‘性别为女’的含义,如果是以往的方式,下面每行的特征索引值都是一致的(但是对性别one-hot这个要用两列表示),但是这里主要是考虑针对one-hot带来的稀疏问题,使用如下一行代替多行的形式(可以用labelencoder进行理解、只不过这里用索引是因为后面要用所以做权值抽取),只保存离散特征所代表的值对应的索引。

[180, 186, 200, 202, 205, 213, 215, 217, 219, 221, 223, 225, 227, 229, 235, 248, 250, 251, 253, 254, 255, 3, 14, 16, 19, 30, 40, 50, 54, 56, 61, 147, 66, 172, 173, 175, 176, 0, 174]
[181, 185, 190, 202, 205, 213, 216, 217, 220, 221, 223, 225, 227, 229, 242, 248, 250, 251, 253, 254, 255, 7, 14, 16, 19, 31, 33, 50, 54, 55, 61, 79, 66, 172, 173, 175, 176, 0, 174]

        *第二个小矩阵是特征值矩阵,这里的特征值是和上面的特征索引矩阵相对应的,存储对应索引位置含义的特征值,以下是两个样本的特征值的例子,可以发现在离散特征值都是为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, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 0.3, 0.6103277807999999, 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.6695564092, 0.3521363372, 3.4641016150999997, 2.0, 0.4086488773474527],
[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, 1.0, 1.0, 1.0, 1.0, 1.0, 0.9, 0.5, 0.7713624309999999, 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 * K
feat_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 * k
self.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 order
self.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

  • 42
    点赞
  • 209
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
Odoo 是一个开源的企业资源计划(ERP)系统,它提供了一套完整的商业应用程序,包括销售、采购、库存管理、生产管理、财务管理、人力资源管理等。下面是 Odoo 的系统架构详解: 1. 前端:Odoo 使用了基于 Web 技术的前端框架,提供了直观、用户友好的界面。前端部分主要负责与用户交互,并将用户输入的数据发送给后端进行处理。 2. Web 服务器:Odoo 支持多种 Web 服务器,如 Nginx、Apache 等。Web 服务器主要负责接收用户请求,并将请求转发给 Odoo 服务器进行处理。 3. Odoo 服务器:Odoo 服务器是整个系统的核心组件,它负责处理用户请求,并根据请求的类型进行相应的操作。Odoo 服务器采用了模块化的架构,每个功能模块都可以独立安装、升级和卸载。 4. 数据库:Odoo 使用关系型数据库来存储数据,常用的数据库包括 PostgreSQL、MySQL 等。所有的数据都存储在数据库中,包括用户信息、产品信息、订单信息等。 5. 模块:Odoo 的功能被组织成多个模块,每个模块负责一个特定的功能领域。例如,销售模块负责管理销售流程,采购模块负责管理采购流程等。用户可以根据自己的需求选择安装相应的模块。 6. 业务逻辑:Odoo 的每个模块都包含了一套完整的业务逻辑。例如,在销售模块中,用户可以创建销售订单、确认订单、生成发票等。这些业务逻辑被封装在模块中,并通过 Odoo 服务器进行处理。 7. API:Odoo 提供了一组丰富的 API,使开发人员能够通过编程的方式来与系统进行交互。开发人员可以使用 API 创建新的模块、扩展现有模块的功能,或者与其他系统进行集成。 总结来说,Odoo 的系统架构包括前端、Web 服务器、Odoo 服务器、数据库、模块、业务逻辑和 API。它提供了一个灵活、可扩展的平台,满足企业各种不同的业务需求。
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值