都2025年了协同过滤推荐算法还不会?带你手把手搓推荐算法,看完还不会来找我!!!

202 阅读8分钟

推荐算法详解

推荐算法原理都类似,学完可以放心套用到其他系统里。

一、以电影推荐算法为例

电影推荐系统旨在根据用户的历史行为、兴趣偏好和其他用户的行为,向用户推荐他们可能感兴趣的电影。本项目实现了多种推荐算法以满足不同场景下的推荐需求,形成了一个综合性的电影推荐系统。

常见的推荐算法及其特点

算法类型原理优点缺点
基于内容的推荐分析物品特征,推荐与用户历史喜好相似的物品不需要其他用户数据;能推荐冷门物品特征提取困难;难以发现用户新兴趣
协同过滤-基于用户寻找相似用户,推荐相似用户喜欢的物品直观易理解;能发现潜在兴趣数据稀疏性问题;冷启动问题;扩展性差
协同过滤-基于物品基于物品间相似度,推荐与用户喜欢物品相似的物品扩展性好;稳定性高物品多样性不足;难以发现用户新兴趣
热门推荐推荐系统中最受欢迎的物品实现简单;解决冷启动问题个性化程度低;创新性不足
混合推荐结合多种推荐方法的优点克服单一算法的局限性实现复杂;参数调优困难

二、本项目实现的推荐算法

本项目采用了混合推荐策略,根据不同的场景和数据可用性,结合了以下算法:

1. 基于用户标签的内容推荐

实现方法recommendByUserTags

核心原理:通过分析用户添加的标签(如喜欢的电影类型、导演、地区等)及其权重,为每部电影计算匹配得分,推荐得分最高的电影。

代码分析

// 1. 查询用户的标签
List<YonghubiaoqianEntity> userTags = yonghubiaoqianService.getUserTags(userId);

// 2. 统计标签类型和权重
Map<String, Float> tagTypeWeights = new HashMap<>();
for (YonghubiaoqianEntity tag : userTags) {
    String key = tag.getBiaoqianleixing() + ":" + tag.getBiaoqianzhi();
    tagTypeWeights.put(key, tag.getQuanzhong());
}

// 3. 为每部电影计算得分
Map<DianyingxinxiEntity, Float> movieScores = new HashMap<>();
for (DianyingxinxiEntity movie : allMovies) {
    float score = 0.0f;
    
    // 根据电影类型计算得分
    if (movie.getDianyingleixing() != null) {
        String typeKey = "类型:" + movie.getDianyingleixing();
        if (tagTypeWeights.containsKey(typeKey)) {
            score += tagTypeWeights.get(typeKey);
        }
    }
    
    // 根据电影地区计算得分
    if (movie.getDianyingdiqu() != null) {
        String diquKey = "地区:" + movie.getDianyingdiqu();
        if (tagTypeWeights.containsKey(diquKey)) {
            score += tagTypeWeights.get(diquKey);
        }
    }
    
    // 根据导演计算得分
    if (movie.getDaoyan() != null) {
        String daoyanKey = "导演:" + movie.getDaoyan();
        if (tagTypeWeights.containsKey(daoyanKey)) {
            score += tagTypeWeights.get(daoyanKey);
        }
    }
    
    // 添加点击量和点赞数的权重
    score += (movie.getClicknum() == null ? 0 : movie.getClicknum()) * 0.01f;
    score += (movie.getThumbsupnum() == null ? 0 : movie.getThumbsupnum()) * 0.02f;
    
    movieScores.put(movie, score);
}

// 4. 根据得分排序电影并返回前N个
List<DianyingxinxiEntity> recommendedMovies = new ArrayList<>(movieScores.keySet());
Collections.sort(recommendedMovies, (m1, m2) -> Float.compare(movieScores.get(m2), movieScores.get(m1)));

适用场景:已有用户标签数据,希望根据用户明确表达的偏好进行推荐。

2. 基于用户历史订单的协同过滤推荐

实现方法recommendByUserOrders

核心原理:分析用户的历史观影记录,找出用户最喜欢的电影类型,然后推荐该类型中用户尚未观看的电影。

代码分析

// 1. 查询用户的历史订单
EntityWrapper<OrdersEntity> orderWrapper = new EntityWrapper<>();
orderWrapper.eq("userid", userId);
orderWrapper.eq("tablename", "dianyingxinxi"); // 确保是电影相关订单
orderWrapper.in("status", new String[]{"已支付", "已完成"}); // 只考虑已经完成的订单

