下面我将为你实现一个基于物品的协同过滤推荐算法,适用于Java电商平台。这个实现包括核心算法、相似度计算和推荐生成。
1. 数据模型
首先定义我们需要的数据模型:
public class Item {
private String itemId; // 商品ID
private String name; // 商品名称
// 其他商品属性...
// 构造方法、getter和setter
}
public class User {
private String userId; // 用户ID
private String username; // 用户名
// 其他用户属性...
// 构造方法、getter和setter
}
public class UserBehavior {
private String userId; // 用户ID
private String itemId; // 商品ID
private double preference; // 偏好分数(如评分、购买次数等)
private long timestamp; // 行为时间戳
// 构造方法、getter和setter
}
2. 相似度计算
基于物品的协同过滤核心是计算物品之间的相似度:
public class ItemSimilarity {
/**
* 计算物品之间的余弦相似度
* @param itemPrefMap 物品-用户偏好矩阵: Map<itemId, Map<userId, preference>>
* @return 物品相似度矩阵: Map<itemId, Map<itemId, similarity>>
*/
public Map<String, Map<String, Double>> calculateCosineSimilarity(
Map<String, Map<String, Double>> itemPrefMap) {
Map<String, Map<String, Double>> similarityMatrix = new HashMap<>();
// 获取所有物品列表
List<String> itemIds = new ArrayList<>(itemPrefMap.keySet());
// 计算每对物品之间的相似度
for (int i = 0; i < itemIds.size(); i++) {
String itemId1 = itemIds.get(i);
Map<String, Double> userPrefs1 = itemPrefMap.get(itemId1);
// 初始化相似度矩阵
similarityMatrix.put(itemId1, new HashMap<>());
for (int j = i; j < itemIds.size(); j++) {
String itemId2 = itemIds.get(j);
if (itemId1.equals(itemId2)) {
// 相同物品相似度为1
similarityMatrix.get(itemId1).put(itemId2, 1.0);
continue;
}
Map<String, Double> userPrefs2 = itemPrefMap.get(itemId2);
// 计算两个物品的共同用户
Set<String> commonUsers = new HashSet<>(userPrefs1.keySet());
commonUsers.retainAll(userPrefs2.keySet());
if (commonUsers.isEmpty()) {
// 没有共同用户,相似度为0
similarityMatrix.get(itemId1).put(itemId2, 0.0);
similarityMatrix.get(itemId2).put(itemId1, 0.0);
continue;
}
// 计算余弦相似度的分子和分母
double dotProduct = 0.0;
double norm1 = 0.0;
double norm2 = 0.0;
for (String userId : commonUsers) {
double pref1 = userPrefs1.get(userId);
double pref2 = userPrefs2.get(userId);
dotProduct += pref1 * pref2;
norm1 += Math.pow(pref1, 2);
norm2 += Math.pow(pref2, 2);
}
// 计算余弦相似度
double similarity = dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
// 对称矩阵,所以两个方向都存储
similarityMatrix.get(itemId1).put(itemId2, similarity);
// 确保对称矩阵的另一半也被填充
if (!similarityMatrix.containsKey(itemId2)) {
similarityMatrix.put(itemId2, new HashMap<>());
}
similarityMatrix.get(itemId2).put(itemId1, similarity);
}
}
return similarityMatrix;
}
/**
* 计算物品之间的改进余弦相似度(考虑用户平均评分)
*/
public Map<String, Map<String, Double>> calculateAdjustedCosineSimilarity(
Map<String, Map<String, Double>> itemPrefMap,
Map<String, Double> userAvgPrefMap) {
// 实现类似上面,但在计算时减去用户平均评分
// ...
return similarityMatrix;
}
}
3. 推荐引擎
基于物品相似度生成推荐:
public class ItemBasedRecommender {
private Map<String, Map<String, Double>> itemSimilarityMatrix;
private Map<String, Map<String, Double>> userItemPrefMap; // 用户-物品偏好矩阵
public ItemBasedRecommender(Map<String, Map<String, Double>> itemSimilarityMatrix,
Map<String, Map<String, Double>> userItemPrefMap) {
this.itemSimilarityMatrix = itemSimilarityMatrix;
this.userItemPrefMap = userItemPrefMap;
}
/**
* 为用户推荐物品
* @param userId 用户ID
* @param topN 推荐数量
* @return 推荐物品ID列表,按推荐分数降序排列
*/
public List<String> recommendItems(String userId, int topN) {
// 获取用户历史行为物品
Map<String, Double> userHistory = userItemPrefMap.getOrDefault(userId, new HashMap<>());
// 存储物品的推荐分数
Map<String, Double> recommendationScores = new HashMap<>();
// 遍历用户历史行为物品
for (Map.Entry<String, Double> entry : userHistory.entrySet()) {
String historyItemId = entry.getKey();
double historyPref = entry.getValue();
// 获取与历史物品相似的物品
Map<String, Double> similarItems = itemSimilarityMatrix.getOrDefault(historyItemId, new HashMap<>());
// 计算推荐分数
for (Map.Entry<String, Double> simEntry : similarItems.entrySet()) {
String candidateItemId = simEntry.getKey();
double similarity = simEntry.getValue();
// 排除用户已经有过行为的物品
if (!userHistory.containsKey(candidateItemId)) {
double score = recommendationScores.getOrDefault(candidateItemId, 0.0);
score += similarity * historyPref;
recommendationScores.put(candidateItemId, score);
}
}
}
// 按推荐分数排序并返回topN
return recommendationScores.entrySet().stream()
.sorted(Map.Entry.<String, Double>comparingByValue().reversed())
.limit(topN)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
}
4. 数据预处理
在实际应用中,我们需要将原始用户行为数据转换为算法需要的格式:
public class DataPreprocessor {
/**
* 将用户行为列表转换为物品-用户偏好矩阵
*/
public Map<String, Map<String, Double>> convertToItemUserMatrix(List<UserBehavior> behaviors) {
Map<String, Map<String, Double>> itemUserMatrix = new HashMap<>();
for (UserBehavior behavior : behaviors) {
String itemId = behavior.getItemId();
String userId = behavior.getUserId();
double preference = behavior.getPreference();
itemUserMatrix.putIfAbsent(itemId, new HashMap<>());
itemUserMatrix.get(itemId).put(userId, preference);
}
return itemUserMatrix;
}
/**
* 将用户行为列表转换为用户-物品偏好矩阵
*/
public Map<String, Map<String, Double>> convertToUserItemMatrix(List<UserBehavior> behaviors) {
Map<String, Map<String, Double>> userItemMatrix = new HashMap<>();
for (UserBehavior behavior : behaviors) {
String userId = behavior.getUserId();
String itemId = behavior.getItemId();
double preference = behavior.getPreference();
userItemMatrix.putIfAbsent(userId, new HashMap<>());
userItemMatrix.get(userId).put(itemId, preference);
}
return userItemMatrix;
}
/**
* 计算每个用户的平均评分
*/
public Map<String, Double> calculateUserAveragePreference(List<UserBehavior> behaviors) {
Map<String, Double> sumMap = new HashMap<>();
Map<String, Integer> countMap = new HashMap<>();
for (UserBehavior behavior : behaviors) {
String userId = behavior.getUserId();
double preference = behavior.getPreference();
sumMap.put(userId, sumMap.getOrDefault(userId, 0.0) + preference);
countMap.put(userId, countMap.getOrDefault(userId, 0) + 1);
}
Map<String, Double> avgMap = new HashMap<>();
for (String userId : sumMap.keySet()) {
avgMap.put(userId, sumMap.get(userId) / countMap.get(userId));
}
return avgMap;
}
}
5. 完整使用示例
public class RecommendationDemo {
public static void main(String[] args) {
// 1. 模拟数据
List<UserBehavior> behaviors = new ArrayList<>();
behaviors.add(new UserBehavior("u1", "i1", 5.0, System.currentTimeMillis()));
behaviors.add(new UserBehavior("u1", "i2", 3.0, System.currentTimeMillis()));
behaviors.add(new UserBehavior("u1", "i3", 4.0, System.currentTimeMillis()));
behaviors.add(new UserBehavior("u2", "i1", 4.0, System.currentTimeMillis()));
behaviors.add(new UserBehavior("u2", "i3", 3.0, System.currentTimeMillis()));
behaviors.add(new UserBehavior("u2", "i4", 4.5, System.currentTimeMillis()));
behaviors.add(new UserBehavior("u3", "i2", 2.5, System.currentTimeMillis()));
behaviors.add(new UserBehavior("u3", "i4", 4.0, System.currentTimeMillis()));
behaviors.add(new UserBehavior("u3", "i5", 3.5, System.currentTimeMillis()));
// 2. 数据预处理
DataPreprocessor preprocessor = new DataPreprocessor();
Map<String, Map<String, Double>> itemUserMatrix = preprocessor.convertToItemUserMatrix(behaviors);
Map<String, Map<String, Double>> userItemMatrix = preprocessor.convertToUserItemMatrix(behaviors);
Map<String, Double> userAvgPrefMap = preprocessor.calculateUserAveragePreference(behaviors);
// 3. 计算物品相似度
ItemSimilarity itemSimilarity = new ItemSimilarity();
Map<String, Map<String, Double>> similarityMatrix = itemSimilarity.calculateCosineSimilarity(itemUserMatrix);
// 4. 创建推荐引擎
ItemBasedRecommender recommender = new ItemBasedRecommender(similarityMatrix, userItemMatrix);
// 5. 为用户生成推荐
String targetUserId = "u1";
List<String> recommendations = recommender.recommendItems(targetUserId, 3);
System.out.println("为用户 " + targetUserId + " 推荐的物品:");
recommendations.forEach(System.out::println);
}
}
6. 优化考虑
在实际电商平台中,还需要考虑以下优化:
-
数据稀疏性问题:
-
使用降维技术(如SVD)
-
结合内容信息进行混合推荐
-
-
实时性要求:
-
增量更新相似度矩阵
-
使用滑动窗口只考虑最近的行为
-
-
冷启动问题:
-
对于新商品,使用基于内容的推荐
-
对于新用户,使用热门推荐或基于人口统计的推荐
-
-
性能优化:
-
使用缓存存储相似度矩阵
-
分布式计算处理大规模数据
-
-
业务规则结合:
-
考虑商品类别、价格区间等业务规则
-
排除已售罄或下架商品
-