基于用户的协同过滤算法个性化推荐系统的设计与实现+springboot+vue源码

协同过滤(Collaborative Filtering, CF)是推荐系统中最经典的算法之一,其核心思想是通过用户的历史行为数据(如评分、点击、购买等)发现用户或物品的相似性,并基于这种相似性进行推荐。协同过滤分为两大类:基于用户的协同过滤和基于物品的协同过滤。

本文将介绍 基于用户的协同过滤 原理, 并设计一个简单的商品推荐系统。

 

算法的步骤

1. 获取所有用户行为数据,构建用户-物品评分矩阵。

2. 目标用户与其它用户的相似度计算: 将用户对商品的评分视为向量,计算余弦相似度。

3. 选取与目标用户相似度最高的 k 个用户作为邻居 。

4. 通过邻居用户的评分进行加权平均预测(权重为用户相似度)。

5. 将预测评分按降序排序,选择评分最高的N个物品作为推荐结果。

 

如果你还是不懂,我这里举一个通俗易懂的实例:

假设我们有3个用户(小明、小红、小张)和4个商品(手机、电脑、平板、耳机)。

小明给手机和电脑评分分别是4分和5分,小红给手机和平板评分是5分和3分,小张给电脑和耳机评分是4分和2分。

 

如下图描述:

 

现在要为小明推荐商品,系统执行的步骤如下:

1. 构建用户-物品评分矩阵,每行代表一个用户,每列代表一个商品;

2. 计算小明与其他用户的余弦相似度,比如与小红的相似度是0.8(因为都给手机较高分),与小张的相似度是0.6;

3. 对于小明未评分的商品(平板和耳机),通过相似用户的评分进行加权预测,如平板的预测分数=(0.8×3)/(0.8)=3分,耳机的预测分数=(0.6×2)/(0.6)=2分;

4. 最后按预测分数降序排序,推荐平板给小明。

 

代码实现

数据库表准备

首先我们需要准备用户的行为数据表:

CREATE TABLE `user_behaviors` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '行为ID,自增主键',
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `item_id` bigint NOT NULL COMMENT '物品ID',
  `rating` decimal(3,1) DEFAULT NULL COMMENT '评分(1-5分)',
  `behavior_type` varchar(20) NOT NULL COMMENT '行为类型(VIEW-浏览,LIKE-喜欢,PURCHASE-购买)',
  `timestamp` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '行为发生时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='用户行为记录表';

 

之所以这里有评分字段,是因为后续要扩展 基于协同过滤算法的用户评分推荐系统

当然这里还要有用户表和商品表:

CREATE TABLE `items` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '物品ID,自增主键',
  `name` varchar(200) NOT NULL COMMENT '物品名称',
  `description` text COMMENT '物品描述',
  `category` varchar(50) DEFAULT NULL COMMENT '物品类别',
  `price` decimal(10,2) DEFAULT NULL COMMENT '物品价格',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='商品表';

CREATE TABLE `users` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID,自增主键',
`username` varchar(50) NOT NULL COMMENT '用户名',
`email` varchar(100) NOT NULL COMMENT '用户邮箱',
`password` varchar(100) NOT NULL COMMENT '用户密码',
PRIMARY KEY (`id`)
) ENGINE=InnoDB  COMMENT='用户表';

 

表建立好之后,我们需要写好基本的用户登录,商品列表,商品详情等基础接口,然后保证有丰富的行为数据落库,这样推荐系统才能发挥作用。

这些基本的接口我就不讲了,主要讲解一下关键的算法代码。

 

完整系统源码我已经整理清楚:

gitcode巅抗目/hadluo2/springboot_vue.git

用户评分矩阵的构建

需要借助Array2DRowRealMatrix算法工具, Array2DRowRealMatrix 是 Apache Commons Math 库中的一个类,用于表示二维实数矩阵,并提供矩阵运算功能。

maven依赖如下:

  <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-math3</artifactId>
            <version>3.6.1</version>
  </dependency>

构建评分矩阵的代码:

       // 获取所有用户的行为数据,用于构建用户-物品评分矩阵
        List<UserBehavior> allBehaviors = userBehaviorRepository.selectList(null);

        if(CollectionUtils.isEmpty(allBehaviors)) {
            return Collections.emptyList();
        }

        // 构建用户和物品的索引映射,方便后续构建评分矩阵
        Map<Long, Integer> userIndex = new HashMap<>();
        Map<Long, Integer> itemIndex = new HashMap<>();
        // 提取用户id
        List<Long> users = allBehaviors.stream().map(UserBehavior::getUserId).distinct().collect(Collectors.toList());
        // 提取物品id
        List<Long> items = allBehaviors.stream().map(UserBehavior::getItemId).distinct().collect(Collectors.toList());
        for (int i = 0; i < users.size(); i++) {
            userIndex.put(users.get(i), i);
        }
        for (int i = 0; i < items.size(); i++) {
            itemIndex.put(items.get(i), i);
        }
        // 初始化评分矩阵,行表示用户,列表示物品   一个 users.size() x  items.size() 大小的矩阵
        RealMatrix ratingMatrix = new Array2DRowRealMatrix(users.size(), items.size());
        // 根据用户行为数据填充评分矩阵
        for (UserBehavior behavior : allBehaviors) {
            if (behavior.getRating() != null) {
                int uIndex = userIndex.get(behavior.getUserId());
                int iIndex = itemIndex.get(behavior.getItemId());
                // 设置 矩阵的 行,列 值 为 评分
                ratingMatrix.setEntry(uIndex, iIndex, behavior.getRating());
            }
        }

 

