轉(zhuǎn)載自datawhale-github
DeepCrossing
1. 動機
這個模型就是一個真正的把深度學習架構(gòu)應用于推薦系統(tǒng)中的模型了藏鹊, 2016年由微軟提出转锈, 完整的解決了特征工程撮慨、稀疏向量稠密化, 多層神經(jīng)網(wǎng)絡(luò)進行優(yōu)化目標擬合等一系列深度學習再推薦系統(tǒng)的應用問題变隔。 這個模型涉及到的技術(shù)比較基礎(chǔ)匣缘,在傳統(tǒng)神經(jīng)網(wǎng)絡(luò)的基礎(chǔ)上加入了embedding鲜棠,殘差連接等思想豁陆,且結(jié)構(gòu)比較簡單,對初學者復現(xiàn)和學習都比較友好竖配。
DeepCrossing模型應用場景是微軟搜索引擎Bing中的搜索廣告推薦进胯, 用戶在輸入搜索詞之后原押, 搜索引擎除了返回相關(guān)結(jié)果诸衔, 還返回與搜索詞相關(guān)的廣告就缆,Deep Crossing的優(yōu)化目標就是預測對于某一廣告竭宰, 用戶是否會點擊切揭,依然是點擊率預測的一個問題锁摔。
這種場景下谐腰,我們的輸入一般會有類別型特征,比如廣告id旁赊,和數(shù)值型特征终畅,比如廣告預算离福,兩種情況妖爷。 對于類別型特征絮识,我們需要進行one-hot編碼處理次舌,而數(shù)值型特征 一般需要進行歸一化處理彼念,這樣算是把數(shù)據(jù)進行了一個簡單清洗逐沙。 DeepCrossing模型就是利用這些特征向量進行CRT預估吩案,那么它的結(jié)構(gòu)長啥樣, 又是怎么做CTR預估的呢? 這又是DeepCrossing的核心內(nèi)容帝簇。
2. 模型結(jié)構(gòu)及原理
為了完成端到端的訓練务热, DeepCrossing模型要在內(nèi)部網(wǎng)絡(luò)結(jié)構(gòu)中解決如下問題:
- 離散類特征編碼后過于稀疏, 不利于直接輸入神經(jīng)網(wǎng)絡(luò)訓練己儒, 需要解決稀疏特征向量稠密化的問題
- 如何解決特征自動交叉組合的問題
- 如何在輸出層中達成問題設(shè)定的優(yōu)化目標
DeepCrossing分別設(shè)置了不同神經(jīng)網(wǎng)絡(luò)層解決上述問題。模型結(jié)構(gòu)如下
<img src="http://ryluo.oss-cn-chengdu.aliyuncs.com/圖片2020100916594542.png" alt="image-20210217173154706" style="zoom:67%;" />
下面分別介紹一下各層的作用:
2.1 Embedding Layer
將稀疏的類別型特征轉(zhuǎn)成稠密的Embedding向量捆毫,Embedding的維度會遠小于原始的稀疏特征向量闪湾。 Embedding是NLP里面常用的一種技術(shù),這里的Feature #1表示的類別特征(one-hot編碼后的稀疏特征向量)绩卤, Feature #2是數(shù)值型特征途样,不用embedding江醇, 直接到了Stacking Layer。 關(guān)于Embedding Layer的實現(xiàn)何暇, 往往一個全連接層即可条辟,Tensorflow中有實現(xiàn)好的層可以直接用。 和NLP里面的embedding技術(shù)異曲同工, 比如Word2Vec魂爪, 語言模型等粗井。
2.2 Stacking Layer
這個層是把不同的Embedding特征和數(shù)值型特征拼接在一起,形成新的包含全部特征的特征向量,該層通常也稱為連接層, 具體的實現(xiàn)如下醉冤,先將所有的數(shù)值特征拼接起來螺捐,然后將所有的Embedding拼接起來,最后將數(shù)值特征和Embedding特征拼接起來作為DNN的輸入,這里TF是通過Concatnate層進行拼接刊苍。
#將所有的dense特征拼接到一起
dense_dnn_list = list(dense_input_dict.values())
dense_dnn_inputs = Concatenate(axis=1)(dense_dnn_list) # B x n (n表示數(shù)值特征的數(shù)量)
# 因為需要將其與dense特征拼接到一起所以需要Flatten埠忘,不進行Flatten的Embedding層輸出的維度為:Bx1xdim
sparse_dnn_list = concat_embedding_list(dnn_feature_columns, sparse_input_dict, embedding_layer_dict, flatten=True)
sparse_dnn_inputs = Concatenate(axis=1)(sparse_dnn_list) # B x m*dim (n表示類別特征的數(shù)量旨怠,dim表示embedding的維度)
# 將dense特征和Sparse特征拼接到一起
dnn_inputs = Concatenate(axis=1)([dense_dnn_inputs, sparse_dnn_inputs]) # B x (n + m*dim)
2.3 Multiple Residual Units Layer
該層的主要結(jié)構(gòu)是MLP百揭, 但DeepCrossing采用了殘差網(wǎng)絡(luò)進行的連接课锌。通過多層殘差網(wǎng)絡(luò)對特征向量各個維度充分的交叉組合请毛, 使得模型能夠抓取更多的非線性特征和組合特征信息街州, 增加模型的表達能力。殘差網(wǎng)絡(luò)結(jié)構(gòu)如下圖所示:
<img src="http://ryluo.oss-cn-chengdu.aliyuncs.com/圖片20201009193957977.png" alt="image-20210217174914659" style="zoom:67%;" />
Deep Crossing模型使用稍微修改過的殘差單元,它不使用卷積內(nèi)核,改為了兩層神經(jīng)網(wǎng)絡(luò)。我們可以看到,殘差單元是通過兩層ReLU變換再將原輸入特征相加回來實現(xiàn)的。具體代碼實現(xiàn)如下:
# DNN殘差塊的定義
class ResidualBlock(Layer):
def __init__(self, units): # units表示的是DNN隱藏層神經(jīng)元數(shù)量
super(ResidualBlock, self).__init__()
self.units = units
def build(self, input_shape):
out_dim = input_shape[-1]
self.dnn1 = Dense(self.units, activation='relu')
self.dnn2 = Dense(out_dim, activation='relu') # 保證輸入的維度和輸出的維度一致才能進行殘差連接
def call(self, inputs):
x = inputs
x = self.dnn1(x)
x = self.dnn2(x)
x = Activation('relu')(x + inputs) # 殘差操作
return x
2.4 Scoring Layer
這個作為輸出層照卦,為了擬合優(yōu)化目標存在蹄葱。 對于CTR預估二分類問題克婶, Scoring往往采用邏輯回歸,模型通過疊加多個殘差塊加深網(wǎng)絡(luò)的深度睁宰,最后將結(jié)果轉(zhuǎn)換成一個概率值輸出红符。
# block_nums表示DNN殘差塊的數(shù)量
def get_dnn_logits(dnn_inputs, block_nums=3):
dnn_out = dnn_inputs
for i in range(block_nums):
dnn_out = ResidualBlock(64)(dnn_out)
# 將dnn的輸出轉(zhuǎn)化成logits
dnn_logits = Dense(1, activation='sigmoid')(dnn_out)
return dnn_logits
3. 總結(jié)
這就是DeepCrossing的結(jié)構(gòu)了喇喉,比較清晰和簡單,沒有引入特殊的模型結(jié)構(gòu)邪驮,只是常規(guī)的Embedding+多層神經(jīng)網(wǎng)絡(luò)喻粹。但這個網(wǎng)絡(luò)模型的出現(xiàn)弥喉,有革命意義。DeepCrossing模型中沒有任何人工特征工程的參與从撼,只需要簡單的特征處理啃奴,原始特征經(jīng)Embedding Layer輸入神經(jīng)網(wǎng)絡(luò)層,自主交叉和學習丹壕。 相比于FM,F(xiàn)FM只具備二階特征交叉能力的模型刁愿,DeepCrossing可以通過調(diào)整神經(jīng)網(wǎng)絡(luò)的深度進行特征之間的“深度交叉”,這也是Deep Crossing名稱的由來痊末。
如果是用于點擊率預估模型的損失函數(shù)就是對數(shù)損失函數(shù):
其中表示真實的標簽(點擊或未點擊),表示Scoring Layer輸出的結(jié)果。但是在實際應用中绽昏,根據(jù)不同的需求可以靈活替換為其他目標函數(shù)补憾。
4. 代碼實現(xiàn)
從模型的代碼結(jié)構(gòu)上來看盈匾,DeepCrossing的模型輸入主要由數(shù)值特征和類別特征組成,并將經(jīng)過Embedding之后的類別特征及類別特征拼接在一起苏遥,詳細的拼接代碼如Staking Layer所示师抄,下面是構(gòu)建模型的核心代碼景用,詳細代碼參考github。
def DeepCrossing(dnn_feature_columns):
# 構(gòu)建輸入層朵锣,即所有特征對應的Input()層幢痘,這里使用字典的形式返回,方便后續(xù)構(gòu)建模型
dense_input_dict, sparse_input_dict = build_input_layers(dnn_feature_columns)
# 構(gòu)建模型的輸入層家破,模型的輸入層不能是字典的形式颜说,應該將字典的形式轉(zhuǎn)換成列表的形式
# 注意:這里實際的輸入與Input()層的對應,是通過模型輸入時候的字典數(shù)據(jù)的key與對應name的Input層
input_layers = list(dense_input_dict.values()) + list(sparse_input_dict.values())
# 構(gòu)建維度為k的embedding層汰聋,這里使用字典的形式返回门粪,方便后面搭建模型
embedding_layer_dict = build_embedding_layers(dnn_feature_columns, sparse_input_dict, is_linear=False)
#將所有的dense特征拼接到一起
dense_dnn_list = list(dense_input_dict.values())
dense_dnn_inputs = Concatenate(axis=1)(dense_dnn_list) # B x n (n表示數(shù)值特征的數(shù)量)
# 因為需要將其與dense特征拼接到一起所以需要Flatten,不進行Flatten的Embedding層輸出的維度為:Bx1xdim
sparse_dnn_list = concat_embedding_list(dnn_feature_columns, sparse_input_dict, embedding_layer_dict, flatten=True)
sparse_dnn_inputs = Concatenate(axis=1)(sparse_dnn_list) # B x m*dim (n表示類別特征的數(shù)量烹困,dim表示embedding的維度)
# 將dense特征和Sparse特征拼接到一起
dnn_inputs = Concatenate(axis=1)([dense_dnn_inputs, sparse_dnn_inputs]) # B x (n + m*dim)
# 輸入到dnn中玄妈,需要提前定義需要幾個殘差塊
output_layer = get_dnn_logits(dnn_inputs, block_nums=3)
model = Model(input_layers, output_layer)
return model
為了方便大家的閱讀,我們這里還給大家畫了一個整體的模型架構(gòu)圖髓梅,幫助大家更好的了解每一塊以及前向傳播拟蜻。(畫的圖不是很規(guī)范,先將就看一下枯饿,后面我們會統(tǒng)一在優(yōu)化一下這個手工圖)酝锅。
<img src="http://ryluo.oss-cn-chengdu.aliyuncs.com/圖片image-20210304222328047.png" alt="image-20210304222328047" style="zoom:67%;" />
下面是一個通過keras畫的模型結(jié)構(gòu)圖,為了更好的顯示奢方,數(shù)值特征和類別特征都只是選擇了一小部分屈张,畫圖的代碼也在github中。