推荐系统模型处理之GridSearch——算法评估和调参

标签: 推荐系统 模型 gridsearch | 发表时间:2022-01-27 03:32 | 作者:小圆规
出处:https://juejin.cn/tag/%E6%9E%B6%E6%9E%84

theme: cyanosis

「这是我参与2022首次更文挑战的第2天,活动详情查看: 2022首次更文挑战」。

推荐系统是什么

每个系统发展到后期,都有这样的需求, 怎么给用户推荐他最感兴趣的内容。为了解决这一问题而提出来的“推荐系统”应运而生。协同过滤是基于“集体智慧”的思想,认为:人们倾向某种人群共性的部分。例如,你想看一部电影,但不知道看哪部,大部分人都会问问周围的人,并倾向选择与自己爱好口味比较类似的人的推荐。

本文会从推荐系统应该怎么建模谈起,主要介绍怎么使用 GridSearch建模、输入数据和算法并进行算法和评估的过程。

GridSearch网格搜索

推荐系统的建模

输入数据集、测试集数据,通过用户对物品的评分数据,

  • user cf:得出用户和用户之间的相似度,给用户推荐其他用户好评的物品。
  • item cf:得出物品和物品之间的相似度,把物品推荐给其他邻居物品好评的用户。

然后拿出测试集,看看预测数据和实际数据是否一致,评估算法和模型。

网格搜索架构

从推荐系统建模可以看出,GridSearch的基本流程如下:

cf2.png

但是GridSearch会对一系列算法和超参数进行评估,选出效果最好的几个算法和参数用于实际情况。

协同推荐算法

协同推荐算法方向已经做了非常多的工作。主要是分成3个方向

  • k近邻算法KNN

    • jaccard算法
    • pip算法
    • cosine算法
  • 基于矩阵分解的算法

    • PMF 概率矩阵分解
  • 深度学习-基于神经网络算法的推荐

推荐系统是怎么运作的

demo:UserKNNGridSearch

这是一个基于UserKNN协同算法的GridSearch

  public class UserKNNGridSearch {
    public static void main(String[] args) throws IOException {
        DataModel datamodel = BenchmarkDataModels.MovieLens100K();
        ParamsGrid grid = new ParamsGrid();
        grid.addParam("numberOfNeighbors", new int[] {25, 50, 75, 100,200,300});
        grid.addParam(
                "metric",
                new UserSimilarityMetric[] {
                        new Correlation(),
                        new Cosine(),
                        new Jaccard()
                });
        grid.addFixedParam("aggregationApproach", UserKNN.AggregationApproach.DEVIATION_FROM_MEAN);
        Map<String, Object> precisionParams = new HashMap<>();//精度参数
        precisionParams.put("numberOfRecommendations", 3);
        precisionParams.put("relevantThreshold", 4.0);
        GridSearch gs =
                new GridSearch(datamodel, grid, UserKNN.class, Precision.class, precisionParams);
        gs.fit();
        gs.printResults(5, false);
    }
}
  • BenchmarkDataModels.MovieLens100K()是Netflix的用户和电影评分(约100K评分)的数据集
  • 向GridSearch输入了三种UserSimilarityMetric:Correlation、Cosine和Jaccard
  • 设置了邻居个数numberOfNeighbors{25, 50, 75, 100,200,300}作为预测的超参数
  • 预测方法选用DEVIATION_FROM_MEAN,也就是 平均值偏差法,下面会详细介绍到
  • 适应Precision作为评估算法
  • 最后输出了3个最好的算法+参数组合

效果

  0.805357 for {metric=Jaccard, numberOfNeighbors=200, aggregationApproach=DEVIATION_FROM_MEAN}
0.804167 for {metric=Jaccard, numberOfNeighbors=300, aggregationApproach=DEVIATION_FROM_MEAN}
0.786310 for {metric=Jaccard, numberOfNeighbors=75, aggregationApproach=DEVIATION_FROM_MEAN}

GridSearch工作原理

