Apache Mahout中推薦算法Slope one源碼分析

關於推薦引擎

        現在的互聯網中,不管是電子商務仍是社交網絡,對數據挖掘的需求都愈來愈大了,而推薦引擎正是數據挖掘完美體現;經過分析用戶歷史行爲,將他可能喜歡內容推送給他,能產生至關好的用戶體驗,這就是推薦引擎。html

推薦算法Slope one的原理

        首先Slope one是一種基於項目的協同過濾算法(Item-based Recommendation),簡單介紹這種算法(若理解有誤,歡迎你們更正,I am just a beginner):根據用戶們對產品的喜愛程度,來將產品分類;舉個簡單例子:好比有10個用戶,其中有9我的即喜歡產品A,也喜歡產品B,但只有2我的喜歡產品C;因而能夠推斷產品A和產品B是屬於同類的,而產品C可能跟它們不是一類。java

        好了話很少講,讓咱們看看Slope one吧!web

        Slope one是經過用戶們對每一個產品的評分,來計算產品間的一個差值;這種計算是經過 線性迴歸算法

 f(x) = ax + b其中a = 1,正如它的名字Slope one(斜率爲一);另外用戶的評分,在Slope one中sql

是必不可少的。這裏舉例看看它的計算方式:下面是一張用戶對書籍的評分表數據庫

 1網絡

 2數據結構

 3oop

用戶A性能

  5

  3

  2

用戶B

  3

  4

未評分

用戶C

未評分

  2 

  5

        

        書1是否適合推薦給用戶C,須要經過Slope one 計算出一個值來斷定:首先獲得書1和書2之間的平均差值X = ((5-3)+(3-4))/ 2 = 0.5,而後經過用戶C對書2的打分獲得相應的推薦值 2+0.5 = 2.5 (推薦引擎會經過推薦值的高低來選擇要推薦的物品),這裏只是經過書2來計算用戶C對書1的推薦值,實際的Slope one算法中若要獲得用戶C對書1的推薦值,會把用戶C評分過的全部書按此方法依次對書1(爲評分的書)算推薦值,而後取平均值獲得,放到表中以下:

(((5-3)+(3-4))/ 2 +2 + (5 - 2)/ 1 + 5 )/ 2 = 5.25 

實際應用中你還能夠設權值,這裏就不深刻了。

        以上是Slope one的原理,接下來看看它在Mahout中是如何設計與實現的。

Mahout中Slope one的設計思路以及代碼實現

        先簡單介紹下,Mahout是Apache的一個開源項目,由Lucene項目組和Hadoop項目組分離出來,它實現了推薦引擎中的大部分經典算法,有興趣的朋友能夠研究研究

        首先咱們須要基礎數據,即用戶對產品的評分,這部分數據能夠來自數據庫也能夠來自文件,Mahout中對此設計了一個簡單的數據庫表,SQL以下:

CREATE TABLE taste_preferences (
    user_id BIGINT NOT NULL,
    item_id BIGINT NOT NULL,
    preference FLOAT NOT NULL,
    PRIMARY KEY (user_id, item_id),
    INDEX (user_id),
    INDEX (item_id)
)

        其次,Mahout在啓動時,會對這部分數據進行處理,算出每對產品間的平均評分差值,已Map<ItemId, Map<ItemId, Average>>的數據結構存放在內存中(固然這幫牛人沒有用Java中Map的實現,本身寫了一個叫FastByIDMap的類)。處理基礎數據的計算代碼以下:

 1. 首先獲取全部評過度的用戶id (7,而dataModel就是用於存放我上面提到的基礎)

 2. 而後依次計算每一個用戶評分過的產品間的平均評分差值 (9,具體在processOneUser中實現)

private void buildAverageDiffs() throws TasteException {
    log.info("Building average diffs...");
    try {
      buildAverageDiffsLock.writeLock().lock();
      averageDiffs.clear();
      long averageCount = 0L;
      LongPrimitiveIterator it = dataModel.getUserIDs();
      while (it.hasNext()) {
        averageCount = processOneUser(averageCount, it.nextLong());
      }
      
      pruneInconsequentialDiffs();
      updateAllRecommendableItems();
      
    } finally {
      buildAverageDiffsLock.writeLock().unlock();
    }
  }

 3. 首先取出該用戶全部評分過的項目和評分值(4)

 4. 依次計算這些項目間的平均評分差值(6 ~ 26),並存儲在內存中。

private long processOneUser(long averageCount, long userID) throws TasteException {
    log.debug("Processing prefs for user {}", userID);
    // Save off prefs for the life of this loop iteration
    PreferenceArray userPreferences = dataModel.getPreferencesFromUser(userID);
    int length = userPreferences.length();
    for (int i = 0; i < length - 1; i++) {
      float prefAValue = userPreferences.getValue(i);
      long itemIDA = userPreferences.getItemID(i);
      FastByIDMap<RunningAverage> aMap = averageDiffs.get(itemIDA);
      if (aMap == null) {
        aMap = new FastByIDMap<RunningAverage>();
        averageDiffs.put(itemIDA, aMap);
      }
      for (int j = i + 1; j < length; j++) {
        // This is a performance-critical block
        long itemIDB = userPreferences.getItemID(j);
        RunningAverage average = aMap.get(itemIDB);
        if (average == null && averageCount < maxEntries) {
          average = buildRunningAverage();
          aMap.put(itemIDB, average);
          averageCount++;
        }
        if (average != null) {
          average.addDatum(userPreferences.getValue(j) - prefAValue);
        }
      }
      RunningAverage itemAverage = averageItemPref.get(itemIDA);
      if (itemAverage == null) {
        itemAverage = buildRunningAverage();
        averageItemPref.put(itemIDA, itemAverage);
      }
      itemAverage.addDatum(prefAValue);
    }
    return averageCount;
  }

        以上是啓動時作的事,而當某個用戶來了,須要爲他計算推薦列表時,就快速許多了(是一個空間換時間的思想),下面的方法是某一個用戶對其某一個他未評分過的產品的推薦值,參數UserId:用戶ID;ItemId:爲評分的產品ID

 1. 再次取出該用戶評分過的全部產品(4):PreferenceArray prefs中保存着ItemID和該用戶對它的評分 

2. 取得上一步獲得的prefs中的全部物品與itemID表明的物品之間的平均評分差值(5),其中

DiffStorage diffStorage對象中放中每對產品間的平均評分差值(而上面啓動時的計算都是在

MySQLJDBCDiffStorage中實現的,計算後的值也存於其中,它是DiffStorage接口的實現),因此

取得的流程很簡單,這裏不貼代碼了


3. 最後就是依次推算評分過的產品到未評分的產品的一個推薦值 = 平均評分差值(二者間的) + 已評分的分值(用

戶對其中一個評分),而後將這些推薦值取個平均數(7 ~ 37),其中11行判斷是否要考慮權重。

private float doEstimatePreference(long userID, long itemID) throws TasteException {
    double count = 0.0;
    double totalPreference = 0.0;
    PreferenceArray prefs = getDataModel().getPreferencesFromUser(userID);
    RunningAverage[] averages = diffStorage.getDiffs(userID, itemID, prefs);
    int size = prefs.length();
    for (int i = 0; i < size; i++) {
      RunningAverage averageDiff = averages[i];
      if (averageDiff != null) {
        double averageDiffValue = averageDiff.getAverage();
        if (weighted) {
          double weight = averageDiff.getCount();
          if (stdDevWeighted) {
            double stdev = ((RunningAverageAndStdDev) averageDiff).getStandardDeviation();
            if (!Double.isNaN(stdev)) {
              weight /= 1.0 + stdev;
            }
            // If stdev is NaN, then it is because count is 1. Because we're weighting by count,
            // the weight is already relatively low. We effectively assume stdev is 0.0 here and
            // that is reasonable enough. Otherwise, dividing by NaN would yield a weight of NaN
            // and disqualify this pref entirely
            // (Thanks Daemmon)
          }
          totalPreference += weight * (prefs.getValue(i) + averageDiffValue);
          count += weight;
        } else {
          totalPreference += prefs.getValue(i) + averageDiffValue;
          count += 1.0;
        }
      }
    }
    if (count <= 0.0) {
      RunningAverage itemAverage = diffStorage.getAverageItemPref(itemID);
      return itemAverage == null ? Float.NaN : (float) itemAverage.getAverage();
    } else {
      return (float) (totalPreference / count);
    }
  }

         Slope one 的源碼已分析完畢。

        其實Slope one推薦算法很流行,被不少網站使用,包括一些大型網站;我我的認爲最主要的緣由是它具有以下優點:

        1. 實現簡單而且易於維護。

        2. 響應即時(只要用戶作出一次評分,它就能有效推薦,根據上面代碼很容易理解),而且用戶的新增評分對推薦數據的改變量較小,應爲在內存中存儲的是物品間的平均差值,新增的差值只需累加一下,且範圍是用戶評分過的產品。

        3. 因爲是基於項目的協同過濾算法,適用於當下火熱的電子商務網站,緣由電子商務網站用戶量在幾十萬到上百萬,產品量相對於之則要小得多,因此對產品歸類從性能上講很高效。

        分析至此,祝你們週末愉快。

參考資料:

1. Slope one http://zh.wikipedia.org/wiki/Slope_one

2. 探索推薦引擎內部的祕密,第 2 部分: 深刻推薦引擎相關算法 - 協同過濾 

    http://www.ibm.com/developerworks/cn/web/1103_zhaoct_recommstudy2/index.html

3. Apache Mahout 源代碼

 

       另:原創blog,轉載請註明http://my.oschina.net/BreathL/blog/41063

相關文章
相關標籤/搜索