Java中的协同过滤推荐系统

2025/04/09

1. 简介

在本教程中,我们将学习有关Java中的Slope One算法的所有内容。

我们还将展示协同过滤(CF)问题的示例实现-这是一种推荐系统使用的机器学习技术。

例如,这可以用来预测用户对特定项目的兴趣。

2. 协同过滤

Slope One算法是一种基于项目的协同过滤系统,这意味着它完全基于用户-项目排名。当我们计算对象之间的相似度时,我们只知道排名的历史,而不是内容本身。然后,此相似度用于预测数据集中不存在的用户-项目对的潜在用户排名

下图展示了获取和计算特定用户评分的完整过程:

首先,用户对系统中的不同项目进行评分。接下来,算法计算相似度。之后,系统对用户尚未评分的项目进行用户评分预测。

有关协同过滤主题的更多详细信息,我们可以参考维基百科文章

3. Slope One算法

Slope One被称为基于评分的协同过滤的最简单形式,它同时考虑了所有对同一项目进行评分的用户的信息以及同一用户对其他项目进行评分的信息,以计算相似度矩阵。

在我们的简单示例中,我们将预测用户对商店中商品的排名。

让我们从针对我们的问题和领域的简单Java模型开始。

3.1 Java模型

在我们的模型中,有两个主要对象-项目和用户,Item类包含项目的名称:

private String itemName;

另一方面,User类包含用户名:

private String username;

最后,我们有一个InputData类,用于初始化数据。假设我们将在商店中创建5种不同的产品:

List<Item> items = Arrays.asList(
    new Item("Candy"), 
    new Item("Drink"), 
    new Item("Soda"), 
    new Item("Popcorn"), 
    new Item("Snacks")
);

此外,我们将创建3个用户,使用0.0 - 1.0的评分标准对上述内容进行随机评分,其中0表示不感兴趣,0.5表示有点感兴趣,1.0表示非常感兴趣。数据初始化后,我们将得到一个包含用户商品排名数据的Map:

Map<User, HashMap<Item, Double>> data;

3.2 差异和频率矩阵

根据现有数据,我们将计算项目之间的关系以及项目出现的次数。对于每个用户,我们检查他/她对项目的评分:

for (HashMap<Item, Double> user : data.values()) {
    for (Entry<Item, Double> e : user.entrySet()) {
        // ...
    }
}

下一步,我们检查该项目是否存在于矩阵中,如果是第一次出现,我们将在Map中创建新条目:

if (!diff.containsKey(e.getKey())) {
    diff.put(e.getKey(), new HashMap<Item, Double>());
    freq.put(e.getKey(), new HashMap<Item, Integer>());
}

第一个矩阵用于计算用户评分之间的差异,它的值可能是正数或负数(因为评分之间的差异可能是负数),并以Double类型存储。另一方面,频率则以Integer类型存储。

下一步,我们将比较所有项目的评级:

for (Entry<Item, Double> e2 : user.entrySet()) {
    int oldCount = 0;
    if (freq.get(e.getKey()).containsKey(e2.getKey())){
        oldCount = freq.get(e.getKey()).get(e2.getKey()).intValue();
    }

    double oldDiff = 0.0;
    if (diff.get(e.getKey()).containsKey(e2.getKey())){
        oldDiff = diff.get(e.getKey()).get(e2.getKey()).doubleValue();
    }
    
    double observedDiff = e.getValue() - e2.getValue();
    freq.get(e.getKey()).put(e2.getKey(), oldCount + 1);
    diff.get(e.getKey()).put(e2.getKey(), oldDiff + observedDiff);
}

如果之前有人对该项目进行过评分,我们将频率计数加1。此外,我们检查该项目评分之间的平均差异并计算新的observedDiff。

请注意,我们将oldDiff和perceivedDiff的总和作为项目的新值

最后,我们计算矩阵内的相似度得分:

for (Item j : diff.keySet()) {
    for (Item i : diff.get(j).keySet()) {
        double oldValue = diff.get(j).get(i).doubleValue();
        int count = freq.get(j).get(i).intValue();
        diff.get(j).put(i, oldValue / count);
    }
}

主要逻辑是将计算出的项目评分差异除以其出现的次数,完成此步骤后,我们可以打印出最终的差异矩阵。

3.3 预测

作为Slope One的主要部分,我们将根据现有数据预测所有缺失的评分。为此,我们需要将用户-项目评分与上一步计算出的差异矩阵进行比较:

for (Entry<User, HashMap<Item, Double>> e : data.entrySet()) {
    for (Item j : e.getValue().keySet()) {
        for (Item k : diff.keySet()) {
            double predictedValue =
              diff.get(k).get(j).doubleValue() + e.getValue().get(j).doubleValue();
            double finalValue = predictedValue  freq.get(k).get(j).intValue();
            uPred.put(k, uPred.get(k) + finalValue);
            uFreq.put(k, uFreq.get(k) + freq.get(k).get(j).intValue());
        }
    }
    // ...
}

之后,我们需要使用以下代码准备“干净”的预测:

HashMap<Item, Double> clean = new HashMap<Item, Double>();
for (Item j : uPred.keySet()) {
    if (uFreq.get(j) > 0) {
        clean.put(j, uPred.get(j).doubleValue() / uFreq.get(j).intValue());
    }
}
for (Item j : InputData.items) {
    if (e.getValue().containsKey(j)) {
        clean.put(j, e.getValue().get(j));
    } else if (!clean.containsKey(j)) {
        clean.put(j, -1.0);
    }
}

对于较大的数据集,需要考虑的技巧是仅使用频率值较大的条目(例如 > 1)。请注意,如果无法预测,则其值将等于-1。

最后,非常重要的一点,如果我们的算法正确运行,我们应该不仅能收到用户未评分商品的预测,还能收到用户已评分商品的重复评分。这些重复的评分不应该发生变化,否则就意味着你的算法实现中存在错误。

3.4 提示

影响Slope One算法的主要因素有几个,以下是一些提高准确性和处理时间的技巧:

  • 对于大型数据集,考虑在数据库端获取用户项目评分
  • 设置评级获取的时间范围,因为人们的兴趣可能会随着时间而改变-这也会减少处理输入数据所需的时间
  • 将大数据集拆分成小数据集-你不需要每天为所有用户计算预测;你可以检查用户是否与预测项目进行了交互,然后将他/她从第二天的处理队列中添加/删除

4. 总结

在本教程中,我们学习了Slope One算法。此外,我们还介绍了项目推荐系统的协同过滤问题。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章