首页 > 编程知识 正文

deepfm算法,deepfm代码详解

时间:2023-05-03 10:06:31 阅读:158136 作者:1504

参考:

源代码: https://github.com/chenglong Chen/tensor flow-deep FM

下载: https://arxiv.org/abs/1703.04247

观察原文可知,deepfm由FM、deep两部分构成,两部分共有一组输入,以下介绍也是从这两个方面展开的。

1.fm部分FM部分我前面的文章https://blog.csdn.net/Guan Bai 4146/article/details/80139806已经介绍,在此简要提及。

从上面的公式(在此,提到与下面的weights(feature_bias ) )和weights (feature _ embedding s ) )相对应的两个不同的参数)可以看出

1.1训练稀疏特征输入法参数,首先,数值型特征可以直接输入到模型中。 (除了必要的变换操作外,fm主要解决稀疏特征问题,这里主要指性别等类别型特征。 在模型中输入这些特征时,特征展开(即一列展开为两列)是必需的。 具体请参考之前博客的题外话。

现在让我列举这一点。 因为这贯穿于整个实现过程。 这里性别为field,展开的2列用feature (以下特征域)和特征)表示)。

1.2调频结构

上面的照片是从原文上剪下的。

主要分为四个部分。 空间功能层、深度嵌入层、调频层和输出层。 图中不同的符号和线段表示不同的计算逻辑。

上一篇文章已经介绍过了,有必要在这里提及。 fm的特点是隐向量的概念

假设有三个变量,他们交叉有三个组合。 其中,是组合特征域的权重参数。 这里直接用特征域来表现。 很容易理解。 如果这三个特征域都是类别特征,容易发生什么现象?

例如,如果矢量内积为0,则使用梯度下降查找参数时,无法训练得到的参数值,输入特征原本就非常稀疏,因此问题变得更加严重。 所以这里引入了隐向量的概念来解决稀疏性问题,

具体来说,假设(这里全部是向量,将维度设为k,将k的大小设定为过参数化),即使是正交的,也可以在之后接受训练,可以大幅度缓和数据稀疏引起的问题。

因此,这里的所有特征参数都用k维的隐藏向量表示。 在理解了上述说明之后,我们来看看如何实现。 以下所有代码都来自源代码DeepFM文件。 这里对每行的代码进行注释和个人理解的说明)。

首先声明两个占位符。 这些占位符在训练中用数据填充

# None * F,None *特征主尺寸,特征索引占位符self.feat _ index=TF.placeholder (TF.int 32,shape=[None 模态占位符(输入的样本数据(self.feat_value=TF.placeholder ) TF.float32,shape=[None,None],name=' feat _ te )

年龄性别25男26女24男

其中,字段有【年龄、性别】2个,feature有【年龄、男性、女性】3个值,预处理部分构建特征索引表【年龄: 0,男性: 1,女性: 2】

首先,由于feat_index数据格式在预处理过程中将原始采样值处理为索引,因此每个采样表的索引表如下所示:

年龄性别010201feat_value是输入的样本值(量化类别特征,并且将相应feature的可能值设为1 )。

