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;
  }
相关文章
相关标签/搜索