余弦相似度计算

    /**
     * 计算两个向量的余弦相似度
     * 余弦相似度用于衡量两个用户的评分模式的相似程度
     * @param vector1 第一个用户的评分向量
     * @param vector2 第二个用户的评分向量
     * @return 相似度值,范围[-1,1],值越大表示越相似
     */
    private double calculateCosineSimilarity(double[] vector1, double[] vector2) {
        double dotProduct = 0.0;
        double norm1 = 0.0;
        double norm2 = 0.0;

        for (int i = 0; i < vector1.length; i++) {
            dotProduct += vector1[i] * vector2[i];
            norm1 += vector1[i] * vector1[i];
            norm2 += vector2[i] * vector2[i];
        }

        if (norm1 == 0 || norm2 == 0) return 0;

        return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
    }

 

根据余弦相似度计算取5个相似的用户作为邻居

用户uhgnoy// 计算目标用户与其他用户的相似度
        int userIdx = userIndex.get(user.getId());
        Map<Integer, Double> userSimilarities = new HashMap<>();

        for (int i = 0; i < users.size(); i++) {
            if (i != userIdx) {
                // 计算 当前用户 与其他的每一个用户的评分向量的 余弦相似度
                double similarity = calculateCosineSimilarity(ratingMatrix.getRow(userIdx), ratingMatrix.getRow(i));
                userSimilarities.put(i, similarity);
            }
        }

        // 选择最相似的5个用户作为邻居用户
        List<Integer> similarUsers = userSimilarities.entrySet().stream()
            // 按相似度值降序排序
            .sorted(Map.Entry.<Integer, Double>comparingByValue().reversed())
            // 取前5个最相似用户
            .limit(5)
            // 提取用户索引
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());

 

最后是计算加权平均,当中还需要进行 归一化处理, 来避免了因用户群体整体相似度偏高/偏低导致的预测偏差,使得推荐结果更贴近用户的真实偏好。

整体代码较长,我就不贴了。

 

最后是controller接口提供API

/**
 * 推荐系统API控制器
 * 提供个性化推荐、热门物品推荐等功能
 */
@RestController
@RequestMapping("/recommendations")
public class RecommendationController {

    @Autowired
    private RecommendationService recommendationService;
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private ItemRepository itemRepository;
    
    @Autowired
    private UserBehaviorRepository userBehaviorRepository;

    /**
     * 获取用户个性化推荐列表
     * @return 推荐的物品列表
     */
    @PostMapping("/user")
    public V getRecommendationsForUser(@RequestBody UserBehavior item) {
        User user = userRepository.selectById(item.getUserId());
        if (user == null) {
            return V.fail("必须登录");
        }
        List<Item> recommendations = recommendationService.recommendItemsForUser(user, 10);
        return V.success(recommendations);
    }

    /**
     * 记录用户行为数据
     * @param behavior 用户行为信息(如:浏览、评分、收藏等)
     * @return 操作结果
     */
    @PostMapping("/behavior")
    public V recordUserBehavior(@RequestBody UserBehavior behavior) {
        // 验证用户ID
        User user = userRepository.selectById(behavior.getUserId());
        if (user == null) {
            return V.fail("用户不存在");
        }
        
        // 验证物品ID
        Item item = itemRepository.selectById(behavior.getItemId());
        if (item == null) {
            return V.fail("物品不存在");
        }
        
        // 验证行为类型
        if (behavior.getBehaviorType() == null || 
            (!behavior.getBehaviorType().equals("VIEW") && 
             !behavior.getBehaviorType().equals("LIKE") && 
             !behavior.getBehaviorType().equals("PURCHASE"))) {
            return V.fail("无效的行为类型");
        }
        
        // 验证评分范围(如果提供了评分)
        if (behavior.getRating() != null && (behavior.getRating() < 1 || behavior.getRating() > 5)) {
            return V.fail("评分必须在1-5之间");
        }
        
        // 设置关联实体和时间戳
        behavior.setTimestamp(LocalDateTime.now());
        // 检查是否存在相同的行为记录
        LambdaQueryWrapper<UserBehavior> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserBehavior::getUserId, behavior.getUserId())
               .eq(UserBehavior::getItemId, behavior.getItemId())
               .eq(UserBehavior::getBehaviorType, behavior.getBehaviorType());


        UserBehavior existingBehavior = userBehaviorRepository.selectOne(wrapper);
        
        try {
            if (existingBehavior != null) {
                // 更新现有记录
                behavior.setId(existingBehavior.getId());
                userBehaviorRepository.updateById(behavior);
            } else {
                // 创建新记录
                userBehaviorRepository.insert(behavior);
            }
            return V.success(behavior);
        } catch (Exception e) {
            return V.fail("保存用户行为失败:" + e.getMessage());
        }
    }

    /**
     * 获取商品列表,支持分页查询
     * @param page 页码,从1开始
     * @param size 每页数量
     * @return 分页的商品列表
     */
    @GetMapping("/items")
    public V getItems(
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {
        Page<Item> pageRequest = new Page<>(page, size);
        IPage<Item> items = itemRepository.selectPage(pageRequest, null);
        return V.success(items);
    }

    /**
     * 商品详情
     */
    @GetMapping("/item-details/{id}")
    public V details(@PathVariable Long id) {
        Item item = itemRepository.selectById(id);
        return V.success(item);
    }
}

 

posted @ 2025-05-19 23:37  qq3993387644  阅读(303)  评论(0)    收藏  举报