码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 一文看懂推荐系统:排序15:DeepFM模型(Factorization-Machine),xDeepFM可不是对DeepFM的改编哦,而是对DCN的改编


    一文看懂推荐系统:排序15:DeepFM模型(Factorization-Machine),xDeepFM可不是对DeepFM的改编哦,而是对DCN的改编

    提示:最近系统性地学习推荐系统的课程。我们以小红书的场景为例,讲工业界的推荐系统。
    我只讲工业界实际有用的技术。说实话,工业界的技术远远领先学术界,在公开渠道看到的书、论文跟工业界的实践有很大的gap,
    看书学不到推荐系统的关键技术。
    看书学不到推荐系统的关键技术。
    看书学不到推荐系统的关键技术。

    王树森娓娓道来**《小红书的推荐系统》**
    GitHub资料连接:http://wangshusen.github.io/
    B站视频合集:https://space.bilibili.com/1369507485/channel/seriesdetail?sid=2249610

    基础知识:
    【1】一文看懂推荐系统:概要01:推荐系统的基本概念
    【2】一文看懂推荐系统:概要02:推荐系统的链路,从召回粗排,到精排,到重排,最终推荐展示给用户
    【3】一文看懂推荐系统:召回01:基于物品的协同过滤(ItemCF),item-based Collaboration Filter的核心思想与推荐过程
    【4】一文看懂推荐系统:召回02:Swing 模型,和itemCF很相似,区别在于计算相似度的方法不一样
    【5】一文看懂推荐系统:召回03:基于用户的协同过滤(UserCF),要计算用户之间的相似度
    【6】一文看懂推荐系统:召回04:离散特征处理,one-hot编码和embedding特征嵌入
    【7】一文看懂推荐系统:召回05:矩阵补充、最近邻查找,工业界基本不用了,但是有助于理解双塔模型
    【8】一文看懂推荐系统:召回06:双塔模型——模型结构、训练方法,召回模型是后期融合特征,排序模型是前期融合特征
    【9】一文看懂推荐系统:召回07:双塔模型——正负样本的选择,召回的目的是区分感兴趣和不感兴趣的,精排是区分感兴趣和非常感兴趣的
    【10】一文看懂推荐系统:召回08:双塔模型——线上服务需要离线存物品向量、模型更新分为全量更新和增量更新
    【11】一文看懂推荐系统:召回09:地理位置召回、作者召回、缓存召回
    【12】一文看懂推荐系统:排序01:多目标模型
    【13】一文看懂推荐系统:排序02:Multi-gate Mixture-of-Experts (MMoE)
    【14】一文看懂推荐系统:排序03:预估分数融合
    【15】一文看懂推荐系统:排序04:视频播放建模
    【16】一文看懂推荐系统:排序05:排序模型的特征
    【17】一文看懂推荐系统:排序06:粗排三塔模型,性能介于双塔模型和精排模型之间
    【18】一文看懂推荐系统:特征交叉01:Factorized Machine (FM) 因式分解机
    【19】一文看懂推荐系统:物品冷启01:优化目标 & 评价指标
    【20】一文看懂推荐系统:物品冷启02:简单的召回通道
    【21】一文看懂推荐系统:物品冷启03:聚类召回
    【22】一文看懂推荐系统:物品冷启04:Look-Alike 召回,Look-Alike人群扩散
    【23】一文看懂推荐系统:物品冷启05:流量调控
    【24】一文看懂推荐系统:物品冷启06:冷启的AB测试
    【25】推荐系统最经典的 排序模型 有哪些?你了解多少?
    【26】一文看懂推荐系统:排序07:GBDT+LR模型
    【27】一文看懂推荐系统:排序08:Factorization Machines(FM)因子分解机,一个特殊的案例就是MF,矩阵分解为uv的乘积
    【28】一文看懂推荐系统:排序09:Field-aware Factorization Machines(FFM),从FM改进来的,效果不咋地
    【29】一文看懂推荐系统:排序10:wide&deep模型,wide就是LR负责记忆,deep负责高阶特征交叉而泛化
    【30】一文看懂推荐系统:排序11:Deep & Cross Network(DCN)
    【31】一文看懂推荐系统:排序12:xDeepFM模型,并不是对DeepFM的改进,而是对DCN的改进哦
    【32】一文看懂推荐系统:排序13:FNN模型(FM+MLP=FNN),与PNN同属上海交大张楠的作品

    【33】一文看懂推荐系统:排序14:PNN模型(Product-based Neural Networks),和FNN一个作者,干掉FM,加上LR+Product


    提示:文章目录

    文章目录

    • 一文看懂推荐系统:排序15:DeepFM模型(Factorization-Machine),xDeepFM可不是对DeepFM的改编哦,而是对DCN的改编
    • 前言
    • DeepFM的模型结构细节
    • 1.1 FM
    • 二、DeepFM的代码实现
      • 2.1 数据集
      • 2.2 FM部分实现
      • 2.3 DNN部分实现
      • 2.4 FM和DNN部分结合
    • 三、总结
    • 总结


    前言

    DeepFM是哈工大和华为合作发表在IJCAI2017上的文章,
    这篇文章也是受到谷歌wide&deep模型的启发,

    是一个左右组合(混合)模型结构,【wide&deep如下】
    在这里插入图片描述

    不同的是,deepFM在wide部分用了FM模型来代替LR模型。
    所以你已经非常明白了吧

    因此,强烈建议在看这篇文章之前,先移步看完我之前写的关于wide&deep的博客

    我们来看看DeepFM相比较wide&deep模型的改进点及优势(前提是你已经很了解wide&deep模型了):

    在wide部分使用FM代替了wide&deep中的LR,

    有了FM自动构造学习二阶(考虑到时间复杂度原因,通常都是二阶)交叉特征的能力,因此不再需要特征工程。

    Wide&Deep模型中LR部分依然需要人工的特征交叉,
    比如【用户已安装的app】与【给用户曝光的app】两个特征做交叉。

    另外,仅仅通过人工的手动交叉,又回到了之前在讲FM模型中提到的,
    比如要两个特征共现,否则无法训练。

    在DeepFM模型中,FM模型与DNN模型共享底层embedding向量,然后联合训练。

    这种方式也更符合现在推荐/广告领域里多任务模型多塔共享底座embedding的方式,
    然后end-to-end训练得到的embedding向量也更加准确。

    其实如果你很熟悉wide&deep模型,再经过上面的介绍,
    你基本已经知道DeepFM的大体网络结构了。

    接下来,本文将从两个方面介绍deepFM:

    DeepFM的模型结构细节
    DeepFM的代码实现
    总结


    DeepFM的模型结构细节

    来看下DeepFM的模型结构(图片来自王喆《深度学习推荐系统》,
    ps:原论文的图不清晰,所以没有直接从原论文取图)
    你看原图
    在这里插入图片描述
    中文新图
    在这里插入图片描述
    整体模型结构也比较简单,自底向上看分别为:

    原始输入层:onehot编码的稀疏输入

    embedding层:FM和DNN共享的底座

    FM与DNN

    输出层

    1.1 FM

    重点说下FM层,先来回顾下FM的公式:我们说了多次了,关键在特征交互部分那一项
    在这里插入图片描述
    在这里插入图片描述
    DNN部分:没什么好讲的,多层全连接网络。

    最终的输出:
    在这里插入图片描述

    二、DeepFM的代码实现

    这部分是本博客的重点,我这里直接用paddle官方的代码讲解下,
    具体代码参见:搞清楚代码细节,有助于我们对DeepFM模型更深入的了解。

    2.1 数据集

    这里用的是Criteo数据集,用于广告CTR预估的数据集,
    关于数据集的介绍参见:Criteo。
    特征方面,这个数据集共26个离散特征,13个连续值特征。

    2.2 FM部分实现

    我给代码增加了详细的注释(主要是矩阵维度的注释),大家看代码即可。

    class FM(nn.Layer):
        def __init__(self, sparse_feature_number, sparse_feature_dim,
                     dense_feature_dim, sparse_num_field):
            super(FM, self).__init__()
            self.sparse_feature_number = sparse_feature_number  # 1000001
            self.sparse_feature_dim = sparse_feature_dim   # 9
            self.dense_feature_dim = dense_feature_dim  # 13
            self.dense_emb_dim = self.sparse_feature_dim  # 9
            self.sparse_num_field = sparse_num_field   # 26
            self.init_value_ = 0.1
            use_sparse = True
            # sparse coding
            # Embedding(1000001, 1, padding_idx=0, sparse=True)
            self.embedding_one = paddle.nn.Embedding(
                sparse_feature_number,
                1,
                padding_idx=0,
                sparse=use_sparse,
                weight_attr=paddle.ParamAttr(
                    initializer=paddle.nn.initializer.TruncatedNormal(
                        mean=0.0,
                        std=self.init_value_ /
                        math.sqrt(float(self.sparse_feature_dim)))))
            # Embedding(1000001, 9, padding_idx=0, sparse=True)
            self.embedding = paddle.nn.Embedding(
                self.sparse_feature_number,
                self.sparse_feature_dim,
                sparse=use_sparse,
                padding_idx=0,
                weight_attr=paddle.ParamAttr(
                    initializer=paddle.nn.initializer.TruncatedNormal(
                        mean=0.0,
                        std=self.init_value_ /
                        math.sqrt(float(self.sparse_feature_dim)))))
    
            # dense coding
            """
            Tensor(shape=[13], dtype=float32, place=CPUPlace, stop_gradient=False,
            [-0.00486396,  0.02755001, -0.01340683,  0.05218775,  0.00938804,  0.01068084,  0.00679830,  
            0.04791596, -0.04357519,  0.06603041, -0.02062148, -0.02801327, -0.04119579]))
            """
            self.dense_w_one = paddle.create_parameter(
                shape=[self.dense_feature_dim],
                dtype='float32',
                default_initializer=paddle.nn.initializer.TruncatedNormal(
                    mean=0.0,
                    std=self.init_value_ /
                    math.sqrt(float(self.sparse_feature_dim))))
    
            # Tensor(shape=[1, 13, 9])
            self.dense_w = paddle.create_parameter(
                shape=[1, self.dense_feature_dim, self.dense_emb_dim],
                dtype='float32',
                default_initializer=paddle.nn.initializer.TruncatedNormal(
                    mean=0.0,
                    std=self.init_value_ /
                    math.sqrt(float(self.sparse_feature_dim))))
        
        
        def forward(self, sparse_inputs, dense_inputs):
            # -------------------- first order term  --------------------
            """
            sparse_inputs: list, length:26, list[tensor], each tensor shape: [2, 1]
            dense_inputs: Tensor(shape=[2, 13]), 2 --> train_batch_size
            """
            # Tensor(shape=[2, 26])
            sparse_inputs_concat = paddle.concat(sparse_inputs, axis=1)
            # Tensor(shape=[2, 26, 1])
            sparse_emb_one = self.embedding_one(sparse_inputs_concat)
            # dense_w_one: shape=[13], dense_inputs: shape=[2, 13]
            # dense_emb_one: shape=[2, 13]
            dense_emb_one = paddle.multiply(dense_inputs, self.dense_w_one)
            # shape=[2, 13, 1]
            dense_emb_one = paddle.unsqueeze(dense_emb_one, axis=2)
            # paddle.sum(sparse_emb_one, 1): shape=[2, 1]
            # paddle.sum(dense_emb_one, 1): shape=[2, 1]
            # y_first_order: shape=[2, 1]
            y_first_order = paddle.sum(sparse_emb_one, 1) + paddle.sum(
                dense_emb_one, 1)
            # -------------------- second order term  --------------------
            # Tensor(shape=[2, 26, 9])
            sparse_embeddings = self.embedding(sparse_inputs_concat)
            # Tensor(shape=[2, 13, 1])
            dense_inputs_re = paddle.unsqueeze(dense_inputs, axis=2)
            # dense_inputs_re: Tensor(shape=[2, 13, 1])
            # dense_w: Tensor(shape=[1, 13, 9])
            # dense_embeddings: Tensor(shape=[2, 13, 9])
            dense_embeddings = paddle.multiply(dense_inputs_re, self.dense_w)
            # Tensor(shape=[2, 39, 9])
            feat_embeddings = paddle.concat([sparse_embeddings, dense_embeddings],
                                            1)
            # sum_square part
            # Tensor(shape=[2, 9])
            # \sum_{i=1}^n(v_{i,f}x_i) ---> for each embedding element: e_i, sum all feature's e_i
            summed_features_emb = paddle.sum(feat_embeddings,
                                             1)  # None * embedding_size
            # Tensor(shape=[2, 9]) 2-->batch_size
            summed_features_emb_square = paddle.square(
                summed_features_emb)  # None * embedding_size
            # square_sum part
            # Tensor(shape=[2, 39, 9])
            squared_features_emb = paddle.square(
                feat_embeddings)  # None * num_field * embedding_size
            # Tensor(shape=[2, 9]) 2-->batch_size
            squared_sum_features_emb = paddle.sum(squared_features_emb,
                                                  1)  # None * embedding_size
            # Tensor(shape=[2, 1])
            y_second_order = 0.5 * paddle.sum(
                summed_features_emb_square - squared_sum_features_emb,
                1,
                keepdim=True)  # None * 1
    
            return y_first_order, y_second_order, feat_embeddings
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114

    2.3 DNN部分实现

    这部分着实没什么好说的,直接略过。

    class DNN(paddle.nn.Layer):
        def __init__(self, sparse_feature_number, sparse_feature_dim,
                     dense_feature_dim, num_field, layer_sizes):
            super(DNN, self).__init__()
            self.sparse_feature_number = sparse_feature_number
            self.sparse_feature_dim = sparse_feature_dim
            self.dense_feature_dim = dense_feature_dim
            self.num_field = num_field
            self.layer_sizes = layer_sizes
            # [351, 512, 256, 128, 32, 1]
            sizes = [sparse_feature_dim * num_field] + self.layer_sizes + [1]
            acts = ["relu" for _ in range(len(self.layer_sizes))] + [None]
            self._mlp_layers = []
            for i in range(len(layer_sizes) + 1):
                linear = paddle.nn.Linear(
                    in_features=sizes[i],
                    out_features=sizes[i + 1],
                    weight_attr=paddle.ParamAttr(
                        initializer=paddle.nn.initializer.Normal(
                            std=1.0 / math.sqrt(sizes[i]))))
                self.add_sublayer('linear_%d' % i, linear)
                self._mlp_layers.append(linear)
                if acts[i] == 'relu':
                    act = paddle.nn.ReLU()
                    self.add_sublayer('act_%d' % i, act)
    
        def forward(self, feat_embeddings):
            """
            feat_embeddings: Tensor(shape=[2, 39, 9])
            """
            # Tensor(shape=[2, 351]) --> 351=39*9, 
            # 39 is the number of features(category feature+ continous feature), 9 is embedding size
            y_dnn = paddle.reshape(feat_embeddings,
                                   [-1, self.num_field * self.sparse_feature_dim])
            for n_layer in self._mlp_layers:
                y_dnn = n_layer(y_dnn)
            return y_dnn
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    2.4 FM和DNN部分结合

        def forward(self, sparse_inputs, dense_inputs):
    
            y_first_order, y_second_order, feat_embeddings = self.fm.forward(
                sparse_inputs, dense_inputs)
            # feat_embeddings: Tensor(shape=[2, 39, 9])
            # y_dnn: Tensor(shape=[2, 1])
            y_dnn = self.dnn.forward(feat_embeddings)
            print("y_dnn:", y_dnn)
    
            predict = F.sigmoid(y_first_order + y_second_order + y_dnn)
    
            return predict
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    三、总结

    总得来说,DeepFM还是一个挺不错的模型,在工业界应用的也挺多。

    还是那句话,如果你的场景下之前是LR,正在往深度学习迁移,
    为了最大化节约成本,可以尝试下wide&deep模型。

    如果原来是xgboost一类的树模型,需要尝试深度学习模型,建议直接deepFM。

    此外,deepFM与DCN都是在2017年发表的,
    因此这两篇paper里均没有直接有过实验数据对比,
    但在DCN V2里给出了实验效果对比,在论文给定的数据集下,两个模型效果差不多。

    注意我之前讲的xDeepFM不是对deepFM的改进,而是对DCN的改进哦!老复杂了,人家DCN挺好的


    总结

    提示:如何系统地学习推荐系统,本系列文章可以帮到你

    (1)找工作投简历的话,你要将招聘单位的岗位需求和你的研究方向和工作内容对应起来,这样才能契合公司招聘需求,否则它直接把简历给你挂了
    (2)你到底是要进公司做推荐系统方向?还是纯cv方向?还是NLP方向?还是语音方向?还是深度学习机器学习技术中台?还是硬件?还是前端开发?后端开发?测试开发?产品?人力?行政?这些你不可能啥都会,你需要找准一个方向,自己有积累,才能去投递,否则面试官跟你聊什么呢?
    (3)今日推荐系统学习经验:之前讲的xDeepFM不是对deepFM的改进,而是对DCN的改进哦!老复杂了,人家DCN挺好的

  • 相关阅读:
    Webpack Chunk 分包规则
    IDEA的优化配置教程
    apache mina实现sftp的文件上传与权限修改功能
    设计原则 | 开放封闭原则
    (SpringBoot)第一章:Spring基本概念和核心思想
    ipad触控笔有必要买原装吗?ipad2023手写笔推荐
    MojoTween:使用「Burst、Jobs、Collections、Mathematics」优化实现的Unity顶级「Tween动画引擎」
    比赛总结:Japan Registry Services (JPRS) Programming Contest 2023 (AtCoder Beginner Contest 324)
    二叉树与递归问题
    Could not load dynamic library ‘libcudart.so.11.0‘; dlerror: libcudart.so.11.0:
  • 原文地址:https://blog.csdn.net/weixin_46838716/article/details/126565975
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号