推薦系統(tǒng)遇上深度學(xué)習(xí)(二十二)--DeepFM升級(jí)版XDeepFM模型強(qiáng)勢(shì)來襲!

秋招基本結(jié)束,讓我們繼續(xù)學(xué)習(xí)!長期有耐心!

今天我們要學(xué)習(xí)的模型是xDeepFM模型,論文地址為:https://arxiv.org/abs/1803.05170。文中包含我個(gè)人的一些理解,如有不對(duì)的地方,歡迎大家指正!廢話不多說,我們進(jìn)入正題!

1、引言

對(duì)于預(yù)測(cè)性的系統(tǒng)來說,特征工程起到了至關(guān)重要的作用。特征工程中,挖掘交叉特征是至關(guān)重要的。交叉特征指的是兩個(gè)或多個(gè)原始特征之間的交叉組合。例如,在新聞推薦場(chǎng)景中,一個(gè)三階交叉特征為AND(user_organization=msra,item_category=deeplearning,time=monday_morning),它表示當(dāng)前用戶的工作單位為微軟亞洲研究院,當(dāng)前文章的類別是與深度學(xué)習(xí)相關(guān)的,并且推送時(shí)間是周一上午。

傳統(tǒng)的推薦系統(tǒng)中,挖掘交叉特征主要依靠人工提取,這種做法主要有以下三種缺點(diǎn):

1)重要的特征都是與應(yīng)用場(chǎng)景息息相關(guān)的,針對(duì)每一種應(yīng)用場(chǎng)景,工程師們都需要首先花費(fèi)大量時(shí)間和精力深入了解數(shù)據(jù)的規(guī)律之后才能設(shè)計(jì)、提取出高效的高階交叉特征,因此人力成本高昂;
2)原始數(shù)據(jù)中往往包含大量稀疏的特征,例如用戶和物品的ID,交叉特征的維度空間是原始特征維度的乘積,因此很容易帶來維度災(zāi)難的問題;
3)人工提取的交叉特征無法泛化到未曾在訓(xùn)練樣本中出現(xiàn)過的模式中。

因此自動(dòng)學(xué)習(xí)特征間的交互關(guān)系是十分有意義的。目前大部分相關(guān)的研究工作是基于因子分解機(jī)的框架,利用多層全連接神經(jīng)網(wǎng)絡(luò)去自動(dòng)學(xué)習(xí)特征間的高階交互關(guān)系,例如FNN、PNN和DeepFM等。其缺點(diǎn)是模型學(xué)習(xí)出的是隱式的交互特征,其形式是未知的、不可控的;同時(shí)它們的特征交互是發(fā)生在元素級(jí)(bit-wise)而不是特征向量之間(vector-wise),這一點(diǎn)違背了因子分解機(jī)的初衷。來自Google的團(tuán)隊(duì)在KDD 2017 AdKDD&TargetAD研討會(huì)上提出了DCN模型,旨在顯式(explicitly)地學(xué)習(xí)高階特征交互,其優(yōu)點(diǎn)是模型非常輕巧高效,但缺點(diǎn)是最終模型的表現(xiàn)形式是一種很特殊的向量擴(kuò)張,同時(shí)特征交互依舊是發(fā)生在元素級(jí)上。

我們用下圖來回顧一下DCN的實(shí)現(xiàn):

下面是我對(duì)文中提到的兩個(gè)重要概念的理解:

bit-wise VS vector-wise
假設(shè)隱向量的維度為3維,如果兩個(gè)特征(對(duì)應(yīng)的向量分別為(a1,b1,c1)和(a2,b2,c2)的話)在進(jìn)行交互時(shí),交互的形式類似于f(w1 * a1 * a2,w2 * b1 * b2 ,w3 * c1 * c2)的話,此時(shí)我們認(rèn)為特征交互是發(fā)生在元素級(jí)(bit-wise)上。如果特征交互形式類似于 f(w * (a1 * a2 ,b1 * b2,c1 * c2))的話,那么我們認(rèn)為特征交互是發(fā)生在特征向量級(jí)(vector-wise)。

explicitly VS implicitly
顯式的特征交互和隱式的特征交互。以兩個(gè)特征為例xi和xj,在經(jīng)過一系列變換后,我們可以表示成 wij * (xi * xj)的形式,就可以認(rèn)為是顯式特征交互,否則的話,是隱式的特征交互。

