文章目录
-
公众号
- 前言
- 1.向量内积与向量外积
-
1.1 向量内积Inner Product
* 1.2 向量外积Outer Product
* 1.3 向量点积Dot Product
* 1.4 向量叉积Cross Product-
- 模型结构
-
-
2.1 IPNN
* 2.2 OPNN-
- 代码实战
-
-
3.1 数据
* 3.3 内积和外积的实现- 参考
公众号
前言
PNN模型是上交大和UCL(University College London)在ICDM 2016上提出的。product思想来源于,在ctr预估中,认为特征之间的关系更多是一种and“且”的关系,而非add"加”的关系。例如,性别为男且喜欢游戏的人群,比起性别男和喜欢游戏的人群,前者的组合比后者更能体现特征交叉的意义[1]。这和FM的特征交叉思想类似。FM是进行one-hot编码特征的交叉,而PNN则是将特征embedding后,加入向量积product层,从而进行特征间的交叉。那么这个Product到底是如何实现的呢?论文的公式写得有点复杂,本文从简介绍,阅读大概需要三分钟。
论文链接:https://arxiv.org/abs/1807.00311 或 https://arxiv.org/pdf/1611.00144.pdf
1.向量内积与向量外积
1.1 向量内积Inner Product
Inner Product就是a,b两个向量对应元素相乘的和[3],或者对a的转置与b使用矩阵乘法:
1.2 向量外积Outer Product
Outer Product就是列向量与行向量相乘,结果是一个矩阵[3]:
1.3 向量点积Dot Product
点积有个别名,叫内积,又叫标量积,向量的积。只不过,提起内积我们常想到的是1.1中的代数定义,而提起点击,常想到的是如下的几何定义[3]:
1.4 向量叉积Cross Product
向量积,数学中又称外积、叉积,物理中称矢积、叉乘,是一种在向量空间中向量的二元运算[2],外积的方向根据右手法则确定[3]:
2. 模型结构
首先是Input层,比如一个样本有三个field,男/女,胖/瘦,高/矮,每个field的取值数分别有2,2,2个,那么形如(0,1,0,1,0,1)的样本就是一条输入x。
Embedding layer就是将x进行映射,比如映射后的维度embed_size为k,假设每个field只有一个非零值,那么embed_x的维度就是field_size*k=3k维。
前两层大家都很熟悉,重点看下product层。product层的左侧z是线性部分,将embedding layer层获得的特征串联起来就是z。右边p是特征交叉部分,根据交叉方式不同,product层分为两种,第一种是采取向量内积的IPNN,第二种是采取向量外积的OPNN。
2.1 IPNN
回顾一下,向量内积,简单说就是两个向量相乘,返回一个实数值的运算。假设field_size为n,那么通过内积获得的特征维度node_size=n(n-1)/2。因此,当使用Inner Product内积时,Product Layer的特征维度就是:field_sizeembed_size+field_size*(field_size-1)/2**。
2.2 OPNN
回顾一下,向量外积Outer product,两个向量相乘返回一个矩阵。通过外积可以在Product Layer获得的矩阵个数为:field_size*(field_size-1)/2。内积的结果可以直接串联,拼接到特征上,矩阵该如何拼接呢?论文中使用了sum pooling的方法,对每个矩阵进行sum求和,得到实数值,然后拼接到z上:
因此,使用Outer Product外积时,Product Layer的特征维度仍然为:field_sizeembed_size+field_size(field_size-1)/2。
3. 代码实战
3.1 数据
论文中使用的是Criteo和iPinyou数据,太大了。本文使用一个小数据来测试,数据和之前fnn博客中的相同。共有22个field,各field中属性取值的可枚举个数为:
1
2
3 1FIELD_SIZES = [1037, 151, 59, 1603, 4, 333, 77890, 1857, 9, 8, 4, 7, 22, 3, 92, 56, 4, 920, 38176, 240, 2697, 4]
2
3
样本x则被划分为:
由于是模拟CTR预估,所以标签y是二分类的,实验中y∈{0,1}.
3.3 内积和外积的实现
设embedding的向量维度为k,num_inputs为n,num_pairs为p。内积的实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 1 row = [] # num_pairs
2 col = [] # num_pairs
3 for i in range(num_inputs-1):
4 for j in range(i+1, num_inputs):
5 row.append(i)
6 col.append(j)
7
8 p = tf.transpose(
9 tf.gather(
10 tf.transpose(xw3d, [1,0,2]), # [num_inputs, batch, k]
11 row), # [num_pairs, batch, k]
12 [1,0,2]) # [batch, num_pairs, k]
13
14 q = tf.transpose(
15 tf.gather(
16 tf.transpose(xw3d, [1,0,2]), # [num_inputs, batch, k]
17 col), # [num_pairs, batch, k]
18 [1,0,2]) # [batch, num_pairs, k]
19
20 p = tf.reshape(p, [-1, num_pairs, embed_size]) # [batch, num_pairs, k]
21 q = tf.reshape(q, [-1, num_pairs, embed_size]) # [batch, num_pairs, k]
22
23 ip = tf.reshape(tf.reduce_sum(p*q, [-1]), [-1, num_pairs])
24 l = tf.concat([xw, ip], 1) # [num_inputs*k + num_pairs]
25
26 for i in range(len(layer_sizes)):
27 w = self.vars['w%d'%i]
28 b = self.vars['b%d'%i]
29 l = utils.activate(tf.matmul(l, w)+b, layer_acts[i])
30 l = tf.nn.dropout(l, self.layer_keeps[i])
31
32
外积部分并没有直接求field_size*(field_size-1)/2个矩阵,而是借助了一个中间矩阵W(shape为(k,p,k))来实现,这样求得的sum pooling是带有权重的,当w全为1时,才是公式中直接的sum pooling。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 1 row = [] # num_pairs
2 col = [] # num_pairs
3 for i in range(num_inputs-1):
4 for j in range(i+1, num_inputs):
5 row.append(i)
6 col.append(j)
7
8 p = tf.transpose(
9 tf.gather(
10 tf.transpose(xw3d, [1,0,2]), # [num_inputs, batch, k]
11 row), # [num_pairs, batch, k]
12 [1,0,2]) # [batch, num_pairs, k]
13
14 q = tf.transpose(
15 tf.gather(
16 tf.transpose(xw3d, [1,0,2]), # [num_inputs, batch, k]
17 col), # [num_pairs, batch, k]
18 [1,0,2]) # [batch, num_pairs, k]
19
20 p = tf.reshape(p, [-1, num_pairs, embed_size]) # [b, p, k]
21 q = tf.reshape(q, [-1, num_pairs, embed_size]) # [b, p, k]
22
23 # k全为1时,就是严格按照公式
24 p = tf.expand_dims(p, 1) # [batch, 1, p, k]
25 k = self.vars['kernel'] # [k, p, k]
26 ip = tf.multiply(k, p) # [batch, k, p, k]
27 ip = tf.reduce_sum(ip, axis=-1) # [batch, k, p]
28 ip = tf.transpose(ip, [0, 2, 1]) # [batch, p, k]
29 ip = tf.multiply(ip, q) # [batch, p, k]
30 ip = tf.reduce_sum(ip, axis=-1) # [batch, p]
31
32 l = tf.concat([xw, ip], 1) # [num_inputs*k + num_pairs]
33
34 for i in range(len(layer_sizes)):
35 w = self.vars['w%d'%i]
36 b = self.vars['b%d'%i]
37 l = utils.activate(tf.matmul(l, w)+b, layer_acts[i])
38 l = tf.nn.dropout(l, self.layer_keeps[i])
39
40
论文开源代码:https://github.com/Atomu2014/product-nets
本文完整数据和代码:https://github.com/wyl6/Recommender-Systems-Samples/tree/master/RecSys%20And%20Deep%20Learning/DNN/pnn