概述
目录
- 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 总结
优点:
- 将 FM 学习得到的隐向量作为 DNN 的输入,隐向量包含了 FM 习得的先验知识,可减轻 DNN 的学习压力;
- FM 只考虑到了二阶特征交互,忽略了高阶特征,后面接 DNN 可弥补该缺陷,提升模型表达能力。
缺点:
- 采用两阶段、非端到端的训练方式,不利于模型的线上部署;
- 将 FM 的隐向量直接拼接作为 DNN 的输入,忽略了 field 的概念;
- 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所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复