精通推荐算法19:特征交叉之DeepFM -- 异构模型Wide侧引入FM

news2024/11/16 11:40:01

1 引言

Wide & Deep的提出,使推荐模型同时具备记忆和泛化能力。通过融合低阶和高阶特征交叉,开启了推荐算法异构模型的风潮。后续越来越多的模型,在其基础上进一步优化,并取得了不错的效果。DeepFM就是其中一个很经典的模型,它主要对Wide侧进行优化。

DeepFM全称“ DeepFM: A Factorization-Machine based Neural Network for CTR Prediction[7]。由哈尔滨工业大学和华为于2017联合提出。它对Wide & Deep模型的优化主要有:

  1. 将Wide侧从LR升级为FM,从而增加了二阶特征自动交叉能力。Wide & Deep模型的Wide侧为逻辑回归,本身并不具备二阶特征交叉能力,需要手工构造交叉特征。DeepFM通过因子分解机的二阶特征自动交叉能力,减少对特征工程的依赖,从而降低了人力成本。
  2. Wide和Deep两侧共享原始输入特征和Embedding特征向量,从而加快模型收敛速度,并进一步提升模型表达能力。

DeepFM模型结构

DeepFM模型包括两部分,Wide侧采用因子分解机(FM),Deep侧采用深度神经网络(DNN)。通过一层线性连接融合两部分,并经过sigmoid激活函数最终输出,如公式4-17所示。模型为点击率(CTR)预估任务,目标函数仍然采用LogLoss。利用联合训练使两部分同时达到最优。两部分的优化器均采用Adam算法。

模型整体结构如图4-8所示。其中左边为FM右边为DNN,二者共享原始输入特征和Embedding向量。FM负责低阶特征交叉(一阶和二阶),拥有较强的记忆能力。DNN负责高阶特征交叉,拥有较强的泛化能力。

先来看FM部分。它主要包括两部分:一阶特征交叉和二阶特征交叉。一阶部分对输入特征进行线性求和(Addition),表达特征间“或”的关系。二阶部分对输入特征的隐向量进行内积(Inner Product)计算,表达特征间“且”的关系。FM计算如公式4-18所示。

在推荐场景下,特征间“且”的关系显然更为重要。这一点是PNN模型提出的核心出发点,在4.4.3章节中已经详细阐述过。另外对于训练数据中没有出现过的特征组合,一阶特征交叉无法表达特征间相关关系,而二阶特征交叉,则可以通过隐向量实现。因此,二阶特征交叉相对来说更重要

DNN部分模型结构和Wide & Deep模型比较类似,仍然采用经典的“Embedding + MLP”结构,不再赘述。主要区别为,DNN与FM两部分,共享了原始输入特征和Embedding向量。这样做有两大好处:

  1. 相当于加入了某种意义上的正则,使得Embedding向量需要同时在低阶和高阶特征交叉中表现良好,有利于提升模型表达能力和鲁棒性
  2. 降低了模型参数量和计算复杂度,使得模型训练速度更快。利用FM隐向量进行二阶特征交叉,代替手工构造的二阶交叉特征,对这一点帮助也很大。

同时需要指出的是,DeepFM中FM和DNN两部分共享的Embedding向量参数,可以通过端到端训练学习。这一点与FNN模型有很大差别。FNN需要先通过FM预训练Embedding向量,然后作为DNN模型的初始化参数。这样一方面使得模型整体性能高度依赖于FM预训练质量,另一方面FM和DNN两部分是脱节的,不能端到端训练。同时由于需要提前得到预训练模型,加大了整体计算复杂度和训练时长。DeepFM的这一优化同样十分重要,千万不要把DeepFM简单理解为,只是将LR替换为了FM。

DeepFM实现代码

DeepFM由FM和DNN两部分组成,FM又包括一阶和二阶特征交叉。下面分模块分别实现。代码基于Keras深度学习库实现,其中Keras版本为2.10。先定义FM一阶特征交叉函数。一阶特征交叉,也就是LR,与Embedding计算类似故可利用Embedding来实现。注意Embedding的输入维度可根据实际场景具体值来确定,输出维度为1。最后将所有Embedding求和即可。

from tensorflow.keras.layers import Layer, Dense, Embedding, Concatenate, Reshape, Add, Subtract, Lambda
from tensorflow.keras import backend as K


