SVD綜述和Mahout中實現

基本介紹

伴隨的電商業務蓬勃發展,推薦系統也受到了格外重視,在一般電商系統中都是採用基於CF(Collaborative filtering)算法原型來作的。該算法是基於這樣基本假設:people who agreed in the past will agree in the future 。java

說到SVD算法不能不說到Netflix舉辦的推薦大賽,此次比賽對推薦系統工業界產生了很大影響,伴隨着提出了不少算法思路,因此本文也是以此次比賽爲主線,參考其中相關的兩篇經典論文,兩篇文章會上傳。算法

CF算法的挑戰:對於每一個user ,未知的item 評分會大致上類似,所以對於那些只給少許item有評分的user將會有比較大的預測偏差,由於這樣的user缺少足夠信息作預測。數據結構

Netflix比賽中最簡單直觀思路就是利用KNN算法思想,對於某個user 某個未評分的item,找到那個user 近鄰的users,而後多個近鄰user經過類似性加權來預測這個未評分的item,這個思路很簡單實現上也不難,可是這裏有個關鍵問題就是如何計算近鄰,如何定義近鄰的類似性,咱們經過計算users 和 objects特徵來計算類似性,可是這個特徵很難去構建好。ide

另一種思路:matrix factorization 矩陣分解,這其中最經常使用的就是Singular Value Decomposition算法,SVD直接找到每一個user和object的特徵,經過user 和object特徵對未評分的item進行評分預測。函數

SVD

SVD跟別的矩陣分解算法相比:沒有強加任何限制而且更容易實現性能


上面是最基本的SVD算法形態,預測函數p 一般就是用簡單的dot product 運算,咱們知道當咱們把一個問題損失函數定義好了,下面要作的就是最優化的問題。學習

這個算法考慮到評分區間問題,須要把預測值限定在指定評分區間優化


對上面損失函數求偏導如公式(5)(6)所示。spa

Batch learning of SVD

因此整個算法流程就以下:(Batch learning of SVD).net


而後論文中也給出瞭如何給U、V矩陣賦初值的算法。

公式中a是評分的下界,n(r)是在[-r,r]的均勻分佈,上面提到的SVD算法是其最標準的形式,可是這個形式不是和大規模而且稀疏的矩陣學習,在這種狀況下梯度降低會有很大的方差而且須要一個很小的學習速率防止發散。

前面提到了Batch learning of SVD ,咱們會發現一個問題就是:若是評分矩陣V 有一些小的變更,好比某個user增長了評分,這是咱們利用Batch learning of SVD 又須要把全部數據學習一次,這會形成很大的計算浪費。


如上所述咱們每次針對Ui :feature vector of user i 進行學習更新

更極端的狀況咱們能夠針對每個評分來進行學習以下圖:


Incomplete incremental learning of SVD

針對上面基於某個user 學習或者基於某個評分學習算法咱們稱爲增量學習(incremental learning)


算法 2 是基於某個user 的 叫作非徹底的增量學習

Complete incremental learning of SVD


算法 3 是基於某個評分進行更新的算法,叫作徹底增量學習算法。

算法3還能夠有一些改進提高空間:加入per-user bias and per-object bias


在論文結論部分:batch learning or incomplete incremental learning 須要一個很小的學習速率而且跟 complete incremental learning 相比性能不穩定,因此綜合得出結論就是complete incremental learning 是對於millions 訓練數據最好選擇。

Mahout中SVD實現

在Mahout中也會發現時基於complete incremental learning 算法進行的實現

 protected void updateParameters(long userID, long itemID, float rating, double currentLearningRate) {
    int userIndex = userIndex(userID);
    int itemIndex = itemIndex(itemID);

    double[] userVector = userVectors[userIndex];
    double[] itemVector = itemVectors[itemIndex];
    double prediction = predictRating(userIndex, itemIndex);
    double err = rating - prediction;

    // adjust user bias
    userVector[USER_BIAS_INDEX] +=
        biasLearningRate * currentLearningRate * (err - biasReg * preventOverfitting * userVector[USER_BIAS_INDEX]);

    // adjust item bias
    itemVector[ITEM_BIAS_INDEX] +=
        biasLearningRate * currentLearningRate * (err - biasReg * preventOverfitting * itemVector[ITEM_BIAS_INDEX]);

    // adjust features
    for (int feature = FEATURE_OFFSET; feature < numFeatures; feature++) {
      double userFeature = userVector[feature];
      double itemFeature = itemVector[feature];

      double deltaUserFeature = err * itemFeature - preventOverfitting * userFeature;
      userVector[feature] += currentLearningRate * deltaUserFeature;

      double deltaItemFeature = err * userFeature - preventOverfitting * itemFeature;
      itemVector[feature] += currentLearningRate * deltaItemFeature;
    }
  }

  private double predictRating(int userID, int itemID) {
    double sum = 0;
    for (int feature = 0; feature < numFeatures; feature++) {
      sum += userVectors[userID][feature] * itemVectors[itemID][feature];
    }
    return sum;
  }

Neflix比賽勝出文章--SVD++

如今工業界一般依賴的是CF協同過濾算法,協同過濾算法當中最成功方法有兩個:1. neighborhood models 經過分析商品和users類似性來構建的模型 2. latent factor models 直接構建users and products profile。

neighborhood method:關鍵在於計算items or users的關係,基於user 和item 是兩種不一樣的方式,在這裏有比較詳細對比分析:http://blog.csdn.net/huruzun/article/details/50910458#t3

Latent factor model:如SVD 經過把items 和users 轉換到latent factor space,於是可使得items 和 users 直接能夠比較。

SVD直觀理解的描述,假設問一個朋友喜歡什麼類型音樂,他列出來了一些藝術家,根據經驗知識咱們知道他喜歡是古典音樂和爵士音樂。這種表達不是那麼精確可是也不是太不精確。若是iTunes 進行歌曲推薦是基於數百萬個流派而不是基於數十億歌曲進行推薦,就能運行的更快;並且對於音樂推薦而言效果不會差太多。SVD就是起到了對數據進行提煉的做用,它從原始數據各個物品偏好中提煉出數量較少但更有通常性的特徵。這種思想恰好解決了推薦中以下case:兩個用戶都是汽車愛好者,可是表現喜好的是不一樣品牌,若是傳統計算類似性兩個用戶不類似,若是使用SVD就能發現二者都是汽車愛好者。詳細參考:http://blog.csdn.net/huruzun/article/details/45248997#t1

Netflix 比賽給你們經驗是:neighborhood models 和 Latent factor model各自擅長解決不一樣層次的數據結構。neighborhood models 擅長解決很是 localized 關係,所以這個模型沒法捕捉到用戶數據中微弱的信號。Latent factor model 擅長全面評估items的聯繫,可是在小的 數據集上不擅長髮現強聯繫,二者各自擅長某些case,因此天然二者擅長不一樣咱們想把二者優點融合。這篇論文創新是並聯起來兩個算法在一塊,而不是之前作過先使用某個算法獲得輸出,再在輸出上用另一個算法。


上面描述的是一個:Baseline estimates

下面說下一個一般的neighborhood model


上述方法是最經典鄰域模型方法,可是做者提出了不一樣見解,上面模型在正式模型中不能夠調整,類似性衡量阻斷了兩個items,沒有分析整個鄰居集下的數據,當鄰居信息是缺失時計算會出問題。


如今參數Wij 不在是根據鄰居計算出來的,而是經過數據最優化獲得Wij的值。如今理解Wij能夠爲 baseline estimate 的一個 offsets。

如今預測函數形式:考慮了implicit feedback


考慮正則化項的損失函數:


更新規則也就是簡單梯度降低算法:


SVD++算法主體流程:


論文中說到SVD++取得了迄今爲止最高的正確率,雖然算法在解釋性上不夠強大。

下面整合了neighborhood model and 正確率最高的LFM--SVD++


上述算法一個學習迭代過程:


  @Override
  protected void updateParameters(long userID, long itemID, float rating, double currentLearningRate) {
    int userIndex = userIndex(userID);
    int itemIndex = itemIndex(itemID);

    double[] userVector = p[userIndex];
    double[] itemVector = itemVectors[itemIndex];

    double[] pPlusY = new double[numFeatures];
    for (int i2 : itemsByUser.get(userIndex)) {
      for (int f = FEATURE_OFFSET; f < numFeatures; f++) {
        pPlusY[f] += y[i2][f];
      }
    }
    double denominator = Math.sqrt(itemsByUser.get(userIndex).size());
    for (int feature = 0; feature < pPlusY.length; feature++) {
      pPlusY[feature] = (float) (pPlusY[feature] / denominator + p[userIndex][feature]);
    }

    double prediction = predictRating(pPlusY, itemIndex);
    double err = rating - prediction;
    double normalized_error = err / denominator;

    // adjust user bias
    userVector[USER_BIAS_INDEX] +=
        biasLearningRate * currentLearningRate * (err - biasReg * preventOverfitting * userVector[USER_BIAS_INDEX]);

    // adjust item bias
    itemVector[ITEM_BIAS_INDEX] +=
        biasLearningRate * currentLearningRate * (err - biasReg * preventOverfitting * itemVector[ITEM_BIAS_INDEX]);

    // adjust features
    for (int feature = FEATURE_OFFSET; feature < numFeatures; feature++) {
      double pF = userVector[feature];
      double iF = itemVector[feature];

      double deltaU = err * iF - preventOverfitting * pF;
      userVector[feature] += currentLearningRate * deltaU;

      double deltaI = err * pPlusY[feature] - preventOverfitting * iF;
      itemVector[feature] += currentLearningRate * deltaI;

      double commonUpdate = normalized_error * iF;
      for (int itemIndex2 : itemsByUser.get(userIndex)) {
        double deltaI2 = commonUpdate - preventOverfitting * y[itemIndex2][feature];
        y[itemIndex2][feature] += learningRate * deltaI2;
      }
    }
  }

  private double predictRating(double[] userVector, int itemID) {
    double sum = 0;
    for (int feature = 0; feature < numFeatures; feature++) {
      sum += userVector[feature] * itemVectors[itemID][feature];
    }
    return sum;
  }
相關文章
相關標籤/搜索