1.動機(jī)
對于CTR問題,被證明的最有效的提升任務(wù)表現(xiàn)的策略是特征組合,在CTR問題的探究歷史上來看就是如何更好地學(xué)習(xí)特征組合,進(jìn)而更加精準(zhǔn)地描述數(shù)據(jù)的特點(diǎn)??梢哉f這是基礎(chǔ)推薦模型到深度學(xué)習(xí)推薦模型遵循的一個(gè)主要思想。而特征組合大牛們通過組合二階特征,三階甚至更高階,但是面臨一個(gè)問題就是隨著階數(shù)的提升,復(fù)雜度就呈幾何倍的升高。這樣即使模型的表現(xiàn)更好了,但是推薦系統(tǒng)在實(shí)時(shí)性要求也不能滿足了。所以很多模型的出現(xiàn)都是為了解決另一個(gè)更加深入的問題:如何更高效的學(xué)習(xí)特征組合?
為了解決上述問題,出現(xiàn)了FM與FFM來優(yōu)化LR特征組合較差的一個(gè)問題。并且在這個(gè)時(shí)候科學(xué)家們已經(jīng)發(fā)現(xiàn)了DNN在特征組合方面的優(yōu)勢,所以又出現(xiàn)了FNN和PNN等使用深度神經(jīng)網(wǎng)絡(luò)的模型。但是DNN也存在局限性。
-
= =DNN= = 當(dāng)我們使用DNN網(wǎng)絡(luò)解決推薦問題的時(shí)候存在網(wǎng)絡(luò)參數(shù)過于龐大的問題,這是因?yàn)樵谶M(jìn)行特征處理的時(shí)候我們需要使用one-hot編碼來處理離散特征,這會導(dǎo)致輸入的維度猛增。這里借AI大會上的一張圖片:
1
這樣龐大的參數(shù)量也是不實(shí)際的。為了解決DNN參數(shù)量過大的局限性,可以采用非常經(jīng)典的Field思想,將One-Hot特征轉(zhuǎn)換為Dense Vector
2
此時(shí)通過增加全連接層就可以實(shí)現(xiàn)高階的特征組合,如下圖所示:

但是仍然缺少低階組合,于是增加FM來表示低階組合。
-
==FNN和PNN==
結(jié)合FM和DNN其實(shí)有兩種方式??梢圆⑿薪Y(jié)合也可以串行結(jié)合。這兩種方式各有幾種代表模型。在DeepFM之前有FNN,雖然在影響力上可能不如DeepFM,但是了解FNN的思想對我們理解DeepFM的特點(diǎn)和優(yōu)點(diǎn)是很有幫助的
4 -
Wide&Deep
FNN和PNN模型仍然有一個(gè)比較明顯的尚未解決的缺點(diǎn):對于低階組合特征學(xué)習(xí)到的比較少,這一點(diǎn)主要是由于FM和DNN的串行方式導(dǎo)致的,也就是雖然FM學(xué)到了低階特征組合,但是DNN的全連接結(jié)構(gòu)導(dǎo)致低階特征并不能在DNN的輸出端較好的表現(xiàn)??磥砦覀円呀?jīng)找到問題所在了,將串行方式改進(jìn)成為并行方式能比較好的解決這個(gè)問題。于是Google提出了wide&deep模型,但是如果深入wide&deep的構(gòu)成方式,雖然將整個(gè)模型的結(jié)構(gòu)調(diào)整為了并行結(jié)構(gòu),在實(shí)際的使用中widemodel中的部分需要比較精巧的特征工程,換句話說人工處理對于模型的效果具有比較大的影響。
5
如上圖所示,該模型仍然存在問題:在Output Units階段直接將低階和高階特征進(jìn)行組合,很容易讓模型最終偏向?qū)W習(xí)到低階或者高階特征,而不能做到很好的結(jié)合。
綜上所述,DeepFM模型橫空出世
2.模型結(jié)構(gòu)與原理

前面的Field和Embedding處理的是和前面的方法相同,如上圖所示綠色部分;DeepFM將Wide部分替換為了FM layer如上圖所示藍(lán)色部分,這幅圖其實(shí)有很多需要注意的地方
- Deep模型部分
- FM模型部分
- Sparse Feature中黃色和灰色節(jié)點(diǎn)代表什么意思
2.1 FM
下圖是一個(gè)FM的結(jié)構(gòu)圖,從圖中大致可以看出FM Layer是由一階特征和二階Concatenate到一起再經(jīng)過一個(gè)Sigmoid得到logits,所以在實(shí)現(xiàn)的時(shí)候需要單獨(dú)考慮Linear部分和FM交叉特征部分。

2.2 Deep