可以看到,GridSearch工作的入口是fit函数。如下:

  public void fit() {

  Iterator<Map<String, Object>> iter = grid.getDevelopmentSetIterator(true, seed);

  int i = 0;
  while (i < this.numIters && iter.hasNext()) {
    Map<String, Object> params = iter.next();
    i++;

    Recommender recommender = null;

    try {
      recommender =
          this.recommenderClass
              .getConstructor(DataModel.class, Map.class)
              .newInstance(this.datamodel, params);
    } 

    if (recommender != null) {
      recommender.fit();
    }

    QualityMeasure qm = null;

    try {
      if (this.qualityMeasureParams == null || this.qualityMeasureParams.isEmpty()) {
        qm = this.qualityMeasureClass.getConstructor(Recommender.class).newInstance(recommender);
      } else {
        qm =
            this.qualityMeasureClass
                .getConstructor(Recommender.class, Map.class)
                .newInstance(recommender, this.qualityMeasureParams);
      }
    } 
    if (qm != null) {
      double score = qm.getScore();
      results.add(new Pair<>(params.toString(), score));
    }
  }
}

主要是一个while循环,在这个循环里面做这样的事情:

  • 选定一个超参数和算法作为recommender
  • recommender计算出 相关度矩阵邻居矩阵
  • 构造成员为recommender的QualityMeasure对象
  • QualityMeasure对象内部会会测试集进行 预测,并和实际数据对比, 评估计算出得分

怎么计算

recommender和QualityMeasure对象的计算和预测都需要大量的计算,一般都会采用并行计算,充分利用系统的core数量。

UserKNN的过程如下:

  @Override
public void fit() {
  System.out.println("\nFitting " + this.toString());
  Parallelizer.exec(this.datamodel.getUsers(), this.metric);
  Parallelizer.exec(this.datamodel.getUsers(), new UserNeighbors());
}
  • 首先并行的计算用户的相似度矩阵。
  • 然后并行的计算用户的neighbors矩阵。

计算用户的相似度矩阵

UserSimilarityMetric计算用户的相似度矩阵,如下:

  @Override
public void run(User user) {
  int userIndex = user.getUserIndex();

  for (int u = 0; u < datamodel.getNumberOfUsers(); u++) {
    User otherUser = datamodel.getUser(u);
    if (userIndex == otherUser.getUserIndex()) {
      similarities[userIndex][u] = Double.NEGATIVE_INFINITY;
    } else {
      similarities[userIndex][u] = this.similarity(user, otherUser);
    }
  }
}

run计算所有用户的相似度,像上面提到的那样,Jaccard算法是其中计算相似度的一种算法,具体如下:

  
public class Jaccard extends UserSimilarityMetric {

  @Override
  public double similarity(User user, User otherUser) {

    int i = 0, j = 0, common = 0;
    while (i < user.getNumberOfRatings() && j < otherUser.getNumberOfRatings()) {
      if (user.getItemAt(i) < otherUser.getItemAt(j)) {
        i++;
      } else if (user.getItemAt(i) > otherUser.getItemAt(j)) {
        j++;
      } else {
        common++;
        i++;
        j++;
      }
    }

    // If there is not items in common, similarity does not exists
    if (common == 0) return Double.NEGATIVE_INFINITY;

    // Return similarity
    return (double) common
        / (double) (user.getNumberOfRatings() + otherUser.getNumberOfRatings() - common);
  }
}

jaccard算法公式就是: $$ \frac{a \bigcap\limits_a^b b} { a \bigcup\limits_a^b b} $$ jaccard用两个用户是否对同一个物品评分来比较两个用户之间的相似度,两个用户对同一个物品的评价越多,则认为两个用户越相似。

计算用户的neighbors矩阵

这个任务主要是跑出邻居,用于真正的预测。如下

  private class UserNeighbors implements Partible<User> {

  @Override
  public void beforeRun() {}

  @Override
  public void run(User user) {
    int userIndex = user.getUserIndex();
    double[] similarities = metric.getSimilarities(userIndex);
    neighbors[userIndex] = Search.findTopN(similarities, numberOfNeighbors);
  }

  @Override
  public void afterRun() {}
}

这个过程很简单,就是找出user相似度topN的邻居放到neighbors矩阵里面。 numberOfNeighbors是超参数,则demo里面配置的。

预测方法