微軟亞洲研究院社會(huì)計(jì)算組提出了一種極深因子分解機(jī)模型(xDeepFM),不僅能同時(shí)以顯式和隱式的方式自動(dòng)學(xué)習(xí)高階的特征交互,使特征交互發(fā)生在向量級(jí),還兼具記憶與泛化的學(xué)習(xí)能力。

我們接下來就來看看xDeepFM這個(gè)模型是怎么做的吧!

2、xDeepFM模型介紹

2.1 Compressed Interaction Network

為了實(shí)現(xiàn)自動(dòng)學(xué)習(xí)顯式的高階特征交互,同時(shí)使得交互發(fā)生在向量級(jí)上,文中首先提出了一種新的名為壓縮交互網(wǎng)絡(luò)(Compressed Interaction Network,簡(jiǎn)稱CIN)的神經(jīng)模型。在CIN中,隱向量是一個(gè)單元對(duì)象,因此我們將輸入的原特征和神經(jīng)網(wǎng)絡(luò)中的隱層都分別組織成一個(gè)矩陣,記為X0 和 Xk。CIN中每一層的神經(jīng)元都是根據(jù)前一層的隱層以及原特征向量推算而來,其計(jì)算公式如下:

其中點(diǎn)乘的部分計(jì)算如下:

我們來解釋一下上面的過程,第k層隱層含有H_k條神經(jīng)元向量。隱層的計(jì)算可以分成兩個(gè)步驟:(1)根據(jù)前一層隱層的狀態(tài)Xk 和原特征矩陣 X0,計(jì)算出一個(gè)中間結(jié)果 Zk+1,它是一個(gè)三維的張量,如下圖所示:

在這個(gè)中間結(jié)果上,我們用Hk+1 個(gè)尺寸為 m*Hk 的卷積核生成下一層隱層的狀態(tài),該過程如圖2所示。這一操作與計(jì)算機(jī)視覺中最流行的卷積神經(jīng)網(wǎng)絡(luò)大體是一致的,唯一的區(qū)別在于卷積核的設(shè)計(jì)。CIN中一個(gè)神經(jīng)元相關(guān)的接受域是垂直于特征維度D的整個(gè)平面,而CNN中的接受域是當(dāng)前神經(jīng)元周圍的局部小范圍區(qū)域,因此CIN中經(jīng)過卷積操作得到的特征圖(Feature Map)是一個(gè)向量,而不是一個(gè)矩陣。

如果你覺得原文中的圖不夠清楚的話,希望下圖可以幫助你理解整個(gè)過程:

CIN的宏觀框架可以總結(jié)為下圖:

可以看出,它的特點(diǎn)是,最終學(xué)習(xí)出的特征交互的階數(shù)是由網(wǎng)絡(luò)的層數(shù)決定的,每一層隱層都通過一個(gè)池化操作連接到輸出層,從而保證了輸出單元可以見到不同階數(shù)的特征交互模式。同時(shí)不難看出,CIN的結(jié)構(gòu)與循環(huán)神經(jīng)網(wǎng)絡(luò)RNN是很類似的,即每一層的狀態(tài)是由前一層隱層的值與一個(gè)額外的輸入數(shù)據(jù)計(jì)算所得。不同的是,CIN中不同層的參數(shù)是不一樣的,而在RNN中是相同的;RNN中每次額外的輸入數(shù)據(jù)是不一樣的,而CIN中額外的輸入數(shù)據(jù)是固定的,始終是X0。

可以看到,CIN是通過(vector-wise)來學(xué)習(xí)特征之間的交互的,還有一個(gè)問題,就是它為什么是顯式的進(jìn)行學(xué)習(xí)?我們先從X1 來開始看,X1 的第h個(gè)神經(jīng)元向量可以表示成:

進(jìn)一步,X^2的第h個(gè)神經(jīng)元向量可以表示成:

最后,第k層的第h個(gè)神經(jīng)元向量可以表示成:

因此,我們能夠通過上面的式子對(duì)特征交互的形式進(jìn)行一個(gè)很好的表示,它是顯式的學(xué)習(xí)特征交叉。

2.2 xDeepFM

將CIN與線性回歸單元、全連接神經(jīng)網(wǎng)絡(luò)單元組合在一起,得到最終的模型并命名為極深因子分解機(jī)xDeepFM,其結(jié)構(gòu)如下圖:

集成的CIN和DNN兩個(gè)模塊能夠幫助模型同時(shí)以顯式和隱式的方式學(xué)習(xí)高階的特征交互,而集成的線性模塊和深度神經(jīng)模塊也讓模型兼具記憶與泛化的學(xué)習(xí)能力。值得一提的是,為了提高模型的通用性,xDeepFM中不同的模塊共享相同的輸入數(shù)據(jù)。而在具體的應(yīng)用場(chǎng)景下,不同的模塊也可以接入各自不同的輸入數(shù)據(jù),例如,線性模塊中依舊可以接入很多根據(jù)先驗(yàn)知識(shí)提取的交叉特征來提高記憶能力,而在CIN或者DNN中,為了減少模型的計(jì)算復(fù)雜度,可以只導(dǎo)入一部分稀疏的特征子集。

3、Tensorflow充電

在介紹xDeepFM的代碼之前,我們先來進(jìn)行充電,學(xué)習(xí)幾個(gè)tf的函數(shù)以及xDeepFM關(guān)鍵過程的實(shí)現(xiàn)。

tf.split
首先我們要實(shí)現(xiàn)第一步:

如何將兩個(gè)二維的矩陣,相乘得到一個(gè)三維的矩陣?我們首先來看一下tf.split函數(shù)的原理:

tf.split(
    value,
    num_or_size_splits,
    axis=0,
    num=None,
    name='split'
)

其中,value傳入的就是需要切割的張量,axis是切割的維度,根據(jù)num_or_size_splits的不同形式,有兩種切割方式:

  1. 如果num_or_size_splits傳入的是一個(gè)整數(shù),這個(gè)整數(shù)代表這個(gè)張量最后會(huì)被切成幾個(gè)小張量。此時(shí),傳入axis的數(shù)值就代表切割哪個(gè)維度(從0開始計(jì)數(shù))。調(diào)用tf.split(my_tensor, 2,0)返回兩個(gè)10 * 30 * 40的小張量。
  2. 如果num_or_size_splits傳入的是一個(gè)向量,那么向量有幾個(gè)分量就分成幾份,切割的維度還是由axis決定。比如調(diào)用tf.split(my_tensor, [10, 5, 25], 2),則返回三個(gè)張量分別大小為 20 * 30 * 10、20 * 30 * 5、20 * 30 * 25。很顯然,傳入的這個(gè)向量各個(gè)分量加和必須等于axis所指示原張量維度的大小 (10 + 5 + 25 = 40)。

好了,從實(shí)際需求出發(fā),我們來體驗(yàn)一下,假設(shè)我們的batch為2,embedding的size是3,field數(shù)量為4。我們先來生成兩個(gè)這樣的tensor(假設(shè)X^k的field也是4 ):

arr1 = tf.convert_to_tensor(np.arange(1,25).reshape(2,4,3),dtype=tf.int32)
arr2 = tf.convert_to_tensor(np.arange(1,25).reshape(2,4,3),dtype=tf.int32)

生成的矩陣如下:

在經(jīng)過CIN的第一步之后,我們目標(biāo)的矩陣大小應(yīng)該是2(batch) * 3(embedding Dimension) * 4(X^k的field數(shù)) * 4(X^0的field數(shù))。如果只考慮batch中第一條數(shù)據(jù)的話,應(yīng)該形成的是 1 * 3 * 4 * 4的矩陣。忽略第0維,想像成一個(gè)長寬為4,高為3的長方體,長方體橫向切割,第一個(gè)橫截面對(duì)應(yīng)的數(shù)字應(yīng)該如下:

那么想要做到這樣的結(jié)果,我們首先按輸入數(shù)據(jù)的axis=2進(jìn)行split:

split_arr1 = tf.split(arr1,[1,1,1],2)
split_arr2 = tf.split(arr2,[1,1,1],2)
print(split_arr1)
print(sess.run(split_arr1))
print(sess.run(split_arr2))

分割后的結(jié)果如下:

通過結(jié)果我們可以看到,我們現(xiàn)在對(duì)每一條數(shù)據(jù),得到了3個(gè)4 * 1的tensor,可以理解為此時(shí)的tensor大小為 3(embedding Dimension) * 2(batch) * 4(X^k 或X^0的field數(shù)) * 1。

此時(shí)我們進(jìn)行矩陣相乘:

res = tf.matmul(split_arr1,split_arr2,transpose_b=True)