def build_fm_first_order(inputs):
    """
    构建FM一阶部分,也就是LR逻辑回归
    @param inputs: 输入特征,包括类别型和连续型特征。连续型特征会经过分桶离散化
    @return: 返回一阶特征交叉结果
    """
    embeddings = []
    for x in inputs:
        # 遍历每个特征,进行Embedding,相当于做 wx + b计算
        # 下面代码中,假设了输入特征5000维,具体场景根据实际值来确定。对于逻辑回归,输出为1维
        embedding = Embedding(input_dim=5000, output_dim=1)(x)
        embedding = Reshape(target_shape=(1,))(embedding)
        embeddings.append(embedding)

    # 加权求和即得到一阶部分的结果
    return Add()(embeddings)

二阶特征交叉为内积计算,实现相对复杂。需要先化简一下,推导如公式4-19所示。

特别需要注意的是,FM和DNN的Embedding是共享的。先把各特征的Embedding向量的相同位置相加,相当于做求和池化(sum pooling),再对池化后的向量逐点取平方。然后对每个Embedding先逐点平方,再把他们加起来。最后两部分向量逐点相减,并将相减后得到的向量各元素累加起来,得到一个标量,即为二阶交叉的结果。实现代码如下。

# 定义求和层
class SumLayer(Layer):
    def __init__(self, **kwargs):
        super(SumLayer, self).__init__(**kwargs)

    def call(self, inputs):
        inputs = K.expand_dims(inputs)
        return K.sum(inputs, axis=1)

    def compute_output_shape(self, input_shape):
        return tuple([input_shape[0], 1])


def build_fm_second_order(embeddings):
    """
    构建FM二阶部分
    @param embeddings: 输入特征embedding拼接后的高维向量,与DNN部分是共享的
    @return: 返回二阶特征交叉结果
    """
    # 先相加后平方
    sum_square_result = Lambda(lambda x: x ** 2)(Add()(embeddings))

    # 先平方后相加
    square_sum_result = Add()([Lambda(lambda x: x ** 2)(emb) for emb in embeddings])

    # 两部分相减
    substract_result = Lambda(lambda x: x * 0.5)(Subtract()([sum_square_result, square_sum_result]))

    # 最后通过求和层输出
    return SumLayer()(substract_result)

DNN层的实现则比较简单,下面例子构建了层全连接神经网络,神经元个数分别为1024、512、256。激活函数为ReLU。可根据实际情况修改DNN层数和每层神经元个数。

def build_dnn(embeddings):
    """
    构建DNN部分
    @param embeddings: 输入特征embedding拼接后的高维向量,与FM部分是共享的
    @return: 返回DNN结果
    """
    # 此处为三层全连接神经网络,神经元个数分别为1024、512、256。可根据实际情况修改
    x_deep = Dense(units=1024, activation="relu")(embeddings)
    x_deep = Dense(units=512, activation="relu")(x_deep)
    deep_out = Dense(units=256, activation="relu")(x_deep)

    return deep_out

各模块实现好之后,开始构建DeepFM整体结构。流程分为4步:

  1. 先构建FM一阶部分,调用对应模块函数即可
  2. 再构建FM二阶部分。由于FM与DNN共享Embedding,故先构建特征向量,每个特征向量长度相同。代码中假设输入特征5000维,可根据具体场景实际值来修改
  3. 再构建DNN部分,调用对应模块函数即可
  4. 最后是融合层。先融合FM的一阶部分和二阶部分,再将FM和DNN通过一层线性连接融合起来,最后通过Sigmoid函数归一化为0到1之间。
def build_deep_fm(inputs):
    """
    构建FM模型
    @param inputs: 输入特征,包括类别型和连续型特征。连续型特征会经过分桶离散化
    @return: 返回FM模型输出结果
    """
    # 1 构建fm一阶部分
    fm_first_order = build_fm_first_order(inputs)

    # 2 构建fm二阶部分
    # 2.1 先构建隐向量,后面DNN也会用到。二者是共享的
    emb_size = 32
    embeddings = []
    for x in inputs:
        # 遍历每个特征,进行Embedding
        # 下面代码中,假设了输入特征5000维,具体场景根据实际值来修改。
        embedding = Embedding(input_dim=5000, output_dim=emb_size)(x)
        embedding = Reshape(target_shape=(emb_size,))(embedding)
        embeddings.append(embedding)

    fm_second_order = build_fm_second_order(embeddings)

    # 3 构建dnn,注意此处embedding与FM是共享的
    dnn_output = build_dnn(embeddings)

    # 4 融合得到最终结果
    # 4.1 先融合FM的一阶部分和二阶部分
    fm_output = Add()([fm_first_order, fm_second_order])

    # 4.2 再融合FM和DNN两部分
    output = Dense(units=1, activation="sigmoid")([fm_output, dnn_output])