UserKNN.AggregationApproach提供了多种预测方法来预测一个用户对物品的rating:

  • MEAN均值:将用户邻居对该物品的评分求平均值

  • WEIGHTED_MEAN,使用带权重的均值,将用户邻居对该物品的评分按相似度加权求平均值

  • 平均值偏差: DEVIATION_FROM_MEAN

  private double predictDeviationFromMean(int userIndex, int itemIndex) {
  User user = this.datamodel.getUser(userIndex);
  double[] similarities = metric.getSimilarities(userIndex);

  double num = 0;
  double den = 0;

  for (int neighborIndex : this.neighbors[userIndex]) {
    if (neighborIndex == -1)
      break; // Neighbors array are filled with -1 when no more neighbors exists

    User neighbor = this.datamodel.getUser(neighborIndex);

    int pos = neighbor.findItem(itemIndex);
    if (pos != -1) {
      double similarity = similarities[neighborIndex];
      double rating = neighbor.getRatingAt(pos);
      double avg = neighbor.getRatingAverage();

      num += similarity * (rating - avg);
      den += similarity;
    }
  }

  return (den == 0) ? Double.NaN : user.getRatingAverage() + num / den;
}

上面的过程:

  • 首先从用户获得该用户的相似度向量
  • 然后,从该用户的邻居们这里获得一些信息
    • 如果邻居有对这个物品的评分(获得这个评分rating和用户的平均评分avg),先找到用户和邻居的相似度similarity
      • 则 $num+=(rating-avg)*similarity$

      • $den += similarity$

  • 按照上述规则,遍历完该用户的所有邻居
    • 一共有n个邻居,如果有邻居曾经对这样的物品打过分,则预测值为: $$ user.getRatingAverage() + \frac{\sum\limits_1^n (rating-avg) \times similarity} {\sum\limits_1^n similarity} $$
    • 否则为该用户预测不了

这样的算法就叫做DEVIATION_FROM_MEAN 聚合法,是上面的demo使用的预测算法。

评估方法

评估方法的入口是QualityMeasure类的getScore方法: 会并行的执行下面的run方法:

  public void run(TestUser testUser) {
  int testUserIndex = testUser.getTestUserIndex();
  double[] predictions = recommender.predict(testUser);
  usersScores[testUserIndex] = getScore(testUser, predictions);
}

@Override
public void afterRun() {
  double sum = 0;
  int count = 0;
  for (double us : usersScores) {
    if (!Double.isNaN(us)) {
      sum += us;
      count++;
    }
  }
  score = sum / count;
}

先并行跑出每个用户的分数,再计算平均值,就是这个算法的得分。

那每个用户的分数是怎么计算出来的呢?是在Precision类的getScore方法,如下:

  protected double getScore(TestUser testUser, double[] predictions) {

  // Items that has been recommended and was relevant to the active user

  int[] recommendations = Search.findTopN(predictions, this.numberOfRecommendations);

  int recommendedAndRelevant = 0, recommended = 0;

  for (int pos : recommendations) {
    if (pos == -1) break;

    double rating = testUser.getTestRatingAt(pos);
    if (rating >= this.relevantThreshold) {
      recommendedAndRelevant++;
    }

    recommended++;
  }

  return (double) recommendedAndRelevant / (double) recommended;
}
  • top N : this.numberOfRecommendations是输入的超参数
  • 选出Top N的预测,如果对于这些物品,用户的真实评价大于relevantThreshold4分,则认为这个是有效的推荐
  • 该测试用户得分就是:$recommendedAndRelevant / recommended$。其中:
    • recommendedAndRelevant是评分大于超参数relevantThreshold的个数
    • recommended是有效的可测试评估的推荐个数

参考

本文是参考了cf4j的实现:https://github.com/ferortega/cf4j

相关 [推荐系统 模型 gridsearch] 推荐:

推荐系统模型处理之GridSearch——算法评估和调参

- - 掘金 架构
「这是我参与2022首次更文挑战的第2天,活动详情查看: 2022首次更文挑战」. 每个系统发展到后期,都有这样的需求, 怎么给用户推荐他最感兴趣的内容. 为了解决这一问题而提出来的“推荐系统”应运而生. 协同过滤是基于“集体智慧”的思想,认为:人们倾向某种人群共性的部分. 例如,你想看一部电影,但不知道看哪部,大部分人都会问问周围的人,并倾向选择与自己爱好口味比较类似的人的推荐.

集成树类模型及其在百度搜索推荐系统中的应用

- - Dustinsea
决策树是经典高效的机器学习分类算法, 非常适用于线性模型效果不能满足需求, 规则描述分布比较合适的场景. 而决策树与传统bagging, boosting思想结合在一起, 就形成集成树模型方法, 包括Random Forest,GBDT等方法. 在百度搜索关键词搜索推荐系统策略中,实验证明集成树模型具有非常高的预估分类准确性.