這里我理解的,tensorflow對(duì)3維及以上矩陣相乘時(shí),矩陣相乘只發(fā)生在最后兩維。也就是說,3 * 2 * 4 * 1 和 3 * 2 * 1 * 4的矩陣相乘,最終的結(jié)果是3 * 2 * 4 * 4。我們來看看結(jié)果:

可以看到,不僅矩陣的形狀跟我們預(yù)想的一樣,同時(shí)結(jié)果也跟我們預(yù)想的一樣。

最后,我們只需要進(jìn)行transpose操作,把batch轉(zhuǎn)換到第0維就可以啦。

res = tf.transpose(res,perm=[1,0,2,3])

這樣,CIN中的第一步就大功告成了,明白了這一步如何用tensorflow實(shí)現(xiàn),那么代碼你也就能夠順其自然的看懂啦!

這一塊完整的代碼如下:

import tensorflow as tf
import numpy as np

arr1 = tf.convert_to_tensor(np.arange(1,25).reshape(2,4,3),dtype=tf.int32)
arr2 = tf.convert_to_tensor(np.arange(1,25).reshape(2,4,3),dtype=tf.int32)


with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    split_arr1 = tf.split(arr1,[1,1,1],2)
    split_arr2 = tf.split(arr2,[1,1,1],2)
    print(split_arr1)
    print(sess.run(split_arr1))
    print(sess.run(split_arr2))
    res = tf.matmul(split_arr1,split_arr2,transpose_b=True)
    print(sess.run(res))
    res = tf.transpose(res,perm=[1,0,2,3])
    print(sess.run(res))

4、XDeepFM的TF實(shí)現(xiàn)

本文的代碼來自github地址:https://github.com/Leavingseason/xDeepFM
而我的github庫中也偷偷把這里面的代碼加進(jìn)去啦:https://github.com/princewen/tensorflow_practice/tree/master/recommendation/Basic-XDeepFM-Demo

真的是寫的非常好的一段代碼,希望大家可以比著自己敲一敲,相信你會(huì)有所收獲。

具體的代碼細(xì)節(jié)我們不展開進(jìn)行討論,我們只說一下數(shù)據(jù)的問題吧:
1、代碼中的數(shù)據(jù)按照ffm的格式存儲(chǔ),格式如下:filed:n th dimension:value,即這個(gè)特征屬于第幾個(gè)field,在所有特征全部按one-hot展開后的第幾維(而不是在這個(gè)field中是第幾維)以及對(duì)應(yīng)的特征值。
2、代碼中使用到的數(shù)據(jù)屬于多值的離散特征。

關(guān)于代碼實(shí)現(xiàn)細(xì)節(jié),我們這里只說一下CIN的實(shí)現(xiàn):

由于X^0 在每一層都有用到,所以我們先對(duì) X^0 進(jìn)行一個(gè)處理:

nn_input = tf.reshape(nn_input, shape=[-1, int(field_num), hparams.dim])
split_tensor0 = tf.split(hidden_nn_layers[0], hparams.dim * [1], 2)

在計(jì)算X^k 時(shí),我們需要用到 X^k-1 的數(shù)據(jù),代碼中用hidden_nn_layers保存這些數(shù)據(jù)。對(duì)X^k-1 進(jìn)行和X^0 同樣的處理:

split_tensor = tf.split(hidden_nn_layers[-1], hparams.dim * [1], 2)

接下來就是我們之前講過的,對(duì)兩個(gè)split之后的tensor進(jìn)行相乘再轉(zhuǎn)置的過程啦:

dot_result_m = tf.matmul(split_tensor0, split_tensor, transpose_b=True)
dot_result_o = tf.reshape(dot_result_m, shape=[hparams.dim, -1, field_nums[0]*field_nums[-1]])
dot_result = tf.transpose(dot_result_o, perm=[1, 0, 2])

接下來,我們需要進(jìn)行CIN的第二步,先回顧一下:

這里我們用1維卷積實(shí)現(xiàn),假設(shè)X^K的field的數(shù)量我們起名為layer_size:

filters = tf.get_variable(name="f_"+str(idx),
                     shape=[1, field_nums[-1]*field_nums[0], layer_size],
                     dtype=tf.float32)

curr_out = tf.nn.conv1d(dot_result, filters=filters, stride=1, padding='VALID')

