絡(luò)如何實(shí)現(xiàn)自動(dòng)特征組合與CTR預(yù)估)
做了這么多年搜索廣告和推薦系統(tǒng)的點(diǎn)擊率預(yù)估我最頭大的事情之一就是特征工程。早期模型大量依賴(lài)人工拼特征組合比如性別男 品類(lèi)游戲、城市一線 時(shí)段晚間這種二階甚至三階交叉每次上新策略都要靠業(yè)務(wù)分析和線上AB實(shí)驗(yàn)反復(fù)驗(yàn)證費(fèi)時(shí)費(fèi)力還不一定能找到有效組合。后來(lái)讀到微軟提出的Deep Crossing模型才發(fā)現(xiàn)自動(dòng)特征組合這條路可以直接交給網(wǎng)絡(luò)自己學(xué)把離散稀疏特征映射成稠密向量后通過(guò)網(wǎng)絡(luò)深層的非線性變換來(lái)自動(dòng)發(fā)現(xiàn)高階交叉效果還往往優(yōu)于手工特征工程。這篇文章就把我對(duì)Deep Crossing的理解、復(fù)現(xiàn)過(guò)程以及落地時(shí)踩過(guò)的一些坑整理出來(lái)希望能給正在做CTR預(yù)估或者推薦召回/排序的讀者一些參考。1. 特征工程的舊時(shí)代為什么手工拼特征注定走不通在討論Deep Crossing之前得先搞清楚一個(gè)問(wèn)題為什么我們這么需要自動(dòng)特征組合不是大家不想做特征工程而是傳統(tǒng)方法在特征組合這件事上的天花板太低低到你只能靠人工去碰運(yùn)氣。1.1 LR與FM的邊界二階交互已經(jīng)是天花板先看最經(jīng)典的邏輯回歸。LR對(duì)每個(gè)特征學(xué)一個(gè)獨(dú)立權(quán)重預(yù)測(cè)時(shí)把特征加權(quán)求和再套sigmoid本質(zhì)上是一個(gè)線性模型。線性模型的好處是簡(jiǎn)單、可控、容易解釋但壞處也很明顯它默認(rèn)特征之間是相互獨(dú)立的。如果用戶剛看完汽車(chē)評(píng)測(cè)和廣告位在首頁(yè)首屏這兩個(gè)信號(hào)單獨(dú)看都不強(qiáng)但組合起來(lái)對(duì)點(diǎn)擊有強(qiáng)拉動(dòng)LR永遠(yuǎn)學(xué)不到這個(gè)規(guī)律除非你在訓(xùn)練樣本里額外構(gòu)造一個(gè)汽車(chē)評(píng)測(cè)首頁(yè)首屏的交叉特征。所以傳統(tǒng)做法是人工枚舉交叉特征。二階交叉還能枚舉到了三階、四階就指數(shù)爆炸。而且很多交叉特征在樣本里非常稀疏比如男性25歲科技頻道晚間這類(lèi)深度組合可能全量樣本里只有幾百個(gè)正例模型學(xué)起來(lái)噪聲極大。FM通過(guò)隱向量?jī)?nèi)積實(shí)現(xiàn)了自動(dòng)二階交叉這是一個(gè)重要突破。每個(gè)特征不再是一個(gè)標(biāo)量權(quán)重而是一個(gè)k維向量?jī)蓚€(gè)特征的交叉權(quán)重由兩個(gè)向量?jī)?nèi)積得到。FM能自動(dòng)學(xué)到哪個(gè)組合重要不需要人工枚舉。這里最讓我欣賞的是它的工程技巧FM把二階交叉參數(shù)的從nn壓縮到nk泛化能力也更強(qiáng)。但FM的邊界非常明顯它只能建模二階交互。你想讓它自動(dòng)發(fā)現(xiàn)性別品類(lèi)時(shí)段三階組合它無(wú)能為力。雖然理論上可以把二階項(xiàng)堆成高階也就是使用更高階的交互項(xiàng)但參數(shù)復(fù)雜度、訓(xùn)練難度都會(huì)失控實(shí)踐中極少有團(tuán)隊(duì)真的去跑三階FM。1.2 GBDTLR的臨時(shí)方案與它的先天問(wèn)題工業(yè)界很長(zhǎng)一段時(shí)間的主流方案是GBDTLR這個(gè)組合解決的問(wèn)題也是自動(dòng)特征組合。原理很巧妙GBDT每棵樹(shù)的葉子節(jié)點(diǎn)代表一個(gè)數(shù)據(jù)分區(qū)路徑訓(xùn)練完成后把樣本落入的葉子節(jié)點(diǎn)位置做成one-hot向量再丟給LR學(xué)習(xí)權(quán)重。相當(dāng)于GBDT把原始特征空間切成了很多高階非線性區(qū)域LR負(fù)責(zé)在這些區(qū)域上學(xué)習(xí)打分。這個(gè)方案在當(dāng)時(shí)算是用一個(gè)模型自動(dòng)發(fā)現(xiàn)高階組合的可行路徑我也在不少項(xiàng)目里用過(guò)效果確實(shí)比純LR好。但它有幾個(gè)先天問(wèn)題讓我始終覺(jué)得別扭第一GBDT對(duì)超高維稀疏類(lèi)別特征很不友好。ID類(lèi)特征比如用戶ID、廣告ID、商品ID做one-hot或hash后會(huì)形成極稀疏的高維輸入GBDT的樹(shù)分裂在這個(gè)空間里效果很差。所以實(shí)際業(yè)務(wù)里通常只把數(shù)值型特征、枚舉型特征放進(jìn)GBDT而那些最重要的ID類(lèi)特征還是留給LR單獨(dú)處理。第二GBDTLR是兩階段串行訓(xùn)練目標(biāo)不一致。GBDT訓(xùn)練時(shí)以預(yù)測(cè)類(lèi)別為準(zhǔn)它內(nèi)部的葉子劃分不一定是為了LR分類(lèi)最優(yōu)而生成再加上GBDT和LR用了不同目標(biāo)函數(shù)整個(gè)系統(tǒng)沒(méi)有端到端聯(lián)合優(yōu)化。第三特征組合不可控。GBDT生成的組合數(shù)量由葉子節(jié)點(diǎn)決定葉子多了容易過(guò)擬合葉子少了組合能力不夠調(diào)起來(lái)非常麻煩。而且模型已經(jīng)生成你沒(méi)法判斷每個(gè)組合的業(yè)務(wù)含義。所以我把這套方案歸為過(guò)渡產(chǎn)物。Deep Crossing打動(dòng)我的一點(diǎn)就是它徹底放下了先把特征組合好再學(xué)權(quán)重的思路把特征Embedding、特征交叉、目標(biāo)優(yōu)化全部放進(jìn)同一個(gè)神經(jīng)網(wǎng)絡(luò)里端到端地完成自動(dòng)特征組合。2. Deep Crossing的四層結(jié)構(gòu)把特征組合從人肉活變成網(wǎng)絡(luò)行為Deep Crossing的網(wǎng)絡(luò)結(jié)構(gòu)非常簡(jiǎn)潔一共就四層Embedding層、Stacking層、多個(gè)Residual Unit組成的殘差層最后是Scoring層。整個(gè)前向過(guò)程可以理解為先把所有高維稀疏特征壓成低維稠密向量拼接成一個(gè)長(zhǎng)向量然后通過(guò)多層殘差網(wǎng)絡(luò)逐層提煉特征交互最后一層輸出點(diǎn)擊率預(yù)估值。2.1 Embedding層從稀疏one-hot到稠密向量這里藏著真正的玄機(jī)Embedding層最容易被忽略因?yàn)楹芏嗳擞X(jué)得它只是降維或者查表。但Deep Crossing里Embedding承擔(dān)的是語(yǔ)義向量化的任務(wù)。原始輸入可能是用戶ID、廣告ID、創(chuàng)意ID、行業(yè)ID、設(shè)備類(lèi)型等類(lèi)別特征。在做獨(dú)熱之后每個(gè)特征的維度可以是幾百萬(wàn)甚至上億直接輸入深度網(wǎng)絡(luò)是災(zāi)難。Deep Crossing為每個(gè)特征類(lèi)別單獨(dú)維護(hù)一個(gè)Embedding矩陣通過(guò)查表把one-hot索引轉(zhuǎn)換成一個(gè)固定維度的稠密向量。這里有一個(gè)值得展開(kāi)的細(xì)節(jié)為什么要每個(gè)類(lèi)別單獨(dú)一個(gè)Embedding矩陣而不是把所有類(lèi)別特征拼成一個(gè)共享詞典比如用戶ID123和廣告ID456如果放在同一個(gè)Embedding空間學(xué)出來(lái)的向量會(huì)混合兩種不同語(yǔ)義網(wǎng)絡(luò)無(wú)法區(qū)分。用戶ID和廣告ID本身就是完全不同空間的對(duì)象強(qiáng)制它們共享向量空間會(huì)破壞各自的結(jié)構(gòu)。所以每個(gè)類(lèi)別一個(gè)獨(dú)立的Embedding矩陣讓每個(gè)ID在自己的空間里單獨(dú)表達(dá)。Embedding維度怎么選我的經(jīng)驗(yàn)是16~32維起步。維度太小表達(dá)力不夠?qū)W不出復(fù)雜語(yǔ)義維度太大帶來(lái)過(guò)擬合因?yàn)镃TR場(chǎng)景的ID類(lèi)特征請(qǐng)求頻次是冪律分布絕大多數(shù)長(zhǎng)尾ID在訓(xùn)練中出現(xiàn)的次數(shù)很少給它們太多維度只會(huì)讓模型去硬記訓(xùn)練樣本。另外建議Embedding維度選4的倍數(shù)方便硬件對(duì)齊推理時(shí)也能快一些。訓(xùn)練完成后Embedding向量本身就是很有價(jià)值的副產(chǎn)物。語(yǔ)義相近的廣告ID會(huì)在向量空間里靠得更近可以直接拿來(lái)做召回的向量化索引。這一點(diǎn)是我在業(yè)務(wù)落地時(shí)才意識(shí)到的Deep Crossing除了解決排序問(wèn)題還順手給了你一套可復(fù)用的稠密特征表達(dá)。2.2 Stacking與Residual Unit為什么非要用殘差Stacking層沒(méi)什么好神秘的就是把所有經(jīng)過(guò)Embedding的特征向量橫向拼接成一個(gè)長(zhǎng)向量。這里要提醒的是拼接順序必須固定否則線上推理和訓(xùn)練時(shí)向量位置的錯(cuò)位會(huì)讓Embedding查表完全失效。我自己在早期原型里就吃過(guò)這個(gè)虧把拼接順序?qū)懰涝谂渲美锊疟苊饩€上出大問(wèn)題。真正值得深挖的是Residual Unit。Deep Crossing的核心創(chuàng)新其實(shí)不在Embedding而在它把殘差網(wǎng)絡(luò)引入了CTR預(yù)估這類(lèi)稀疏場(chǎng)景。Residual Unit的基礎(chǔ)結(jié)構(gòu)是兩層全連接ReLU形式上可以寫(xiě)成h ReLU(W2 * ReLU(W1 * x b1) b2) y h x這里的y h x就是殘差連接的關(guān)鍵。如果沒(méi)有這條恒等映射路徑多層網(wǎng)絡(luò)在訓(xùn)練時(shí)梯度要逐層回傳層數(shù)一多很容易出現(xiàn)梯度消失CTR數(shù)據(jù)又是海量稀疏樣本收斂極慢。加了殘差連接后梯度可以通過(guò)恒等路徑直接回傳到淺層整個(gè)深層網(wǎng)絡(luò)被拆解成一系列增量學(xué)習(xí)模塊。每個(gè)殘差單元不需要從零學(xué)出一個(gè)完整映射只需要在輸入基礎(chǔ)上學(xué)一個(gè)小的修正量。這好比做菜時(shí)不是每次從洗菜切菜開(kāi)始而是在上一道工序基礎(chǔ)上加一點(diǎn)調(diào)味最終成品的復(fù)雜度由多個(gè)模塊共同疊加出來(lái)。在CTR場(chǎng)景里這種增量映射尤其適用。底層Embedding已經(jīng)包含了單特征語(yǔ)義殘差單元第一層負(fù)責(zé)在稠密向量上做非線性變換第二層把變換結(jié)果再加回原始向量。堆疊多個(gè)殘差單元后網(wǎng)絡(luò)就能逐步組合出二階、三階甚至更高階的特征交互。讀者可以把每個(gè)殘差單元想象成一個(gè)特征組合機(jī)兩個(gè)向量在這個(gè)單元里被擠壓、變換、融合再把融合結(jié)果傳給下一個(gè)單元。實(shí)驗(yàn)里我用到的通用配置是堆疊2~3個(gè)Residual Unit每個(gè)單元隱層寬度256~512。堆太多并不總是好事這個(gè)后面聊過(guò)擬合時(shí)細(xì)說(shuō)。2.3 Scoring層與損失設(shè)計(jì)點(diǎn)擊率預(yù)估的最后一步Scoring層結(jié)構(gòu)上就是一層線性變換把殘差網(wǎng)絡(luò)輸出的深層特征向量映射成一個(gè)logit再經(jīng)過(guò)sigmoid得到點(diǎn)擊概率。訓(xùn)練目標(biāo)用二元交叉熵?fù)p失這也是CTR預(yù)估的標(biāo)配。端到端的意義在這里真正體現(xiàn)出來(lái)。損失從最終輸出回傳時(shí)每一層的參數(shù)包括Embedding矩陣、殘差單元權(quán)重都會(huì)向著降低預(yù)測(cè)誤差的方向一起更新。手工特征工程時(shí)代你只能在模型外圍繞彎去構(gòu)造特征Deep Crossing把特征表達(dá)和特征組合統(tǒng)一進(jìn)了同一個(gè)目標(biāo)函數(shù)里這是它和所有兩階段方案的本質(zhì)區(qū)別。還有一個(gè)小細(xì)節(jié)Score層之前是否需要額外的全連接層做過(guò)渡論文里沒(méi)有特別強(qiáng)調(diào)我的實(shí)踐是在殘差單元之后通常不再加額外全連接直接線性映射到1維即可減少參數(shù)也讓殘差網(wǎng)絡(luò)輸出的高層語(yǔ)義直接參與決策。模型是否要增加這一層可以在驗(yàn)證集上看AUC變化來(lái)決定。3. 動(dòng)手復(fù)現(xiàn)一個(gè)可落地的Deep Crossing實(shí)現(xiàn)理論說(shuō)再多不如直接跑一個(gè)能用的實(shí)現(xiàn)。下面我用PyTorch給出一份可以在公開(kāi)數(shù)據(jù)集比如Criteo或者M(jìn)ovieLens轉(zhuǎn)化率數(shù)據(jù)上直接跑的代碼框架。重點(diǎn)不是把代碼貼滿而是把每個(gè)環(huán)節(jié)的關(guān)鍵決策講清楚。3.1 數(shù)據(jù)準(zhǔn)備稀疏特征與連續(xù)特征怎么喂進(jìn)模型實(shí)際數(shù)據(jù)集里特征一般分兩類(lèi)類(lèi)別特征和連續(xù)特征。以廣告點(diǎn)擊日志為例類(lèi)別特征包括用戶ID、廣告ID、素材ID、行業(yè)ID、設(shè)備型號(hào)等連續(xù)特征包括出價(jià)、曝光時(shí)長(zhǎng)、歷史點(diǎn)擊率等。我的做法是類(lèi)別特征先做ID映射每個(gè)特征維護(hù)一個(gè)字典把原始字符串映射成連續(xù)整數(shù)ID未出現(xiàn)的值統(tǒng)一映射到0。數(shù)值特征做歸一化連續(xù)特征用min-max或者log變換壓縮到[0,1]區(qū)間避免量綱差異影響網(wǎng)絡(luò)訓(xùn)練。準(zhǔn)備樣本時(shí)類(lèi)別特征保存的是ID整數(shù)而不是one-hot向量真正轉(zhuǎn)one-hot的工作交給Embedding層完成。這里有個(gè)很關(guān)鍵的工程習(xí)慣在訓(xùn)練開(kāi)始前把每個(gè)類(lèi)別特征的詞典大小統(tǒng)計(jì)出來(lái)因?yàn)閚n.Embedding初始化需要知道詞表大小。如果是全部特征共享一個(gè)詞表那就是共享Embedding模式Deep Crossing建議每個(gè)類(lèi)別特征獨(dú)立詞表所以你需要為每個(gè)類(lèi)別特征單獨(dú)建一個(gè)nn.Embedding。3.2 核心代碼Residual Unit與整體結(jié)構(gòu)下面是一個(gè)簡(jiǎn)潔的PyTorch實(shí)現(xiàn)把Deep Crossing縮成幾個(gè)可以直接改的類(lèi)。import torch import torch.nn as nn import torch.nn.functional as F class ResidualUnit(nn.Module): def __init__(self, hidden_units, dropout0.0): super().__init__() self.fc1 nn.Linear(hidden_units, hidden_units) self.fc2 nn.Linear(hidden_units, hidden_units) self.dropout nn.Dropout(dropout) def forward(self, x): h F.relu(self.fc1(x)) h self.fc2(h) h self.dropout(h) return h x class DeepCrossing(nn.Module): def __init__(self, feature_dims, embed_dim32, hidden_units256, num_residual_units3, dropout0.0): super().__init__() # feature_dims: dict例如 {user_id: 100000, ad_id: 50000} self.embeddings nn.ModuleDict({ name: nn.Embedding(num_embeddingsdim, embedding_dimembed_dim) for name, dim in feature_dims.items() }) total_dim len(feature_dims) * embed_dim self.residual_blocks nn.Sequential(*[ ResidualUnit(hidden_units, dropout) for _ in range(num_residual_units) ]) self.projection nn.Linear(total_dim, hidden_units) self.scoring nn.Linear(hidden_units, 1) def forward(self, inputs): # inputs: dict of LongTensor每個(gè)都對(duì)應(yīng)一個(gè)類(lèi)別特征ID embedding_list [self.embeddings[name](inputs[name]) for name in inputs] stacked torch.cat(embedding_list, dim1) x F.relu(self.projection(stacked)) x self.residual_blocks(x) logit self.scoring(x).squeeze(1) return logit有幾個(gè)地方要特別說(shuō)明。第一個(gè)是projection層。殘差單元要求輸入輸出維度相等而Embedding拼接后的維度比如4個(gè)特征每個(gè)32維拼接后128維通常不等于后面殘差單元的寬度256。所以需要先用一個(gè)投影層把拼接向量映射到統(tǒng)一寬度再接殘差塊。第二個(gè)是殘差塊內(nèi)部不降維。我傾向于所有殘差單元的隱藏寬度都保持一致否則殘差連接兩側(cè)維度對(duì)不上實(shí)現(xiàn)起來(lái)會(huì)很別扭。第三個(gè)是Embedding的初始化。PyTorch默認(rèn)Embedding初始化是標(biāo)準(zhǔn)正態(tài)分布實(shí)際訓(xùn)練中我建議把Embedding參數(shù)換成更小的均勻分布初始化比如uniform_(-0.05, 0.05)。CTR特征ID大量是低頻的初始化分布太寬會(huì)導(dǎo)致低頻ID的向量在訓(xùn)練初期亂撞后續(xù)很難收斂。訓(xùn)練循環(huán)的代碼反而沒(méi)什么特別用二元交叉熵加Adam優(yōu)化器就可以跑起來(lái)model DeepCrossing(feature_dims{user_id: 200000, ad_id: 80000}, ...) criterion nn.BCEWithLogitsLoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3)3.3 訓(xùn)練細(xì)節(jié)優(yōu)化器、學(xué)習(xí)率、批量大小Deep Crossing論文里使用的優(yōu)化器是Adagrad初始學(xué)習(xí)率設(shè)在0.01量級(jí)。我在自己的數(shù)據(jù)集上對(duì)比過(guò)Adam和Adagrad發(fā)現(xiàn)Adam通常收斂更快尤其是在Embedding參數(shù)多、學(xué)習(xí)率敏感的情況下Adam的逐參數(shù)自適應(yīng)特性讓調(diào)參過(guò)程更省心。想完全復(fù)現(xiàn)論文的讀者可以用Adagrad工程落地用Adam問(wèn)題不大。batch size的選擇對(duì)CTR模型影響很大。我的建議是從1024起步數(shù)據(jù)量特別大時(shí)可以用4096。batch太小梯度噪聲大Embedding更新不穩(wěn)定batch太大訓(xùn)練速度慢且模型容易收斂到尖銳極小值泛化能力差一點(diǎn)。另外注意訓(xùn)練時(shí)的負(fù)采樣比例正負(fù)樣本比例嚴(yán)重失衡會(huì)讓模型輸出的概率整體偏低統(tǒng)計(jì)AUC時(shí)還好看但線上校準(zhǔn)就很痛苦。建議訓(xùn)練集內(nèi)保證正樣本比例不低于5%到10%。訓(xùn)練到最后一個(gè)epoch時(shí)不妨把Embedding學(xué)習(xí)率調(diào)低一個(gè)量級(jí)。這一步對(duì)Embedding向量的質(zhì)量有明顯幫助可以讓低頻ID在后期微調(diào)得更穩(wěn)定我當(dāng)時(shí)試了之后AUC有小幅提升你也可以試試。4. 實(shí)際業(yè)務(wù)落地時(shí)踩過(guò)的坑紙上談兵階段一切都很美好真正放到業(yè)務(wù)里跑各種小問(wèn)題就冒出來(lái)了。我把自己踩過(guò)或者幫朋友排查過(guò)的坑挑幾個(gè)最典型的寫(xiě)在這里。4.1 低頻特征不收斂Embedding的冷啟動(dòng)問(wèn)題CTR場(chǎng)景最大的特點(diǎn)之一就是冪律分布。大部分用戶ID、廣告ID出現(xiàn)的次數(shù)極少。一個(gè)Embedding向量如果只被更新過(guò)幾十次基本無(wú)法學(xué)到有效語(yǔ)義它相當(dāng)于一個(gè)隨機(jī)向量不僅對(duì)預(yù)測(cè)沒(méi)有幫助還會(huì)給后續(xù)網(wǎng)絡(luò)帶來(lái)噪聲。我當(dāng)時(shí)在一個(gè)廣告推薦項(xiàng)目里發(fā)現(xiàn)線上新廣告ID帶來(lái)的曝光很少離線訓(xùn)練時(shí)所有低頻廣告ID的Embedding幾乎都停留在初始值附近。這直接導(dǎo)致模型對(duì)新廣告毫無(wú)區(qū)分能力冷啟動(dòng)階段AUC下跌明顯。幾個(gè)實(shí)測(cè)有效的做法對(duì)出現(xiàn)次數(shù)小于閾值的ID做統(tǒng)一替換并入一個(gè)UNK桶讓模型至少學(xué)到一個(gè)未知ID的通用表示。使用特征hash把原始ID映射到固定大小的hash空間用hash碰撞來(lái)共享參數(shù)適合需要及時(shí)上線新ID的廣告場(chǎng)景。對(duì)低頻ID的Embedding做正則約束例如限制L2范數(shù)避免它們偏離初始值太遠(yuǎn)。在訓(xùn)練時(shí)對(duì)ID做概率性dropout讓模型不依賴(lài)單個(gè)ID對(duì)冷啟動(dòng)也有微弱幫助。4.2 深層殘差網(wǎng)絡(luò)也會(huì)過(guò)擬合第一次把殘差單元從2層加到5層時(shí)離線AUC漲了一點(diǎn)我當(dāng)時(shí)還挺開(kāi)心結(jié)果上了AB實(shí)驗(yàn)線上實(shí)際CTR反而沒(méi)漲。復(fù)盤(pán)時(shí)才意識(shí)到離線AUC的上漲部分來(lái)自模型對(duì)訓(xùn)練集里高維稀疏ID組合的死記硬背而這種記憶并不能泛化到未見(jiàn)過(guò)的新組合上。控制過(guò)擬合的思路按我的經(jīng)驗(yàn)排序第一優(yōu)先減少殘差單元層數(shù)或隱藏寬度。Deep Crossing本身就不是越深越好2~3個(gè)殘差單元已經(jīng)能覆蓋足夠高的特征組合階數(shù)再增加層數(shù)只是讓模型容量增大。第二在Embedding輸出上添加Dropout。有論文和工程實(shí)踐都表明在Embedding層做dropout能顯著提升稀疏模型的泛化能力。我在殘差單元內(nèi)部加了0.1~0.3的dropout效果比在最后加Dropout更直接。第三對(duì)Embedding參數(shù)做L2約束。這個(gè)操作相當(dāng)于限制了ID向量的范數(shù)尤其是低頻ID能有效減少記住噪聲。4.3 線上pipeline與訓(xùn)練pipeline的一致性問(wèn)題這個(gè)坑幾乎每個(gè)團(tuán)隊(duì)都會(huì)遇到。訓(xùn)練時(shí)用的特征ID字典是按歷史日志構(gòu)建的線上推理時(shí)會(huì)出現(xiàn)訓(xùn)練期間從未見(jiàn)過(guò)的ID。如果線上直接把未知ID映射成0通常對(duì)應(yīng)UNK理論上沒(méi)問(wèn)題但危險(xiǎn)的是很多人沒(méi)有在訓(xùn)練數(shù)據(jù)里顯式構(gòu)造未知ID的樣本導(dǎo)致模型根本沒(méi)見(jiàn)過(guò)這個(gè)桶線上遇到時(shí)只能輸出隨機(jī)初始化Embedding對(duì)應(yīng)的結(jié)果。我的建議是訓(xùn)練時(shí)留出一部分最低頻的ID把它們強(qiáng)制替換為UNK桶編號(hào)讓模型在訓(xùn)練階段就學(xué)會(huì)未知ID該給出什么樣的保守預(yù)測(cè)。同時(shí)線上與訓(xùn)練共用同一份Embedding表不要各自維護(hù)一份詞典否則一定會(huì)出現(xiàn)字典對(duì)不上的事故。用版本號(hào)管理Embedding表和特征映射配置線上加載時(shí)強(qiáng)制校驗(yàn)版本號(hào)是我目前用過(guò)最穩(wěn)妥的方式。5. 復(fù)盤(pán)Deep Crossing的歷史坐標(biāo)和后來(lái)者很多東西的價(jià)值要放到歷史坐標(biāo)里看才清楚。Deep Crossing提出的2016年深度神經(jīng)網(wǎng)絡(luò)在推薦系統(tǒng)領(lǐng)域剛開(kāi)始大規(guī)模出現(xiàn)之后的FNN、PNN、WideDeep、DeepFM等模型一個(gè)接一個(gè)涌出來(lái)?;仡^看Deep Crossing最大的貢獻(xiàn)未必是它自身的效果而是它證明了Embedding多層非線性殘差結(jié)構(gòu)這個(gè)組合可以在大規(guī)模稀疏場(chǎng)景下穩(wěn)定落地。5.1 和同期模型的定位差異為了講清楚這些模型的區(qū)別我列一張自己整理的對(duì)比表模型特征組合方式顯式低階項(xiàng)關(guān)鍵結(jié)構(gòu)Deep Crossing深層殘差網(wǎng)絡(luò)自動(dòng)組合無(wú)Embedding 殘差單元FNN先FM得到Embedding再上層DNN無(wú)依賴(lài)預(yù)訓(xùn)練EmbeddingFM初始化EmbeddingPNN在Embedding后加Product層做向量?jī)?nèi)積/外積有一定二階顯式交叉Embedding Product LayerWideDeepWide線性部分Deep隱式交叉有Wide線性組合Wide Deep雙分支DeepFMFM捕獲低階DNN捕獲高階有FM代替WideFM Deep端到端聯(lián)合訓(xùn)練這張表的讀法是WideDeep和DeepFM都保留了顯式的低階特征項(xiàng)用來(lái)彌補(bǔ)深度網(wǎng)絡(luò)對(duì)強(qiáng)記憶信號(hào)的弱化PNN則努力在Embedding向量之間直接引入乘積操作一步到位建模二階向量交互DeepCrossing最純粹它相信只要網(wǎng)絡(luò)足夠深高階組合都能自動(dòng)學(xué)出來(lái)。5.2 它的局限純粹是把雙刃劍。Deep Crossing的局限也很典型。第一沒(méi)有顯式的低階交叉。殘差網(wǎng)絡(luò)雖然能學(xué)高階組合但低階的強(qiáng)信號(hào)比如某廣告本身很優(yōu)質(zhì)這種主效應(yīng)被淹沒(méi)在深層非線性變換里反而沒(méi)有線性模型那么敏銳。實(shí)踐中觀察到如果數(shù)據(jù)集里低階信號(hào)主導(dǎo)Deep Crossing在AUC上可能只比LR高一點(diǎn)而DeepFM因?yàn)閹е鳩M的低階項(xiàng)往往表現(xiàn)更穩(wěn)健。第二Embedding維度成為表達(dá)瓶頸。整個(gè)模型的信息容量很大程度由各Embedding向量維度決定而殘差單元是在拼接后的長(zhǎng)向量上做變換計(jì)算量和參數(shù)量都隨著特征數(shù)量線性膨脹。特征數(shù)量一多訓(xùn)練和推理成本明顯上升。第三沒(méi)有注意力機(jī)制。今天大家習(xí)以為常的根據(jù)用戶歷史行為動(dòng)態(tài)加權(quán)不同特征的機(jī)制在Deep Crossing里是缺席的。它把每個(gè)特征視為同等參與組合不會(huì)刻意強(qiáng)化當(dāng)前樣本更需要哪些特征。5.3 對(duì)今天模型的遺產(chǎn)盡管Deep Crossing現(xiàn)在看起來(lái)結(jié)構(gòu)簡(jiǎn)單但它的范式影響力無(wú)處不在。把稀疏ID轉(zhuǎn)換為Embedding再通過(guò)多層非線性網(wǎng)絡(luò)做特征交互這個(gè)過(guò)程幾乎成為了推薦/廣告深度學(xué)習(xí)模型的標(biāo)準(zhǔn)底座。今天的主流模型要么在這個(gè)底座上加了注意力比如注意力跨網(wǎng)絡(luò)模型要么加了序列建模把行為序列通過(guò)GRU/Transformer編碼后拼進(jìn)來(lái)要么加了低階結(jié)構(gòu)修正比如DeepFM的FM分支、WideDeep的Wide分支。我自己在后續(xù)好幾個(gè)項(xiàng)目里做模型迭代時(shí)也是以Deep Crossing的Embedding殘差網(wǎng)絡(luò)為起點(diǎn)逐步加入行為序列、注意力、多任務(wù)塔。這個(gè)演進(jìn)路徑說(shuō)明了它作為地基的可靠性你不需要推翻它只需要在它之上按場(chǎng)景疊加新機(jī)制。從實(shí)踐角度看如果你剛開(kāi)始嘗試自動(dòng)特征組合我建議直接復(fù)現(xiàn)一個(gè)Deep Crossing作為baseline跑通后再對(duì)比DeepFM等進(jìn)階模型。這樣你能清楚地看到低階項(xiàng)、交互項(xiàng)、注意力機(jī)制分別帶來(lái)了多少增量收益而不是一開(kāi)始就上一個(gè)黑盒大模型。最后說(shuō)一點(diǎn)個(gè)人體會(huì)。如果你剛接觸CTR模型不要急著引入各種花哨的新結(jié)構(gòu)。先把Deep Crossing這類(lèi)基礎(chǔ)模型吃透理解Embedding、Stacking、殘差單元各自到底在解決什么問(wèn)題再去看后面的模型就有一種萬(wàn)變不離其宗的感覺(jué)。我至今仍會(huì)在我負(fù)責(zé)的系統(tǒng)中保留一個(gè)Deep Crossing版本作為長(zhǎng)期對(duì)照模型用來(lái)衡量新增模塊的真實(shí)效果。它的價(jià)值不會(huì)隨時(shí)間褪色反而會(huì)成為你理解整個(gè)深度推薦模型演化史的坐標(biāo)系。