?簡介

最近忙於工作,沒怎麼看新的論文,今天把之前寫的一點記錄分享一下~

本文主要介紹新浪微博機器學習團隊發表在RecSys19上的一項工作:FiBiNET: Combining Feature Importance and Bilinear feature Interaction for Click-Through Rate Prediction

文章指出當前的許多通過特徵組合進行CTR預估的工作主要使用特徵向量的內積或哈達瑪積來計算交叉特徵,這種方法忽略了特徵本身的重要程度。提出通過使用Squeeze-Excitation network (SENET) 結構動態學習特徵的重要性以及使用一個雙線性函數來更好的建模交叉特徵。

下面對該模型進行一個簡單的介紹並提供核心代碼實現以及運行demo,細節問題請參閱論文。

模型結構

整體結構

圖中可以看到相比於我們熟悉的基於深度學習

的CTR預估模型,主要增加了SENET LayerBilinear-Interaction Layer兩個結構。下面就針對這兩個結構進行簡單的說明。

SENET Layer

SENET Layer的主要作用是學習不同特徵的一個重要程度,對重要特徵加權,對蘊含信息量不多的特徵進行削弱。對於該結構的更詳細的介紹可以參考論文Squeeze-and-Excitation Networks

該使用特徵組的embedding向量作為輸入,產生一個特徵組權重向量 A=[{a_1,...,a_i,...a_f}] ,最後將原始特徵組embedding向量 E 乘上 A 得到一組新的embedding向量 V=[{v_1,...,v_i,...v_f}] 具體來說,分為3個步驟:

Squeeze

這一步主要是對每個特徵組中的embedding向量進行匯總統計量的操作。文章使用了池化操作來對原始特徵組embedding向量 E=[e_1,...,e_f] 進行壓縮表示得到統計向量 Z=[z_1,...,z_i,...z_f] ,其中 z_i 表示第 i 個特徵的全局信息。 z_i 可以通過如下的平均池化的方式計算得到: z_i=F_{sq}(e_i)=frac{1}{k}sum_{t=1}^ke_i^{(t)} 當然,也可以使用最大池化的方式,文章表示平均池化效果要好於最大池化。

Excitation

這一步基於特徵組的壓縮統計量來學習特徵組的重要性權重,文章使用兩層的神經網路來學習。第一層為一個維度縮減層,第二層為維度提升層。形式化表示為: A=F_{ex}(Z)=sigma_2(W_2sigma_1(W_1Z)) ,其中 Ain R^f 是一個向量, sigma_1sigma_2 是激活函數,需要學習的參數為 W_1in R^{f 	imesfrac{f}{r}}W_2in R^{frac{f}{r} 	imes f}r 為縮減比例參數。

Re-Weight

這一步利用Excitation操作得到的特徵重要性權重來對原始的特徵組embedding向量重新賦權, 新的embedding向量通過如下的方式計算得到

V=F_{ReWeight}(A,E)=[a_1cdot e_1,...,a_fcdot e_f]=[v_1,...,v_f] ,其中 a_i in R,e_i in R^k,v_i in R^k

Bilinear-Interaction

傳統的特徵交叉方式廣泛採用了內積(fm,ffm等)和哈達瑪積(AFM,NFM等)。而這兩種方式在稀疏數據上很難有效對特徵交叉進行建模。 文章提出結合內積和哈達瑪積並引入一個額外的參數矩陣 W 來學習特徵交叉,

交叉向量 p_{ij} 可以通過一下三種方式計算得到:

Field-All Type

p_{ij}=v_icdot Wodot v_j 這種情況下,所有特徵組交叉時共享一個參數矩陣 W ,額外參數量為 k	imes k

Field-Each Type

p_{ij}=v_icdot W_iodot v_j 這種情況下,每個特徵組$i$維護一個參數矩陣 W_i ,額外參數量為 (f-1)	imes k	imes k

Filed-Interaction Type

