【CTR模型】TensorFlow2.0 的 DeepFM 实现与实战(附代码+数据)
CTR 系列文章:
本篇文章講解 DeepFM 的 tensorflow2.0 實現,并使用 Criteo 數據集的子集加以實踐。如果在看本文時有所困惑,可以看看DeepFM的相關理論: CTR深度學習模型之 DeepFM 模型解讀 。
本文使用的數據下載地址于代碼獲取地址在文末獲取。
首先了解一下 Criteo數據集,它由有39個特征,1個label列,其中以I開頭的為數值型特征,以C開頭的為類別特征:
可以看到數據中有缺失值需要填充,并且類別變量需要進行類別編碼(onehot 編碼的任務交給模型),這部分預處理的代碼不詳細講了。
而下面這張圖是 DeepFM 的網絡結構:
FM 部分
一階特征
要構建立此模型,第一步是要構造模型的輸入并且對各個輸入進行加權求和,如下圖綠色箭頭所示:
在 CTR 任務中,數據主要分為兩個類型,一種是數值型的連續變量(Dense type),另一種是類別型的離散數(Sparse type)。為了更方便的構造模型的輸入,我們先提取出不同類型的特征:
# 數值型 dense_feats = [f for f in cols if f[0] == "I"] # 類別型 sparse_feats = [f for f in cols if f[0] == "C"]對于數值型特征,構造其模型輸入與加權求和的代碼如下:
# 構造每個 dense 特征的輸入 dense_inputs = [] for f in dense_feats:_input = Input([1], name=f)dense_inputs.append(_input) # 將輸入拼接到一起,方便連接 Dense 層 concat_dense_inputs = Concatenate(axis=1)(dense_inputs) # ?, 13 # 然后連上輸出為1個單元的全連接層,表示對 dense 變量的加權求和 fst_order_dense_layer = Dense(1)(concat_dense_inputs) # ?, 1上面代碼的注釋中 ? 表示輸入數據的 batch_size。
對于每一個 sparse 特征,一般都是進行one-hot以后再轉化為embedding特征,但實際上由于稀疏性的存在,很多位置的 xix_ixi? 取0時,對應的 wixiw_i x_iwi?xi? 也為0。因此,可以將 sparse 特征 embedding 到 1維,然后通過 embedding lookup 的方式來找到對應的 wiw_iwi? 。
這里舉個例子:假設我們的性別特征取值有-1,0,1三種,某個樣本的取值為1,則其one-hot以后為[0, 0, 1]向量,我們進行線性回歸時會得到 w1×0+w2×0+w3×1w_1 \times 0 + w_2 \times 0 +w_3 \times 1w1?×0+w2?×0+w3?×1 ,僅僅只有 w3w_3w3? 被保留下來。因此,可以對性別構造一個 3*1 的 embedding 向量,然后通過 embedding lookup 得到系數。
相關 embedding 與加權求和的代碼如下:
# 這里單獨對每一個 sparse 特征構造輸入, # 目的是方便后面構造二階組合特征 sparse_inputs = [] for f in sparse_feats:_input = Input([1], name=f)sparse_inputs.append(_input)sparse_1d_embed = [] for i, _input in enumerate(sparse_inputs):f = sparse_feats[i]voc_size = total_data[f].nunique()# 使用 l2 正則化防止過擬合reg = tf.keras.regularizers.l2(0.5)_embed = Embedding(voc_size, 1, embeddings_regularizer=reg)(_input)# 由于 Embedding 的結果是二維的,# 因此如果需要在 Embedding 之后加入 Dense 層,則需要先連接上 Flatten 層_embed = Flatten()(_embed)sparse_1d_embed.append(_embed) # 對每個 embedding lookup 的結果 wi 求和 fst_order_sparse_layer = Add()(sparse_1d_embed)到這里為止,分別完成了對 Dense 特征與 Sparse 特征的加權求和,接下來就是將二者的結果再求和:
linear_part = Add()([fst_order_dense_layer, fst_order_sparse_layer])到這里,上圖中綠色箭頭表示的加權求和的功能就實現了。
二階特征組合
對于 FM 模塊而言,在進行特征組合之前,每個 sparse 特征需要先進行 embedding,如下圖中綠色方框所示:
相關代碼如下:
# embedding size k = 8# 只考慮sparse的二階交叉 sparse_kd_embed = [] for i, _input in enumerate(sparse_inputs):f = sparse_feats[i]# 注意,nan 不會被 nunique() 所統計voc_size = total_data[f].nunique()reg = tf.keras.regularizers.l2(0.7)_embed = Embedding(voc_size, k, embeddings_regularizer=reg)(_input)sparse_kd_embed.append(_embed)接下來就是要進行特征組合,如果對 n 個 sparse 特征兩兩組合,那么復雜度應該是 O(n2)O(n^2)O(n2) ,但是可以對特征組合的公式加以化簡:
∑i=1n∑j=i+1n<vi,vj>xixj=12∑i=1n∑j=1n<vi,vj>xixj?12∑i=1n<vi,vi>xixi=12(∑i=1n∑j=1n∑f=1kvifvjfxixj?∑i=1n∑f=1kvifvifxixi)=12(∑f=1k∑i=1nvifxi∑j=1nvjfxj?∑i=1n∑f=1kvifvifxixi)=12∑f=1k((∑i=1nvifxi)2?∑i=1nvif2xi2)\begin{aligned} & \sum_{i=1}^n{\sum_{j=i+1}^n{<v_i,v_j>x_ix_j}} \\ & = \frac{1}{2}\sum_{i=1}^n{\sum_{j=1}^n{<v_i,v_j>x_ix_j}}-\frac{1}{2}\sum_{i=1}^n{<v_i,v_i>x_ix_i}\\ & = \frac{1}{2}\left(\sum_{i=1}^n{\sum_{j=1}^n{\sum_{f=1}^k{v_{if}v_{jf}x_ix_j}}}-\sum_{i=1}^n{\sum_{f=1}^k{v_{if}v_{if}x_ix_i}}\right) \\ & = \frac{1}{2}\left(\sum_{f=1}^k{\sum_{i=1}^n{v_{if}x_i\sum_{j=1}^n{v_{jf}x_j}}}-\sum_{i=1}^n{\sum_{f=1}^k{v_{if}v_{if}x_ix_i}}\right) \\ &= \frac{1}{2}\sum_{f=1}^k\left(\left(\sum_{i=1}^n{v_{if}x_i}\right)^2-\sum_{i=1}^n{v_{if}^2x_i^2}\right) \end{aligned} ?i=1∑n?j=i+1∑n?<vi?,vj?>xi?xj?=21?i=1∑n?j=1∑n?<vi?,vj?>xi?xj??21?i=1∑n?<vi?,vi?>xi?xi?=21????i=1∑n?j=1∑n?f=1∑k?vif?vjf?xi?xj??i=1∑n?f=1∑k?vif?vif?xi?xi????=21????f=1∑k?i=1∑n?vif?xi?j=1∑n?vjf?xj??i=1∑n?f=1∑k?vif?vif?xi?xi????=21?f=1∑k????(i=1∑n?vif?xi?)2?i=1∑n?vif2?xi2?????
這樣一來,是復雜度就降低為: O(kn)O(kn)O(kn)。如果你對這部分的知識感興趣,可以參考我之前的這篇文章:CTR經典模型串講:FM / FFM / 雙線性 FFM 相關推導與理解。由于只對 sparse 特征進行特征組合,因此 xix_ixi? 非 0 即 1,因此實際需要計算的只有 xi=1x_i = 1xi?=1 的數據,于是公式又可以表示為:
∑i=1n∑j=i+1n<vi,vj>xixj=12∑f=1k((∑i=1nvif)2?∑i=1nvif2)\begin{aligned} & \sum_{i=1}^n{\sum_{j=i+1}^n{<v_i,v_j>x_ix_j}} \\ & = \frac{1}{2}\sum_{f=1}^k\left(\left(\sum_{i=1}^n{v_{if}}\right)^2-\sum_{i=1}^n{v_{if}^2}\right) \end{aligned} ?i=1∑n?j=i+1∑n?<vi?,vj?>xi?xj?=21?f=1∑k????(i=1∑n?vif?)2?i=1∑n?vif2?????
好,接下來就是用代碼實現公式的計算,首先進行 (∑i=1nvif)2\left(\sum_{i=1}^n{v_{if}}\right)^2(∑i=1n?vif?)2 這部分的計算:
# 1.將所有 sparse 特征 (?, 1, k)的embedding拼接起來, # 得到 (?, n, k)的矩陣,其中n為特征數,k為embedding大小 concat_sparse_kd_embed = Concatenate(axis=1)(sparse_kd_embed) # ?, n, k# 2.先求和再平方 sum_kd_embed = Lambda(lambda x: K.sum(x, axis=1))(concat_sparse_kd_embed) # ?, k square_sum_kd_embed = Multiply()([sum_kd_embed, sum_kd_embed]) # ?, k然后是的計算 ∑i=1nvif2\sum_{i=1}^n{v_{if}^2}∑i=1n?vif2?:
# 3.先平方再求和 square_kd_embed = Multiply()([concat_sparse_kd_embed, concat_sparse_kd_embed]) # ?, n, k sum_square_kd_embed = Lambda(lambda x: K.sum(x, axis=1))(square_kd_embed) # ?, k最后是完整公式的計算:
# 4.相減除以2 sub = Subtract()([square_sum_kd_embed, sum_square_kd_embed]) # ?, k sub = Lambda(lambda x: x*0.5)(sub) # ?, k snd_order_sparse_layer = Lambda(lambda x: K.sum(x, axis=1, keepdims=True))(sub) # ?, 1DNN 部分
這部分的結構如下圖所示:
這部分主要是全連接層為主,用于實現高階的特征組合:
flatten_sparse_embed = Flatten()(concat_sparse_kd_embed) # ?, n*k fc_layer = Dropout(0.5)(Dense(256, activation='relu')(flatten_sparse_embed)) # ?, 256 fc_layer = Dropout(0.3)(Dense(256, activation='relu')(fc_layer)) # ?, 256 fc_layer = Dropout(0.1)(Dense(256, activation='relu')(fc_layer)) # ?, 256 fc_layer_output = Dense(1)(fc_layer) # ?, 1完善模型
接下來就是將 FM 和 DNN 部分的輸出組合起來,構成完整的模型:
output_layer = Add()([linear_part, snd_order_sparse_layer, fc_layer_output]) output_layer = Activation("sigmoid")(output_layer)model = Model(dense_inputs+sparse_inputs, output_layer) model.compile(optimizer="adam", loss="binary_crossentropy", metrics=["binary_crossentropy", tf.keras.metrics.AUC(name='auc')])模型訓練
相關代碼如下:
train_data = total_data.loc[:500000-1] valid_data = total_data.loc[500000:]train_dense_x = [train_data[f].values for f in dense_feats] train_sparse_x = [train_data[f].values for f in sparse_feats] train_label = [train_data['label'].values]val_dense_x = [valid_data[f].values for f in dense_feats] val_sparse_x = [valid_data[f].values for f in sparse_feats] val_label = [valid_data['label'].values]model.fit(train_dense_x+train_sparse_x, train_label, epochs=5, batch_size=256,validation_data=(val_dense_x+val_sparse_x, val_label),)最后,本文的代碼鏈接在:https://github.com/zxxwin/tf2_deepfm 。
數據下載地址為:鏈接:https://pan.baidu.com/s/1Qy3yemu1LYVtj0Wn47myHQ 提取碼:pv7u
參考文章:
CTR預估模型:DeepFM/Deep&Cross/xDeepFM/AutoInt代碼實戰與講解
NELSONZHAO/zhihu/ctr_models/DeepFM
總結
以上是生活随笔為你收集整理的【CTR模型】TensorFlow2.0 的 DeepFM 实现与实战(附代码+数据)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 银行一类卡和二类卡的区别(招商银行一类卡
- 下一篇: 【CTR模型】TensorFlow2.0