• 经典算法题每日演练——第六题 协同推荐SlopeOne 算法


              相信大家对如下的Category都很熟悉,很多网站都有类似如下的功能,“商品推荐”,"猜你喜欢“,在实体店中我们有导购来为我们服务,在网络上

    我们需要同样的一种替代物,如果简简单单的在数据库里面去捞,去比较,几乎是完成不了的,这时我们就需要一种协同推荐算法,来高效的推荐浏览者喜

    欢的商品。

    一:概念

         SlopeOne的思想很简单,就是用均值化的思想来掩盖个体的打分差异,举个例子说明一下:

    在这个图中,系统该如何计算“王五“对”电冰箱“的打分值呢?刚才我们也说了,slopeone是采用均值化的思想,也就是:R王五 =4-{[(5-10)+(4-5)]/2}=7 。

    下面我们看看多于两项的商品,如何计算打分值。

    rb = (n * (ra - R(A->B)) + m * (rc - R(C->B)))/(m+n)

    注意: a,b,c 代表“商品”。

             ra 代表“商品的打分值”。

            ra->b  代表“A组到B组的平均差(均值化)”。

           m,n 代表人数。

    根据公式,我们来算一下。

    r王五 = (2 * (4 - R(洗衣机->彩电)) + 2 * (10 - R(电冰箱->彩电))+ 2 * (5 - R(空调->彩电)))/(2+2+2)=6.8

    是的,slopeOne就是这么简单,实战效果非常不错。

    二:实现

    1:定义一个评分类Rating。

     1     /// <summary>
     2     /// 评分实体类
     3     /// </summary>
     4     public class Rating
     5     {
     6         /// <summary>
     7         /// 记录差值
     8         /// </summary>
     9         public float Value { get; set; }
    10 
    11         /// <summary>
    12         /// 记录评分人数,方便公式中的 m 和 n 的值
    13         /// </summary>
    14         public int Freq { get; set; }
    15 
    16         /// <summary>
    17         /// 记录打分用户的ID
    18         /// </summary>
    19         public HashSet<int> hash_user = new HashSet<int>();
    20 
    21         /// <summary>
    22         /// 平均值
    23         /// </summary>
    24         public float AverageValue
    25         {
    26             get { return Value / Freq; }
    27         }
    28     }

    2: 定义一个产品类

     1     /// <summary>
     2     /// 产品类
     3     /// </summary>
     4     public class Product
     5     {
     6         public int ProductID { get; set; }
     7 
     8         public string ProductName { get; set; }
     9 
    10         /// <summary>
    11         /// 对产品的打分
    12         /// </summary>
    13         public float Score { get; set; }
    14     }

    3:SlopeOne类

         参考了网络上的例子,将二维矩阵做成线性表,有效的降低了空间复杂度。

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Text;
      5 
      6 namespace SupportCenter.Test
      7 {
      8     #region Slope One 算法
      9     /// <summary>
     10     /// Slope One 算法
     11     /// </summary>
     12     public class SlopeOne
     13     {
     14         /// <summary>
     15         /// 评分系统
     16         /// </summary>
     17         public static Dictionary<int, Product> dicRatingSystem = new Dictionary<int, Product>();
     18 
     19         public Dictionary<string, Rating> dic_Martix = new Dictionary<string, Rating>();
     20 
     21         public HashSet<int> hash_items = new HashSet<int>();
     22 
     23         #region 接收一个用户的打分记录
     24         /// <summary>
     25         /// 接收一个用户的打分记录
     26         /// </summary>
     27         /// <param name="userRatings"></param>
     28         public void AddUserRatings(IDictionary<int, List<Product>> userRatings)
     29         {
     30             foreach (var user1 in userRatings)
     31             {
     32                 //遍历所有的Item
     33                 foreach (var item1 in user1.Value)
     34                 {
     35                     //该产品的编号(具有唯一性)
     36                     int item1Id = item1.ProductID;
     37 
     38                     //该项目的评分
     39                     float item1Rating = item1.Score;
     40 
     41                     //将产品编号字存放在hash表中
     42                     hash_items.Add(item1.ProductID);
     43 
     44                     foreach (var user2 in userRatings)
     45                     {
     46                         //再次遍历item,用于计算俩俩 Item 之间的差值
     47                         foreach (var item2 in user2.Value)
     48                         {
     49                             //过滤掉同名的项目
     50                             if (item2.ProductID <= item1Id)
     51                                 continue;
     52 
     53                             //该产品的名字
     54                             int item2Id = item2.ProductID;
     55 
     56                             //该项目的评分
     57                             float item2Rating = item2.Score;
     58 
     59                             Rating ratingDiff;
     60 
     61                             //用表的形式构建矩阵
     62                             var key = Tools.GetKey(item1Id, item2Id);
     63 
     64                             //将俩俩 Item 的差值 存放到 Rating 中
     65                             if (dic_Martix.Keys.Contains(key))
     66                                 ratingDiff = dic_Martix[key];
     67                             else
     68                             {
     69                                 ratingDiff = new Rating();
     70                                 dic_Martix[key] = ratingDiff;
     71                             }
     72 
     73                             //方便以后以后userrating的编辑操作,(add)
     74                             if (!ratingDiff.hash_user.Contains(user1.Key))
     75                             {
     76                                 //value保存差值
     77                                 ratingDiff.Value += item1Rating - item2Rating;
     78 
     79                                 //说明计算过一次
     80                                 ratingDiff.Freq += 1;
     81                             }
     82 
     83                             //记录操作人的ID,方便以后再次添加评分
     84                             ratingDiff.hash_user.Add(user1.Key);
     85                         }
     86                     }
     87                 }
     88             }
     89         }
     90         #endregion
     91 
     92         #region 根据矩阵的值,预测出该Rating中的值
     93         /// <summary>
     94         /// 根据矩阵的值,预测出该Rating中的值
     95         /// </summary>
     96         /// <param name="userRatings"></param>
     97         /// <returns></returns>
     98         public IDictionary<int, float> Predict(List<Product> userRatings)
     99         {
    100             Dictionary<int, float> predictions = new Dictionary<int, float>();
    101 
    102             var productIDs = userRatings.Select(i => i.ProductID).ToList();
    103 
    104             //循环遍历_Items中所有的Items
    105             foreach (var itemId in this.hash_items)
    106             {
    107                 //过滤掉不需要计算的产品编号
    108                 if (productIDs.Contains(itemId))
    109                     continue;
    110 
    111                 Rating itemRating = new Rating();
    112 
    113                 // 内层遍历userRatings
    114                 foreach (var userRating in userRatings)
    115                 {
    116                     if (userRating.ProductID == itemId)
    117                         continue;
    118 
    119                     int inputItemId = userRating.ProductID;
    120 
    121                     //获取该key对应项目的两组AVG的值
    122                     var key = Tools.GetKey(itemId, inputItemId);
    123 
    124                     if (dic_Martix.Keys.Contains(key))
    125                     {
    126                         Rating diff = dic_Martix[key];
    127 
    128                         //关键点:运用公式求解(这边为了节省空间,对角线两侧的值呈现奇函数的特性)
    129                         itemRating.Value += diff.Freq * (userRating.Score + diff.AverageValue * ((itemId < inputItemId) ? 1 : -1));
    130 
    131                         //关键点:运用公式求解 累计每两组的人数
    132                         itemRating.Freq += diff.Freq;
    133                     }
    134                 }
    135 
    136                 predictions.Add(itemId, itemRating.AverageValue);
    137             }
    138 
    139             return predictions;
    140         }
    141         #endregion
    142     }
    143     #endregion
    144 
    145     #region 工具类
    146     /// <summary>
    147     /// 工具类
    148     /// </summary>
    149     public class Tools
    150     {
    151         public static string GetKey(int Item1Id, int Item2Id)
    152         {
    153             return (Item1Id < Item2Id) ? Item1Id + "->" + Item2Id : Item2Id + "->" + Item1Id;
    154         }
    155     }
    156     #endregion
    157 }

    4: 测试类Program

        这里我们灌入了userid=1000,2000,3000的这三个人,然后我们预测userID=3000这个人对 “彩电” 的打分会是多少?

     1     public class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             SlopeOne test = new SlopeOne();
     6 
     7             Dictionary<int, List<Product>> userRating = new Dictionary<int, List<Product>>();
     8 
     9             //第一位用户
    10             List<Product> list = new List<Product>()
    11             {
    12                 new Product(){ ProductID=1, ProductName="洗衣机",Score=5},
    13                 new Product(){ ProductID=2, ProductName="电冰箱", Score=10},
    14                 new Product(){ ProductID=3, ProductName="彩电", Score=10},
    15                 new Product(){ ProductID=4, ProductName="空调", Score=5},
    16             };
    17 
    18             userRating.Add(1000, list);
    19 
    20             test.AddUserRatings(userRating);
    21 
    22             userRating.Clear();
    23             userRating.Add(1000, list);
    24 
    25             test.AddUserRatings(userRating);
    26 
    27             //第二位用户
    28             list = new List<Product>()
    29             {
    30                 new Product(){ ProductID=1, ProductName="洗衣机",Score=4},
    31                 new Product(){ ProductID=2, ProductName="电冰箱", Score=5},
    32                 new Product(){ ProductID=3, ProductName="彩电", Score=4},
    33                  new Product(){ ProductID=4, ProductName="空调", Score=10},
    34             };
    35 
    36             userRating.Clear();
    37             userRating.Add(2000, list);
    38 
    39             test.AddUserRatings(userRating);
    40 
    41             //第三位用户
    42             list = new List<Product>()
    43             {
    44                 new Product(){ ProductID=1, ProductName="洗衣机", Score=4},
    45                 new Product(){ ProductID=2, ProductName="电冰箱", Score=10},
    46                 new Product(){ ProductID=4, ProductName="空调", Score=5},
    47             };
    48 
    49             userRating.Clear();
    50             userRating.Add(3000, list);
    51 
    52             test.AddUserRatings(userRating);
    53 
    54             //那么我们预测userID=3000这个人对 “彩电” 的打分会是多少?
    55             var userID = userRating.Keys.FirstOrDefault();
    56             var result = userRating[userID];
    57 
    58             var predictions = test.Predict(result);
    59 
    60             foreach (var rating in predictions)
    61                 Console.WriteLine("ProductID= " + rating.Key + " Rating: " + rating.Value);
    62         }
    63     }

  • 相关阅读:
    解决QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/tmp/runtime-root'问题
    error while loading shared libraries:libQt5SerialPort.so问题解决
    洛谷P4343 [SHOI2015]自动刷题机
    十一届山东省大学生程序设计竞赛 部分题解
    洛谷P4185 [USACO18JAN]MooTube G 题解
    洛谷P4588 [TJOI2018]数学计算
    洛谷P2085《最小函数值》
    多项式小记
    CF932F 【Escape Through Leaf】
    'ipconfig' 不是内部或外部命令,也不是可运行的程序 或批处理文件。持续思考ing
  • 原文地址:https://www.cnblogs.com/huangxincheng/p/2782647.html
Copyright © 2020-2023  润新知