推薦排序模型3——DeepFM及python(DeepCTR)實現

CTR(Click-Through-Rate)即點擊通過率,是互聯網廣告常用的術語,指網絡廣告(圖片廣告/文字廣告/關鍵詞廣告/排名廣告/視頻廣告等)的點擊到達率,即該廣告的實際點擊次數除以廣告的展現量。
CTR是衡量互聯網廣告效果的一項重要指標。
CTR預估數據特點:
1)輸入中包含類別型和連續型數據。類別型數據需要one-hot,連續型數據可以先離散化再one-hot,也可2)以直接保留原值
3)維度非常高
4)數據非常稀疏
5)特徵按照Field分組
CTR預估重點在於學習組合特徵。注意,組合特徵包括二階、三階甚至更高階的,階數越高越複雜,越不容易學習。
Google的論文研究得出結論:高階和低階的組合特徵都非常重要,同時學習到這兩種組合特徵的性能要比只考慮其中一種的性能要好。在DeepFM提出之前,已有LR,FM,FFM,FNN,PNN(以及三種變體:IPNN,OPNN,PNN*),Wide&Deep模型,這些模型在CTR或者是推薦系統中被廣泛使用。本篇重點總結DeepFM算法。

FM系列模型1——FM、FFM及python(xlearn)實現


1,概述

LR/SVM這樣的模型無法建模非線性的特徵交互(interaction),我們只能人工組特徵的交叉或者組合,比如通過數據分析發現時間和食譜類的app存在比較強的關聯關係,然後把這個特徵加到線性模型裏去。這種方法的確定是需要人工做特徵工程,成本很高,而且很多複雜的特徵交互很難發現,就像啤酒和紙尿布這樣的關係。

FM通過給每個特徵引入一個低維稠密的隱向量來減少二階特徵交互的參數個數,同時實現信息共享,使得在非常稀疏的特徵向量上的學習更加容易泛化。理論上FM可以建模二階以上的特徵交互,但是由於計算量急劇增加,實際一般只用二階的模型。

而(深度)神經網絡的優勢就是可以多層的網絡結構更好的進行特徵表示,從而學習高階的非線性關係。DeepFM的思想就是結合FM在一階和二階特徵的簡潔高效和深度學習在高階特徵交互上的優勢,同時通過共享FM和DNN的Embedding來減少參數量和共享信息,從而得到更好的模型。

2,DeepFM算法

特點:FM提取低階組合特徵,Deep提取高階組合特徵。端到端完成,不需要人工特徵。而且共享feature_embedding,FM和Deep共享輸入和feature embedding不但使得訓練更快,而且使得訓練更加準確。

假設訓練數據的個數爲n,每一個訓練樣本爲(x,y)(x,y),其中(X由m個field組成,而y∈0,1表示用戶是否點擊。(X的field可以是category的field,比如性別和地域等;也可以是連續的field,比如年齡。總的field個數可能只有幾百個,但是如果用one-hot編碼展開的話,這個特徵向量會非常高維並且稀疏。我們把每一個xix_i記爲xi=[xfield1,xfield2,,xfieldm]x_i=[x_{field1},x_{field2},…,x_{fieldm}],其中每一個xfieldix_{fieldi}是其向量表示(連續的field就是1維的,而Category的field則用one-hot展開)。

DeepFM包括FM和DNN兩個部分,最終的預測由兩部分輸出的相加得到:

y^=sigmoid(yFM+yDNN)\hat{y} = sigmoid(y_{FM}+y_{DNN})
其中yFMy_{FM}是FM模型的輸出,而yDNNy_{DNN}是DNN的輸出。模型的結構如下圖所示,它包含FM和DNN兩個組件。DeepFM如下圖所示:
DeepFM

2.1, FM 部分

DeepFM 拆成兩部分來看,首先是FM:
在這裏插入圖片描述
yFM=w,x+j1=1dj2=j1+1dVi,Vjxj1xj2\begin{aligned} {y}_{FM} = & \langle w,x \rangle+ \sum_{j_1=1}^{d}\sum_{j_2=j_1+1}^{d}\langle V_i ,V_j\rangle x_{j_1}x_{j_2} \end{aligned}

這幅圖如何與公式對應起來呢?addition Unit反映的是1階的特徵,inner product反映的是二階特徵。可能你會說,從公式上看這應該是個標量,爲什麼反映在圖上是個向量呢?這裏稍有不同,一階特徵和二階特徵該怎麼提取還是怎麼提取,不過最後沒有加起來,而是並聯在了一起。實際實現中採用的是FM化簡之後的內積公式,最終的維度是:field_size + embedding_size,我們把這兩個維度解釋一下,就可以理解整個流程了。

1)field size
我們先看一階特徵的提取。首先我們明確x是one-hot之後的結果,是一階特徵,其權值就是w,假設one-hot之後特徵數量是feature_size,那麼W的維度就是 (feature_size, 1)。這裏 是把X和W每一個位置對應相乘相加。由於X是one-hot之後的,所以相當於是進行了一次Embedding,X在W上進行一次嵌入,或者說是一次選擇,選擇的是W的行,按照X中不爲0的那些特徵對應的index,選擇W中row=index的行。

對於每一個Field都執行這樣的操作,就選出來了XiX_i Embedding之後的表示。注意到,每個Field都肯定會選出且僅選出W中的某一行,因爲W的列數是固定的,每一個Field都選出W.cols作爲對應的新特徵。

把每個Field選出來的這些W的行,拼接起來就得到了X Embedding後的新的表示:維度是num(Field) * num(W.cols)。雖然每個Field的長度可能不同,但是都是在W中選擇一行,所以選出來的長度是相同的。**爲什麼?因爲每個field是one-hot的不管field多寬,一個樣本只會選一行,這也是Embedding的一個特性:雖然輸入的Field長度不同,但是Embedding之後的長度是相同的。**如果num(W.cols)爲1(W.cols可以看做是編碼維度),那麼特徵維度就是num(Field)即field size。

綜上黑線部分是一個全連接,權重即W,至於addition, 其實並沒有什麼加法,看做一階特徵輸出就行了。

2)embedding_size