// 2. 收集用户观看过的电影ID和类型,统计类型频率
Set<Long> watchedMovieIds = new HashSet<>();
Map<String, Integer> typeFrequency = new HashMap<>();
for (OrdersEntity order : userOrders) {
    Long movieId = order.getGoodid();
    watchedMovieIds.add(movieId);
    
    // 查询该电影的类型
    DianyingxinxiEntity movie = this.selectById(movieId);
    if (movie != null && movie.getDianyingleixing() != null) {
        String type = movie.getDianyingleixing();
        typeFrequency.put(type, typeFrequency.getOrDefault(type, 0) + 1);
    }
}

// 3. 找出用户最喜欢的电影类型(出现频率最高的)
String favoriteType = null;
int maxFrequency = 0;
for (Map.Entry<String, Integer> entry : typeFrequency.entrySet()) {
    if (entry.getValue() > maxFrequency) {
        maxFrequency = entry.getValue();
        favoriteType = entry.getKey();
    }
}

// 4. 根据用户最喜欢的类型推荐电影
EntityWrapper<DianyingxinxiEntity> wrapper = new EntityWrapper<>();
if (favoriteType != null) {
    wrapper.eq("dianyingleixing", favoriteType);
}
wrapper.notIn("id", watchedMovieIds); // 排除已经看过的电影
wrapper.orderBy("clicknum", false); // 按点击量降序排序

适用场景:用户有观影历史,希望基于用户隐性表达的类型偏好进行推荐。

3. 基于用户收藏的协同过滤推荐

实现方法recommendByUserStoreup

核心原理:分析用户收藏的电影,找出用户最喜欢的电影类型,然后推荐该类型中用户尚未收藏的电影。

代码分析:与recommendByUserOrders类似,但数据源从订单变为收藏记录,排序标准从点击量变为点赞数。

适用场景:用户有收藏历史,希望基于用户主动收藏行为进行推荐。

4. 基于用户相似度的协同过滤推荐

实现方法recommendByUserSimilarity

核心原理:首先计算当前用户与其他用户的相似度,然后推荐相似用户观看或收藏但当前用户尚未接触的电影。相似度计算采用了多种算法的加权组合。

相似度计算的核心算法

  1. 编辑距离相似度(50%权重):比较用户标签的文本相似度
  2. 余弦相似度(40%权重):在标签向量空间中计算用户的余弦相似度
  3. 标签共现矩阵相似度(10%权重):基于标签在用户群体中的共现频率计算相似度

代码分析

// 1. 计算编辑距离相似度
double totalEditDistanceSimilarity = 0.0;
int pairCount = 0;

for (YonghubiaoqianEntity currentTag : currentUserTags) {
    for (YonghubiaoqianEntity otherTag : otherUserTags) {
        if (currentTag.getBiaoqianleixing().equals(otherTag.getBiaoqianleixing())) {
            String value1 = currentTag.getBiaoqianzhi();
            String value2 = otherTag.getBiaoqianzhi();
            
            int distance = calculateLevenshteinDistance(value1, value2);
            int maxLength = Math.max(value1.length(), value2.length());
            
            // 归一化编辑距离为相似度(1 - 距离/最大长度)* 100 (转为100分制)
            double similarity = maxLength == 0 ? 100.0 : (1.0 - ((double) distance / maxLength)) * 100;
            
            // 应用权重
            double weightedSimilarity = similarity * (currentTag.getQuanzhong() / 5.0) * (otherTag.getQuanzhong() / 5.0);
            totalEditDistanceSimilarity += weightedSimilarity;
            pairCount++;
        }
    }
}

// 计算平均编辑距离相似度
editDistanceSimilarity = pairCount > 0 ? totalEditDistanceSimilarity / pairCount : 0;

// 2. 计算余弦相似度
// ... 省略构建向量和计算余弦相似度的代码 ...
cosineSimilarity = calculateCosineSimilarity(currentUserVector, otherUserVector) * 100;

// 3. 计算标签共现矩阵相似度
// ... 省略计算共现矩阵相似度的代码 ...
coOccurrenceSimilarity = maxPossibleOccurrences > 0 ? (totalOccurrences * 100.0 / maxPossibleOccurrences) : 0;

// 综合加权计算最终相似度
double finalSimilarity = editDistanceSimilarity * 0.5 + cosineSimilarity * 0.4 + coOccurrenceSimilarity * 0.1;

适用场景:系统有足够的用户标签数据,希望发现用户潜在兴趣,提供更具个性化和多样性的推荐。

5. 热门电影推荐(冷启动解决方案)

实现方法:在各种推荐结果不足时的补充策略

核心原理:当其他推荐方法无法提供足够的推荐结果时,根据电影的点击量等热门程度指标,推荐热门电影。

代码分析

