協同過濾#

協同過濾(Collaborative Filtering,CF)是推薦系統最經典的方法。

它的核心思想是利用群體智慧:相似的用戶喜歡相似的物品,相似的物品被相似的用戶喜歡。

為什麼今天還要學 CF?因為它是後面所有方法的概念基礎:矩陣分解 是 CF 的降維版本、向量召回則是 Item-CF 的向量化版本。理解 CF 才能看懂後續演進的動機。

基本概念#

用戶 - 物品矩陣#

        物品1  物品2  物品3  物品4
用戶A    5      3      ?      1
用戶B    4      ?      ?      1
用戶C    1      1      ?      5
用戶D    ?      1      5      4

CF 的核心任務是填充矩陣中的空白,預測用戶對未評分物品的評分(或互動機率)。

兩種主要方法#

方法核心思想比對的對象
User-Based CF相似的用戶喜歡相似的物品用戶相似度
Item-Based CF用戶喜歡與歷史物品相似的物品物品相似度

User-Based 協同過濾#

演算法流程#

1. 計算用戶之間的相似度
2. 找到目標用戶的 K 個最相似用戶
3. 用這 K 個用戶的評分加權預測目標用戶的評分
4. 推薦預測評分最高的物品

預測公式#

r̂(u,i) = r̄_u + Σ[sim(u,v) × (r(v,i) - r̄_v)] / Σ|sim(u,v)|
  • r̄_u:用戶 u 的平均評分(去除個人打分偏差)
  • sim(u,v):用戶 u 和 v 的相似度
  • r(v,i):用戶 v 對物品 i 的評分

優缺點#

優點缺點
可以發現用戶潛在興趣用戶量大時計算複雜
推薦結果可解釋用戶冷啟動問題
實作簡單相似度矩陣需頻繁更新

Item-Based 協同過濾#

演算法流程#

1. 計算物品之間的相似度
2. 對用戶歷史評分過的每個物品
3. 找到該物品的 TopK 相似物品
4. 以歷史評分加權預測用戶對新物品的評分

預測公式#

r̂(u,i) = Σ[sim(i,j) × r(u,j)] / Σ|sim(i,j)|
  • sim(i,j):物品 i 和 j 的相似度
  • r(u,j):用戶 u 對物品 j 的評分

與 User-Based 的比較#

維度User-BasedItem-Based
比對對象用戶相似度矩陣物品相似度矩陣
適用場景用戶少、物品多用戶多、物品少
更新頻率需頻繁更新相對穩定
工業應用較少廣泛(Amazon、Netflix)
冷啟動物品冷啟動較好用戶冷啟動較好
可解釋性「跟你像的人也喜歡」「因為你看過 X」

工業實踐:Item-Based CF 更常用,原因有三:

  1. 物品數量通常比用戶少,相似度矩陣較小
  2. 物品之間的相似度比用戶口味更穩定,可以離線計算
  3. 解釋性更好:「因為你看過 X,所以推薦 Y」直觀

相似度計算方法#

餘弦相似度#

cos(u,v) = (u·v) / (|u| × |v|)
         = Σ(r_ui × r_vi) / √(Σr_ui²) × √(Σr_vi²)

只看「方向」不看「強度」,最常用。

皮爾遜相關係數#

pearson(u,v) = Σ[(r_ui - r̄_u)(r_vi - r̄_v)] /
               √[Σ(r_ui - r̄_u)²] × √[Σ(r_vi - r̄_v)²]

皮爾遜相關係數先扣掉每位用戶的平均評分,可消除「有人習慣打高分、有人習慣打低分」的偏差。在顯式評分場景(如電影評分)特別有用。

Jaccard 相似度#

jaccard(u,v) = |I_u ∩ I_v| / |I_u ∪ I_v|

只看是否互動過,不看評分強度。適合純隱式回饋(點擊、瀏覽)。

三者怎麼選?#

資料型態建議方法
隱式回饋(0/1)Jaccard、餘弦
顯式評分(1~5)皮爾遜
高維稀疏向量餘弦

三者的取捨其實是「保留什麼、丟掉什麼」:

  • 餘弦忽略強度(magnitude),對「打分習慣」不敏感,但對互動次數多的長尾用戶有偏。
  • 皮爾遜先各自扣掉平均,移除個人打分基準(有人天生打高 1 分),最適合顯式評分。
  • Jaccard只看「有沒有互動過」,丟掉強度資訊——停留 10 秒和 10 分鐘都算一次。

共通鐵則:相似度只在共同互動過的物品上算才有意義;重疊太少時(例如只有 1 個共同物品)餘弦可能算出 1.0,看似完美其實極不可靠。

相似度計算的優化#

問題:熱門物品偏差#

熱門物品與任何物品的共同用戶都很多,容易導致相似度被「灌水」。

解決方案:IUF(Inverse User Frequency)#

借用 TF-IDF 的思想,把活躍用戶的貢獻降權:

sim(i,j) = Σ[1/log(1 + |N_u|)] / √(|N_i| × |N_j|)
  • N_u:用戶 u 評分過的物品數
  • 活躍用戶(什麼都看)對相似度的貢獻被打折扣

歸一化處理#

避免某些物品的相似度普遍偏高,在每個物品的相似度向量內做正規化:

sim_norm(i,j) = sim(i,j) / max(sim(i,·))

鄰居選取與可靠度#

算完相似度後,要決定「拿幾個鄰居來預測」以及「哪些鄰居可信」。

鄰居數 K 怎麼選#

K 太小K 太大
鄰居少、雜訊大混進不相似的鄰居、預測被稀釋
  • 實務常見 K 落在 20~50,且需依資料集與場景調參,沒有萬用常數。
  • 也可改用相似度門檻取代固定 K:只納入 sim > 門檻(如 0.5)的鄰居,讓每個用戶自適應鄰居數。
  • 鄰居過少的冷門用戶,退回熱門物品或全域平均作為保底。

加權聚合與最小共現#

預測公式裡的 sim(u,v) 就是權重——越相似的鄰居,評分占比越高,分母 Σ|sim| 把權重歸一化。為了避免「樣本太少的高相似度」誤導:

  • 最小共現門檻:只在共同物品數 ≥ 某值(如 5)時才採信該相似度。
  • 對小樣本做收縮(shrinkage):往全域平均靠攏,樣本越少收縮越強。

Item-Based 在這點天生較穩:物品之間的共同評分用戶通常比「兩個用戶的共同物品」多,所以 item-item 相似度比 user-user 更可靠,這也是工業界偏好 Item-Based 的隱藏原因之一。

實踐中的挑戰#

1. 稀疏性問題#

用戶 - 物品矩陣通常 99.9% 以上為空,導致直接算相似度時樣本太少、不可靠。

緩解方案:

  • 降維:矩陣分解,把稀疏矩陣壓成稠密的 latent factor(第 4 章
  • 聚類:先把用戶或物品分群,群內再算
  • 預設投票:用平均值或先驗填補空格

2. 可擴展性問題#

規模計算複雜度
m 用戶O(m²) 用戶相似度
n 物品O(n²) 物品相似度

緩解方案:

  • 離線預計算 + 線上查表
  • 分散式計算(Spark MLlib)
  • 近似最近鄰搜尋(ANN,例如 Faiss、HNSW)

3. 冷啟動問題#

類型表現緩解方案
新用戶無歷史行為引導選擇興趣、使用人口統計學特徵
新物品無人評分基於內容的推薦、編輯推薦

工程實作要點#

相似度矩陣存儲#

存儲方案
# 使用稀疏矩陣存儲
from scipy.sparse import csr_matrix

# 只存儲非零元素
similarity = csr_matrix((data, (row, col)), shape=(n_items, n_items))

# 線上查詢時,常用 Redis 存 TopK 相似物品
# key: item_id, value: [(similar_item_id, score), ...]

增量更新#

1. 新增用戶行為時,只更新相關物品的相似度
2. 定期全量重算,保證準確性
3. 用雙 Buffer 切換,避免線上服務中斷

離線 / 線上分離#

階段任務頻率
離線計算相似度矩陣每天 / 每小時
線上查相似物品、加權打分即時

CF 作為召回階段#

工業系統很少把 CF 當成最終排序,而是當成召回(候選生成):快速、近似、覆蓋廣。

百萬物品 ─ Item-CF + ANN ─▶ 數千候選 ─ 排序模型 ─▶ Top-N
  • CF 負責「在 50ms 內從百萬物品撈出數千個相關候選」,重點是召回率
  • 真正的精排在後面,用更豐富的特徵(用戶屬性、上下文)與深度模型重排(第 6 章)。
  • 這也解釋了為何 Item-Based + ANN 比 User-Based 更實用:物品 embedding 可離線預算,使用者只需即時查詢,正好接上雙塔架構(第 7 章)。

評估指標#

預測準確度(顯式評分)#

RMSE = √[Σ(r̂_ui - r_ui)² / n]
MAE = Σ|r̂_ui - r_ui| / n

排序指標(推薦清單品質)#

指標計算方式
Precision@K推薦清單中相關物品的比例
Recall@K相關物品被推薦的比例
NDCG@K考慮位置的排序品質

工業界更看排序指標,因為使用者只看 Top 幾筆,預測絕對值是否準不太重要,相對排序對才重要。

總結#

要點說明
核心思想利用群體智慧,相似用戶 / 物品有相似偏好
User-Based找相似用戶,適合用戶少物品多的場景
Item-Based找相似物品,工業界更常用
相似度餘弦、皮爾遜、Jaccard,依資料型態選擇
挑戰稀疏性、可擴展性、冷啟動
工程實踐離線算相似度,線上查表召回
演進方向➡️ 矩陣分解(降維)➡️ 向量召回(embedding)

下一章:矩陣分解