return output

DeepFM总结

DeepFM通过对Wide侧优化,提升了异构模型表达能力,是推荐算法中的经典模型。虽然早在2017年就提出,距今已经很多年,但仍然广泛应用于各大推荐场景。与Deep Crossing、FNN、PNN、Wide & Deep等模型相比,它的优势十分明显,主要有:

  1. 不需要手工构造交叉特征,降低了对特征工程的依赖。相比之下,Wide & Deep仍然需要。
  2. 模型可同时进行低阶和高阶特征交叉,兼顾了记忆和泛化两大能力。相比之下,Deep Crossing、FNN、PNN等模型则缺失了低阶特征交叉能力。

可以端到端联合训练FM和DNN两部分模型参数,从而降低计算复杂度并提升模型表达能力。相比之下,FNN需要先预训练FM,然后再训练DNN,无法端到端学习。

5 作者新书推荐


历经两年多,花费不少心血,终于撰写完成了这部新书。本文在4.6节中重点阐述了

源代码:扫描图书封底二维码,进入读者群,群公告中有代码下载方式

微信群:图书封底有读者微信群,作者也在群里,任何技术、offer选择和职业规划的问题,都可以咨询。

详细介绍和全书目录,详见

精通推荐算法,限时半价,半日达icon-default.png?t=N7T8https://u.jd.com/VbCJsCz

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1975513.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

渗透测试--钓鱼网站实验

实验原理 使用工具 setoo1kit,构造钓鱼网站,钓鱼网站可以选择为比较知名的网站,例如学信网。被攻击者访问了钓鱼网站,输入自己的真实账号密码进行登录,账号密码会被 kali 收集 实验步骤 打开 kali 终端,输…

PyCharm中安装和使用FittenCode的AI插件助手

AI 逐步进入生活的方方面面,在编程开发中也不例外,下面简单记录一下 PyCharm IDE中安装和使用FittenCode插件AI助手的过程; 1,假设本地已经安装 PyCharm IDE, 如果还没有安装到如下地址进行下载2024年社区版: Thank …

短视频平台引流玩法

今天盘点了4大视频类的主流平台的精准引流触点,拿去用吧!

Qt pdf文件转换操作

qt文件转换操作&#xff0c;包括word转为pdf&#xff1b;Excel转为pdf&#xff1b;PPT转为pdf&#xff1b;image转为pdf&#xff1b;pdf转为文本文件&#xff1b;pdf导出图片&#xff1b;接口如下所示&#xff1a; #pragma once #include <QObject> #include "file…

模拟实现strcat(字符串追加)

1.我们要知道stcat的作用是什么&#xff0c;字符串追加。 2.我们进行模仿&#xff0c;我们先将arr1不断&#xff0c;直到“\0”,我们加在后面。 //模拟实现strcat(字符串追加) char* my_strcat(char* arr1, const char* arr2) {assert(arr1 && arr2);char ret arr1;…

揭秘!格行随身WiFi:如何在‘内卷’市场中逆袭?如何重新定义市场新标准?随身WIFI哪个品牌网速快,续航时间长?随身WiFi热卖第一名

近年来&#xff0c;随身WiFi市场竞争激烈&#xff0c;部分商家为追求利润最大化&#xff0c;不惜采取偷工减料等不正当手段&#xff0c;导致产品质量参差不齐&#xff0c;消费者在使用过程中频繁遭遇信号不稳定、网络卡顿、电池不耐用等问题。这种“内卷”现象不仅损害了消费者…

Hive命令为表增加字段(内置数据库)

【实验目的】 1) 了解hive操作命令 2) 熟练操作hive数据库 【实验原理】 进入hive shell环境&#xff0c;确保hive中存在已经创建的表结构&#xff0c;然后可以通过alter命令对表的字段进行修改。 【实验环境】 本次环境是&#xff1a;centos6.5 Hadoop-2.4.1 jdk1.7.0_7…

【数据结构与算法】单链表、双链表和循环单链表中头指针未知的情况下能否删除某节点

在单链表、双链表和循环单链表中&#xff0c;若仅知道指针p指向某结点&#xff0c;不知道头指针&#xff0c;能否将结点p从相应的链表中删去&#xff1f;为什么&#xff1f;若可以&#xff0c;其时间复杂度各为多少&#xff1f; 单链表&#xff1a;不能 在单链表中&#xff0c…

一些爬虫代码的解析

import requests from bs4 import BeautifulSoup import time import logging import json from concurrent.futures import ThreadPoolExecutor import random# 配置日志 logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s)# 目标网页…