年龄男女261261241注意:这里每行的样本都是两列。 (与域维相同。 虽然类别类型特征对应的feature不同,例如第一行中的第二列示例表示男性特征,第二行中的第二列示例表示女性特征。 他们的权重通过feat_index中每行的索引相关联。

接下来,我们将讨论每一层代码的开发。

1.2.1从sparse feature层到dense embedding层: # None * F * K样本数*特征区域数*隐藏向量长,最后得到的嵌入式s维(none,none,8 ) self 、39、1 )输入特征值的维度feat _ value=TF.reshape (self.feat _ value,shape=[-1,self.field_size,1]#?39、8 ) w * xself.embeddings=tf.multi

ply(self.embeddings, feat_value)

注:解释的时候都以一条样本输入说明,代码注释中的?和None都表示样本数

第一行解释:

self.weights["feature_embeddings"]是特征的权重参数,注意这里是特征的权重,参看之前那篇文章关于fm和ffm区别的叙述中,原始的fm模型对每个特征都是有一个特征权重的(注意是特征不是特征域)。具体定义如下 # feature_size:特征长度,embedding_size:隐向量长度weights["feature_embeddings"] = tf.Variable(tf.random_normal([self.feature_size, self.embedding_size], 0.0, 0.01),name="feature_embeddings") embedding_lookup(params, ids)表示按后面ids从params里面选择对应索引的值,也就是说从按照特征索引(feat_index)从特征权重集(weights[feature_embeddings])中选择对应的特征权重。仍然以上面的例子说明,

weights[feature_embeddings]就是【年龄(0)的权重,男(1)的权重,女(2)的权重】,以第一个样本索引【0,1】为例,就是选出了【年龄(0)的权重,男(1)的权重】

第二到三行解释:

第二行主要是二维向三维的转换,变成:样本数 * 特征域数 * 1,以一条样本为例就是上面的feat_value的第一行【25,0】

第三行是矩阵对应元素相乘,也就是每个特征权重乘以样本特征值,仍然以第一条样本为例就是【25 * 年龄的权重,0 * 男的权重】

从图中也能看出这两层之间也就是权重连接的关系,维度大小并没有变化。

1.2.2 FM层:

从结构图中我们也可以看到fm的数据源来自前面两层,sparse和dense都有数据输入。

从前文和公式我们也能知道,fm分为一阶项()和二阶项()两部分,

先说一阶项:

# None * 特征域数 * 1self.y_first_order = tf.nn.embedding_lookup(self.weights["feature_bias"], self.feat_index)# None * 特征域数 axis=2最内层元素维度的加和self.y_first_order = tf.reduce_sum(tf.multiply(self.y_first_order, feat_value), 2)# None * 特征域数# 第一层dropoutself.y_first_order = tf.nn.dropout(self.y_first_order, self.dropout_keep_fm[0])

第一行和上面类似,就是选择对应输入特征的权重值(中的),不做赘述,其中weights["feature_bias"]的定义如下:

weights["feature_bias"] = tf.Variable(tf.random_uniform([self.feature_size, 1], 0.0, 1.0), name="feature_bias")

接下来第二行和第三行就是做对应的运算()和添加dropout层。

二阶项:

# sum_square part 和平方,None * K 就是w_1 * x_1... + w_n * x_n...self.summed_features_emb = tf.reduce_sum(self.embeddings, 1)self.summed_features_emb_square = tf.square(self.summed_features_emb) # None * K 所有特征加和平方 (w_1 * x_1 + w_n * x_n...)^2# square_sum part 平方和self.squared_features_emb = tf.square(self.embeddings)self.squared_sum_features_emb = tf.reduce_sum(self.squared_features_emb, 1) # None * K# second order (和平方-平方和) / 2对应元素相减,得到(x_1 * y_1 + ... + x_n * y_n)二阶特征交叉,二阶项本质是各向量交叉后求和的值,所以维度是Kself.y_second_order = 0.5 * tf.subtract(self.summed_features_emb_square, self.squared_sum_features_emb) # None * Kself.y_second_order = tf.nn.dropout(self.y_second_order, self.dropout_keep_fm[1]) # None * K fm第二层的dropout

代码就是实现下面的计算逻辑:

2. Deep部分

模型结构如下:

原始的deepfm深度部分使用的就是普通的dnn结构,如下代码:

# None * (F*K) (样本数, 特征域长度 * 隐向量长度)self.y_deep = tf.reshape(self.embeddings, shape=[-1, self.field_size * self.embedding_size])self.y_deep = tf.nn.dropout(self.y_deep, self.dropout_keep_deep[0])for i in range(0, len(self.deep_layers)): self.y_deep = tf.add(tf.matmul(self.y_deep, self.weights["layer_%d" %i]), self.weights["bias_%d"%i]) # None * layer[i] * 1 (wx+b) if self.batch_norm: # 对参数批正则化 self.y_deep = self.batch_norm_layer(self.y_deep, train_phase=self.train_phase, scope_bn="bn_%d" %i) # None * layer[i] * 1 self.y_deep = self.deep_layers_activation(self.y_deep) # 激活层 # dropout at each Deep layer 每层都有一个dropout参数 self.y_deep = tf.nn.dropout(self.y_deep, self.dropout_keep_deep[1+i])

首先将dense层输出进行reshape维度变换(将特征域对应的向量拉直平铺),添加dropout层

深度结构一共两层,循环部分分别添加以下步骤:、批正则化(可选)、激活层、dropout层,正常的dnn结构。

上面使用的权重参数(),声明如下:

# num_layer:dnn层数num_layer = len(self.deep_layers)# 特征域个数 * 隐向量长度input_size = self.field_size * self.embedding_size# 标准差glorot设定参数的标准,标准差=sqrt(2/输入维度+输出维度)glorot = np.sqrt(2.0 / (input_size + self.deep_layers[0]))# layer_0是和dense层输出做计算,所以参数维度是[特征域个数*隐向量长]weights["layer_0"] = tf.Variable(np.random.normal(loc=0, scale=glorot, size=(input_size, self.deep_layers[0])), dtype=np.float32)weights["bias_0"] = tf.Variable(np.random.normal(loc=0, scale=glorot, size=(1, self.deep_layers[0])), dtype=np.float32) # 1 * layers[0]for i in range(1, num_layer): glorot = np.sqrt(2.0 / (self.deep_layers[i-1] + self.deep_layers[i])) # layer_1第2层 32*32, layers[i-1] * layers[i] weights["layer_%d" % i] = tf.Variable(np.random.normal(loc=0, scale=glorot, size=(self.deep_layers[i-1], self.deep_layers[i])), dtype=np.float32) # 1 * layer[i] weights["bias_%d" % i] = tf.Variable(np.random.normal(loc=0, scale=glorot, size=(1, self.deep_layers[i])), dtype=np.float32)

按空行分界,先说空行上半部分

deep_layer列表存储的是两层dnn的输出维度,glorot是参数初始化的一种理论,这里不赘述。

深度部分的输入就是dense embedding层的输出,第一层计算的时候进行了reshape拉直处理,所以参数的输入大小(input_size)=特征域个数 * 隐向量长度,这里就是分别声明权重和偏置项。

再说空行下半部分,这里声明的时候range是从1开始的,所以这里是从dnn的第二层开始声明对应的参数变量(,每一层输出维度的超参存储在deep_layer中)。

以上,fm部分和dnn部分都已经解释清楚了,接下来就是收尾。

3.FM和Deep的组合收尾

先看下整体的结构图(以上结构图均截自原文):

最后这部分就是将fm和deep部分组合到一起,也就是低阶特征和高阶特征的组合,最后计算输出,先看下代码:

# fm的一阶项、二阶项、深度输出项拼接,变成 样本数 * 79concat_input = tf.concat([self.y_first_order, self.y_second_order, self.y_deep], axis=1)# 最后全连接层权重 * 输出 + 偏置self.out = tf.add(tf.matmul(concat_input, self.weights["concat_projection"]), self.weights["concat_bias"])

第一行就是一阶项+二阶项(fm部分)、deep部分拼接(拉直铺平)

第二行就是最后的全连接层,计算逻辑就是。至此,deepfm结构实现完毕。

4.总结

deepfm已经在工业界得到了普遍的应用,写这篇分享的目的也是希望自己能够从理论到实践有一个更深入的理解,不当之处还望多多指正。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。