推荐系统之NFM

今天开始了推荐系统的第四个模型Neural Factorization Machine(简称NFM)的学习,这是新加坡国立大学在2017年发表的论文。它的思想也是结合FM的二阶特征抽取能力,将其和DNN进行有机的融合。具体论文地址如下:https://arxiv.org/pdf/1708.05027.pdf

NFM的结构似曾相识,跟上一篇提到的PNN有类似之处,结合上一篇的DeepFM的主题,这几个模型简直可以成为“将FM放入DNN”有X种办法的系列。这边再次简单回顾一下,
FM的模型: \hat{y}_{FM}(x)=w_{0}+\sum_{i=1}^{N} w_{i} x_{i}+\sum_{i=1}^{N}\sum_{j=i+1}^{N}(v_i,v_j)x_ix_j
预训练的参数,包含三部分:偏置项w_0,一阶特征的权重w_i,和二阶相互特征的隐向量v_i。其对应关系如下图所示:

源于"AI上推荐 之 FNN、DeepFM与NFM"文章

FNN、PNN的相关网络结构如下图所示:

源自deepFM论文

FNN:是将FM采用SGD预训练好的weight初始化网络结构,同时,DNN用RBM预训练,再放入神经网络中,进行统一的训练方式,加快模型的收敛速度。其实,FNN里面用到FM的隐向量v_i本身也是分特征域建模的思想,之后将FM抽取的dense embedding直接进行拼接,成为DNN的输入。FNN的不足之处是,(1)仅考虑了高阶特征的提取(2)需要两阶段建模,一阶段需要对FM和DNN分别进行预训练,同时,FM的预训练结果对模型最终的效果影响较大,因为学习率不能太大,否则会抹平FM的预训练输入(3)FM提取后的dense embedding输入至DNN时,仅采用了简单的拼接,不再区分特征域。

PNN: 增加了product层,改进了特征交叉的方式。具体地,product层包含两部分,二阶交叉层product和一阶线性层z。二阶交叉层product可以是各个FM field embedding的内积或者外积,一阶线性层z是FM field embedding的简单拼接。从一定程度上面,改进了FNN的模型,不过由于product层后面直接接入的DNN,导致了低阶特征的表达依旧是不足够的。

DeepFM:借鉴了Wide&Deep的网络结构,将简单的LR层用FM来替代,综合地利用了FM提取的低阶特征和DNN学习到的高阶特征,将两者进行有机的结合。具体的介绍可以参考上一篇博文。

好,至此我们对于前期的模型FM、FNN、PNN和DeepFM有了简单的了解,现在我们具体来看NFM的思想:虽然FM很巧妙地提取了交叉特征的信息,但是,它说到底仍旧是个多元线性的模型,对于特征之间的非线性交互作用很难近似表达,因而,应该用表达性更强大的神经网络结构来对交叉效应进行更灵活地表达。
\hat{y}_{NFM}(x)=w_{0}+\sum_{i=1}^{N} w_{i} x_{i}+f(x)

NFM 具体的网络结构图如下:


源自NFM论文

输入层:输入层为稀疏特征向量,这和one-hot是不同的,有点label encoding的意味,每一个元素都是一个field。
FM embedding 层:利用FM对输入层特征进行提取,得到对应的交叉系数的隐变量,输出特征域非0的隐变量。
Bi-Interaction层:这是NFM的创新之处。我们将得到的embedding层的\nu_x集合输入到Bi-Interation层,它是将embedding向量的集合通过池化的操作转换为一个向量:
f_{BI}(\nu_x)= \sum_{i=1}^{N}\sum_{j=i+1}^{N}\nu_ix_i \odot x_j\nu_j,其中,\odot是元素积,表示两个向量间对应元素相乘。因此,Bi-Interaction池化后的输出是k维的向量,也就是将特征间的二阶交叉效应映射到embedding空间中。Bi-Interaction的优势为将特征间的交叉效应以k维向量表示,同时,没有引入额外的参数,且也是在线性时间复杂度内完成的。
预测层:注意在多层隐藏层的DNN结构之后的预测层是回归问题,并没有接sigmoid函数。 f(\mathbf{x}) =h^Tz_L

具体代码实现如下:

def NFM(linear_feature_columns, dnn_feature_columns):
    """
    搭建NFM模型,上面已经把所有组块都写好了,这里拼起来就好
    :param linear_feature_columns: A list. 里面的每个元素是namedtuple(元组的一种扩展类型,同时支持序号和属性名访问组件)类型,表示的是linear数据的特征封装版
    :param dnn_feature_columns: A list. 里面的每个元素是namedtuple(元组的一种扩展类型,同时支持序号和属性名访问组件)类型,表示的是DNN数据的特征封装版
    """
    # 构建输入层,即所有特征对应的Input()层, 这里使用字典的形式返回, 方便后续构建模型
    # 构建模型的输入层,模型的输入层不能是字典的形式,应该将字典的形式转换成列表的形式
    # 注意:这里实际的输入与Input()层的对应,是通过模型输入时候的字典数据的key与对应name的Input层
    dense_input_dict, sparse_input_dict = build_input_layers(linear_feature_columns+dnn_feature_columns)
    input_layers = list(dense_input_dict.values()) + list(sparse_input_dict.values())
    
    # 线性部分的计算 w1x1 + w2x2 + ..wnxn + b部分,dense特征和sparse两部分的计算结果组成,具体看上面细节
    linear_logits = get_linear_logits(dense_input_dict, sparse_input_dict, linear_feature_columns)
    
    # DNN部分的计算
    # 首先,在这里构建DNN部分的embedding层,之所以写在这里,是为了灵活的迁移到其他网络上,这里用字典的形式返回
    # embedding层用于构建FM交叉部分以及DNN的输入部分
    embedding_layers = build_embedding_layers(dnn_feature_columns, sparse_input_dict, is_linear=False)
    
    # 过特征交叉池化层
    pooling_output = get_bi_interaction_pooling_output(sparse_input_dict, dnn_feature_columns, embedding_layers)
    
    # 加个BatchNormalization
    pooling_output = BatchNormalization()(pooling_output)
    
    # dnn部分的计算
    dnn_logits = get_dnn_logits(pooling_output)
    
    # 线性部分和dnn部分的结果相加,最后再过个sigmoid
    output_logits = Add()([linear_logits, dnn_logits])
    output_layers = Activation("sigmoid")(output_logits)
    
    model = Model(inputs=input_layers, outputs=output_layers)
    
    return model

思考题:NFM中的特征交叉与FM中的特征交叉有何异同,分别从原理和代码实现上进行对比分析
从原理上看,NFM的特征交叉部分出来是个K维向量,而FM的特征交叉部分出来就是一个值,前者的交叉效应表达更细致;
从代码上看,两者的差别也就在于算残差平方和的时候,求和的维度的不同。前者FM Layer为所有元素的求和,而Bi-Interaction Layer为隐向量的对应元素的求和。
FM层的定义如下:

class FM_Layer(Layer):
    def __init__(self):
        super(FM_Layer, self).__init__()

    def call(self, inputs):
        # 优化后的公式为: 0.5 * 求和(和的平方-平方的和)  =>> B x 1
        concated_embeds_value = inputs # B x n x k

        square_of_sum = tf.square(tf.reduce_sum(concated_embeds_value, axis=1, keepdims=True)) # B x 1 x k
        sum_of_square = tf.reduce_sum(concated_embeds_value * concated_embeds_value, axis=1, keepdims=True) # B x1 xk
        cross_term = square_of_sum - sum_of_square # B x 1 x k
        cross_term = 0.5 * tf.reduce_sum(cross_term, axis=2, keepdims=False) # B x 1

        return cross_term

    def compute_output_shape(self, input_shape):
        return (None, 1)

而,NFM中的Bi-Interaction Layer如下

class BiInteractionPooling(Layer):
    def __init__(self):
        super(BiInteractionPooling, self).__init__()

    def call(self, inputs):
        # 优化后的公式为: 0.5 * (和的平方-平方的和)  =>> B x k
        concated_embeds_value = inputs # B x n x k

        square_of_sum = tf.square(tf.reduce_sum(concated_embeds_value, axis=1, keepdims=False)) # B x k
        sum_of_square = tf.reduce_sum(concated_embeds_value * concated_embeds_value, axis=1, keepdims=False) # B x k
        cross_term = 0.5 * (square_of_sum - sum_of_square) # B x k

        return cross_term

    def compute_output_shape(self, input_shape):
        return (None, input_shape[2])

参考资料:

  1. [DataWhale学习资料](https://github.com/datawhalechina/team-learning-rs/blob/master/DeepRecommendationModel/NFM.md
  2. [AI上推荐 之 FNN、DeepFM与NFM(FM在深度学习中的身影重现)]
    https://blog.csdn.net/wuzhongqiang/article/details/109532267?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161442951716780255224635%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=161442951716780255224635&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_v1~rank_blog_v1-1-109532267.pc_v1_rank_blog_v1&utm_term=NFM
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容