xDeepFM架構理解及實現

  • 2019 年 12 月 19 日
  • 筆記

本文主要是為了講解xDeepFM的框架,及如何用tensorflow去實現主幹部分,如果需要直接拆箱可用,可以參考:xDeepFM,我的部分程式碼也來自於其中,這邊主要是和大家一起對比著看下,xDeepFM到底做了哪些事情?我的工程實現程式碼等待我司項目上線穩定後開源。

XDeepFM到底extreme在哪裡?

首先,我在做論壇帖子推薦的時候遇到這麼一個問題(問題真實,問題內容純屬虛構),用戶A:帶有如下標籤[籃球、足球、健身],用戶B:帶有如下標籤[籃球,電腦,蔡徐坤],在使用deepfm做精排的時候,常常會把A和B所看的內容互相推薦,很明顯,A是運動達人,而B是二次元達人,這樣推薦是存在很大問題的。 我在處理的時候,採取了兩種套路:

  • 改變Memorization為attention網路,強化feature直接的聯繫,讓B中的電腦與蔡徐坤進行綁定,而不是讓籃球電腦蔡徐坤進行混合綁定,讓Memorization去記憶的時候進行權重傾斜
  • Memorization通常為低階特徵交互,那我就升高階數,svm告訴我們,在越高的維度上我們越有可能把數據集進行越離散的切分,XDeepFM就相當於把DeepFM中的1維+2維,變成了1維+2維+(l+1)維特徵組合

XDeepFM如何實現的?

網上大多數版本都是甩出的正方體圖+公式,我覺得很不清晰,這邊就不貼了,我直接貼程式碼及解釋:

nn_input = tf.reshape(nn_input, shape=[-1, int(field_num), self.hparams.dim])

我們知道,無論是deepfm還是XDeepFM在初始化的時候,都會把feature進行onehotencoding後向量化,然後再壓縮成一個[batch,dim*FIELD_COUNT]的input,這邊只是將其還原成網上常見的正方體的形式:[batch,field_num,dim]

split_tensor0 = tf.split(hidden_nn_layers[0], self.hparams.dim * [1], 2)

這個就是XDeepFM最有意思的地方,我們正常理解都是把特徵按照不同的特徵進行切分組合,在XDeepFM它卻按照dim進行切開然後進行點擊,而且是外積,所以大家難以理解的就在這邊,用數據來說就是:

tf.convert_to_tensor(np.arange(1,13).reshape(2,2,3),dtype=tf.int32)    array([[[ 1,  2,  3],          [ 4,  5,  6]],           [[ 7,  8,  9],          [10, 11, 12]]], dtype=int32)  切割完為:  [array([[[ 1],           [ 4]],            [[ 7],           [10]]], dtype=int32),             array([[[ 2],           [ 5]],            [[ 8],           [11]]], dtype=int32),             array([[[ 3],           [ 6]],            [[ 9],           [12]]], dtype=int32)]

這邊沒有按照[1,2,3]這種方式去切分,而是按照[[1],[4]]這種方式,在dim上進行切分。

dot_result_m = tf.matmul(split_tensor0, split_tensor, transpose_b=True)這一步也是很有意思,沒有常規意義上做點擊dot,做的外積,生生的把向量鋪開了:

array([[[[  1,   4],           [  4,  16]],            [[ 49,  70],           [ 70, 100]]],             [[[  4,  10],           [ 10,  25]],            [[ 64,  88],           [ 88, 121]]],             [[[  9,  18],           [ 18,  36]],            [[ 81, 108],           [108, 144]]]], dtype=int32)

把剛才的[[1],[4]]按照外積的形式去處理了,並得到了一個[bacth_size,dim,field_nums[0] * field_nums[-1]]的形式。再通過[1,field_nums[-1] * field_nums[0],output]進行卷積處理相當於在dim的每一維上進行融合,融合的是所有特徵dim的每一維。最後才sum_pooling操作,外接一個輸出層壓縮到1個輸出值。

CIN為什麼要搞這麼複雜,比deepfm好在哪?

看程式碼就知道,剛才CIN的過程可以進行N次,

image

這裡面的l代表著從0層開始了l次的特徵組合,比起deepfm的2次,對特徵的組合更深,能解釋的空間更為複雜

CIN很完美嗎?

雖然作者在論文剛開始的時候就吐槽了DCN的低端,認為DCN其實就是init層的N次交叉,但是我認為DCN的殘差項保證了特徵的1~l+1特徵都有,而CIN中去除了殘差項,雖然更快了,但是相當於丟棄了1~l階特徵的組合結果,不見得可取

實操經驗分享?

  • XDeepFM比起循環神經網路是快了不止一個量級的,基於DNN+CNN這種架構下,並行策略保證它很高效的執行速度。
  • 如果是baseline的嘗試,建議優先DeepFM,絕大多數情況下,DeepFM已經滿足了我們的基礎需求。我司實際項目的效果下XDeepFM在離線數據集上目前也只有0.1%的提升,但是程式碼量及code review的壓力卻大了很多。
  • 在走XDeepFM的路子時候,可以考慮把DeepFM中的Memorization層加入DIN中的Attention,完全不遜色。

更多程式碼內容歡迎follow我的個人Github,如果有任何演算法、程式碼疑問都歡迎通過郵箱發消息給我。