我們知道二階特徵提取部分的公式是可以簡化的,:
在這裏插入圖片描述
k是什麼?k是v的維度,也就是embedding的大小
這裏最後的結果中是在[1,K]上的一個求和。 K就是w(w可以分解爲v)的列數,就是Embedding後的維度,也就是embedding_size。也就是說,在DeepFM的FM模塊中,最後沒有對結果從[1,K]進行求和。而是把這K個數拼接起來形成了一個K維度的向量,如上圖所示。

所以這裏得到的是維度爲embedding_size的特徵向量,綜合field size部分得到上圖FM layer部分。

因此FM需要訓練的有兩部分:
1) input_vector和Addition Unit相連的全連接層,也就是1階的Embedding矩陣
2)Sparse Feature到Dense Embedding的Embedding矩陣,中間也是全連接的,要訓練的是中間的權重矩陣,這個權重矩陣也就是隱向量V(全連接的w分解得到v,v也可合併成w)

FM Component總結:

1)FM模塊實現了對於1階和2階組合特徵的建模。
2)沒有使用預訓練
3)沒有人工特徵工程
4)embedding矩陣的大小是:特徵數量 * 嵌入維度, 然後用一個index表示選擇了哪個特徵。

2.2,DNN部分

DNN 部分如下,在deepctr中默認的DNN是包含兩層128神經元的神經網絡。

在這裏插入圖片描述

Deep Component是用來學習高階組合特徵的。網絡裏面黑色的線是全連接層,參數需要神經網絡去學習。

由於CTR或推薦系統的數據one-hot之後特別稀疏,如果直接放入到DNN中,參數非常多,我們沒有這麼多的數據去訓練這樣一個網絡。所以增加了一個Embedding層,用於降低緯度。

這裏繼續補充下Embedding層,兩個特點:
1)儘管輸入的長度不同,但是映射後長度都是相同的.embedding_size 或 k(FM中有講到,一個field不管多長,都只選一行)
2)embedding層的參數其實是全連接的Weights,是通過神經網絡自己學習到的,值得注意的是:FM模塊和Deep模塊是共享feature embedding的,也就是參數V。

下圖爲embedding層示意圖,起到壓縮稠密左右,k也是向量v的維度。
在這裏插入圖片描述

3,DeepFM實現

3.1,數據準備

這裏使用了學術界常用的Criteo數據集,這是一個展示廣告的數據集,有Criteo提供,曾經是Kaggle的一個比賽,下載地址

簡單看下數據:

     label   I1  I2     I3  ...       C23       C24       C25       C26
0        0  NaN   3  260.0  ...  3a171ecb  c0d61a5c       NaN       NaN
1        0  NaN  -1   19.0  ...  be7c41b4  ded4aac9       NaN       NaN
2        0  0.0   0    2.0  ...  bcdee96c  6d5d1302       NaN       NaN
3        0  NaN  13    1.0  ...  32c7478e  3b183c5c       NaN       NaN
4        0  0.0   0  104.0  ...  32c7478e  0d4a6d1a  001f3601  92c878de

