首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 更多程式設計知識

C# 實作 Slope One 協同過濾推薦演算法完整教學

     編輯:更多程式設計知識

前言:為何選擇 Slope One?

協同過濾 是推薦系統核心技術。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 評分的用戶數 (信心權重)
  • 僅需遍歷用戶已評分項目,複雜度 O(用戶評分數 × 平均共現項目數)

完整 C# 實作代碼

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 _allItems 追蹤所有出現過的項目 ID 預測遍歷完整項目集,避免遺漏冷門項目

效能與擴展性分析

  • 訓練複雜度:O(U × I²) — U 用戶數,I 平均用戶評分項目數。實務上 I 通常 < 100,極快。
  • 預測複雜度:O(I_target × I_user) — 目標項目數 × 用戶已評分項目數。
  • 記憶體:O(N²) — N 為總項目數。項目超過 10 萬時矩陣過大,需改用稀疏矩陣或近似最近鄰。
  • 平行化:AddUserRatings 可用 Parallel.ForEach 多執行緒訓練 (需鎖保護 _diffMatrix 或用 ConcurrentDictionary)。

實務落地建議

  1. 隱式回饋轉換:點擊/瀏覽/購買 → 隱式評分 (如 1/3/5),再餵入 Slope One。
  2. 冷啟動策略:新項目無共現資料時,回退至熱門排行、內容基礎推薦或隨機探索。
  3. 定期離線重訓:每日/每週全量重建 DiffMatrix,清洗異常評分。
  4. 混合推薦:Slope One (協同) + Content-Based (屬性相似) + 熱門回退,加權融合。
  5. 持久化:DiffMatrix 序列化為 JSON/Protobuf 存入 Redis/DB,服務重啟秒級恢復。

結語

Slope One 證明了簡單演算法在工程落地上往往比複雜模型更具生命力。它無需 GPU、無需調參、程式碼不足 100 行,卻能在電商、內容平台、內部工具推薦中穩定產出業務價值。掌握其核心——項目對評分差異的穩定性假設,你就擁有了隨時可部署的推薦基線模型。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved