協同過濾推薦演算法之Slope One的介紹
原文地址:http://blog.sina.com.cn/s/blog_4d9a06000100am1d.html
現在做的一個專案中需要用到推薦演算法, 在網上查了一下. Beyond Search介紹了一個協同過濾演算法(Collaborative Filtering) : Slope One;和其它類似演算法相比, 它的最大優點在於演算法很簡單, 易於實現, 執行效率高, 同時推薦的準確性相對很高;
基本概念
Slope One的基本概念很簡單, 例子1, 使用者X, Y和A都對Item1打了分. 同時使用者X,Y還對Item2打了分, 使用者A對Item2可能會打多少分呢?
User | Rating to Item 1 | Rating to Item 2 |
X | 5 | 3 |
Y | 4 | 3 |
A | 4 | ? |
根據SlopeOne演算法, 應該是:4 - ((5-3) + (4-3))/2 = 2.5.
解釋一下. 使用者X對Item1的rating是5, 對Item2的rating是3, 那麼他可能認為Item2應該比Item1少兩分. 同時使用者Y認為Item2應該比Item1少1分. 據此我們知道所有對Item1和Item2都打了分的使用者認為Item2會比Item1平均少1.5分. 所以我們有理由推薦使用者A可能會對Item2打(4-1.5)=2.5分;
很簡單是不是? 找到對Item1和Item2都打過分的使用者, 算出rating差的平均值, 這樣我們就能推測出對Item1打過分的使用者A對Item2的可能Rating, 並據此向A使用者推薦新專案.
這裡我們能看出Slope One演算法的一個很大的優點, 在只有很少的資料時候也能得到一個相對準確的推薦, 這一點可以解決Cold Start的問題.
加權演算法
接下來我們看看加權演算法(Weighted Slope One). 如果有100個使用者對Item1和Item2都打過分, 有1000個使用者對Item3和Item2也打過分. 顯然這兩個rating差的權重是不一樣的. 因此我們的計算方法是
(100*(Rating 1 to 2) + 1000(Rating 3 to 2)) / (100 + 1000)。更詳細的加權演算法例項:請看
上面討論的是使用者只對專案的喜好程度打分.還有一種情況下使用者也可以對專案的厭惡程度打分. 這時可以使用雙極SlopeOne演算法(BI-Polar SlopeOne). 我還在研究這篇論文,搞懂了再寫吧, 呵呵;
Slope One 演算法是由 Daniel Lemire 教授在 2005 年提出.
這裡可以找到論文原文(PDF);上面也列出了幾個參考實現. 現在有Python, Java和Erlang, 還沒有C#.這篇:
tutorial about how to implement Slope One in Python是一個很好的怎麼實現SlopeOne並使用它來推薦的例子。
例子:
- 首先計算item1和item2的平均差值,((5-3)+(3-4))/2=0.5,還有item1和item3的平均差值,就是5-2=3,然後推算lucy對item1的評分,根據item1和item2的平均差值來看lucy對item1的評分可能為2+0.5=2.5,同理根據item1和item3的平均差值lucy對item1的評分可能為5+3=8.
- 現在如何取捨那?使用加權平均數應該是一種比較好的方法:(因為2.5是根據兩個值推算的,8是通過一個只推算的)
- slope one 演算法差不多真的就是這麼簡單了!
- 有一個開源的Java程式taste裡面有一個完整的slope one演算法的實現,包括程式和一個關於grouplens資料的例項程式(或者說是驗證程式……)。
- 個人覺得slope one 很好、很強大呀!足夠簡單,推薦準確度也不遜色與其他複雜的推薦演算法(當然,這個東西更大程度上取決與資料樣本)。而且taste程式寫的也很不錯,稍加改造應該就可以用了。
上一篇簡單介紹了Slope One演算法的概念, 這次介紹C#實現
使用基於Slope One演算法的推薦需要以下資料:
1. 有一組使用者
2. 有一組Items(文章, 商品等)
3. 使用者會對其中某些專案打分(Rating)表達他們的喜好
Slope One演算法要解決的問題是, 對某個使用者, 已知道他對其中一些Item的Rating了, 向他推薦一些他還沒有Rating的Items, 以增加銷售機會. :-)
一個推薦系統的實現包括以下三步:
1. 計算出任意兩個Item之間Rating的差值
2. 輸入某個使用者的Rating記錄, 推算出對其它Items的可能Rating值
3. 根據Rating的值排序, 給出Top Items;
第一步:例如我們有三個使用者和4個Items, 使用者打分的情況如下表.
Ratings | User1 | User2 | User3 |
Item1 | 5 | 4 | 4 |
Item2 | 4 | 5 | 4 |
Item3 | 4 | 3 | N/A |
Item4 | N/A | 5 | 5 |
在第一步中我們的工作就是計算出Item之間兩兩的打分之差, 也就是使說計算出以下矩陣:
Item1 | Item2 | Item3 | Item4 | |
Item1 | N/A | 0/3 | 2/2 | -2/2 |
Item2 | 0/3 | N/A | 2/2 | -1/2 |
Item3 | -2/2 | -2/2 | N/A | -2/1 |
Item4 | 2/2 | 1/2 | 2/1 | N/A |
考慮到加權演算法, 還要記錄有多少人對這兩項打了分(Freq), 我們先定義一個結構來儲存Rating:
public class Rating
{
public float Value { get; set; }
public int Freq { get; set; }
public float AverageValue
{
get {return Value / Freq;}
}
}
我決定用一個Dictionary來儲存這個結果矩陣:
public class RatingDifferenceCollection : Dictionary<string, Rating>
{
private string GetKey(int Item1Id, int Item2Id)
{
return Item1Id + "/" + Item2Id;
}
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; }
}
}
接下來我們來實現SlopeOne類. 首先建立一個RatingDifferenceCollection來儲存矩陣, 還要建立HashSet來保持系統中總共有哪些Items:
public class SlopeOne
{
public RatingDifferenceCollection _DiffMarix = new RatingDifferenceCollection();// The dictionary to keep the diff matrix
public HashSet<int> _Items = new HashSet<int>();// Tracking how many items totally
方法AddUserRatings接收一個使用者的打分記錄(Item-Rating): public void AddUserRatings(IDictionary<int, float> userRatings)
AddUserRatings中有兩重迴圈, 外層迴圈遍歷輸入中的所有Item, 內層迴圈再遍歷一次, 計算出一對Item之間Rating的差存入_DiffMarix, 記得Freq加1, 以記錄我們又碰到這一對Items一次:
Rating ratingDiff = _DiffMarix[item1Id, item2Id];
ratingDiff.Value += item1Rating - item2Rating;
ratingDiff.Freq += 1;
對每個使用者呼叫AddUserRatings後, 建立起矩陣. 但我們的矩陣是以表的形式儲存:
Rating Dif | Freq | |
Item1-2 | 0 | 3 |
Item1-3 | 1 | 2 |
Item2-1 | 0 | 3 |
Item2-3 | 1 | 2 |
Item3-1 | -1 | 2 |
Item3-2 | -1 | 2 |
Item1-4 | -1 | 2 |
Item2-4 | -0.5 | 2 |
Item3-4 | -2 | 1 |
Item4-1 | 1 | 2 |
Item4-2 | 0.5 | 2 |
Item4-3 | 2 | 1 |
第二步:輸入某個使用者的Rating記錄, 推算出對其它Items的可能Rating值:
public IDictionary<int, float> Predict(IDictionary<int, float> userRatings)
也是兩重迴圈, 外層迴圈遍歷_Items中所有的Items; 內層遍歷userRatings, 用此使用者的ratings結合第一步得到的矩陣, 推算此使用者對系統中每個專案的Rating:
Rating itemRating = new Rating(); // Prediction of this user's rating
...
Rating diff = _DiffMarix[itemId, inputItemId]: