工程實踐#

推薦系統的落地需要解決許多工程問題。本章涵蓋資料採集、即時推薦、實驗平台、服務化等實踐經驗。

資料採集#

採集要素#

要素說明範例
用戶標識唯一識別用戶user_id、device_id、cookie
行為類型用戶做了什麼曝光、點擊、購買、收藏
行為對象作用於什麼物品item_id
時間戳記什麼時候發生timestamp
上下文發生的環境頁面、位置、設備

客戶端 vs 服務端埋點#

類型優點缺點
客戶端埋點能記錄前端事件(滑動、停留、曝光)上報易丟失、版本碎片化
服務端埋點穩定可靠、不需發版看不到前端純客戶端行為

實務上兩者並用:曝光 / 滑動 / 停留 用客戶端,下單 / 支付 用服務端。

埋點規範#

{
  "event_type": "click",
  "user_id": "u123456",
  "item_id": "i789",
  "timestamp": 1706313600000,
  "page_id": "home",
  "position": 3,
  "recommend_id": "rec_xxx",
  "device": {
    "os": "iOS",
    "version": "17.0"
  }
}

recommend_id 非常重要,用於關聯曝光與後續行為,是追蹤推薦效果與訓練樣本的串接鍵。沒有 recommend_id 等於沒有訓練資料。

資料品質保障#

問題影響解決方案
資料丟失訓練資料不完整多端上報、本地快取重傳
資料延遲特徵更新不及時流批一體架構
資料錯誤模型效果下降資料校驗、異常監控
重複資料統計偏差去重機制
Position Bias高位天然點擊率高,污染訓練位置特徵建模、IPS 校正、用戶側截斷

Position Bias 是新手最常踩的坑:用戶點擊第一個位置不代表喜歡,可能只是因為它在最上面。如果不處理,模型會學到「越靠前越好」這種無意義的規律。常見處理方式是把 position 當特徵餵給模型,預測時固定 position=0。

資料流:批次、流式與 CDC#

採集到的資料要怎麼流進特徵與模型?這決定了系統的新鮮度與成本。

批次 vs 流式#

維度批次處理(Batch)流式處理(Stream)
延遲高(小時 / 天級)低(秒級)
成本低(離峰算、可重跑)高(常駐、狀態管理)
資料範圍全量歷史最近事件
典型用途模型訓練、全量特徵即時特徵、即時計數

選型看延遲需求:能等就用批次(便宜又能重跑),要秒級反應才上流式。這也呼應 第 8 章 的 Lambda / Kappa——兩者其實是同一個取捨在不同層的展現。

變更資料捕捉(CDC)#

要把生產資料庫的變更同步進特徵管線,最穩健的做法是 CDC(Change Data Capture)

  • 日誌式 CDC:直接解析資料庫的 WAL / binlog,捕捉每一筆 insert/update/delete,對來源庫零侵入。
  • 通常先做一次初始快照,再持續追日誌;下游用 日誌壓縮(log compaction) 保留每個鍵的最新狀態。

事件日誌作為單一真相來源#

CDC 捕捉的是「資料庫狀態的變化」,而事件溯源(Event Sourcing) 記錄的是「用戶意圖的不可變事實」(曝光、點擊本身)。在推薦系統,這串不可變事件日誌就是訓練資料與特徵的源頭:

用戶行為事件 → Kafka(不可變日誌) → ┬→ 流式:即時特徵
                                      ├→ 批次:訓練樣本
                                      └→ 回放:重建任意時點的特徵

事件日誌「只追加、可重播」帶來兩個關鍵能力:(1) 出問題能重播重建歷史狀態;(2) 同一份事實能同時餵給批次與流式,天然保證兩條路徑的一致性——這正是解決下文「訓練 / 服務偏移」的基礎。

去重策略#

內容去重:SimHash#

處理相似內容,避免推薦重複新聞 / 影片:

1. 對文本分詞,計算每個詞的權重
2. 每個詞雜湊成二進位向量,0 變成 -1
3. 加權求和,正數變 1、負數變 0
4. 得到 SimHash 指紋

特點:

  • 相似內容的 SimHash 接近
  • 用漢明距離判斷相似度
  • 計算高效

用戶去重:Bloom Filter#

防止重複推薦用戶已看過的內容:

1. 準備長度為 m 的二進位向量
2. 使用 k 個獨立雜湊函數
3. 每個已推薦物品雜湊到 k 個位置,設為 1
4. 查詢時,k 個位置都為 1 則認為已推薦過

Bloom Filter 有偽陽性(可能把未推薦的判為已推薦),但可控。實務上用 Redis Bloom Filter 即可。

即時推薦#

三個層次#

層次說明難度
給得及時服務回應快基本要求
用得及時特徵即時更新中等
改得及時模型即時更新困難

即時架構#

用戶行為 → Kafka → 流計算 → 特徵更新 → 線上服務
                    ↓
                模型增量更新
                    ↓
              推薦結果更新

增量更新 Item-CF#

當用戶產生新行為時:

增量更新邏輯
輸入:用戶 u 對物品 i 產生行為

步驟:
1. 取出用戶歷史行為物品列表 [j1, j2, ...]
2. 對每個 j:
   - 增加 (i, j) 的共現次數
   - 增加 i 的評分用戶數
3. 重算所有相關物品對的相似度
4. 更新推薦結果

效率優化#

方法說明
剪枝相似度收斂後不再更新(Hoeffding 不等式)
滑窗只保留最近 K 條行為
採樣對高頻行為降採樣
合併批次處理多條行為再更新
快取熱門資料放入快取

實驗平台#