此時(shí)我們curr_out的大小就是 Batch * Embedding Size * Layer size,我們需要進(jìn)行一下轉(zhuǎn)置:

curr_out = tf.transpose(curr_out, perm=[0, 2, 1])

接下來就是最后一步,進(jìn)行sumpooling,如下圖:

代碼中有兩種選擇方式,direct方式和非direct方式,direct方式,直接把完整curr_out作為最后輸出結(jié)果的一部分,同時(shí)把完整的curr_out作為計(jì)算下一個(gè)隱藏層向量的輸入。非direct方式,把curr_out按照layer_size進(jìn)行均分,前一半作為計(jì)算下一個(gè)隱藏層向量的輸入,后一半作為最后輸出結(jié)果的一部分。


if direct:
    hparams.logger.info("all direct connect")
    direct_connect = curr_out
    next_hidden = curr_out
    final_len += layer_size
    field_nums.append(int(layer_size))

else:
    hparams.logger.info("split connect")
    if idx != len(hparams.cross_layer_sizes) - 1:
        next_hidden, direct_connect = tf.split(curr_out, 2 * [int(layer_size / 2)], 1)
        final_len += int(layer_size / 2)
    else:
        direct_connect = curr_out
        next_hidden = 0
        final_len += layer_size
    field_nums.append(int(layer_size / 2))

final_result.append(direct_connect)
hidden_nn_layers.append(next_hidden)

最后 ,經(jīng)過sum_pooling操作,再拼接一個(gè)輸出層,我們就得到了CIN部分的輸出:

result = tf.concat(final_result, axis=1)
result = tf.reduce_sum(result, -1)

hparams.logger.info("no residual network")
w_nn_output = tf.get_variable(name='w_nn_output',
                              shape=[final_len, 1],
                              dtype=tf.float32)
b_nn_output = tf.get_variable(name='b_nn_output',
                              shape=[1],
                              dtype=tf.float32,
                              initializer=tf.zeros_initializer())
self.layer_params.append(w_nn_output)
self.layer_params.append(b_nn_output)
exFM_out = tf.nn.xw_plus_b(result, w_nn_output, b_nn_output)

5、總結(jié)

我們今天介紹的xDeepFM模型,由linear、DNN、CIN三部分組成,其中CIN實(shí)現(xiàn)了自動(dòng)學(xué)習(xí)顯式的高階特征交互,同時(shí)使得交互發(fā)生在向量級(jí)上。該模型在幾個(gè)數(shù)據(jù)集上都取得了超過DeepFM模型的效果。

參考文獻(xiàn)

1、論文:https://arxiv.org/abs/1803.05170
2、特征交互:一種極深因子分解機(jī)模型(xDeepFM):https://www.xianjichina.com/news/details_81731.html
3、https://blog.csdn.net/SangrealLilith/article/details/80272346
4、https://github.com/Leavingseason/xDeepFM

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本系列已經(jīng)寫了二十篇了,但推薦系統(tǒng)的東西還有很多值得探索和學(xué)習(xí)的地方。不過在這之前,我們先靜下心來,一起回顧下之前...
    文哥的學(xué)習(xí)日記閱讀 15,515評(píng)論 2 43
  • 文章主要分為:一、深度學(xué)習(xí)概念;二、國內(nèi)外研究現(xiàn)狀;三、深度學(xué)習(xí)模型結(jié)構(gòu);四、深度學(xué)習(xí)訓(xùn)練算法;五、深度學(xué)習(xí)的優(yōu)點(diǎn)...
    艾剪疏閱讀 22,216評(píng)論 0 58
  • 說出來慚愧,我沒上過高中和大學(xué),這使我的人生似乎也缺失了一些精彩,多了一些遺憾。我是從初中直接考了一所師范...
    芳華醉春風(fēng)閱讀 304評(píng)論 0 0
  • 今天中午放學(xué)后,媽媽帶我去接送站吃飯,我們吃飽了飯,就想回家了,可是媽媽沒開車來,我們得坐803路公交車回家,媽媽...
    靜如思閱讀 223評(píng)論 0 1
  • 從事教育工作這么多年以來,我發(fā)現(xiàn)很多父母在家庭教育方面存在畸形的思想。在各級(jí)的升學(xué)考試中,分?jǐn)?shù)成了父母衡量孩子是好...
    吳悅溪閱讀 323評(píng)論 0 0

友情鏈接更多精彩內(nèi)容