其中第1列是真實的標籤,0表示沒有點擊而1表示點擊。第2-14列是13個連續的特徵,第15-40列是26個Category的特徵。

3.2, DeepCTR簡介

接下來使用的代碼主要採用開源的DeepCTR,相應的API文檔可以在這裏閱讀。DeepCTR是一個易於使用、模塊化和可擴展的深度學習CTR模型庫,它內置了很多核心的組件從而便於我們自定義模型,它兼容tensorflow 1.4+和2.0+。除了DeepFM,DeepCTR還包括更多更新的模型:
在這裏插入圖片描述

DeepCTR 的安裝很簡單,使用pip即可,包括 pip install deepctr[cpu] 和 pip install deepctr[gpu]兩種。

3.2.1 導入模型,初始化數據

#導入模型
import pandas as pd
from sklearn.preprocessing import LabelEncoder, MinMaxScaler
from sklearn.model_selection import train_test_split
from deepctr.models import DeepFM
from deepctr.inputs import  SparseFeat, DenseFeat,get_feature_names

data = pd.read_csv('./criteo_sample.txt')

sparse_features = ['C' + str(i) for i in range(1, 27)]
dense_features = ['I'+str(i) for i in range(1, 14)]

data[sparse_features] = data[sparse_features].fillna('-1', )
data[dense_features] = data[dense_features].fillna(0,)
target = ['label']

這是一個帶表頭的csv文件,通過pandas讀取成列(features),其中C1-C26是Category的特徵(sparse_features),而I1-I13是連續的特徵(dense_features)。因爲有缺失的值,我們把Category的缺失值設置爲”-1”,而把連續的缺失值設置爲0。

3.2.2, 數據預處理

接下來需要將category類型的數據轉換成數值型,將連續型數據歸一化:

for feat in sparse_features:
    lbe = LabelEncoder()
    data[feat] = lbe.fit_transform(data[feat])
    
mms = MinMaxScaler(feature_range=(0,1))
data[dense_features] = mms.fit_transform(data[dense_features])

數據處理後大概是這個樣子:

     label        I1        I2        I3        I4  ...  C22  C23  C24  C25  C26
0        0  0.000000  0.001332  0.092362  0.000000  ...    0    1   96    0    0
1        0  0.000000  0.000000  0.006750  0.402299  ...    0    7  112    0    0
2        0  0.000000  0.000333  0.000710  0.137931  ...    0    6   53    0    0
3        0  0.000000  0.004664  0.000355  0.045977  ...    0    0   32    0    0
4        0  0.000000  0.000333  0.036945  0.310345  ...    0    0    5    1   47

3.2.3, 生成feature_columns

feature_columns是一些結構化的信息,包含特徵名稱、embedding維度等,這裏將稠密特徵信息(連續特徵)與稀疏特徵信息(類別特徵)分開。

sparse_feature_columns = [SparseFeat(feat, vocabulary_size=data[feat].nunique(),embedding_dim=4)
                           for i,feat in enumerate(sparse_features)]
# 或者hash,vocabulary_size通常要大一些,以避免hash衝突太多
# sparse_feature_columns = [SparseFeat(feat, vocabulary_size=1e6,embedding_dim=4,use_hash=True)
#                            for i,feat in enumerate(sparse_features)]#The dimension can be set according to data
dense_feature_columns = [DenseFeat(feat, 1)
                      for feat in dense_features]
                      
dnn_feature_columns = sparse_feature_columns + dense_feature_columns
linear_feature_columns = sparse_feature_columns + dense_feature_columns
feature_names = get_feature_names(linear_feature_columns + dnn_feature_columns)

3.2.4,訓練模型

train, test = train_test_split(data, test_size=0.2)

train_model_input = {name:train[name].values for name in feature_names}
test_model_input = {name:test[name].values for name in feature_names}

model = DeepFM(linear_feature_columns,dnn_feature_columns,task='binary')
model.compile("adam", "binary_crossentropy",
              metrics=['binary_crossentropy'], )

history = model.fit(train_model_input, train[target].values,
                    batch_size=256, epochs=10, verbose=2, validation_split=0.2, )
pred_ans = model.predict(test_model_input, batch_size=256)

首先定義好訓練測試樣本,然後用DeepFM類定義一個deepfm模型,需要傳入的參數是linear_feature_columns和dnn_feature_columns。還有一個參數task=’binary’表明這是一個二分類的問題。接下來是Keras的標準流程:compile、fit和predict。

參考文獻:
https://www.sohu.com/a/251772910_633698
http://fancyerii.github.io/2019/12/19/deepfm/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章