前言

今天主要通過兩篇論文介紹如何將CNN應用在傳統的結構化數據預測任務中,盡量以精簡的語言說明主要問題,並提供代碼實現和運行demo,細節問題請參閱論文。

  • CIKM15 《A Convolutional Click Prediction Model》
  • WWW19《Feature Generation by Convolutional Neural Network for Click-Through Rate Prediction》

CNN在計算機視覺領域佔據著主導地位,在自然語言處理領域也有廣泛的應用。基於點擊率預測任務和自然語言處理中一些任務的相似性(大規模稀疏特徵),NLP的一些方法和CTR預測任務的方法其實也是可以互通的。

A Convolutional Click Prediction Model

模型結構

主要思想

通過一個(width, 1)的kernel進行對特徵的embedding矩陣進行二維卷積,其中width表示的每次對連續width個特徵進行卷積運算,之後使用一個Flexible pooling機制進行池化操作進行特徵聚合和壓縮表示,堆疊若干層後將得到特徵矩陣作為MLP的輸入,得到最終的預測結果。

這裡解釋兩個問題

  1. 為什麼強調是連續width個特徵進行卷積

我們都知道CNN之所以在CV領域大放異彩是由於其具有如下特性

  • 參數共享 通常一個特徵檢測子(如邊緣檢測)在圖像某一部位有用也在其他部位生效。
  • 稀疏連接 每一層的輸出只依賴於前一層一小部分的輸入

在NLP任務中由於語句天然存在前後依賴關係,所以使用CNN能獲得一定的特徵表達,那麼在CTR任務中使用CNN能獲得特徵提取的功能嗎?

答案是能,但是效果可能沒有那麼好,問題就出在卷積是對連續的width個特徵進行計算,這導致了我們輸入特徵的順序發生變化就會引起結果的變化,而在CTR任務中,我們的特徵輸入是沒有順序的。

這相當於我們給了一個先驗在裡面,就是連續的width個特徵進行組合更具有意義。

雖然我們可以使用類似空洞卷積的思想增加感受野來使得卷積計算的時候跨越多個特徵,但是這仍然具有一定的隨機性。 所以使用CNN進行CTR任務的特徵提取的一個難點就在於其計算的是局部特徵組合,無法有效捕捉全局組合特徵。

2. Flexible pooliong是什麼?

其實就是Max Pooling,只不過每次沿某一維度取p個最大的,不是1個最大的。 p的取值根據當前池化層數和總層數自適應計算,其中i是當前層數,l是總層數

核心代碼

這裡就簡單貼一下卷積池化還有最後全連接層的對應代碼,完整的代碼請參考

CCPM?

github.com

for i in range(1, l + 1):
filters = conv_filters[i - 1]
width = conv_kernel_width[i - 1]
k = max(1, int((1 - pow(i / l, l - i)) * n)) if i < l else 3

conv_result = tf.keras.layers.Conv2D(filters=filters, kernel_size=(width, 1), strides=(1, 1), padding=same,
activation=tanh, use_bias=True, )(pooling_result)
pooling_result = KMaxPooling(
k=min(k, conv_result.shape[1].value), axis=1)(conv_result)

Feature Generation by Convolutional Neural Network for Click-Through Rate Prediction

文章的主要貢獻點有2個:

  • 使用重組層進行特徵生成緩解了CCPM中CNN無法有效捕獲全局組合特徵的問題
  • FGCNN作為一種特徵生成方法,可以和任意模型進行組合

模型結構

  • 分組嵌入

由於原始特徵既要作為後續模型的輸入,又要作為FGCNN模塊的輸入,所以原始特徵的embedding向量可能會遇到梯度耦合的問題。 這裡對於FGCNN模塊使用一套獨立的embedding向量,避免梯度耦合的問題。

  • 卷積層和池化層

卷積和池化和CCPM類似,池化層使用的是普通的Max Pooling。

  • 重組層

我們之前提到了,使用CNN進行CTR任務的特徵提取的一個難點就在於其計算的是局部特徵組合。所以這裡作者提出使用一個重組的機制來生成全局組合特徵,做法是將池化後的Feature Maps( S^i )展平成一個向量,然後使用單層的神經網路進行特徵組合,輸出維度 R^i 受超參數控制。

  • 拼接層

