1. 引言
本教程将带你深入理解 Slope One 算法 的 Java 实现。我们将通过一个完整的 协同过滤(CF) 案例展开,这是推荐系统常用的机器学习技术。
该技术可用于预测用户对特定物品的兴趣偏好
2. 协同过滤原理
Slope One 是一种 基于物品的协同过滤算法,完全依赖用户-物品评分数据。计算物品相似度时,我们只关注评分历史而非物品内容本身。这种相似度最终用于预测数据集中未出现的用户-物品评分组合。
下图展示了协同过滤的完整流程:
- 用户对系统中的物品进行评分
- 算法计算物品间相似度
- 系统预测用户未评分物品的评分
关于协同过滤的更多细节可参考 维基百科
3. Slope One 算法详解
Slope One 因其简洁性而得名,是最简单的非平凡物品协同过滤算法。它同时考虑:
- 所有评分同一物品的用户数据
- 同一用户评分的其他物品数据
3.1 Java 模型设计
我们先构建核心领域模型:
// 物品类
private String itemName;
// 用户类
private String username;
通过 InputData
类初始化测试数据。假设商店有五种商品:
List<Item> items = Arrays.asList(
new Item("糖果"),
new Item("饮料"),
new Item("苏打水"),
new Item("爆米花"),
new Item("零食")
);
创建三个用户,使用 0.0-1.0 评分(0=无兴趣,0.5=一般兴趣,1.0=非常感兴趣)。初始化后得到用户评分数据结构:
Map<User, HashMap<Item, Double>> data;
3.2 差异矩阵与频率矩阵
基于现有数据计算物品间关系及出现频次:
for (HashMap<Item, Double> user : data.values()) {
for (Entry<Item, Double> e : user.entrySet()) {
// 处理每个评分项
}
}
关键步骤:
检查物品是否存在于矩阵,首次出现则创建新条目:
if (!diff.containsKey(e.getKey())) { diff.put(e.getKey(), new HashMap<Item, Double>()); freq.put(e.getKey(), new HashMap<Item, Integer>()); }
diff
矩阵存储评分差异(可为负值),freq
矩阵存储频次遍历所有物品评分对计算差异:
for (Entry<Item, Double> e2 : user.entrySet()) { int oldCount = freq.get(e.getKey()).getOrDefault(e2.getKey(), 0); double oldDiff = diff.get(e.getKey()).getOrDefault(e2.getKey(), 0.0); double observedDiff = e.getValue() - e2.getValue(); freq.get(e.getKey()).put(e2.getKey(), oldCount + 1); diff.get(e.getKey()).put(e2.getKey(), oldDiff + observedDiff); }
⚠️ 注意:这里存储的是差异累计值而非平均值
计算最终相似度分数:
for (Item j : diff.keySet()) { for (Item i : diff.get(j).keySet()) { double oldValue = diff.get(j).get(i); int count = freq.get(j).get(i); diff.get(j).put(i, oldValue / count); // 计算平均差异 } }
3.3 评分预测
算法核心步骤:预测缺失评分
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) + e.getValue().get(j);
double finalValue = predictedValue * freq.get(k).get(j);
uPred.put(k, uPred.getOrDefault(k, 0.0) + finalValue);
uFreq.put(k, uFreq.getOrDefault(k, 0) + freq.get(k).get(j));
}
}
// 后续处理...
}
生成"干净"的预测结果:
HashMap<Item, Double> clean = new HashMap<>();
for (Item j : uPred.keySet()) {
if (uFreq.get(j) > 0) {
clean.put(j, uPred.get(j) / uFreq.get(j));
}
}
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
}
}
✅ 验证要点:算法必须满足:
- 预测用户未评分的物品
- 用户已评分物品的预测值应与原始值一致
- 若不一致,说明实现存在 bug
3.4 实战技巧
影响 Slope One 效果的关键因素:
优化方向 | 具体措施 |
---|---|
数据获取 | ✅ 大数据集建议在数据库端完成用户-物品评分查询 |
时间维度 | ✅ 设置评分时间窗口(用户兴趣会随时间变化) ✅ 减少数据处理时间 |
数据分片 | ✅ 无需每日计算所有用户预测 ✅ 根据用户交互动态调整处理队列 |
4. 总结
本教程完整实现了 Slope One 算法,并展示了其在物品推荐系统中的协同过滤应用。
完整代码实现可在 GitHub 项目 中获取。