經典算法題08-協同過濾算法

相信大家對如下的類別都很熟悉,很多網站都有類似如下的功能,“商品推薦”,”猜你喜歡“。

在實體店中我們有導購來爲我們服務,在網絡上我們需要同樣的一種替代物,如果簡簡單單的在數據庫裏面去撈,去比較,幾乎是完成不了的,這時我們就需要一種協同推薦算法,來高效的推薦瀏覽者喜歡的商品。

這裏寫圖片描述這裏寫圖片描述

一:概念

協同過濾算法(Collaborative Filtering),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.3

是的,slopeOne就是這麼簡單,實戰效果非常不錯。

四:編碼

核心代碼:SlopeOne類

參考了網絡上的例子,將二維矩陣做成線性表,有效的降低了空間複雜度。

package com.xm.math.slopeone;

import java.util.*;

/**
 * Created by xuming on 2016/6/20.
 * Slope One 算法
 */
public class SlopeOne {
    /// <summary>
    /// 評分系統
    /// </summary>
    public static Map<Integer, Product> dicRatingSystem = new HashMap<>();
    public Map<String, Rating> dic_Martix = new HashMap<>();
    public HashSet<Integer> hash_items = new HashSet<Integer>();
    /// <summary>
    /// 接收一個用戶的打分記錄
    /// </summary>
    /// <param name="userRatings"></param>
    public void AddUserRatings(HashMap<Integer, List<Product>> userRatings) {
        for (Map.Entry<Integer, List<Product>> user1 : userRatings.entrySet()) {
            List<Product> value = user1.getValue();
            for (Product item1 : value) {
                //該產品的編號(具有唯一性)
                int item1Id = item1.ProductID;
                //該項目的評分
                float item1Rating = item1.Score;
                //將產品編號字存放在hash表中
                hash_items.add(item1.ProductID);
                for (Map.Entry<Integer, List<Product>> user2 : userRatings.entrySet()) {
                    List<Product> value2 = user2.getValue();
                    for (Product item2 : value2) {
                        //過濾掉同名的項目
                        if (item2.ProductID <= item1Id)
                            continue;
                        //該產品的名字
                        int item2Id = item2.ProductID;
                        //該項目的評分
                        float item2Rating = item2.Score;
                        Rating ratingDiff;
                        //用表的形式構建矩陣
                        String key = Tools.GetKey(item1Id, item2Id);
                        //將倆倆 Item 的差值 存放到 Rating 中
                        if (dic_Martix.keySet().contains(key))
                            ratingDiff = dic_Martix.get(key);
                        else {
                            ratingDiff = new Rating();
                            dic_Martix.put(key, ratingDiff);
                        }
                        //方便以後以後userrating的編輯操作,(add)
                        if (!ratingDiff.hash_user.contains(user1.getKey())) {
                            //value保存差值
                            ratingDiff.Value += item1Rating - item2Rating;

                            //說明計算過一次
                            ratingDiff.Freq += 1;
                        }
                        //記錄操作人的ID,方便以後再次添加評分
                        ratingDiff.hash_user.add(user1.getKey());
                    }
                }
            }
        }
    }

    /// <summary>
    /// 根據矩陣的值,預測出該Rating中的值
    /// </summary>
    /// <param name="userRatings"></param>
    /// <returns></returns>
    public HashMap<Integer, Float> Predict(List<Product> userRatings) {
        HashMap<Integer, Float> predictions = new HashMap<Integer, Float>();
        List<Integer> productIDs = new ArrayList<Integer>();
        userRatings.forEach(i -> productIDs.add(i.ProductID));//lambda
        //循環遍歷_Items中所有的Items
        for (Integer itemId : this.hash_items) {
            //過濾掉不需要計算的產品編號
            if (productIDs.contains(itemId))
                continue;
            Rating itemRating = new Rating();
            // 內層遍歷userRatings
            for (Product userRating : userRatings) {
                if (userRating.ProductID == itemId)
                    continue;
                int inputItemId = userRating.ProductID;
                //獲取該key對應項目的兩組AVG的值
                String key = Tools.GetKey(itemId, inputItemId);
                if (dic_Martix.keySet().contains(key)) {
                    Rating diff = dic_Martix.get(key);
                    //關鍵點:運用公式求解(這邊爲了節省空間,對角線兩側的值呈現奇函數的特性)
                    itemRating.Value += diff.Freq * (userRating.Score + diff.AverageValue * ((itemId < inputItemId) ? 1 : -1));
                    //關鍵點:運用公式求解 累計每兩組的人數
                    itemRating.Freq += diff.Freq;
                }
            }
            predictions.put(itemId, itemRating.AverageValue=(itemRating.Value/itemRating.Freq));
        }
        return predictions;
    }
}

具體代碼見我的github

五:結果

這裏寫圖片描述

六:擴展

相似性計算

我儘量不使用複雜的數學公式,一是怕大家看不懂,難理解,二是公式不好畫,太麻煩了。

所謂計算相似度,有兩個比較經典的算法

  • Jaccard算法,就是交集除以並集,詳細可以看看這篇文章
  • 餘弦距離相似性算法,這個算法應用很廣,一般用來計算向量間的相似度,具體公式大家google一下吧,或者看看這裏
  • 各種其他算法,比如歐氏距離算法等等。

不管使用Jaccard還是用餘弦算法,本質上需要做的還是求兩個向量的相似程度,使用哪種算法完全取決於現實情況。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章