為什麼需要實驗平台#

資料驅動的核心是做對比實驗。沒有實驗平台,所有「我覺得這樣比較好」都只是猜測。

A/B 測試三要素#

要素說明
流量用戶造訪,實驗樣本來源
參數不同策略或模型的配置
結果日誌記錄,分析實驗效果

Google 分層實驗架構#

        全部流量
            ↓
    ┌───────┴───────┐
    域 1           域 2
    ↓               ↓
  層 1 → 桶 1-5    獨立實驗
    ↓
  層 2 → 桶 1-5
    ↓
  層 3 → 桶 1-5

核心概念:

概念說明
域(Domain)流量的劃分
層(Layer)參數子集的測試
桶(Bucket)實驗組與對照組

為什麼要分層? 同時跑多個獨立實驗時,分層能讓不同層的流量正交(同一用戶可能在 A 層落入實驗組、B 層落入對照組),大幅提升並行實驗能力。

流量分配#

def get_bucket(user_id, layer_id, n_buckets=1000):
    """
    計算用戶落入哪個桶
    - 同一用戶在同一層始終落入同一桶(體驗一致)
    - 不同層的分配相互獨立(無偏置)
    """
    hash_key = f"{user_id}_{layer_id}"
    hash_value = md5(hash_key.encode()).hexdigest()
    return int(hash_value, 16) % n_buckets

實驗規模估算#

Google 公式:

N >= 10.5 × (s/θ)²
  • s:指標標準差
  • θ:希望檢測的敏感度(如 2% 的 CTR 變化)

這個規模保證 90% 的統計檢力(power)。

不要看到 CTR 漲了 0.1% 就上線。先看:(1) 樣本量是否足夠 (2) 統計顯著性 (3) 多目標一起看(CTR 漲但時長跌就不一定是進步)。

服務化#

線上服務要求#

要求指標
延遲P99 < 100ms
可用性99.99%
QPS視業務量級

微服務架構#

API Gateway
    ↓
┌───┴───┐
召回服務  排序服務
    ↓        ↓
Redis   模型服務

模型部署#

方式適用場景
嵌入式小模型,直接載入到服務行程
RPC 服務大模型,獨立部署
TensorFlow ServingTF 模型標準化部署
TorchServePyTorch 模型部署
TritonNVIDIA GPU 推理服務、多框架支援

線上監控#

線上推薦系統至少要監控四類指標:

  1. 服務指標:QPS、延遲(P50/P99)、錯誤率
  2. 業務指標:CTR、轉換率、人均時長
  3. 資料指標:曝光數、特徵覆蓋率、空召回率
  4. 模型指標:分數分佈、預測 / 實際 CTR 偏差(calibration)

任何一類異常都可能在用戶感知前先報警。

訓練 / 服務偏移#

「線下評估漲,線上指標跌」最常見的元兇是 訓練 / 服務偏移(training-serving skew):訓練時與服務時的特徵不一致。

偏移來源例子
計算邏輯不同訓練用 Python 算特徵,線上用 Java 重寫一份
時間點不同訓練不小心用到「未來」資訊(特徵穿越)
分佈漂移新用戶、季節變化讓線上分佈偏離訓練分佈

緩解的核心是 Feature Store:離線訓練與線上推理讀同一份特徵定義與計算邏輯,並對特徵做版本管理;線上再加 schema 與數值範圍校驗,異常即時攔截。偵測上,持續比對「訓練 vs 服務」的特徵分佈、監控 calibration 漂移。

可重現性#

要能重現一次訓練結果,必須把這些一起釘版本:訓練程式碼、資料切分、特徵定義、超參數、依賴環境。配合不可變事件日誌,就能「從某個時間點回放重建特徵」,讓實驗可追溯、可複現——這也是 A/B 實驗能拿來做嚴謹決策的前提。

開源工具#

演算法層#

任務工具
文本處理FastText、Gensim、jieba
協同過濾Surprise、Implicit
矩陣分解Spark ALS、Implicit
深度學習TensorFlow、PyTorch
向量檢索Faiss、HNSW、ScaNN、Milvus

平台層#

任務工具
訊息佇列Kafka、Pulsar
流計算Flink(主流)、Spark Streaming
批次處理Spark
儲存Redis、Cassandra、HBase
搜尋引擎ElasticSearch、OpenSearch
Feature StoreFeast、Tecton

完整推薦框架(學習用)#

專案語言特點
SurprisePython簡單易用,適合入門
RecBolePython深度學習推薦框架
LensKitJava學術界常用

工業界很少用「大而全」的框架,建議按模組選工具自己組合,更容易診斷問題與優化。

團隊組建#

最小配置#

角色數量職責
演算法工程師1-2資料分析、模型訓練、效果評估
後端工程師2推薦服務、API 開發
資料工程師1-2資料採集、特徵管線、日誌管理

工程師核心素質#

素質說明
工程能力快速交付高效、低 bug 的程式碼
理論基礎能看懂論文、理解演算法原理
視覺化思維把資料規律直觀呈現出來
業務 sense把模型指標連回真實業務 KPI

總結#

要點說明
資料採集規範埋點、客戶 / 服務端互補、處理 Position Bias
資料流批次 vs 流式、CDC、事件日誌作為單一真相來源
去重策略SimHash 內容去重、Bloom Filter 用戶去重
即時推薦三個層次:服務即時、特徵即時、模型即時
實驗平台分層實驗、無偏流量分配、足夠樣本量
服務化低延遲、高可用、四類指標監控、防訓練/服務偏移
開源工具按模組選擇、自己組合

下一章:品質與安全