相信大家對如下的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 }