FNN 网络介绍与源码浅析
前言
周五晚上分享 paper !!! 感动自己一把~ 😂 感觉本周看的 Paper 还挺多的, 打算尽可能都做下记录, 方便日后查阅.
广而告之
可以在微信中搜索 “珍妮的算法之路” 或者 “world4458” 关注我的微信公众号;另外可以看看知乎专栏 PoorMemory-机器学习, 以后文章也会发在知乎专栏中;
文章信息
- 论文标题: Deep Learning over Multi-field Categorical Data – A Case Study on User Response Prediction
- 论文地址: https://arxiv.org/abs/1601.02376
- 代码地址: https://github.com/wnzhang/deep-ctr, 这是作者提供的基于 Theano 的代码; 另外我在 https://github.com/Atomu2014/product-nets/blob/master/python/models.py (PNN 作者的实现) 以及 https://github.com/shenweichen/DeepCTR/blob/master/deepctr/models/fnn.py (浅梦的实现) 看到了基于 TF 的实现, 所有后续关于代码介绍采用的 PNN 作者的实现. (关于 PNN 的介绍, 可以查看我的博客 Product-based Neural Network (PNN) 介绍与源码浅析
- 发表时间: European Conference on Information Retrieval, 2016
- 论文作者: Weinan Zhang, Tianming Du, and Jun Wang
- 作者单位: University College London
核心观点
本文主要介绍了两个网络 FNN (FM supported NN) 以及 SNN (sampling-based NN), 我重点看了下 FNN 而不关心 SNN. FNN 的核心是在设计 Embedding Layer 时, 使用的参数量和 FM 一致, 这样的话, 可以通过预训练 FM, 然后使用 FM 的参数来初始化 Embedding Layer. 由于存在预训练的过程,
所以降低了整个网络的训练过程中的开销, 模型可以更快的收敛. 在 xDeepFM 论文中有提到 FNN 的不足: FNN (PNN 亦如此) 只关注于高阶的特征交叉, 而忽视了低阶的交叉特征. (Wide & Deep 与 DeepFM 通过引入线性层(shallow component), 和 DNN (deep component) 结合来解决低阶交叉特征被忽视的问题.)
核心观点介绍
FNN 的网络结构图如下:
可以发现, 主要还是经典的 Embedding + MLP 结构. 其核心在于设计 Embedding Layer 时, 按照 FM 的思路来设置. 回忆 FM 的公式是:
y F M ( x ) : = sigmoid ( w 0 + ∑ i = 1 N w i x i + ∑ i = 1 N ∑ j = i + 1 N ⟨ v i , v j ⟩ x i x j ) y_{\mathrm{FM}}(\boldsymbol{x}):=\operatorname{sigmoid}\left(w_{0}+\sum_{i=1}^{N} w_{i} x_{i}+\sum_{i=1}^{N} \sum_{j=i+1}^{N}\left\langle\boldsymbol{v}_{i}, \boldsymbol{v}_{j}\right\rangle x_{i} x_{j}\right) yFM(x):=sigmoid(w0+i=1∑Nwixi+i=1∑Nj=i+1∑N⟨vi,vj⟩xixj)
那么 FNN 在设计 Embedding 时, 设置每个 Field 对应的 Embedding 权重为 W i ∈ R ( K + 1 ) × ( e n d i − s t a r t i + 1 ) \bm{W}^i\in\mathbb{R}^{(K + 1)\times (\mathrm{end}_i - \mathrm{start}_i + 1)} Wi∈R(K+1)×(endi−starti+1), 其中 s t a r t i \mathrm{start}_i starti 和 e n d i \mathrm{end}_i endi 是第 i i i 个 Field 下的特征的起始和结束索引值. 注意到每个特征的 emb z i \bm{z}_i zi 大小为 K + 1 K + 1 K+1, 它表示为:
z i = W i ⋅ x [ start i : end i ] = ( w i , v i 1 , v i 2 , … , v i K ) \boldsymbol{z}_{i}=\boldsymbol{W}^{i} \cdot \boldsymbol{x}\left[\operatorname{start}_{i}: \operatorname{end}_{i}\right]=\left(w_{i}, v_{i}^{1}, v_{i}^{2}, \ldots, v_{i}^{K}\right) zi=Wi⋅x[starti:endi]=(wi,vi1,vi2,…,viK)
它可以看成由两部分组成, 其中 w i w_{i} wi 正好和 FM 中的一阶特征对应的权重对应起来, 而 ( v i 1 , v i 2 , … , v i K ) \left(v_{i}^{1}, v_{i}^{2}, \ldots, v_{i}^{K}\right) (vi1,vi2,…,viK) 和 FM 中每个特征对应的隐向量对应起来了. 这样的话. 另外注意到网络结构图中还有一个 w 0 w_0 w0, 可以认为是偏置. 这样的, Embedding Layer 中的权重和 FM 中的权重参数一致, 因此可以使用预训练的 FM 中的权重来初始化网络的 Embedding Layer.
在 https://github.com/Atomu2014/product-nets/blob/master/python/models.py 中实现了 FNN: (这个代码是 PNN 的作者实现的, 有的细节可以查看 Product-based Neural Network (PNN) 介绍与源码浅析, 哈哈, 随时随地打广告 😂 😂 😂, 原作者的代码基于 Theano 实现, 不太想看~~)
核心代码如下, 非常简单:
## num_inputs 是 Field 的个数
for i in range(num_inputs):
init_vars.append(('embed_%d' % i, [field_sizes[i], embed_size], 'xavier', dtype))
node_in = num_inputs * embed_size
for i in range(len(layer_sizes)):
init_vars.append(('w%d' % i, [node_in, layer_sizes[i]], 'xavier', dtype))
init_vars.append(('b%d' % i, [layer_sizes[i]], 'zero', dtype))
node_in = layer_sizes[i]
## xw 就是 W * x, 得到每个特征对应的 Embedding, 然后 Concat 起来
## 最后输入到 MLP 中
w0 = [self.vars['embed_%d' % i] for i in range(num_inputs)]
xw = tf.concat([tf.sparse_tensor_dense_matmul(self.X[i], w0[i]) for i in range(num_inputs)], 1)
l = xw
for i in range(len(layer_sizes)):
wi = self.vars['w%d' % i]
bi = self.vars['b%d' % i]
print(l.shape, wi.shape, bi.shape)
l = tf.nn.dropout(
utils.activate(
tf.matmul(l, wi) + bi,
layer_acts[i]),
self.layer_keeps[i])
l = tf.squeeze(l)
self.y_prob = tf.sigmoid(l)
结论
夜里 12:06, 收摊喽~~ 😂