C#实现SlopeOne协同过滤推荐算法的完整代码示例

Ryan
Ryan
发布时间:2026-10-14 | 6人浏览过
原创

SlopeOne是一种简单高效的协同过滤推荐算法,特别适合物品数量相对固定且用户行为数据可获取的场景。本文提供了一套完整的C#实现代码,涵盖数据模型定义、差值矩阵计算及评分预测逻辑,帮助开发者快速集成或研究该算法。

算法核心原理与准备

SlopeOne算法的核心思想是利用物品之间的平均评分差值来进行预测。如果一个用户给物品A打了高分,而通常物品B比物品A低2分,那么当该用户给物品A打分时,可以预测其会给物品B打低2分的分数。这种关系存储在差值矩阵中,通过累加所有相关物品的预测并取加权平均来得到最终结果。

在C#中实现该算法,主要需要两个关键数据结构:

  1. Rating类:用于封装单个物品的评分统计信息,包含累计总分(Value)、累计频率(Freq)以及计算出的平均分(AverageValue)。
  2. RatingDifferenceCollection类:继承自Dictionary,专门用于存储任意两个物品ID之间的评分差值统计信息。它通过规范化的Key(按ID大小排序)来确保同一对物品的唯一性。

C#代码实现细节

第1步:定义基础数据类

首先需要定义Rating类,这是记录单次预测或训练过程中评分数据的容器。
该结构体包含总分、次数和平均值属性,方便后续计算。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SlopeOne
{
    public class Rating
    {
        public float Value { get; set; }
        public int Freq { get; set; }
        
        public float AverageValue
        {
            get { return Value / Freq; }
        }
    }
    // ... 后续代码见下方完整代码块
}

第2步:实现差值收集集合

RatingDifferenceCollection是算法的训练数据库。它继承了Dictionary<string, Rating>,并提供了便捷的方法来访问特定物品对的差值。

  • 目的:以O(1)的时间复杂度存储和检索物品间的评分关系。
  • 关键点:GetKey方法确保无论输入顺序如何,物品对(1,2)和(2,1)都映射到同一个键(如"1/2"),避免重复存储。

第3步:构建SlopeOne核心算法类

这是整个算法的大脑,主要包含三个方法:

  • AddUserRatings:接受一个用户的评分字典,遍历其中所有物品对,累加它们之间的评分差值到_DiffMarix中。这一步相当于“训练”模型,让算法记住物品间的偏好关系。
  • AddUerRatings:批量添加多个用户的评分数据,内部循环调用上述方法。
  • Predict:根据新用户提供已知物品的评分,预测其未知物品的可能评分。算法会查找所有已知物品与目标物品的差值,结合用户实际评分和差值的平均值,计算出加权预测值。

完整代码示例

