我是靠谱客的博主 明理小白菜,最近开发中收集的这篇文章主要介绍推荐算法(七)——FM 与 DNN 的另一种结合产物 FNN,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

目录

    • 1 介绍
    • 2 原理
      • 2.1 Sparse Binary Features
      • 2.2 Dense Real Layer
      • 2.3 Hidden Layer
    • 3 总结
    • 5 代码实践
    • 写在最后

1 介绍

本文为 推荐系统专栏 的第七篇文章,内容围绕 FNN 的原理及代码展开。

在这里插入图片描述
论文传送门:Deep Learning over Multi-field Categorical Data– A Case Study on User Response Prediction

代码传送门:FNN

FNN 是 2016 年产出的模型, 跟 DeepFM 一样,也是 FM 与 DNN 结合的产物,不同的是,FNN 采用的是串行拼接的结合方式,将 DNN 接在 FM 层后方,以减轻全连接层构造隐式特征的工作。

2 原理

FNN 并不是端到端的训练方式,而是两阶段的训练方式。阶段一训练一个 FM,阶段二训练一个带嵌入层的 DNN。

阶段一:先使用带标签的训练集有监督的训练 FM 模型,训练完成后,会得到每个特征对应的隐向量。若输入特征维度为 n,隐向量维度为 k,则隐向量矩阵 W 的形状就为 [n, k]。

阶段二:然后用隐向量矩阵 W 初始化 DNN 的嵌入层,然后再有监督训练 DNN 即可。DNN 的输入包含了 FM 学到的先验知识,可减轻 DNN 的学习压力。
在这里插入图片描述

2.1 Sparse Binary Features

模型的原始输入为稀疏的 0-1 向量,由稠密的数值特征和经过 onehot 处理得到的类别特征拼接而成,图中一个 field 表示一个经过 onehot 的类别特征。

2.2 Dense Real Layer

该层为嵌入层,作用是将每个 field 中的稀疏01向量,单独映射得到低维稠密特征(图中为4维的稠密特征,即 FM 隐向量的维度)。跟一般的 Embedding 嵌入层不同是,该嵌入层的权重由 FM 训练得到的隐向量初始化得到。

每个 field 的输入为一个01向量,只有一个特征的取值为1,乘上嵌入矩阵即可得到该特征对应的隐向量,数值特征直接乘上自己的隐向量即可,然后将所有 field 映射得到的隐向量 concat 作为全连接的输入即可。(相当于输入的每维特征都乘上自己对应的隐向量,只不过每个 field 只会得到一个隐向量,然后横向拼接所有隐向量即为输入)

2.3 Hidden Layer

标准的三层全连接,最后一层映射到一维接 sigmoid 得到概率输出,即为预测的 CTR 概率。

3 总结

优点:

  1. 将 FM 学习得到的隐向量作为 DNN 的输入,隐向量包含了 FM 习得的先验知识,可减轻 DNN 的学习压力;
  2. FM 只考虑到了二阶特征交互,忽略了高阶特征,后面接 DNN 可弥补该缺陷,提升模型表达能力。

缺点:

  1. 采用两阶段、非端到端的训练方式,不利于模型的线上部署;
  2. 将 FM 的隐向量直接拼接作为 DNN 的输入,忽略了 field 的概念;
  3. FNN 未考虑低阶特征组合,低阶、高阶特征是同等重要的。

5 代码实践

Layer 搭建:

import tensorflow as tf
from tensorflow.keras.layers import Layer, Dense, Dropout

class FM_layer(Layer):
    def __init__(self, k, w_reg=1e-4, v_reg=1e-4):
        super().__init__()
        self.k = k
        self.w_reg = w_reg
        self.v_reg = v_reg

    def build(self, input_shape):
        self.w0 = self.add_weight(name='w0', shape=(1,),
                                  initializer=tf.zeros_initializer(),
                                  trainable=True)
        self.w1 = self.add_weight(name='w1', shape=(input_shape[-1], 1),
                                  initializer=tf.random_normal_initializer(),
                                  trainable=True,
                                  regularizer=tf.keras.regularizers.l2(self.w_reg))
        self.v = self.add_weight(name='v', shape=(input_shape[-1], self.k),
                                  initializer=tf.random_normal_initializer(),
                                  trainable=True,
                                  regularizer=tf.keras.regularizers.l2(self.v_reg))

    def call(self, inputs, **kwargs):
        linear_part = tf.matmul(inputs, self.w1) + self.w0

        inter_part1 = tf.pow(tf.matmul(inputs, self.v), 2)
        inter_part2 = tf.matmul(tf.pow(inputs, 2), tf.pow(self.v, 2))
        inter_part = tf.reduce_sum(inter_part1-inter_part2, axis=-1, keepdims=True) / 2

        output = linear_part + inter_part
        return tf.nn.sigmoid(output)

class DNN_layer(Layer):
    def __init__(self, hidden_units, output_dim, activation='relu', dropout=0.2):
        super().__init__()
        self.hidden_layers = [Dense(i, activation=activation) for i in hidden_units]
        self.output_layer = Dense(output_dim, activation=None)
        self.dropout_layer = Dropout(dropout)

    def call(self, inputs, **kwargs):
        x = inputs
        for layer in self.hidden_layers:
            x = layer(x)
            x = self.dropout_layer(x)
        output = self.output_layer(x)
        return tf.nn.sigmoid(output)

Model 搭建:

FNN 为两阶段训练方式,所以有两个单独模型: FM 与 DNN。

from layer import FM_layer, DNN_layer
from tensorflow.keras.models import Model

class FM(Model):
    def __init__(self, k, w_reg=1e-4, v_reg=1e-4):
        super().__init__()
        self.fm = FM_layer(k, w_reg, v_reg)

    def call(self, inputs, training=None, mask=None):
        output = self.fm(inputs)
        return output

class DNN(Model):
    def __init__(self, hidden_units, output_dim, activation='relu'):
        super().__init__()
        self.dnn = DNN_layer(hidden_units, output_dim, activation)

    def call(self, inputs, training=None, mask=None):
        output = self.dnn(inputs)
        return output

模型训练代码:

代码实现时,我把两个模型的训练过程写在了一起,可端到端的完成训练。

from model import FM, DNN
from utils import create_criteo_dataset

import tensorflow as tf
from tensorflow.keras import optimizers, losses, metrics
from sklearn.metrics import accuracy_score

if __name__=='__main__':
    file_path = 'criteo_sample.txt'
    (X_train, y_train), (X_test, y_test) = create_criteo_dataset(file_path, test_size=0.5)
    k = 8

#**************** Statement 1 of Training *****************#
    model = FM(k)
    optimizer = optimizers.SGD(0.01)

    train_dataset = tf.data.Dataset.from_tensor_slices((X_train, y_train))
    train_dataset = train_dataset.batch(32).prefetch(tf.data.experimental.AUTOTUNE)
    model.compile(optimizer=optimizer, loss='binary_crossentropy', metrics=['accuracy'])
    model.fit(train_dataset, epochs=200)

#**************** Statement 2 of Training *****************#
    # 获取 FM 训练得到的隐向量矩阵 self.v
    v = model.variables[2]  # [None, onehot_dim, k]

    X_train = tf.cast(tf.expand_dims(X_train, -1), tf.float32)  #[None, onehot_dim, 1]
    # 原始输入乘上隐向量矩阵 self.v
    X_train = tf.reshape(tf.multiply(X_train, v), shape=(-1, v.shape[0]*v.shape[1])) #[None, onehot_dim*k]

    hidden_units = [256, 128, 64]
    model = DNN(hidden_units, 1, 'relu')
    optimizer = optimizers.SGD(0.0001)

    train_dataset = tf.data.Dataset.from_tensor_slices((X_train, y_train))
    train_dataset = train_dataset.batch(32).prefetch(tf.data.experimental.AUTOTUNE)
    model.compile(optimizer=optimizer, loss='binary_crossentropy', metrics=['accuracy'])
    model.fit(train_dataset, epochs=50)

完整可运行的代码可在文末 Github 仓库中查看。

写在最后

下一篇预告: 推荐算法(八)——显式特征交互模型 PNN 原理及代码实战

各种推荐算法的模型复现,可参照以下 Github 仓库:

Recommend-System-tf2.0

希望看完此文的你,能够有所收获!

最后

以上就是明理小白菜为你收集整理的推荐算法(七)——FM 与 DNN 的另一种结合产物 FNN的全部内容,希望文章能够帮你解决推荐算法(七)——FM 与 DNN 的另一种结合产物 FNN所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(40)

评论列表共有 0 条评论

立即
投稿
返回
顶部