協同過濾 是推薦系統核心技術。Slope One (Daniel Lemire, 2005) 以極簡單、可線性擴展、無需矩陣分解著稱,適合中小型資料集、即時推薦、項目較少但用戶多的場景。本文提供完整 C# 實作與核心邏輯解析。
假設用戶對項目 i 與 j 的評分差異 diff(i,j) = rating(i) - rating(j) 在所有用戶中平均恆定。預測用戶 u 對未評分項目 x 的評分:
pred(u, x) = Σ [ freq(j,x) * (rating_u(j) + avg_diff(x,j)) ] / Σ freq(j,x)
avg_diff(x,j):項目 x 與 j 平均評分差 (離線預計算)freq(j,x):同時對 j 與 x 評分的用戶數 (信心權重)using System;
using System.Collections.Generic;
using System.Linq;
namespace SlopeOneRecommender
{
///
/// 評分差異統計結構
///
public class RatingDiff
{
public double SumDiff { get; set; } // 累積評分差
public int Count { get; set; } // 共現次數
public double AvgDiff => Count > 0 ? SumDiff / Count : 0;
}
///
/// 雙向項目對鍵值對集合 (item1 < item2 統一鍵)
///
public class DiffMatrix : Dictionary
{
private static string Key(int item1, int item2) =>
item1 < item2 ? $"{item1}/{item2}" : $"{item2}/{item1}";
public bool Contains(int item1, int item2) => ContainsKey(Key(item1, item2));
public RatingDiff this[int item1, int item2]
{
get => this[Key(item1, item2)];
set => this[Key(item1, item2)] = value;
}
}
///
/// Slope One 推薦引擎核心
///
public class SlopeOne
{
private readonly DiffMatrix _diffMatrix = new DiffMatrix();
private readonly HashSet _allItems = new HashSet();
///
/// 單用戶評分資料訓練 (增量更新友好)
///
public void AddUserRatings(IDictionary userRatings)
{
foreach (var (item1, rating1) in userRatings)
{
_allItems.Add(item1);
foreach (var (item2, rating2) in userRatings)
{
if (item2 <= item1) continue; // 避免重複配對 (i,j) 與 (j,i)
var diff = rating1 - rating2;
if (!_diffMatrix.Contains(item1, item2))
_diffMatrix[item1, item2] = new RatingDiff();
_diffMatrix[item1, item2].SumDiff += diff;
_diffMatrix[item1, item2].Count++;
}
}
}
///
/// 批次訓練多用戶
///
public void Train(IEnumerable> allUserRatings)
{
foreach (var ratings in allUserRatings)
AddUserRatings(ratings);
}
///
/// 為目標用戶生成推薦預測
///
public IDictionary Predict(IDictionary userRatings)
{
var predictions = new Dictionary();
foreach (var targetItem in _allItems)
{
if (userRatings.ContainsKey(targetItem)) continue; // 已評分略過
double weightedSum = 0;
int totalWeight = 0;
foreach (var (ratedItem, userRating) in userRatings)
{
if (!_diffMatrix.Contains(targetItem, ratedItem)) continue;
var diff = _diffMatrix[targetItem, ratedItem];
// 預測公式:用戶評分 + 平均差異 (注意方向)
double predictedRating = userRating + diff.AvgDiff * (targetItem < ratedItem ? 1 : -1);
weightedSum += predictedRating * diff.Count;
totalWeight += diff.Count;
}
if (totalWeight > 0)
predictions[targetItem] = weightedSum / totalWeight;
}
return predictions;
}
}
// ========== 測試與示範 ==========
public static class Program
{
public static void Main()
{
var engine = new SlopeOne();
// 訓練資料:4 用戶 × 4 項目
var trainData = new List>
{
new() { {1, 5}, {2, 4}, {3, 4} },
new() { {1, 4}, {2, 5}, {3, 3}, {4, 5} },
new() { {1, 4}, {2, 4}, {4, 5} },
new() { {1, 5}, {3, 4} }
};
engine.Train(trainData);
// 目標用戶:對項目 1,3,4 有評分,預測項目 2
var targetUser = new Dictionary { {1, 5}, {3, 4}, {4, 5} };
var predictions = engine.Predict(targetUser);
Console.WriteLine("=== Slope One 推薦預測結果 ===");
foreach (var (item, score) in predictions.OrderByDescending(p => p.Value))
Console.WriteLine($"Item {item}: Predicted Rating = {score:F3}");
}
}
}
| 模組 | 設計重點 | 效能/正確性影響 |
|---|---|---|
DiffMatrix 鍵值標準化 |
item1 < item2 ? "i1/i2" : "i2/i1" |
確保 (i,j) 與 (j,i) 映射同一儲存槽,儲存空間減半、查詢 O(1) |
增量訓練 AddUserRatings |
支援單用戶/批次呼叫 | 新用戶註冊、評分更新時無需全量重訓,線上學習友好 |
| 預測時方向修正 | targetItem < ratedItem ? 1 : -1 |
因儲存時固定小項目在前,預測需根據實際順序決定加減號 |
| 加權平均 | Σ(pred * freq) / Σ(freq) |
共現用戶越多權重越大,自動過濾稀疏不可靠關聯 |
HashSet |
追蹤所有出現過的項目 ID | 預測遍歷完整項目集,避免遺漏冷門項目 |
AddUserRatings 可用 Parallel.ForEach 多執行緒訓練 (需鎖保護 _diffMatrix 或用 ConcurrentDictionary)。DiffMatrix,清洗異常評分。DiffMatrix 序列化為 JSON/Protobuf 存入 Redis/DB,服務重啟秒級恢復。Slope One 證明了簡單演算法在工程落地上往往比複雜模型更具生命力。它無需 GPU、無需調參、程式碼不足 100 行,卻能在電商、內容平台、內部工具推薦中穩定產出業務價值。掌握其核心——項目對評分差異的穩定性假設,你就擁有了隨時可部署的推薦基線模型。