以下是整合了上述结构的完整C#控制台应用程序代码,可直接复制到项目中运行测试。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SlopeOne
{
    public class Rating
    {
        public float Value { get; set; }
        public int Freq { get; set; }
        public float AverageValue
        {
            get { return Value / Freq; }
        }
    }

    public class RatingDifferenceCollection : Dictionary<string, Rating>
    {
        private string GetKey(int Item1Id, int Item2Id)
        {
            return (Item1Id < Item2Id) ? Item1Id + "/" + Item2Id : Item2Id + "/" + Item1Id ;
        }

        public bool Contains(int Item1Id, int Item2Id)
        {
            return this.Keys.Contains<string>(GetKey(Item1Id, Item2Id));
        }

        public Rating this[int Item1Id, int Item2Id]
        {
            get { return this[this.GetKey(Item1Id, Item2Id)]; }
            set { this[this.GetKey(Item1Id, Item2Id)] = value; }
        }
    }

    public class SlopeOne
    {
        public RatingDifferenceCollection _DiffMarix = new RatingDifferenceCollection();
        public HashSet<int> _Items = new HashSet<int>;

        public void AddUserRatings(IDictionary<int, float> userRatings)
        {
            foreach (var item1 in userRatings)
            {
                int item1Id = item1.Key;
                float item1Rating = item1.Value;
                _Items.Add(item1.Key);

                foreach (var item2 in userRatings)
                {
                    if (item2.Key <= item1Id) continue; // Eliminate redundancy
                    int item2Id = item2.Key;
                    float item2Rating = item2.Value;
                    Rating ratingDiff;

                    if (_DiffMarix.Contains(item1Id, item2Id))
                    {
                        ratingDiff = _DiffMarix[item1Id, item2Id];
                    }
                    else
                    {
                        ratingDiff = new Rating();
                        _DiffMarix[item1Id, item2Id] = ratingDiff;
                    }

                    ratingDiff.Value += item1Rating - item2Rating;
                    ratingDiff.Freq += 1;
                }
            }
        }

        public void AddUerRatings(IList<IDictionary<int, float>> Ratings)
        {
            foreach(var userRatings in Ratings)
            {
                AddUserRatings(userRatings);
            }
        }

        public IDictionary<int, float> Predict(IDictionary<int, float> userRatings)
        {
            Dictionary<int, float> Predictions = new Dictionary<int, float>;

            foreach (var itemId in this._Items)
            {
                if (userRatings.Keys.Contains(itemId))
                    continue;

                Rating itemRating = new Rating();

                foreach (var userRating in userRatings)
                {
                    if (userRating.Key == itemId) continue;
                    int inputItemId = userRating.Key;

                    if (_DiffMarix.Contains(itemId, inputItemId))
                    {
                        Rating diff = _DiffMarix[itemId, inputItemId];
                        itemRating.Value += diff.Freq * (userRating.Value + diff.AverageValue * ((itemId < inputItemId) ? 1 : -1));
                        itemRating.Freq += diff.Freq;
                    }
                }
                Predictions.Add(itemId, itemRating.AverageValue);
            }
            return Predictions;
        }

        public static void Test()
        {
            SlopeOne test = new SlopeOne();
            Dictionary<int, float> userRating = new Dictionary<int, float>;
            
            // 用户1评分: 1-5, 2-4, 3-4
            userRating.Add(1, 5);
            userRating.Add(2, 4);
            userRating.Add(3, 4);
            test.AddUserRatings(userRating);

            // 用户2评分: 1-4, 2-5, 3-3, 4-5
            userRating = new Dictionary<int, float>;
            userRating.Add(1, 4);
            userRating.Add(2, 5);
            userRating.Add(3, 3);
            userRating.Add(4, 5);
            test.AddUserRatings(userRating);

            // 用户3评分: 1-4, 2-4, 4-5
            userRating = new Dictionary<int, float>;
            userRating.Add(1, 4);
            userRating.Add(2, 4);
            userRating.Add(4, 5);
            test.AddUserRatings(userRating);

            // 预测新用户评分: 1-5, 3-4,求物品2和4的预测值
            userRating = new Dictionary<int, float>;
            userRating.Add(1, 5);
            userRating.Add(3, 4);
            
            IDictionary<int, float> Predictions = test.Predict(userRating);
            foreach (var rating in Predictions)
            {
                Console.WriteLine("Item " + rating.Key + " Rating: " + rating.Value);
            }
        }
    }
}

测试结果说明

运行Test()方法后,程序将输出对未知物品的预测评分。例如,对于只给了物品1为5分、物品3为4分的新用户,算法会根据历史数据中物品1、3与其他物品(如物品2、4)的关系,给出相应的推荐分值。

该实现展示了SlopeOne算法在C#中的基本用法。在实际生产环境中,建议将_Items和_DiffMarix持久化到数据库或内存缓存中,以避免每次启动重新计算,同时处理稀疏矩阵时的性能优化。

以上就是C#实现协同过滤算法(SlopeOne)的详细内容,更多关于C#算法开发的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表