rocket 如何解决消息堆积问题、如何消息丢失问题、r安全问题(设置密码)、时间复杂度。

20240803 一、 如何解决消息堆积问题&#xff1f;一般认为单条队列消息差值>10w时 算堆积问题生产太快了线程数量的设置挤压问题 消费者消费出现问题如果堆积的消息不想要了&#xff0c;可以直接跳过堆积 二、 信息丢失问题为什么会丢失解决思路1 记录下来解决思路2 使用roc…

BulingBuling - 活法自如 [Reset Your Routine] - 2

2. What matters most to you? 学习如何优先考虑适合自己的日常工作。 Learn how to prioritize what fits into your routine. Get to your why (找到原因) 因此&#xff0c;当你开始制定新的个性化日常计划时&#xff0c;其中一个关键因素就是要深入挖掘什么对你来说才是真…

Kubernets(k8s) 网络原理一:Pod与宿主机通信

对于刚接触K8S的同学来说&#xff0c;K8S网络显得尤为复杂&#xff0c;例如Pod如何访问主机以及pod间如何进行通信等。本系列文章将站在一个初学者角度&#xff0c;逐层刨析Kubernetes网络实现原理&#xff0c;并利用基本的Linux命令加以实现。 网络虚拟化基石&#xff1a;net…

【Qt】QDial和QSlider

QDial QDial类用于创建一个旋转式的圆形控件&#xff0c;通过鼠标点击旋转、方向键或者pageUp和pageDown调整一个值。常用在需要进行连续调整的场景&#xff0c;比如音量控制、亮度控制、透明度调节等 常见属性 属性说明value持有的值minimum持有值所能到达的最小值maximum持有…

Fiddler抓包及设置

1、打开Fiddler.exe 2、设置过滤&#xff0c;只抓取具体网页或APP 3、勾选 Request Headers 中的 Hide if url contains 过滤项&#xff0c;贴入下方正则表达式&#xff1a;REGEX:(?index)/[^\?/]*\.(css|ico|jpg|png|gif|bmp|wav|js|jpeg|webp)(\?.*)?$&#xff0c;表示过…

云计算 docker 管理镜像和容器

docker的概述 命令说明docker version查看服务器与客户端版本docker info查看 docker 服务配置信息 容器安装部署dnf install -y docker-ce systemctl enable --now docker 配置镜像仓库 镜像概述 镜像管理命令 镜像管理命令说明docker images查看本机镜像docker pull 镜像名…

HDU1100——Trees Made to Order以及卡特兰数

HDU1100——Trees Made to Order 题目描述 Problem - 1100 运行代码 #include <iostream> #include <vector> using namespace std; vector<long long> C(21, 1); // 第21个卡特兰数达到65亿 // 预处理卡特兰数 void Catalan() {for (int i 1; i < 2…

网络学习:应用层DNS域名解析协议

目录 一、简介 二、工作流程 一、简介 DNS( Domain Name System)是“域名系统”的英文缩写&#xff0c;是一种组织成域层次结构的计算机和网络服务命名系统&#xff0c;它用于TCP/IP网络&#xff0c;它所提供的服务是用来将主机名和域名转换为IP地址的工作。 同时,DNS…

[Leetcode 875][Medium]-爱吃香蕉的珂珂-二分搜索

目录 一、题目描述 二 、整体思路 三 、代码 一、题目描述 原题地址 二 、整体思路 题目要求在时间h内(含h)&#xff0c;求解最小速度k。那么首先要知道速度与吃香蕉所用时间的关系。 假设速度为k&#xff0c;那么吃香蕉所用时间t就等于每堆香蕉piles[i]除以速度k所得的向…

电子元器件—电容和电感(一篇文章搞懂电路中的电容和电感)(笔记)(面试考试必备知识点)电容和电感作用、用途、使用、注意事项、特点等(面试必备)-笔记(详解)

作者&#xff1a;Whappy 座右铭&#xff1a;不曾拥有&#xff0c;何来失去&#xff01; 时间&#xff1a;2024年8月2日08:40:04 一、电容的作用 储能&#xff1a; 电容器通过充电储存电荷在电容板上&#xff0c;形成电场储存电能。当需要释放储存的电能时&#xff0c;电荷…

django集成pytest进行自动化单元测试实战

文章目录 一、引入pytest相关的包二、配置pytest1、将django的配置区分测试环境、开发环境和生产环境2、配置pytest 三、编写测试用例1、业务测试2、接口测试 四、进行测试 在Django项目中集成Pytest进行单元测试可以提高测试的灵活性和效率&#xff0c;相比于Django自带的测试…