1. 程式人生 > >基於使用者(user-based)的協同過濾推薦演算法的初步理解以及程式碼實現

基於使用者(user-based)的協同過濾推薦演算法的初步理解以及程式碼實現

總論

    協同過濾是目前最經典的推薦演算法。
    分而理之,協同,指通過線上資料找到使用者可能喜歡的物品;過濾,濾掉一些不值得推薦的資料。
    協同過濾推薦分為三種類型。第一種是基於使用者(user-based)的協同過濾,第二種是基於專案(item-based)的協同過濾,第三種是基於模型(model based)的協同過濾。
    我認為,選擇哪種型別,取決於業務場景。需要考慮的是,user和item的數量比,誰的數量級小就選擇哪個模式的cf;在業務處於的場景中,user和item的多變性比較,選擇多變性較小的一方,在後續的維護,會方便很多;最後一點,明確你的推薦覆蓋要求。
    今天主要講基於使用者。
    凡是推薦演算法離不開大資料的基礎,基於使用者型別的資料一般是如下格式:
使用者id
,外物id,使用者對外物的評分,以上是一般物聯網的演算法模型,(舉一反三,分析系統日誌,建立如下模型,使用者id,資源id,使用者對資源的訪問次數,--->實現推薦頁面 )

演算法基礎實現

  看這塊的時候,我是真的後悔大學沒好好學數學!!!
  首先讓我們拆分下過程。 基於使用者的協同過濾 可分為2步,
  1.找到相似度高的使用者
  2.根據1步驟獲取的資訊,推薦源使用者相對喜歡,並且未採取過的行為
    下面這個是大名鼎鼎的Jaccard公式,很簡單, 取2個使用者的選擇集的交集,跟2個使用者的選擇集的並集,進行計算。但很顯然,若要計算整個使用者集合,
時間複雜度是O(n^2
),太慢了。

這裡寫圖片描述

    後續就有了利用倒排查表進行優化如下: 可以建立個5X5矩陣,以使用者、行為為維度。
 行為     使用者
 1      1,2,3
 2      2,3,5
 3      1,3,5
 4      3,4,5
 5      2,3,4

到這裡,已經可以獲得相似使用者了。

    下面這個公式可以完成之前提到的步驟二,p(u,i)-使用者u對行為i的權重,S(u,k)表示和使用者u相似的K個使用者,N(i)表示採取過行為i的使用者集合,Wuv表示使用者u和使用者v的相似度,Rvi表示使用者v對行為i的權重。
    Rvi部分可以大做文章,使用者對某個行為的權重值定義,需要以業務為基礎。在這裡舉個例子,如果某個行為在整個使用者群裡執行的次數不多,但某兩個使用者多次執行,可判斷這兩個使用者對該行為權重極大,也就是對該行為興趣濃厚,也就是這兩個使用者極其得相似,我把這個現象稱為,單熱群冷現象。

這裡寫圖片描述

演算法數學總結到此結束,感覺數學功底是真的差,上面那個公式看了半天,後來還是諮詢了大學裡數學師父~~~

Mahout實現

    Mahout是hadoop全家桶裡的一員,提供一些可擴充套件的機器學習領域經典演算法的實現,旨在幫助開發人員更加方便快捷地建立智慧應用程式。
    之前說的一大堆,其實開發中,都是用不到的,它已經在它的庫裡給你實現了~~直接上程式碼,程式碼也很簡單。
public class MahoutTest {

    public static void main(String[] args) throws IOException, TasteException {
        String file = "D:\\test.txt";
        //模型建立
        DataModel model = new FileDataModel(new File(file));
        //根據模型獲取userId迭代器
        LongPrimitiveIterator iter = model.getUserIDs();
        UserSimilarity user = new EuclideanDistanceSimilarity(model);
        //2代表--限制在模型中的使用者數量
        NearestNUserNeighborhood neighbor = new NearestNUserNeighborhood(2, user, model);
        Recommender r = new GenericUserBasedRecommender(model, neighbor, user);
        while (iter.hasNext()) {
            long uid = iter.nextLong();
            //3代表--所需要的行為數
            List<RecommendedItem> list = r.recommend(uid, 3);
            System.out.printf("uid:%s", uid);
            for (RecommendedItem ritem : list) {
                System.out.printf("(%s,%f)", ritem.getItemID(), ritem.getValue());
            }
            System.out.println();
        }
    }
}

原始碼解析

核心類如下,粗略講一下我這裡用到的genericUserBasedRecommender

這裡寫圖片描述

這裡寫圖片描述

與上圖程式碼順序一致
1.校驗傳參
2.獲取相似使用者id
3.獲取相似使用者的所有行為資訊
4.獲取評估資訊
5.獲取結果集 getTopItems方法如下
 public static List<RecommendedItem> getTopItems(int howMany,
                                                  LongPrimitiveIterator possibleItemIDs,
                                                  IDRescorer rescorer,
                                                  Estimator<Long> estimator) throws TasteException {
    Preconditions.checkArgument(possibleItemIDs != null, "argument is null");
    Preconditions.checkArgument(estimator != null, "argument is null");

    Queue<RecommendedItem> topItems = new PriorityQueue<RecommendedItem>(howMany + 1,
      Collections.reverseOrder(ByValueRecommendedItemComparator.getInstance()));
    boolean full = false;
    double lowestTopValue = Double.NEGATIVE_INFINITY;
    while (possibleItemIDs.hasNext()) {
      long itemID = possibleItemIDs.next();
      if (rescorer == null || !rescorer.isFiltered(itemID)) {
        double preference;
        try {
          preference = estimator.estimate(itemID);
        } catch (NoSuchItemException nsie) {
          continue;
        }
        double rescoredPref = rescorer == null ? preference : rescorer.rescore(itemID, preference);
        if (!Double.isNaN(rescoredPref) && (!full || rescoredPref > lowestTopValue)) {
          topItems.add(new GenericRecommendedItem(itemID, (float) rescoredPref));
          if (full) {
            topItems.poll();
          } else if (topItems.size() > howMany) {
            full = true;
            topItems.poll();
          }
          lowestTopValue = topItems.peek().getValue();
        }
      }
    }
    int size = topItems.size();
    if (size == 0) {
      return Collections.emptyList();
    }
    List<RecommendedItem> result = Lists.newArrayListWithCapacity(size);
    result.addAll(topItems);
    Collections.sort(result, ByValueRecommendedItemComparator.getInstance());
    return result;
  }
這裡用了PriorityQueue,利用了其特殊建構函式 指定比較器,指定初始容量。
public PriorityQueue(int initialCapacity,
                         Comparator<? super E> comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }

查看了優先順序佇列的原始碼,其本質是:PriorityQueue會對入隊的元素進行排序,所以在佇列頂端的總是最小的元素。
end!