文章目錄
-
- 公衆号
- 前言
- 1.向量内積與向量外積
-
- 1.1 向量内積Inner Product
- 1.2 向量外積Outer Product
- 1.3 向量點積Dot Product
- 1.4 向量叉積Cross Product
- 2. 模型結構
-
- 2.1 IPNN
- 2.2 OPNN
- 3. 代碼實戰
-
- 3.1 資料
- 3.3 内積和外積的實作
- 參考
公衆号
關注公衆号:推薦算法工程師,輸入"進群",加入交流群,和小夥伴們一起讨論機器學習,深度學習,推薦算法.
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicGcq5Cdhh2Yld3LcR3cp5Wbfd3bsZmcvNnblR3Lc5mbk9CXn5WauJXYlx2XwVWZk9CXzd2bsJ2Xy9mZfN3Ztl2LcJXZ0NXYt9CXvlmLiVHa0l2ZuYDb5d3LcZDb5d3Lc12bj5CduVGdu92YyV2c1JWdoRXan5ydhJ3Lc9CX6MHc0RHaiojIsJye.jpg)
前言
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中屬性取值的可枚舉個數為:
樣本x則被劃分為:
由于是模拟CTR預估,是以标簽y是二分類的,實驗中y∈{0,1}.
3.3 内積和外積的實作
設embedding的向量次元為k,num_inputs為n,num_pairs為p。内積的實作如下:
row = [] # num_pairs
col = [] # num_pairs
for i in range(num_inputs-1):
for j in range(i+1, num_inputs):
row.append(i)
col.append(j)
p = tf.transpose(
tf.gather(
tf.transpose(xw3d, [1,0,2]), # [num_inputs, batch, k]
row), # [num_pairs, batch, k]
[1,0,2]) # [batch, num_pairs, k]
q = tf.transpose(
tf.gather(
tf.transpose(xw3d, [1,0,2]), # [num_inputs, batch, k]
col), # [num_pairs, batch, k]
[1,0,2]) # [batch, num_pairs, k]
p = tf.reshape(p, [-1, num_pairs, embed_size]) # [batch, num_pairs, k]
q = tf.reshape(q, [-1, num_pairs, embed_size]) # [batch, num_pairs, k]
ip = tf.reshape(tf.reduce_sum(p*q, [-1]), [-1, num_pairs])
l = tf.concat([xw, ip], 1) # [num_inputs*k + num_pairs]
for i in range(len(layer_sizes)):
w = self.vars['w%d'%i]
b = self.vars['b%d'%i]
l = utils.activate(tf.matmul(l, w)+b, layer_acts[i])
l = tf.nn.dropout(l, self.layer_keeps[i])
外積部分并沒有直接求field_size*(field_size-1)/2個矩陣,而是借助了一個中間矩陣W(shape為(k,p,k))來實作,這樣求得的sum pooling是帶有權重的,當w全為1時,才是公式中直接的sum pooling。
row = [] # num_pairs
col = [] # num_pairs
for i in range(num_inputs-1):
for j in range(i+1, num_inputs):
row.append(i)
col.append(j)
p = tf.transpose(
tf.gather(
tf.transpose(xw3d, [1,0,2]), # [num_inputs, batch, k]
row), # [num_pairs, batch, k]
[1,0,2]) # [batch, num_pairs, k]
q = tf.transpose(
tf.gather(
tf.transpose(xw3d, [1,0,2]), # [num_inputs, batch, k]
col), # [num_pairs, batch, k]
[1,0,2]) # [batch, num_pairs, k]
p = tf.reshape(p, [-1, num_pairs, embed_size]) # [b, p, k]
q = tf.reshape(q, [-1, num_pairs, embed_size]) # [b, p, k]
# k全為1時,就是嚴格按照公式
p = tf.expand_dims(p, 1) # [batch, 1, p, k]
k = self.vars['kernel'] # [k, p, k]
ip = tf.multiply(k, p) # [batch, k, p, k]
ip = tf.reduce_sum(ip, axis=-1) # [batch, k, p]
ip = tf.transpose(ip, [0, 2, 1]) # [batch, p, k]
ip = tf.multiply(ip, q) # [batch, p, k]
ip = tf.reduce_sum(ip, axis=-1) # [batch, p]
l = tf.concat([xw, ip], 1) # [num_inputs*k + num_pairs]
for i in range(len(layer_sizes)):
w = self.vars['w%d'%i]
b = self.vars['b%d'%i]
l = utils.activate(tf.matmul(l, w)+b, layer_acts[i])
l = tf.nn.dropout(l, self.layer_keeps[i])
論文開源代碼:https://github.com/Atomu2014/product-nets
本文完整資料和代碼:https://github.com/wyl6/Recommender-Systems-Samples/tree/master/RecSys%20And%20Deep%20Learning/DNN/pnn
參考
[1] https://zhuanlan.zhihu.com/p/43693276
[2] https://baike.baidu.com/item/%E5%90%91%E9%87%8F%E5%A4%96%E7%A7%AF/11031485?fr=aladdin
[3] https://blog.csdn.net/minmindianzi/article/details/84820362