p_{ij}=v_icdot W_{ij}odot v_j 每對交互特徵 p_{ij} 都有一個參數矩陣 W_{ij} ,額外參數量為 frac{f(f-1)}{2}	imes k	imes k

最終,交叉層由原始的特徵組embedding向量 E 以及SENET層輸出的embedding向量 V 分別得到交叉向量 p=[p_1,...,p_i,...p_n]q=[q_1,...,q_i,...q_n] ,其中 p_i,q_iin R^k 為向量。

Combination Layer

組合層將交叉向量 pq 進行拼接操作,得到結果向量 c=F_{concat}(p,q)=[p_1,...,p_i,...p_n,q_1,...,q_i,...q_n]=[c_1,...,c_{2n}] 。若直接對 c 向量中的元素進行求和並使用一個sigmoid函數輸出,則得到一個淺層的CTR預估模型,若將該向量輸入深度神經網路,則得到一個深度CTR預估模型。

實驗結果對比

文章在criteo和avazu兩個公開數據集上進行了大量的對比實驗,這裡只貼出FiBiNET相比於其他模型的一個對比結果,其他實驗細節請參閱論文~

淺層FiBiNET

深層FiBiNET

核心代碼

這邊只簡單貼一下運行部分的代碼,預處理和構造參數的代碼請參考 github.com/shenweichen/

SENET Layer

Z = tf.reduce_mean(inputs,axis=-1,)

A_1 = tf.nn.relu(self.tensordot([Z,self.W_1]))
A_2 = tf.nn.relu(self.tensordot([A_1,self.W_2]))
V = tf.multiply(inputs,tf.expand_dims(A_2,axis=2))

Bilinear Interaction Layer

if self.type == "all":
p = [tf.multiply(tf.tensordot(v_i,self.W,axes=(-1,0)),v_j) for v_i, v_j in itertools.combinations(inputs, 2)]
elif self.type == "each":
p = [tf.multiply(tf.tensordot(inputs[i],self.W_list[i],axes=(-1,0)),inputs[j]) for i, j in itertools.combinations(range(len(inputs)), 2)]
elif self.type =="interaction":
p = [tf.multiply(tf.tensordot(v[0],w,axes=(-1,0)),v[1]) for v,w in zip(itertools.combinations(inputs,2),self.W_list)]

運行用例

首先確保你的python版本為2.7,3.4,3.5或3.6,然後pip install deepctr[cpu]或者pip install deepctr[gpu], 再去下載一下demo數據 然後直接運行下面的代碼吧!

import pandas as pd
from sklearn.metrics import log_loss, roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder, MinMaxScaler

from deepctr.models import FiBiNET
from deepctr.inputs import SparseFeat, DenseFeat,get_fixlen_feature_names

if __name__ == "__main__":
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]

# 1.Label Encoding for sparse features,and do simple Transformation for dense features
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])

# 2.count #unique features for each sparse field,and record dense feature field name

fixlen_feature_columns = [SparseFeat(feat, data[feat].nunique())
for feat in sparse_features] + [DenseFeat(feat, 1,)
for feat in dense_features]

dnn_feature_columns = fixlen_feature_columns
linear_feature_columns = fixlen_feature_columns

fixlen_feature_names = get_fixlen_feature_names(linear_feature_columns + dnn_feature_columns)

# 3.generate input data for model

train, test = train_test_split(data, test_size=0.2)
train_model_input = [train[name] for name in fixlen_feature_names]

test_model_input = [test[name] for name in fixlen_feature_names]

# 4.Define Model,train,predict and evaluate
model = FiBiNET(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)
print("test LogLoss", round(log_loss(test[target].values, pred_ans), 4))
print("test AUC", round(roc_auc_score(test[target].values, pred_ans), 4))

參考文獻

  • FiBiNET: Combining Feature Importance and Bilinear feature Interaction for Click-Through Rate Prediction
  • Squeeze-and-Excitation Networks

推薦閱讀:

相關文章