Deep Model是為了學(xué)習(xí)高階的特征組合,在上圖中使用全連接的方式將Dense Embedding輸入到Hidden Layer,這里面Dense Embeddings就是為了解決DNN中參數(shù)爆炸的問題,這也是推薦系統(tǒng)中常用的處理方式。
Embedding層的輸出是將所有id類特征對應(yīng)的Embedding向量concat到一起輸入到DNN中。其中表示第i個(gè)field的embedding,m是field的數(shù)量。
上一層的輸出作為下一層的輸入,我們得到:
其中
表示激活函數(shù),
分別表示該層的輸入、權(quán)重和偏置。
最后進(jìn)入DNN部分輸出使用sigmod激活函數(shù)進(jìn)行激活:
3.代碼實(shí)現(xiàn)
DeepFM在模型的結(jié)構(gòu)圖中顯示,模型大致由兩部分組成,一部分是FM,還有一部分就是DNN, 而FM又由一階特征部分與二階特征交叉部分組成,所以可以將整個(gè)模型拆成三部分,分別是一階特征處理linear部分,二階特征交叉FM以及DNN的高階特征交叉。在下面的代碼中也能夠清晰的看到這個(gè)結(jié)構(gòu)。此外每一部分可能由是由不同的特征組成,所以在構(gòu)建模型的時(shí)候需要分別對這三部分輸入的特征進(jìn)行選擇。
linear_logits: 這部分是有關(guān)于線性計(jì)算,也就是FM的前半部分
的計(jì)算。對于這一塊的計(jì)算,我們用了一個(gè)get_linear_logits函數(shù)實(shí)現(xiàn),后面再說,總之通過這個(gè)函數(shù),我們就可以實(shí)現(xiàn)上面這個(gè)公式的計(jì)算過程,得到linear的輸出, 這部分特征由數(shù)值特征和類別特征的onehot編碼組成的一維向量組成,實(shí)際應(yīng)用中根據(jù)自己的業(yè)務(wù)放置不同的一階特征(這里的dense特征并不是必須的,有可能會將數(shù)值特征進(jìn)行分桶,然后在當(dāng)做類別特征來處理)
fm_logits: 這一塊主要是針對離散的特征,首先過embedding,然后使用FM特征交叉的方式,兩兩特征進(jìn)行交叉,得到新的特征向量,最后計(jì)算交叉特征的logits
dnn_logits: 這一塊主要是針對離散的特征,首先過embedding,然后將得到的embedding拼接成一個(gè)向量(具體的可以看代碼,也可以看一下下面的模型結(jié)構(gòu)圖),通過dnn學(xué)習(xí)類別特征之間的隱式特征交叉并輸出logits值
def DeepFM(linear_feature_columns, dnn_feature_columns):
# 構(gòu)建輸入層,即所有特征對應(yīng)的Input()層,這里使用字典的形式返回,方便后續(xù)構(gòu)建模型
dense_input_dict, sparse_input_dict = build_input_layers(linear_feature_columns + dnn_feature_columns)
# 將linear部分的特征中sparse特征篩選出來,后面用來做1維的embedding
linear_sparse_feature_columns = list(filter(lambda x: isinstance(x, SparseFeat), linear_feature_columns))
# 構(gòu)建模型的輸入層,模型的輸入層不能是字典的形式,應(yīng)該將字典的形式轉(zhuǎn)換成列表的形式
# 注意:這里實(shí)際的輸入與Input()層的對應(yīng),是通過模型輸入時(shí)候的字典數(shù)據(jù)的key與對應(yīng)name的Input層
input_layers = list(dense_input_dict.values()) + list(sparse_input_dict.values())
# linear_logits由兩部分組成,分別是dense特征的logits和sparse特征的logits
linear_logits = get_linear_logits(dense_input_dict, sparse_input_dict, linear_sparse_feature_columns)
# 構(gòu)建維度為k的embedding層,這里使用字典的形式返回,方便后面搭建模型
# embedding層用戶構(gòu)建FM交叉部分和DNN的輸入部分
embedding_layers = build_embedding_layers(dnn_feature_columns, sparse_input_dict, is_linear=False)
# 將輸入到dnn中的所有sparse特征篩選出來
dnn_sparse_feature_columns = list(filter(lambda x: isinstance(x, SparseFeat), dnn_feature_columns))
fm_logits = get_fm_logits(sparse_input_dict, dnn_sparse_feature_columns, embedding_layers) # 只考慮二階項(xiàng)
# 將所有的Embedding都拼起來,一起輸入到dnn中
dnn_logits = get_dnn_logits(sparse_input_dict, dnn_sparse_feature_columns, embedding_layers)
# 將linear,FM,dnn的logits相加作為最終的logits
output_logits = Add()([linear_logits, fm_logits, dnn_logits])
# 這里的激活函數(shù)使用sigmoid
output_layers = Activation("sigmoid")(output_logits)
model = Model(input_layers, output_layers)
return model





