参考文章:
TensorFlow 2.0 implementation of Product-based Neural Network[1]
Reference:
[1] Product-based Neural Networks for User ResponsePrediction,
Yanru Qu, Han Cai, Kan Ren, Weinan Zhang, Yong Yu, Ying Wen, Jun Wang
[2] Tensorflow implementation of PNN
https://github.com/Snail110/Awesome-RecSystem-Models/blob/master/Model/PNN_TensorFlow.py
[3] https://zhuanlan.zhihu.com/p/37522266
概述
该系列主要是复现一些经典的网络结构与顶会论文的网络结构,我一开始看论文,以为看到网络结构和了解结构原理后,就完全学到了这篇论文的精髓,谁知,等到自己想要用这个网络结构时,无法打通理解与代码复现的这一步,这就导致我在科研或者工作时的束手无措,因此,我就决定探究如何将一篇论文中的结构和论文中的公式用代码的形式复现出来。
深度学习框架:tensorflow2.0 ,numpy。
语言:python。
复现的代码全部在:https://github.com/Snail110/recsys。
0.介绍
PNN 全程是product-based Neural Network,认为在embedding输入到MLP之后学习的交叉特征表达并不充分,提出了一种product layer的思想,基于乘法的运算来提现特征交叉的DNN网络结构,如下:
1.网络结构
该部分主要是将论文中公式与结构图对应起来,理解每一个公式的含义以及网络结构图中每一部分的输入输出。
首先,当你看完一篇论文并理解了论文的主要思想后,需要尝试着将网络结构与论文中的每一步的数学公式一一对应上,在心中或者图片上协商每一个环节的数学公式,然后考虑用深度学习框架来实现。
首先这篇论文中有数学公式(1),(2),(3)对应着网络模型。
然后需要一步一步的将公式对应到网络模型中,
公式(1)
输出层是一层全连接层,经过sigmoid函数映射到0,1之间,得到点击率的预测值。
公式(2)
l2层
根据l1层的输出,经过全连接层,并使用relu激活得到结果
公式(3)
输入为三个部分:分别是lz lp,b1,b1是偏置。重点介绍lz,lp.
product layer
在ctr预估中,特征之间关系更多的是一种and关系,因此需要获取特征a且特征b的组合更加提现特征交叉的意义。
product layer分为两个部分,一部分是线性部分lz,一部分是非线性部分lp,
二者形式:
公式中的圈点符号代表矩阵的点乘:
Embedding Layer
Embedding Layer跟deepfm中相同,将每个field的特征换成同样长度的向量
(f1,f2,f3…fn)
损失函数
2.product laer详细介绍
在上面说到
了解z,p代表什么意思,z是线性信号向量,p是非线性信号向量
因此:
z
z就是embedding的直接等于映射,
p:
不同的函数g会有两种PNN计算方法,内积inner PNN和外积 outer PNN
先定义embedding 大小为M,field大小为N,而lz,lp长度为D1(就是下一层的长度)。
在这里需要明白,field指的是 比如训练集有特征:[性别,年龄,购买次数,点赞次数,浏览次数],这些就是这是field,field大小就是这些特征的数量。
2.1 IPNN
IPNN示意图:
从图上可以看出内积pij:
pij为 embedding中fi,fj向量的点乘,得到就是数。
pij为一个数,那么p大小为N*N。但是由于计算p的事件复杂度很大,仅此论文进行了优化,得知p是一个对称矩阵,那么权重W是对称矩阵
将W分解为
因此
从这里看出来 权重每一个wu对应一个fi,那么也就是说权重N个w对应field。
,那么权重大小为D1 * N,
最终得到:
2.2OPNN
OPNN为外积形式,将每个field外积成一个方阵,然后再通过范数为一个数。
OPNN中p的计算方式如下:
此时pij为MM的矩阵,计算一个pij的时间复杂度为MM,因此w应该为D1MM才可以w * p = 一个数。
2.代码复现
该部分主要是按照网络结构图,用代码的方式实现。在代码实现的过程中,我们需要紧紧结合数学公式体会其中的含义以及如何用代码来实现这些数学公式。
我是基于数据集:https://www.kaggle.com/c/porto-seguro-safe-driver-prediction来实现的。
class PNN(tf.keras.Model):
def __init__(self, num_feat, num_field, dropout_deep, deep_layer_sizes, product_layer_dim=10, reg_l1=0.01,
reg_l2=1e-5, embedding_size=10, product_type='outer'):
super().__init__()
self.reg_l1 = reg_l1
self.reg_l2 = reg_l2
self.num_feat = num_feat # F =features nums
self.num_field = num_field # N =fields of a feature
self.product_layer_dim = product_layer_dim # D1 pnn dim
self.dropout_deep = dropout_deep
# Embedding 这里采用embeddings层因此大小为F* M F为特征数量,M为embedding的维度
feat_embeddings = tf.keras.layers.Embedding(num_feat, embedding_size,
embeddings_initializer='uniform') # F * M
self.feat_embeddings = feat_embeddings
# 定义随机初始化
initializer = tf.initializers.GlorotUniform()
# linear part 线性层就是embedding层的复制,因此线性信号权重大小是D1 * N * M,为什么因此是线性层维度为 D1,embedding层维度为N* M
# 因此权重大小为D1 * N *M
self.linear_weights = tf.Variable(
initializer(shape=(product_layer_dim, num_field, embedding_size))) # D1 * N * M
# quadratic part
self.product_type = product_type
if product_type == 'inner':
self.theta = tf.Variable(initializer(shape=(product_layer_dim, num_field))) # D1 * N
else:
self.quadratic_weights = tf.Variable(
initializer(shape=(product_layer_dim, embedding_size, embedding_size))) # D1 * M * M
# fc layer
self.deep_layer_sizes = deep_layer_sizes
# 神经网络方面的参数
for i in range(len(deep_layer_sizes)):
setattr(self, 'dense_' + str(i), tf.keras.layers.Dense(deep_layer_sizes[i]))
setattr(self, 'batchNorm_' + str(i), tf.keras.layers.BatchNormalization())
setattr(self, 'activation_' + str(i), tf.keras.layers.Activation('relu'))
setattr(self, 'dropout_' + str(i), tf.keras.layers.Dropout(dropout_deep[i]))
# last layer
self.fc = tf.keras.layers.Dense(1, activation=None, use_bias=True)
def call(self, feat_index, feat_value):
# call函数接收输入变量
# embedding part feat_index = inputs为输入 feat_embeddings为一个layer。
feat_embedding_0 = self.feat_embeddings(feat_index) # Batch * N * M
# print(feat_value.get_shape())
feat_embedding = tf.einsum('bnm,bn->bnm', feat_embedding_0, feat_value)
# linear part
lz = tf.einsum('bnm,dnm->bd', feat_embedding, self.linear_weights) # Batch * D1
# quadratic part
if self.product_type == 'inner':
theta = tf.einsum('bnm,dn->bdnm', feat_embedding, self.theta) # Batch * D1 * N * M
lp = tf.einsum('bdnm,bdnm->bd', theta, theta) # Batch * D1
else:
embed_sum = tf.reduce_sum(feat_embedding, axis=1) # Batch * M
p = tf.einsum('bm,bn->bmn', embed_sum, embed_sum)
lp = tf.einsum('bmn,dmn->bd', p, self.quadratic_weights) # Batch * D1
y_deep = tf.concat((lz, lp), axis=1)
y_deep = tf.keras.layers.Dropout(self.dropout_deep[0])(y_deep)
for i in range(len(self.deep_layer_sizes)):
y_deep = getattr(self, 'dense_' + str(i))(y_deep)
y_deep = getattr(self, 'batchNorm_' + str(i))(y_deep)
y_deep = getattr(self, 'activation_' + str(i))(y_deep)
y_deep = getattr(self, 'dropout_' + str(i))(y_deep)
output = self.fc(y_deep)
return output
3.总结
你看,通过这样一步步将公式与代码对应起来,就好实现多了,对于不同的计算公式采用不同的函数需要多看文档,这样才可以选用正确的api。
最后,如果需要获取全部代码,请看下我的github上仓库:https://github.com/Snail110/recsys
这里面是用tensorflow2.0框架来写。如果觉得我实现的还不错,记得给我一个星星哦。
来源:CSDN
作者:一碗竹叶青
链接:https://blog.csdn.net/baidu_38127162/article/details/103878278