// 如果推荐结果不足,补充热门电影
if (uniqueMovies.size() < limit) {
    EntityWrapper<DianyingxinxiEntity> wrapper = new EntityWrapper<>();
    wrapper.notIn("id", uniqueMovies.keySet()); // 排除已经推荐的电影
    wrapper.orderBy("clicknum", false); // 按点击量降序排序
    
    Page<DianyingxinxiEntity> page = new Page<>(1, limit - uniqueMovies.size());
    List<DianyingxinxiEntity> popularMovies = this.selectPage(page, wrapper).getRecords();
    
    for (DianyingxinxiEntity movie : popularMovies) {
        uniqueMovies.put(movie.getId(), movie);
        reasonMap.put(movie.getId(), "热门推荐");
    }
}

适用场景:新用户没有历史数据;其他推荐算法无法提供足够推荐结果;需要提高推荐结果的多样性。

三、混合推荐策略

系统通过recommendMoviesWithReason方法实现了混合推荐策略,结合上述所有算法的优势:

  1. 优先级排序:算法按照用户相似度推荐 > 标签推荐 > 收藏推荐 > 观看记录推荐 > 热门推荐的优先级依次执行。

  2. 去重处理:确保不会向用户重复推荐相同的电影。

  3. 推荐理由:为每部推荐电影提供个性化的推荐理由,提高用户对推荐结果的信任和接受度。

  4. 限制控制:可通过参数控制返回的推荐数量。

代码分析

// 基于用户相似度的推荐优先级最高
if (similarityRecommendations != null && !similarityRecommendations.isEmpty()) {
    for (DianyingxinxiEntity movie : similarityRecommendations) {
        uniqueMovies.put(movie.getId(), movie);
        // 使用来自相似用户推荐方法的原因
        reasonMap.put(movie.getId(), similarityReasons.get(movie.getId()));
    }
}

// 计算剩余可以添加的推荐数量
int remainingSlots = limit - uniqueMovies.size();
if (remainingSlots <= 0) {
    // 如果相似度推荐已经填满或超过了limit,直接返回结果
    // ...
}

// 添加标签推荐(优先级高)
for (DianyingxinxiEntity movie : tagRecommendations) {
    if (!uniqueMovies.containsKey(movie.getId())) {
        otherRecommendations.add(movie);
        // 添加推荐原因
        String reason = "根据您的喜好推荐";
        if (userTagsMap.containsKey("类型") && userTagsMap.get("类型").equals(movie.getDianyingleixing())) {
            reason = "您喜欢" + movie.getDianyingleixing() + "类型的电影";
        } else if (userTagsMap.containsKey("地区") && userTagsMap.get("地区").equals(movie.getDianyingdiqu())) {
            reason = "您喜欢" + movie.getDianyingdiqu() + "地区的电影";
        } else if (userTagsMap.containsKey("导演") && userTagsMap.get("导演").equals(movie.getDaoyan())) {
            reason = "您喜欢导演" + movie.getDaoyan() + "的作品";
        } else if (userTagsMap.containsKey("演员") && movie.getZhuyan().contains(userTagsMap.get("演员"))) {
            reason = "您喜欢演员" + userTagsMap.get("演员") + "的作品";
        }
        otherReasonMap.put(movie.getId(), reason);
    }
}

// 添加收藏推荐(次优先级)
// ...

// 添加订单推荐(最低优先级)
// ...

// 从合并的推荐中选择填充剩余的位置
// ...

// 如果推荐结果仍不足,补充热门电影
// ...

四、推荐算法的优化与改进

已实现的优化

  1. 多算法加权融合:通过组合多种相似度计算方法,提高相似度计算的准确性。

  2. 个性化推荐理由:为每部推荐电影提供具体的推荐理由,增强用户体验。

  3. 冷启动解决方案:对于新用户或数据不足的情况,使用热门推荐兜底。

  4. 标签权重机制:考虑用户标签的权重,使推荐结果更贴合用户真实偏好。

未来可能的改进方向

  1. 引入深度学习模型:如神经协同过滤、深度兴趣网络等,进一步提高推荐精度。

  2. 时间衰减因子:考虑用户兴趣随时间的变化,赋予近期行为更高权重。

  3. 上下文感知推荐:结合用户当前场景(如时间、节日)进行推荐。

  4. 多样性与新颖性优化:在保证相关性的同时,增加推荐结果的多样性。

  5. A/B测试框架:建立推荐算法效果评估机制,不断迭代优化。

五、总结

本项目实现的电影推荐系统采用了混合推荐策略,综合了基于内容的推荐、协同过滤推荐和热门推荐等多种方法,能够适应不同场景和数据条件。通过多种相似度计算算法的融合,以及清晰的推荐理由机制,系统能够为用户提供个性化、多样化的电影推荐,有效提升用户体验。

随着数据量的增加和用户反馈的积累,系统还可以进一步引入深度学习等先进技术,持续优化推荐效果。