經過若干重組後,將重組後生成的特徵拼接上原始的特徵作為新的輸入,後面可以使用各種其他的方法,如LR,FM,DeepFM等。

實驗結果對比

  • IPNN-FGCNN於其他stoa模型的對比

  • 作為特徵生成模型的效果

核心代碼

這裡分兩部分介紹,一個是FGCNN的特徵生成模塊,一個使用FGCNN進行特徵擴充的IPNN介紹。

FGCNN模塊

embedding_size = inputs.shape[-1].value
pooling_result = tf.keras.layers.Lambda(lambda x: tf.expand_dims(x, axis=3))(inputs)

new_feature_list = []

for i in range(1, len(self.filters) + 1):
filters = self.filters[i - 1]
width = self.kernel_width[i - 1]
new_filters = self.new_maps[i - 1]
pooling_width = self.pooling_width[i - 1]
conv_result = tf.keras.layers.Conv2D(filters=filters, kernel_size=(width, 1), strides=(1, 1),padding=same,activation=tanh, use_bias=True, )(pooling_result)
pooling_result = tf.keras.layers.MaxPooling2D(pool_size=(pooling_width, 1))(conv_result)
flatten_result = tf.keras.layers.Flatten()(pooling_result)
new_result = tf.keras.layers.Dense(pooling_result.shape[1].value * embedding_size * new_filters,
activation=tanh, use_bias=True)(flatten_result)
new_feature_list.append(tf.keras.layers.Reshape((pooling_result.shape[1].value * new_filters, embedding_size))(new_result))
new_features = concat_fun(new_feature_list, axis=1)

使用FGCNN進行特徵擴充的IPNN

完整代碼請參考

FGCNN?

github.com

  • 特徵分組嵌入

根據輸入特徵分別得到deep_emb_listfg_deep_emb_list,其中fg_deep_emb_list用於FGCNN模塊的輸入。

deep_emb_list, fg_deep_emb_list, linear_logit, inputs_list = preprocess_input_embedding(feature_dim_dict,embedding_size,l2_reg_embedding,l2_reg_linear, init_std, seed, True)

  • CNN特徵生成模塊 通過封裝好的FGCNNLayer,生成CNN提取的組合特徵new_features

new_features = FGCNNLayer(conv_filters, conv_kernel_width, new_maps, pooling_width)(fg_input)

  • 拼接層 將原始embedding輸入和新特徵拼接,生成組合輸入combined_input

combined_input = concat_fun([origin_input, new_features], axis=1)

  • 交叉層 這部分可以看作是一個獨立的模型,論文裏是用的IPNN模型,其實這裡可以自由的替換成任意結構,deepctr.layers.interaction裡面的大部分層都可以在這裡使用。

inner_product = tf.keras.layers.Flatten()(InnerProductLayer()(
tf.keras.layers.Lambda(unstack, mask=[None] * combined_input.shape[1].value)(combined_input)))
linear_signal = tf.keras.layers.Flatten()(combined_input)
dnn_input = tf.keras.layers.Concatenate()([linear_signal, inner_product])
dnn_input = tf.keras.layers.Flatten()(dnn_input)

final_logit = DNN(dnn_hidden_units, dropout_rate=dnn_dropout,
l2_reg=l2_reg_dnn)(dnn_input)
final_logit = tf.keras.layers.Dense(1, use_bias=False)(final_logit)
output = PredictionLayer(task)(final_logit)

model = tf.keras.models.Model(inputs=inputs_list, outputs=output)

運行用例

首先確保你的python版本為2.7,3.4,3.5或3.6,然後pip install deepctr(如果已經安裝了tensorflow-gpu,請使用命令 pip install deepctr --no-deps,), 再去下載一下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 FGCNN
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 = FGCNN( 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))

參考資料

  • A Convolutional Click Prediction Model
  • Feature Generation by Convolutional Neural Network for Click-Through Rate Prediction

推薦閱讀:

相關文章