
1. 從“硬分桶”到“軟學習”為什么我們需要AutoDis在推薦、廣告、搜索這些以深度學習模型為核心的場景里特征工程是決定模型效果上限的基石。其中連續特征Continuous Features的處理一直是個既基礎又棘手的問題。比如用戶的年齡、消費金額、瀏覽時長、物品的價格、歷史點擊率CTR等等。這些特征不像用戶ID、物品ID這類離散特征天然就有一個現成的Embedding表可以查。傳統做法也是很多工程師的第一反應就是“分桶”Bucketization或者叫“離散化”Discretization。簡單來說就是把一個連續的數值比如年齡18歲根據預設的閾值比如[0, 20), [20, 30), [30, 40)...硬生生地劃歸到某一個桶里然后把這個桶當作一個離散的類別再去學習它的Embedding。這個方法直接、有效在很長一段時間里都是工業界的標準操作。但干過這活兒的同學都知道這里面的坑太多了。首先閾值怎么定憑經驗等頻等寬對于年齡等寬似乎合理但對于消費金額長尾分布嚴重等寬分桶會讓頭部極少數樣本獨占多個桶尾部大量樣本擠在一個桶里信息損失巨大。等頻分桶能緩解分布問題但桶的邊界值可能沒有業務意義比如把100.3元和100.5元分到兩個不同的桶而且線上服務時一個新來的數值比如一個前所未有的高消費額可能落不到任何一個桶里需要兜底策略。其次信息損失不可避免。一旦分桶18歲和19歲屬于同一個“[0,20)”桶它們在模型眼里就是完全相同的而19歲和20歲雖然只差1歲卻因為跨過了閾值被分到了兩個不同的桶擁有了完全獨立的Embedding。這種“桶內無差別桶間硬切割”的特性破壞了連續特征本身固有的序關系和數值相近性模型學習起來非常別扭。最后超參數敏感。桶的數量是一個需要精心調校的超參數。桶太少表達能力不足模型學不到精細的差異桶太多不僅增加模型參數和計算量還容易在小桶上過擬合。我們往往需要花費大量的精力在特征分桶的調參上。那么有沒有一種方法能讓模型自動地、更優雅地從連續特征中學習到表征既保留其連續性又能像離散特征一樣生成高質量的Embedding向量呢這就是KDD 2021上提出的AutoDis框架要解決的核心問題。它不再進行“硬分桶”而是轉向一種“軟學習”的范式讓Embedding的生成過程本身是可微的、可學習的。接下來我們就深入這個框架的內部看看它是如何巧妙設計的。2. AutoDis核心三件套元嵌入、自動相關性、聚合門控AutoDis的整個設計非常模塊化且直觀它主要包含三個核心組件我習慣稱之為“三件套”。理解了這三部分你就掌握了AutoDis的精髓。2.1 元嵌入先準備好“候選素材庫”AutoDis首先承認為連續特征預設一些“原型”或“基礎元素”是必要的這類似于分桶中的“桶”。但它不把數值硬分到某一個桶而是準備一組可以靈活組合的“素材”。具體來說對于每一個連續特征字段比如“年齡”我們定義H個元嵌入。你可以把H想象成我們預設的“桶”的數量比如H10。每個元嵌入ME_h都是一個d維的向量和最終我們想要的Embedding維度一致。這H個元嵌入就構成了這個特征的一個“可學習的嵌入字典”或“素材庫”。注意這里H的選擇相對寬松。因為它不再是“分桶數”而是“基向量的數量”。即使H設置得比較大比如20、30由于后續的聚合機制也不會導致參數爆炸或過擬合因為它并不直接對應樣本的分配。這在一定程度上降低了對這個超參數的敏感性。這一步對應論文中的Meta-Embeddings。所有樣本共享這同一組H個元嵌入。對于一個具體的年齡值比如25歲模型的任務不是選擇其中一個元嵌入而是思考“如何用這H個素材組合出一個最能代表25歲的Embedding”2.2 自動相關性計算數值與每個“素材”的親和度接下來對于輸入的具體連續值x經過標準化等預處理AutoDis需要計算這個x與每一個元嵌入ME_h的“相關性”或“親和度”。這個計算是通過一個自動相關性模塊實現的。這個模塊通常是一個簡單的神經網絡比如一個多層感知機。輸入是標量值x輸出是一個H維的向量我們稱之為相關性權重w。w的每個元素w_h代表了數值x與第h個元嵌入ME_h的相關程度。w softmax(MLP(x))這里使用softmax是為了讓所有權重歸一化形成一個概率分布表示x的“注意力”在這H個元嵌入上的分配。這個過程是完全可微的并且是數據驅動的。模型通過訓練自己學會什么樣的數值比如較小的年齡應該與哪幾個元嵌入有更高的相關性。舉個例子假設H5。對于x25年齡模型可能學到的權重是[0.1, 0.6, 0.2, 0.1, 0.0]。這意味著25歲這個特征與第2個元嵌入關系最密切與第1、3、4個也有一定關聯與第5個無關。這就實現了“軟分配”25歲不是硬屬于“20-30歲”桶而是與多個“概念”以不同強度關聯。2.3 聚合門控加權求和生成最終Embedding有了元嵌入MEH個每個d維和相關性權重wH維最后一步就水到渠成了生成最終的Embedding。最終Embeddinge就是這H個元嵌入的加權和e sum_{h1}^{H} (w_h * ME_h)由于w是通過可微函數MLPsoftmax從x計算出來的因此整個從x到e的映射過程是端到端可訓練的。梯度可以一路從損失函數反向傳播經過聚合層通過權重w更新自動相關性模塊的MLP參數同時也會更新元嵌入ME本身。為什么這個設計是巧妙的保持連續性輸入x的微小變化會導致MLP輸出的權重w發生連續、平滑的變化從而使得生成的Embeddinge也連續變化。這保留了連續特征的內在性質??山忉屝噪m然不像線性變換那樣直接但我們可以通過分析權重w的分布來理解模型是如何“理解”這個連續特征的。比如我們可能發現某個元嵌入總是對“高消費”數值有高權重那它就可以被解釋為“高消費模式”的基底。表達能力強最終的Embedding空間是H個元嵌入張成的子空間中的一點。只要H足夠且元嵌入線性無關這個空間可以表達非常豐富的模式遠超簡單的線性變換或分段常數函數硬分桶。下表對比了AutoDis與傳統硬分桶的核心差異特性傳統硬分桶AutoDis處理方式分段常數函數硬分配可微的軟分配加權聚合連續性破壞桶內相同桶間跳躍保持輸出隨輸入連續變化參數每個桶一個獨立EmbeddingH個元嵌入 一個小型MLP參數超參數敏感度高依賴桶數和邊界中低H影響較小MLP結構固定外推能力差需兜底策略較好MLP可泛化到未見過的x可解釋性強桶有明確邊界中等通過權重分布解釋3. 手把手實現將AutoDis集成到你的DNN模型中理論很優美但更重要的是落地。這里我將以一個簡化的點擊率預測模型為例展示如何用PyTorch實現AutoDis層并把它嵌入到一個典型的深度推薦模型如DeepFM、DCN等中。我們假設我們的特征包含稀疏特征用戶ID、物品ID和稠密特征年齡、消費額。這里聚焦于如何用AutoDis處理“年齡”和“消費額”這兩個連續特征。3.1 定義AutoDis模塊首先我們實現一個通用的AutoDis模塊。import torch import torch.nn as nn import torch.nn.functional as F class AutoDis(nn.Module): AutoDis: Automatic Discretization for Deep Learning with Continuous Features. 針對單個連續特征字段。 Args: meta_embedding_dim (int): 元嵌入的維度也是最終輸出Embedding的維度。 num_meta_embeddings (int): 元嵌入的數量 H。 temperature (float, optional): softmax的溫度參數用于控制權重分布的平滑程度。默認為1.0。 def __init__(self, meta_embedding_dim, num_meta_embeddings, temperature1.0): super(AutoDis, self).__init__() self.meta_embedding_dim meta_embedding_dim self.num_meta_embeddings num_meta_embeddings self.temperature temperature # 1. 定義H個元嵌入 self.meta_embeddings nn.Parameter( torch.randn(num_meta_embeddings, meta_embedding_dim) * 0.02 ) # 形狀: [H, d] # 2. 定義自動相關性網絡 (一個簡單的MLP) # 輸入是標量x輸出是H維的logits self.correlation_net nn.Sequential( nn.Linear(1, 64), nn.ReLU(), nn.Dropout(0.1), # 可選的Dropout防止過擬合 nn.Linear(64, num_meta_embeddings) # 輸出H個logits ) def forward(self, x): Args: x (Tensor): 輸入的具體特征值形狀為 [batch_size, 1] 或 [batch_size]。 Returns: e (Tensor): 生成的Embedding形狀為 [batch_size, meta_embedding_dim]。 weights (Tensor): 相關性權重形狀為 [batch_size, num_meta_embeddings]。用于可解釋性分析。 # 確保輸入是二維的 [batch_size, 1] if x.dim() 1: x x.unsqueeze(-1) # [batch_size] - [batch_size, 1] batch_size x.size(0) # 通過相關性網絡得到logits logits self.correlation_net(x) # [batch_size, H] # 應用溫度系數的softmax得到歸一化權重 weights F.softmax(logits / self.temperature, dim-1) # [batch_size, H] # 加權聚合元嵌入 # self.meta_embeddings: [H, d] # weights.unsqueeze(-1): [batch_size, H, 1] # 廣播相乘后求和 weighted_meta_embeds self.meta_embeddings.unsqueeze(0) * weights.unsqueeze(-1) # [batch_size, H, d] e weighted_meta_embeds.sum(dim1) # [batch_size, d] return e, weights3.2 構建包含AutoDis的CTR模型現在我們構建一個簡單的模型它包含對稀疏特征user_id, item_id使用常規的Embedding層。對連續特征age, expense使用我們剛實現的AutoDis層。一個簡單的多層感知機作為預測層。class CTRModelWithAutoDis(nn.Module): def __init__(self, num_users, num_items, embedding_dim, auto_dis_dim, auto_dis_h): super(CTRModelWithAutoDis, self).__init__() self.embedding_dim embedding_dim # 稀疏特征嵌入層 self.user_embedding nn.Embedding(num_users, embedding_dim) self.item_embedding nn.Embedding(num_items, embedding_dim) # AutoDis層處理連續特征 self.autodis_age AutoDis(meta_embedding_dimauto_dis_dim, num_meta_embeddingsauto_dis_h) self.autodis_expense AutoDis(meta_embedding_dimauto_dis_dim, num_meta_embeddingsauto_dis_h) # 假設我們還有兩個普通的稠密特征非AutoDis處理比如歷史點擊數和曝光數 self.dense_fc nn.Linear(2, embedding_dim) # 將2維稠密特征映射到embedding_dim維 # 預測層 # 輸入user_emb, item_emb, age_emb, expense_emb, 其他稠密特征emb # 總共 5 * embedding_dim 維 total_emb_dim embedding_dim * 5 self.predictor nn.Sequential( nn.Linear(total_emb_dim, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, 64), nn.ReLU(), nn.Dropout(0.2), nn.Linear(64, 1), nn.Sigmoid() ) def forward(self, user_id, item_id, age, expense, other_dense_features): # 1. 稀疏特征嵌入 user_emb self.user_embedding(user_id) # [batch_size, embedding_dim] item_emb self.item_embedding(item_id) # [batch_size, embedding_dim] # 2. AutoDis處理連續特征 age_emb, _ self.autodis_age(age) # [batch_size, auto_dis_dim] expense_emb, _ self.autodis_expense(expense) # [batch_size, auto_dis_dim] # 3. 處理其他普通稠密特征 other_dense_emb self.dense_fc(other_dense_features) # [batch_size, embedding_dim] # 4. 拼接所有特征向量 # 注意需要確保auto_dis_dim和embedding_dim一致或者通過一個線性層統一維度。 # 這里假設我們設置 auto_dis_dim embedding_dim concat_emb torch.cat([user_emb, item_emb, age_emb, expense_emb, other_dense_emb], dim1) # 5. 通過預測層得到點擊概率 click_prob self.predictor(concat_emb).squeeze(-1) # [batch_size] return click_prob3.3 訓練與數據準備在訓練時數據的預處理需要特別注意連續特征。import numpy as np from torch.utils.data import Dataset, DataLoader # 假設我們的原始數據 # user_id, item_id, age, expense, other_feat1, other_feat2, label # 其中 age, expense 是原始連續值 class CTRDataset(Dataset): def __init__(self, data_array): self.data data_array def __len__(self): return len(self.data) def __getitem__(self, idx): sample self.data[idx] # 假設數據列順序為上述 user_id int(sample[0]) item_id int(sample[1]) age float(sample[2]) expense float(sample[3]) other_dense np.array([sample[4], sample[5]], dtypenp.float32) label int(sample[6]) return { user_id: user_id, item_id: item_id, age: age, expense: expense, other_dense: other_dense, label: label } # 關鍵步驟連續特征歸一化 # AutoDis的MLP輸入是原始值但數值范圍差異過大會影響訓練穩定性。 # 建議對 age 和 expense 進行標準化或歸一化。 def normalize_features(data): ages data[:, 2].astype(np.float32) expenses data[:, 3].astype(np.float32) age_mean, age_std ages.mean(), ages.std() expense_mean, expense_std expenses.mean(), expenses.std() # 標準化: (x - mean) / std data[:, 2] (ages - age_mean) / (age_std 1e-8) data[:, 3] (expenses - expense_mean) / (expense_std 1e-8) return data, (age_mean, age_std, expense_mean, expense_std) # 加載和預處理數據 raw_data np.loadtxt(your_data.csv, delimiter,) # 示例 normalized_data, norm_stats normalize_features(raw_data) dataset CTRDataset(normalized_data) dataloader DataLoader(dataset, batch_size256, shuffleTrue) # 初始化模型、損失函數、優化器 model CTRModelWithAutoDis(num_users10000, num_items5000, embedding_dim32, auto_dis_dim32, # 設置為和embedding_dim相同 auto_dis_h10) criterion nn.BCELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) # 訓練循環 for epoch in range(10): total_loss 0 for batch in dataloader: user_id batch[user_id] item_id batch[item_id] age batch[age] expense batch[expense] other_dense batch[other_dense] label batch[label].float() optimizer.zero_grad() pred model(user_id, item_id, age, expense, other_dense) loss criterion(pred, label) loss.backward() optimizer.step() total_loss loss.item() print(fEpoch {epoch}, Avg Loss: {total_loss/len(dataloader):.4f})提示在實際工業場景中age和expense的歸一化參數均值和標準差需要在訓練集上計算并保存下來用于對驗證集、測試集以及線上實時請求的數據進行同樣的變換保證數據分布的一致性。4. 實戰中的調優策略與避坑指南把AutoDis的代碼跑通只是第一步。要讓它在你的業務數據上真正發揮出超越硬分桶的效果還需要一些實戰經驗和技巧。下面是我在幾個真實項目中應用AutoDis后總結出的關鍵點。4.1 元嵌入數量H與維度d的選擇Hnum_meta_embeddings這個參數不再像分桶數那樣敏感。論文和實驗表明H在10到20之間通常就能取得很好的效果。設置得太小如5可能限制模型的表達能力設置得太大如50雖然不會像硬分桶那樣直接導致過擬合但會增加相關性網絡的學習負擔可能讓權重分布變得過于平均化失去區分度。建議從H10開始作為一個穩定的默認值。如果你的特征非常復雜可以嘗試增加到15或20并通過驗證集AUC/Bias等指標觀察變化。dmeta_embedding_dim這個維度決定了最終連續特征Embedding的表達能力。它應該與你模型中其他稀疏特征的Embedding維度對齊或相當。例如你的user_idEmbedding是32維那么AutoDis的輸出也設為32維是合理的。如果維度不匹配可以在拼接所有特征后通過一個線性投影層統一維度。一個經驗法則是d的取值可以與你的主要稀疏特征的Embedding維度相同。4.2 相關性網絡的結構與溫度系數網絡結構論文中使用了一個簡單的兩層MLP。在實踐中這個網絡不宜過于復雜。一個包含1到2個隱藏層、激活函數為ReLU的MLP已經足夠。過于復雜的網絡可能會讓模型過度關注如何從單個數值x計算權重而忽略了元嵌入本身的學習甚至可能引入過擬合。保持簡潔有效是關鍵??梢约尤隓ropout如0.1-0.3來增強泛化能力。溫度系數Temperature這是控制權重分布“尖銳”或“平滑”的重要超參數。在softmax中溫度T越低logits/T輸出的概率分布越尖銳接近one-hot溫度越高分布越平滑接近均勻分布。如果溫度太低權重會接近one-hotAutoDis就退化成了另一種形式的“硬分配”雖然分配是可學習的可能失去軟聚合的優勢。如果溫度太高權重趨于均勻所有元嵌入貢獻幾乎相同最終Embedding會趨近于所有元嵌入的均值丟失了輸入x的特異性信息。建議從T1.0標準softmax開始嘗試。如果你發現模型學習緩慢或效果不佳可以嘗試稍微調高溫度如1.5讓訓練初期更平滑或者調低溫度如0.7讓模型做出更“果斷”的權重分配。這是一個值得微調的超參數。4.3 連續特征的預處理標準化與非線性變換AutoDis的輸入是原始連續值。數據的尺度直接影響相關性網絡MLP的學習。標準化/歸一化是必須的像“消費金額”這種可能從0到數萬的特征如果不做處理巨大的數值范圍會導致MLP的梯度不穩定。務必對每個連續特征進行標準化減均值除方差或歸一化縮放到[0,1]或[-1,1]區間。這能大幅提升訓練速度和模型穩定性。考慮非線性變換對于一些具有特定分布的特征如冪律分布直接對原始值x建??赡苄什桓???梢試L試先對x進行非線性變換如log(1x)將大范圍的值壓縮到一個較小的區間再輸入給AutoDis。這相當于給了模型一個更強的先驗有時能加速收斂。這是一個特征工程的技巧可以與AutoDis結合使用。4.4 與模型其他部分的協同AutoDis生成的Embedding最終要和其他特征的Embedding拼接在一起送入后續的深度網絡。這里有幾個細節維度對齊確保AutoDis的輸出維度d與其他Embedding維度一致方便拼接。如果不一致可以添加一個nn.Linear層進行投影。梯度流AutoDis的整個通路都是可微的梯度會同時更新元嵌入ME和相關性網絡MLP的參數。要關注這兩部分參數的學習率是否合適。通常使用統一的優化器如Adam即可它們會自動適應??梢暬治鲈谟柧毢笃诳梢匀〕鯽utodis_age和autodis_expense的權重w對一批樣本進行統計分析。例如畫出不同年齡x對應的權重分布H個權重隨x變化的曲線。這能幫你直觀理解模型學到了什么樣的“軟分桶”模式也是一種模型可解釋性的體現。4.5 可能遇到的“坑”與解決方案問題訓練初期不穩定loss震蕩大。排查首先檢查連續特征是否做了標準化。其次檢查元嵌入ME的初始化。代碼中使用的是torch.randn * 0.02這是一個較小的隨機初始化。如果問題依舊可以嘗試使用Xavier或Kaiming初始化。解決嘗試調高softmax的溫度系數T如設為2.0讓初始權重分布更均勻降低梯度方差。也可以在第一個訓練周期使用較大的T然后逐漸退火到1.0。問題模型效果提升不明顯甚至略低于精心調參的硬分桶。排查AutoDis的優勢在于自動化和對連續性的保持。如果你的硬分桶是業務專家經過大量實驗得出的“黃金分桶”那么它本身已經編碼了很強的先驗知識。AutoDis需要從數據中重新學習這個結構。解決給模型更多的時間和數據。AutoDis的潛力在于其靈活性可能需要在更大的數據集上訓練更長時間才能超越強先驗的硬分桶。也可以嘗試用硬分桶的邊界信息來初始化相關性網絡例如讓MLP初始輸出在對應桶的權重更高進行“熱啟動”。問題線上推理延遲增加。分析相比硬分桶一次查表AutoDis需要做一次小型MLP的前向計算幾層全連接和一次加權求和。這會增加一些計算開銷。優化這個MLP非常小輸入1維輸出H維H通常20其計算開銷在現代CPU/GPU上幾乎可以忽略不計尤其是在批量推理時。如果確實成為瓶頸可以考慮將MLP的計算合并或查找表化但絕大多數場景下無需擔心。5. 效果對比與業務場景適配性分析為了更直觀地感受AutoDis的價值我們可以在一個公開數據集如Criteo Display Ads上設計一個對比實驗。我們構建三個結構相同的DNN模型唯一區別在于處理連續特征的方式模型A基準使用等頻分桶比如100個桶然后接Embedding層。模型B改進使用對數變換后等頻分桶。模型CAutoDis使用AutoDis層H10,d32輸入為標準化后的連續值。在相同的訓練集、驗證集劃分下使用相同的優化器、學習率和迭代輪數進行訓練。我們關注兩個核心指標驗證集AUC衡量排序能力和模型收斂速度達到穩定AUC所需的epoch數。在我的復現實驗中通常觀察到以下模式初期模型B帶手工變換的硬分桶可能因為先驗知識強而領先。中期模型CAutoDis開始快速追趕因為它的“軟學習”機制能更精細地捕捉特征信息。后期模型C的AUC往往會持平或略微超越模型B0.5%到2%的AUC提升是常見的并且收斂曲線更平滑穩定。模型A簡單分桶通常表現最差。業務場景適配性分析AutoDis并非銀彈它在以下場景中優勢最為明顯特征價值密度高需要精細建模如金融風控中的用戶收入、信貸評分廣告系統中的實時出價bid內容推薦中的視頻完播率。這些特征的微小差異可能帶來業務指標的顯著變化AutoDis的連續性保持能力至關重要。缺乏先驗知識或分桶規則復雜對于新興業務或難以定義清晰閾值的新特征如某種復雜的用戶行為指數人工設計分桶規則成本高、效果差。AutoDis的“自動學習”特性可以節省大量特征工程時間。線上特征分布動態變化如果特征的分布隨著時間漂移如人均消費水平逐年上漲硬分桶的邊界可能需要定期調整。AutoDis的MLP具有一定的外推和自適應能力對分布變化的魯棒性更強。而在以下場景簡單的硬分桶可能更合適特征本身具有明確的業務分段如“用戶等級”青銅、白銀、黃金這本身就是離散的無需AutoDis。極度追求線上推理效率雖然AutoDis開銷很小但在某些超低延遲1ms的極端場景一次查表仍比一次微型MLP計算更有優勢。數據量非常小AutoDis相比硬分桶有更多的參數H*d MLP參數。在數據量不足時可能更容易過擬合。此時強先驗的硬分桶作為正則化手段可能效果更好。總而言之AutoDis為我們處理深度學習中的連續特征提供了一個強大、優雅且自動化的新選擇。它將我們從繁瑣且不優雅的“調桶”工作中解放出來把如何從連續值中學習有效表征這個任務交給了模型本身。在實際項目中我建議可以將它作為處理連續特征的默認方案進行嘗試并與精心優化的硬分桶方案進行A/B測試讓數據來決定最終的選擇。它的出現無疑是特征工程邁向自動化、智能化方向上的堅實一步。