Min-Hash和推荐系统

- - xlvector - Recommender System
前几年看Google News Recommendation的那篇Paper,对里面提到的MinHash的算法基本没有注意,因为之前的习惯都是只注意论文的模型那块,至于怎么优化模型一般都只是扫一眼. 不过最近看了大量的Google Paper,发现Google在实现一个算法方面确实有很多独到之处. 其实,Min-Hash是LSH(Locality Sensitive Hash)的一种,我之前对LSH的了解仅仅限于知道它能把两个相似的东西Hash成两个汉明距离接近的2进制数.

推荐系统实战

- - 博客园_首页
推荐算法:基于特征的推荐算法. 推荐算法准确度度量公式:. 其中,R(u)表示对用户推荐的N个物品,T(u)表示用户u在测试集上喜欢的物品集合. 集合相似度度量公式(N维向量的距离度量公式):. 其中,N(u)表示用户u有过正反馈的物品集合. 其中,S(u,k)表示和用户u兴趣最接近的K个用户集合;N(i)表示对物品i有过正反馈的用户集合;w(u,v)表示用户u和用户v的兴趣相似度;r(v,i)表示用户v对物品i的兴趣.

推荐系统杂谈

- - 后端技术杂谈 | 飒然Hang
推荐系统是近些年非常火的技术,不管是电商类软件还是新闻类app,都号称有精准的推荐系统能给你推送你最感兴趣的内容. 现象级的资讯类app“今日头条”就得益于此成为了势头非常猛的一款产品. 本文就针对推荐系统讲述一些相关概念和实践经验. 首先需要明确的就是推荐系统的目标,一般来说不外乎以下几个:. 用户满意性:首当其冲的,推荐系统主要就是为了满足用户的需求,因此准确率是评判一个推荐系统好坏的最关键指标.

个性化推荐系统综述

- Tony - 所有文章 - UCD大社区
上个月写过一篇产品推荐的文章,详情请见《我所了解的产品推荐》,内容很泛,多为工作心得. 本周读了几篇相关的论文,收获颇多,分享点干货. 以下内容摘自《个性化推荐系统的研究进展》,该文发表于2009年1月的《自然科学进展》专题评述,作者是刘建国、周涛、汪秉宏. 我略去了具体的算法和许多公式,重点看原理、思路和比较.

推荐系统开源工具 – SVDFeature

- Roger - Resys China
SVDFeature是我们(上海交大Apex实验室)在参加KDDCUP 2011期间开发的. 通过这个工具,我们和港科大(HKUST)的联合小组InnerPeace在KDDCUP 2011中获得Track 1第三名,并创造单模型最好成绩. 在此分享给大家,并希望和大家有更多的交流. (1)基于feature的可扩展性 —— SVDFeature实现了我们的基础模型feature-based matrix factorization.

Reculike : 开源论文推荐系统

- votis - Resys China
今天这篇博文主要总结一下reculike的系统架构. 两周前我们宣布发布了reculike的alpha版. 本着分享的原则,今天在这儿介绍一下我们的各个模块的设计方法. 我们这个项目一开始叫paperlens,这是因为我们想学习业界的前辈movielens,开发一个源代码和数据都开源的系统. 关于数据的开源,我想当用户数达到一定程度后,每个月会dump一次我们所有的数据库(密码等隐私信息除外),放到网络上供大家下载.

推荐系统那些事儿1

- - 冰火岛
知识库:用户知识库,Item知识库,用户评分数据(显性和隐性)等.不同的业务背景不一样,譬如电商,社交网络,视频,app应用等. 协同过滤引擎:根据用户评分数据集,通过collaborative filtering方法,计算用户喜欢的top N item. 数据格式: userid, itemid,score.

下一代个性化推荐系统

- - 技术改变世界 创新驱动中国 - 《程序员》官网
本文结合技术及社会需求发展的大背景,讲述了当前推荐系统的价值及所面临的挑战,并指出了下一代个性化推荐系统的设计思路及需要注意的问题. 作为个性化推荐系统核心的协同过滤(Collabora-tive Filtering)算法,是Goldberg等人在1992年的一篇学术论文中最早提出的. 他们在这篇文章中提出一种方法,在一个新闻组中,根据 用户下载的新闻计算他们之间在口味上的相似程度,并利用这种相似程度为他们进一步推荐相关的新闻.