SpringBoot中实现基于用户的协同过滤推荐算法

大二的时候就想搞一下推荐算法了,但是那时候不想倒腾,加上没啥参考资料,小破站上也没有啥教程,全是打广告推销卖作品的,大三又在考研,所以就没时间搞,现在自己的毕设里要加,木有办法,自己丰衣足食,通过查资料,理解别人的代码,自己也算简单实现了一下,还有更复杂的协同过滤算法,现在自己还没有理解,后面有时间再理解消化一下😂。话不多说,开干!

简单介绍

很明显,基于用户的协同过滤推荐算法是基于用户的行为去进行推荐,我这里是基于用户评分这个行为去过滤推荐的,我这里的例子是,贴吧里面有很多帖子,用户可以对帖子进行评分,我系统里需要实现给用户推荐帖子。首先肯定就得用一张表存储用户对帖子的评分情况,即stick_score:

整个流程就是,首先根据stick_score表中的数据找出与目标用户最相似的三个用户,把他们的id存下来,然后根据他们的id再回到stick_score中找出目标用户没评分但是相似用户评过分的帖子id,然后根据推荐次数推荐给目标用户(当然这里是默认有这种情况,没有的话可以按照自己需求去推荐,下面会讲述)。

No.1 推荐模块的封装

我们可以将系统过滤推荐算法核心实现代码进行一个封装,放在工具类里进行实现,比如,在我这,需要封装一个帖子推荐模块,那就在common的工具类里封装一个StickRecommend类,如下所示:

接下来我们看一下这个核心代码的实现:

1.加载用户评分数据(loadRatingsFromDB

这部分的作用就是预处理数据库stick_score里面的数据,将各部分数据映射成Map<Integer, Map<Integer, Double>> 数据结构,方便后续进行数据处理。

    // 加载用户评分数据的方法
    // 该方法从数据库中加载用户对各个帖子(stick)的评分数据
    // 返回一个Map,其中key是用户ID,value是一个Map,存储该用户对各个帖子的评分
    public static Map<Integer, Map<Integer, Double>> loadRatingsFromDB(Connection conn) throws SQLException {
        Map<Integer, Map<Integer, Double>> userRatings = new HashMap<>();

        // 使用Statement执行SQL查询,从数据库表 "stick_score" 获取用户评分数据
        try (Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT user_id, stick_id, score FROM stick_score")) {
            // 遍历查询结果集,将数据按照用户ID和帖子ID组织成Map
            while (rs.next()) {
                int userId = rs.getInt("user_id");  // 获取用户ID
                int stickId = rs.getInt("stick_id");  // 获取帖子ID
                double rating = rs.getDouble("score");  // 获取评分

                // 将评分数据存储到Map中,Map的结构是:用户ID -> (帖子ID -> 评分)
                userRatings.computeIfAbsent(userId, k -> new HashMap<>()).put(stickId, rating);
            }
        }
        return userRatings;  // 返回存储评分数据的Map
    }

2.计算余弦相似度(cosineSimilarity

这部分就是基于两个用户对相同帖子的评分计算他们的相似度。使用余弦相似度公式,衡量两个用户评分模式之间的相似程度,越接近1说明越相似。大致的过程就是如下:

1.计算两个用户对共同帖子的评分的点积(dotProduct)。

2.计算每个用户的评分的平方和的平方根(normA 和 normB)。

3.返回点积与两个评分向量模长的商,即得到用户之间的余弦相似度。

说明:余弦相似度的范围是 [−1,1][−1,1],其中 1 表示完全相似,0 表示没有相似度,-1 表示完全不相似。

    // 计算余弦相似度
    // 该方法用于计算两个用户之间的相似度,基于他们对相同帖子的评分进行计算
    // 余弦相似度的值越大,说明两个用户的兴趣越相似
    public static double cosineSimilarity(Map<Integer, Double> ratings1, Map<Integer, Double> ratings2) {
        // 获取两个用户的评分的共同帖子ID
        Set<Integer> commonBooks = new HashSet<>(ratings1.keySet());
        commonBooks.retainAll(ratings2.keySet());  // 求交集,找到共同评分的帖子

        // 如果两个用户没有共同评分的帖子,相似度为0
        if (commonBooks.isEmpty()) return 0;

        double dotProduct = 0.0;  // 点积
        double normA = 0.0;  // 向量A的模
        double normB = 0.0;  // 向量B的模

        // 计算点积和各自向量的模
        for (int stickId : commonBooks) {
            double rating1 = ratings1.get(stickId);  // 用户1对该帖子的评分
            double rating2 = ratings2.get(stickId);  // 用户2对该帖子的评分
            dotProduct += rating1 * rating2;  // 计算点积
            normA += Math.pow(rating1, 2);  // 计算用户1评分的平方和
            normB += Math.pow(rating2, 2);  // 计算用户2评分的平方和
        }

        // 计算并返回余弦相似度
        return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
    }

这里补充一下:

其中:

  • aiai​ 和 bibi​ 是用户对帖子的评分。
  • 分子是两个用户对共同帖子的评分的点积(即评分值相乘再求和)。
  • 分母是两个用户评分的模长,即评分平方和的平方根。

3.找到最相似的三个用户(findTopThreeSimilarUsers

这里显然很关键,只有找到了和目标用户相似的用户,才可以进行相关推荐,推荐推荐,和你相类似的人做这个,就可以猜测你也可能做这个,我就推给你,其实我感觉抖音有时就是这种情况,你的好友点赞了啥,诶他就推给你了。这里大致的流程就是遍历所有用户,计算每个用户与目标用户的相似度,找到与目标用户最相似的三个用户。使用优先队列(最大堆)存储和排序相似度,确保只保留最相似的三位用户。

  // 找到与目标用户最相似的三个用户
    // 该方法通过计算余弦相似度来找到与目标用户最相似的三个用户
    public static List<Integer> findTopThreeSimilarUsers(int targetUserId, Map<Integer, Map<Integer, Double>> userRatings) {
        // 使用优先队列(最大堆)存储用户相似度和用户ID
        PriorityQueue<Map.Entry<Integer, Double>> pq = new PriorityQueue<>(
                (e1, e2) -> Double.compare(e1.getValue(), e2.getValue())  // 按相似度升序排序
        );

        // 遍历所有用户,计算与目标用户的相似度
        for (Map.Entry<Integer, Map<Integer, Double>> entry : userRatings.entrySet()) {
            int userId = entry.getKey();  // 用户ID
            if (userId == targetUserId) continue;  // 跳过目标用户本身

            // 计算余弦相似度
            double similarity = cosineSimilarity(userRatings.get(targetUserId), entry.getValue());
            // 将用户和相似度存入优先队列
            Map.Entry<Integer, Double> user = new AbstractMap.SimpleEntry<>(userId, similarity);
            pq.offer(user);

            // 如果队列中的用户数超过3个,移除最不相似的用户
            if (pq.size() > 3) {
                pq.poll();
            }
        }

        // 从优先队列中提取出最相似的三个用户
        List<Integer> topUsers = new ArrayList<>();
        while (!pq.isEmpty()) {
            topUsers.add(pq.poll().getKey());  // 提取用户ID
        }
        return topUsers;  // 返回最相似的三个用户的ID
    }

4.基于相似用户推荐帖子(recommendStickOnSimilarUsers

万事具备了,可以推荐了。这里就是根据与目标用户最相似的几个用户的评分,推荐目标用户可能感兴趣的帖子。如果相似用户评分过的帖子目标用户未评分,则推荐该帖子。根据推荐次数排序,返回最受欢迎的帖子。当然里面推荐多少,都可以自己微调。

 // 基于相似用户推荐帖子
    // 该方法基于与目标用户最相似的用户来推荐帖子
    public static List<Map.Entry<Integer, Integer>> recommendStickOnSimilarUsers(Map<Integer, Map<Integer, Double>> userRatings,
                                                                                 List<Integer> similarUsers, int targetUserId) {
        Map<Integer, Integer> stickVotes = new HashMap<>();  // 存储每个帖子的推荐次数

        // 遍历所有相似用户,统计他们推荐过的帖子
        for (int userId : similarUsers) {
            Map<Integer, Double> userBooks = userRatings.getOrDefault(userId, Collections.emptyMap());
            for (Map.Entry<Integer, Double> bookRating : userBooks.entrySet()) {
                // 如果目标用户没有评价过该帖子,则计入推荐次数
                if (!userRatings.get(targetUserId).containsKey(bookRating.getKey())) {
                    stickVotes.merge(bookRating.getKey(), 1, Integer::sum);  // 增加该帖子的推荐次数
                }
            }
        }

        // 将推荐次数进行排序,从高到低
        List<Map.Entry<Integer, Integer>> sortedsticks = new ArrayList<>(stickVotes.entrySet());
        sortedsticks.sort(Map.Entry.<Integer, Integer>comparingByValue().reversed());

        // 只返回前5个推荐的帖子
        List<Map.Entry<Integer, Integer>> recommendStick = new ArrayList<>();
        System.out.println("为用户 " + targetUserId + " 推荐的帖子:");
        for (int i = 0; i < Math.min(5, sortedsticks.size()); i++) {
            recommendStick.add(sortedsticks.get(i));  // 获取前5个帖子
            System.out.println("帖子ID: " + sortedsticks.get(i).getKey() + ", 被推荐次数: " + sortedsticks.get(i).getValue());
        }

        return recommendStick;  // 返回推荐的帖子列表
    }

5.完整实现代码

到这里可以说工作完成大半了,接下来就是写接口调用咱们封装的模块了。我这里给出完整的StickRecommend的代码:

package com.nchu.common.recommend;
import java.sql.*;
import java.util.*;

public class StickRecommend {

    // 数据库连接信息
    public static final String DB_URL = "jdbc:mysql://localhost:3306/warmheart";
    public static final String USER = "数据库用户名";
    public static final String PASS = "自己的密码";


    // 加载用户评分数据的方法
    // 该方法从数据库中加载用户对各个帖子(stick)的评分数据
    // 返回一个Map,其中key是用户ID,value是一个Map,存储该用户对各个帖子的评分
    public static Map<Integer, Map<Integer, Double>> loadRatingsFromDB(Connection conn) throws SQLException {
        Map<Integer, Map<Integer, Double>> userRatings = new HashMap<>();

        // 使用Statement执行SQL查询,从数据库表 "stick_score" 获取用户评分数据
        try (Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT user_id, stick_id, score FROM stick_score")) {
            // 遍历查询结果集,将数据按照用户ID和帖子ID组织成Map
            while (rs.next()) {
                int userId = rs.getInt("user_id");  // 获取用户ID
                int stickId = rs.getInt("stick_id");  // 获取帖子ID
                double rating = rs.getDouble("score");  // 获取评分

                // 将评分数据存储到Map中,Map的结构是:用户ID -> (帖子ID -> 评分)
                userRatings.computeIfAbsent(userId, k -> new HashMap<>()).put(stickId, rating);
            }
        }
        return userRatings;  // 返回存储评分数据的Map
    }

    // 计算余弦相似度
    // 该方法用于计算两个用户之间的相似度,基于他们对相同帖子的评分进行计算
    // 余弦相似度的值越大,说明两个用户的兴趣越相似
    public static double cosineSimilarity(Map<Integer, Double> ratings1, Map<Integer, Double> ratings2) {
        // 获取两个用户的评分的共同帖子ID
        Set<Integer> commonBooks = new HashSet<>(ratings1.keySet());
        commonBooks.retainAll(ratings2.keySet());  // 求交集,找到共同评分的帖子

        // 如果两个用户没有共同评分的帖子,相似度为0
        if (commonBooks.isEmpty()) return 0;

        double dotProduct = 0.0;  // 点积
        double normA = 0.0;  // 向量A的模
        double normB = 0.0;  // 向量B的模

        // 计算点积和各自向量的模
        for (int stickId : commonBooks) {
            double rating1 = ratings1.get(stickId);  // 用户1对该帖子的评分
            double rating2 = ratings2.get(stickId);  // 用户2对该帖子的评分
            dotProduct += rating1 * rating2;  // 计算点积
            normA += Math.pow(rating1, 2);  // 计算用户1评分的平方和
            normB += Math.pow(rating2, 2);  // 计算用户2评分的平方和
        }

        // 计算并返回余弦相似度
        return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
    }

    // 找到与目标用户最相似的三个用户
    // 该方法通过计算余弦相似度来找到与目标用户最相似的三个用户
    public static List<Integer> findTopThreeSimilarUsers(int targetUserId, Map<Integer, Map<Integer, Double>> userRatings) {
        // 使用优先队列(最大堆)存储用户相似度和用户ID
        PriorityQueue<Map.Entry<Integer, Double>> pq = new PriorityQueue<>(
                (e1, e2) -> Double.compare(e1.getValue(), e2.getValue())  // 按相似度升序排序
        );

        // 遍历所有用户,计算与目标用户的相似度
        for (Map.Entry<Integer, Map<Integer, Double>> entry : userRatings.entrySet()) {
            int userId = entry.getKey();  // 用户ID
            if (userId == targetUserId) continue;  // 跳过目标用户本身

            // 计算余弦相似度
            double similarity = cosineSimilarity(userRatings.get(targetUserId), entry.getValue());
            // 将用户和相似度存入优先队列
            Map.Entry<Integer, Double> user = new AbstractMap.SimpleEntry<>(userId, similarity);
            pq.offer(user);

            // 如果队列中的用户数超过3个,移除最不相似的用户
            if (pq.size() > 3) {
                pq.poll();
            }
        }

        // 从优先队列中提取出最相似的三个用户
        List<Integer> topUsers = new ArrayList<>();
        while (!pq.isEmpty()) {
            topUsers.add(pq.poll().getKey());  // 提取用户ID
        }
        return topUsers;  // 返回最相似的三个用户的ID
    }

    // 基于相似用户推荐帖子
    // 该方法基于与目标用户最相似的用户来推荐帖子
    public static List<Map.Entry<Integer, Integer>> recommendStickOnSimilarUsers(Map<Integer, Map<Integer, Double>> userRatings,
                                                                                 List<Integer> similarUsers, int targetUserId) {
        Map<Integer, Integer> stickVotes = new HashMap<>();  // 存储每个帖子的推荐次数

        // 遍历所有相似用户,统计他们推荐过的帖子
        for (int userId : similarUsers) {
            Map<Integer, Double> userBooks = userRatings.getOrDefault(userId, Collections.emptyMap());
            for (Map.Entry<Integer, Double> bookRating : userBooks.entrySet()) {
                // 如果目标用户没有评价过该帖子,则计入推荐次数
                if (!userRatings.get(targetUserId).containsKey(bookRating.getKey())) {
                    stickVotes.merge(bookRating.getKey(), 1, Integer::sum);  // 增加该帖子的推荐次数
                }
            }
        }

        // 将推荐次数进行排序,从高到低
        List<Map.Entry<Integer, Integer>> sortedsticks = new ArrayList<>(stickVotes.entrySet());
        sortedsticks.sort(Map.Entry.<Integer, Integer>comparingByValue().reversed());

        // 只返回前5个推荐的帖子
        List<Map.Entry<Integer, Integer>> recommendStick = new ArrayList<>();
        System.out.println("为用户 " + targetUserId + " 推荐的帖子:");
        for (int i = 0; i < Math.min(5, sortedsticks.size()); i++) {
            recommendStick.add(sortedsticks.get(i));  // 获取前5个帖子
            System.out.println("帖子ID: " + sortedsticks.get(i).getKey() + ", 被推荐次数: " + sortedsticks.get(i).getValue());
        }

        return recommendStick;  // 返回推荐的帖子列表
    }
}

No.2 推荐接口的实现

我的项目是基于SpringBoot和mybatis-plus框架,大家可以按照自己的需求去写。接下来依次看一下各个层的代码实现。

1.controller层

这里代码简单,不多说啥。

    @GetMapping("/recommend")
    public R getRecommend(@RequestParam Integer userId){
        List<Sticks> list = sticksService.recommendSticks(userId);
        return R.ok().data("stickList",list);
    }

2.service层

我的推荐策略就是用户没有评分历史时,直接推荐五个点赞量最高的帖子;而在有评分历史的情况下,则利用协同过滤算法根据相似用户的偏好来推荐,但是如果相似用户评分过的帖子在目标用户这里也都评价了呢,那没办法再直接给他推荐五个点赞量最高达到帖子了😂,这里可以依据自己的情况而定。


public interface ISticksService extends IService<Sticks> {
     //分页查询
     PageResult<Sticks> findByPage(Integer page,Integer pageSize);
     //获取最热
     PageResult<Sticks> getHot(Integer page,Integer pageSize);
     //获取最新
     PageResult<Sticks> getNew(Integer page,Integer pageSize);
     //推荐贴子
     List<Sticks> recommendSticks(Integer userId);
}
public interface IStickScoreService extends IService<StickScore> {
     List<Sticks> recommend(Integer userId);
}

---------------------------------------------------------------------------------------------
这里是 StickScoreService实现类的部分代码:
@Service  
public class StickScoreServiceImpl extends ServiceImpl<StickScoreMapper, StickScore> implements IStickScoreService {

    @Autowired 
    @Lazy  // 懒加载注解,表示在第一次需要该 Bean 时才会加载
    private ISticksService sticksService;  

    @Override
    public List<Sticks> recommend(Integer userId) {
        List<Sticks> stickList = new ArrayList<>();  // 用来存储最终推荐的帖子列表
        try (
                // 获取数据库连接
                Connection conn = DriverManager.getConnection(StickRecommend.DB_URL, StickRecommend.USER, StickRecommend.PASS)
        ) {
            // 从数据库加载用户评分数据
            Map<Integer, Map<Integer, Double>> userRatings = StickRecommend.loadRatingsFromDB(conn);

            // 找到与目标用户最相似的三个用户
            List<Integer> similarUsers = StickRecommend.findTopThreeSimilarUsers(userId, userRatings);
            System.out.println("与用户 " + userId + " 最相似的三个用户为:" + similarUsers);

            // 根据与相似用户的评分,推荐目标用户可能感兴趣的帖子
            List<Map.Entry<Integer, Integer>> list = StickRecommend.recommendStickOnSimilarUsers(userRatings, similarUsers, userId);

            // 遍历推荐的帖子列表
            for (Map.Entry<Integer, Integer> map : list) {
                // map.getKey() 获取帖子 ID
                LambdaQueryWrapper<Sticks> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(Sticks::getId, map.getKey());  // 根据帖子 ID 构建查询条件
                Sticks sticks = sticksService.getOne(queryWrapper);  // 从数据库查询帖子信息
                stickList.add(sticks);  // 将查询到的帖子添加到推荐列表中
            }
        } catch (SQLException e) {
            e.printStackTrace(); 
        }
        return stickList;
    }
}
---------------------------------------------------------------------------------------------
这是SticksService实现类的部分代码
    @Autowired
    @Lazy
    private IStickScoreService stickScoreService;
 @Override
    public List<Sticks> recommendSticks(Integer userId) {
        LambdaQueryWrapper<StickScore> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(StickScore::getUserId,userId);
        List<StickScore> stickScoreList = stickScoreService.list(queryWrapper);

        LambdaQueryWrapper<Sticks> wrapper = new LambdaQueryWrapper<>();
        wrapper.orderBy(true,false,Sticks::getLikes);
        wrapper.last("LIMIT 5");
        //如果用户从未评价过,则推荐帖子点赞量最高的5个帖子,反之评价过则按协同过滤算法进行推荐
        if(stickScoreList.isEmpty()) {
            List<Sticks> sticks = this.list(wrapper);
            return sticks;
        }else {
            List<Sticks> list = stickScoreService.recommend(userId);
            if(list.isEmpty()){
                //如果所有帖子都评价过,则推荐5个点赞量最高的帖子
                List<Sticks> sticks = this.list(wrapper);
                return sticks;
            }else {
                return list;
            }
        }
    }

No3.运行结果

到这里基本就结束了,大致的流程就是这样了,看一下结果,我用apifox测的。

可以看到是可以进行相关推荐的,基础功能算是达到了。

对了这里再补充一下我这里遇到的一个问题:

2025-02-08 15:34:07.935  WARN 9792 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sticksController': Unsatisfied dependency expressed through field 'sticksService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sticksServiceImpl': Unsatisfied dependency expressed through field 'stickScoreService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'stickScoreServiceImpl': Unsatisfied dependency expressed through field 'sticksService'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'sticksServiceImpl': Requested bean is currently in creation: Is there an unresolvable circular reference?
2025-02-08 15:34:07.938  INFO 9792 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2025-02-08 15:34:07.949  INFO 9792 --- [           main] ConditionEvaluationReportLoggingListener :

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2025-02-08 15:34:07.968 ERROR 9792 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   :

APPLICATION FAILED TO START

Description:

The dependencies of some of the beans in the application context form a cycle:

sticksController (field private com.yaojie.service.ISticksService com.yaojie.controller.SticksController.sticksService)
┌─────┐
|  sticksServiceImpl (field private com.yaojie.service.IStickScoreService com.yaojie.service.impl.SticksServiceImpl.stickScoreService)
↑     ↓
|  stickScoreServiceImpl (field private com.yaojie.service.ISticksService com.yaojie.service.impl.StickScoreServiceImpl.sticksService)
└─────┘

Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.

进程已结束,退出代码1

这个错误是由于 Spring Bean 的循环依赖(circular dependency)导致的。

SticksController 需要 SticksService

SticksServiceImpl 需要 StickScoreService

StickScoreServiceImpl 需要 SticksService

我这里是使用 Spring 的 @Lazy 注解。通过将其中一个依赖注入设置为懒加载(延迟加载),可以打破循环依赖。

@Service
public class SticksServiceImpl implements ISticksService {

    @Autowired
    @Lazy
    private IStickScoreService stickScoreService;
}

No4.总结

这里的协同过滤推荐逻辑实现还是不算很复杂的,我看到的一个音乐协同过滤推荐,里面复杂的多了,之后有时间再研究研究,这次也是学到了一些东西的。😁

我也是带着学习的目的去尝试写的,所以代码肯定有很多没写好的地方,博客也是随心写的,也许会有很多错,大家手下留情,一起交流一起学习!一起进步!

欢迎来我个人博客知足的blog一起交流

基于用户协同过滤算法是一种推荐系统算法,在Java中使用Spring Boot可以很方便地实现。下面是一个基于用户协同过滤算法的简单实现步骤: 1. 数据准备:首先需要准备用户数据和用户对商品的评分数据。可以使用数据库或者文本文件等方式存储数据。 2. 计算用户相似度:使用余弦相似度计算用户之间的相似度。可以使用Java中的向量计算库实现。 3. 找到相似用户:根据用户相似度,找到与目标用户相似度最高的K个用户。 4. 推荐商品:将这K个用户评价高的商品推荐给目标用户。 下面是一个简单的基于用户协同过滤算法的Java代码实现: ```java @Service public class UserBasedCollaborativeFiltering { @Autowired private UserRatingRepository userRatingRepository; public List<String> recommendProducts(String userId, int k) { // 获取所有用户评分数据 List<UserRating> userRatings = userRatingRepository.findAll(); // 计算用户相似度 Map<String, Map<String, Double>> userSimilarities = new HashMap<>(); for (UserRating ur1 : userRatings) { String user1 = ur1.getUserId(); for (UserRating ur2 : userRatings) { String user2 = ur2.getUserId(); if (user1.equals(user2)) { continue; } double similarity = cosineSimilarity(ur1.getRatings(), ur2.getRatings()); if (similarity > 0) { if (!userSimilarities.containsKey(user1)) { userSimilarities.put(user1, new HashMap<>()); } userSimilarities.get(user1).put(user2, similarity); } } } // 找到相似用户 Map<String, Double> similarities = userSimilarities.get(userId); if (similarities == null) { return Collections.emptyList(); } List<String> similarUsers = new ArrayList<>(similarities.keySet()); similarUsers.sort(Comparator.comparing(similarities::get).reversed()); if (similarUsers.size() > k) { similarUsers = similarUsers.subList(0, k); } // 推荐商品 Set<String> recommended = new HashSet<>(); for (String user : similarUsers) { List<UserRating> ratings = userRatingRepository.findByUserId(user); for (UserRating r : ratings) { if (r.getRatings() > 3 && !userRatingRepository.existsByUserIdAndProductId(userId, r.getProductId())) { recommended.add(r.getProductId()); } } } return new ArrayList<>(recommended); } private double cosineSimilarity(Map<String, Double> v1, Map<String, Double> v2) { double dotProduct = 0; double norm1 = 0; double norm2 = 0; for (String key : v1.keySet()) { if (v2.containsKey(key)) { dotProduct += v1.get(key) * v2.get(key); } norm1 += v1.get(key) * v1.get(key); } for (String key : v2.keySet()) { norm2 += v2.get(key) * v2.get(key); } if (norm1 == 0 || norm2 == 0) { return 0; } return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2)); } } ``` 以上代码中的`UserRating`类包含`userId`和`productId`作为主键,以及`ratings`作为用户对商品的评分。`UserRatingRepository`是一个Spring Data JPA的Repository接口,用于访问数据库中的用户评分数据。 在Spring Boot中,可以使用`@RestController`注解创建一个RESTful API来接收用户请求,并返回推荐结果。例如: ```java @RestController public class RecommenderController { @Autowired private UserBasedCollaborativeFiltering recommender; @GetMapping("/recommend/{userId}") public List<String> recommend(@PathVariable String userId) { return recommender.recommendProducts(userId, 10); } } ``` 以上代码中的`recommend`方法接收一个`userId`参数,并调用`UserBasedCollaborativeFiltering`实现推荐算法,返回推荐结果。可以使用浏览器或者curl等工具访问该API,即可得到推荐结果。 以上是一个简单的基于用户协同过滤算法实现,可以根据实际需求进行修改和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值