开发者

Springboot实现推荐系统的协同过滤算法

开发者 https://www.devze.com 2025-05-10 10:59 出处:网络 作者: ueanaIU潇潇子
目录前言基本原理 算法分类 计算方法应用场景 代码实现 前言
目录
  • 前言
    • 基本原理 
    • 算法分类 
    • 计算方法
    • 应用场景 
  • 代码实现 

    前言

    协同过滤算法(Collaborative Filtering)是一种在推荐系统中广泛使用的算法,用于预测用户对物品(如商品、电影、音乐等)的偏好,从而实现个性化推荐。下面是详细介绍: 

    基本原理 

    基于用户行为数据:协同过滤算法的核心是利用用户对物品的行为数据,这些行为数据可以包括用户的评分(如电影评分从 1 - 5 星)、购买记录、浏览历史、点赞 / 收藏等。通过分析这些数据来发现用户之间的相似性或者物品之间的相似性。

    相似性度量

    用户相似性:如果两个用户对很多相同物品的评价(或行为)相似,那么这两个用户就具有较高的相似性。例如,用户 A 和用户 B 都对电影 1 打了 4 星,对电影 2 打了 3 星,对电影 3 打了 5 星,通过计算这种相似性,我们可以认为他们的观影偏好相似。

    物品相似性:如果很多用户对两个物品的评价(或行为)相似,那么这两个物品就具有较高的相似性。比如,很多用户既喜欢电影《泰坦尼克号》又喜欢电影《罗马假日》,这两部电影在用户偏好上就具有一定的相似性。

    算法分类 

    基于用户的协同过滤(User - based Collaborative Filtering)

    原理:先找到与目标用户兴趣相似的其他用户(称为 “邻居用户”),然后根据这些邻居用户对物品的偏好来预测目标用户对未见过(或未评价)物品的偏好。例如,目标用户 A 没有看过电影 M,但是与 A 相似的用户 B、C 都对电影 M 评价很高,那么就可以推测用户 A 也可能喜欢电影 M。

    Springboot实现推荐系统的协同过滤算法

    案例:有A、B、C三个用户,已知A四种水果都喜欢,C喜欢的两种水果是A喜欢的,那么可以认为A和C是相似的,则可以把A喜欢的但不知道C是否喜欢的那两种水果推荐给C.

    Springboot实现推荐系统的协同过滤算法

    基于物品的协同过滤(Item - based Collaborative Filtering)

    原理:先计算物品之间的相似度,然后根据用户已经评价(或行为)过的物品与其他物品的相似度,来预测用户对其他未评价(或未行为)物品的偏好。例如,用户 A 喜欢电影《教父》,而电影《教父》和电影《美国往事》相似度很高,那么可以推测用户 A 也可能喜欢电影《美国往事》。

    计算方法

    在协同过滤算法中,用于计算用户或物品之间相似度的方法有很多种,下面我们将介绍两种:

    1、杰卡德相似系数(Jaccard Similarity Coefficient) 

    杰卡德相似系数主要用于衡量有限集合之间的相似性。在协同过滤的场景下,通常是将用户对物品的行为(如是否购买、是否喜欢等)看作集合元素。对于两个用户 A 和 B,

    Springboot实现推荐系统的协同过滤算法

    也就是说,杰卡德相似系数是两个用户共同感兴趣的物品数量与他们感兴趣的所有物品数量之比 

    2、余弦相似度 

    首先,在协同过滤中会构建一个用户 - 物品评分矩阵。假设我们有个用户和个物品,矩阵中的元素表示用户对物品的评分。例如,在电影推荐系统中,行代表用户,列代表电影,矩阵中的每个元素就是用户对电影的评分(如果用户没有评分可以用 0 或其他默认值表示)。对于两个用户 A 和 B

    Springboot实现推荐系统的协同过滤算法

    优点 

    • 个性化推荐:能够根据用户的历史行为为每个用户提供个性化的推荐,推荐的物品与用户的兴趣紧密相关,提高了用户发现感兴趣物品的概率。
    • 不需要物品内容信息:与基于内容的推荐算法不同,协同过滤算法不需要对物品的内容(如电影的剧情、商品的描述等)进行分析,只依赖用户的行为数据,因此可以适用于各种类型的物品推荐。可以发现新的兴趣点:能够发现用户潜在的兴趣点,例如用户可能没有意识到自己会喜欢某个物品,但通过协同过滤算法的推荐,用户可能会发现并喜欢上这个新的物品。

    缺点 

    • 冷启动问题:
      • 用户冷启动:对于新用户,由于没有足够的行为数据,很难找到与其相似的用户或者根据其行为来推荐物品。例如,一个新注册的用户还没有对任何电影进行评分,就很难为他推荐合适的电影。
      • 物品冷启动:新加入的物品由于没有用户行为数据,也很难被推荐出去。比如,一个新上映的电影,如果还没有用户对其进行评分或其他行为,就很难出现在推荐列表中。
    • 数据稀疏问题:在实际应用中,用户对物品的行为数据往往是非常稀疏的。例如,一个电商平台有大量的商品和用户,但每个用户可能只购买或浏览了很少一部分商品,这就导致用户 - 物品评分矩阵(或行为矩阵)中有大量的空白,影响了相似度计算的准确性和推荐质量。

    应用场景 

    • 电商平台推荐:根据用户的购买历史、浏览记录等推荐用户可能感兴趣的android商品,如亚马逊、淘宝等电商平台都广泛使用协同过滤算法来提高用户的购买转化率。
    • 视频和音乐平台推荐:像 Netflix(视频平台)、Spotify(音乐平台)等,根据用户的观看 / 收听历史、评分等行为推荐新的视频或音乐。
    • 社交平台推荐:在社交网络中推荐用户可能感兴趣的人、群组、内容等,例如领英(LinkedIn)可能会根据用户的职业兴趣和关注的人来推荐新的人脉或行业内容。

    代码实现 

    首先我们需要数据库中需要有一张表,比如说评分表score,表中的字段如下,

    Springboot实现推荐系统的协同过滤算法

    然后我们就可以使用下面的代码从这张表中获取数据,

    ​
    package com.bishe.utils.recommend;
    import Java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    /**
     * 该类提供基于用户评分数据生成推荐物品的相关功能。
     */
    public class UserCF {
      private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database"; // 数据库地址
        private static final String USER = "your_username"; // 数据库用户名
        private static final String PASSWORD = "your_password"; // 数据库密码
        // 程序入口
        public static void main(String[] args) {
            // 从数据库中获取评分数据
            Map<Integer, Map<Integer, Integer>> ratings = fetchRatingsFromDatabase();
            int userId = 1; // 要推荐的用户ID
            int numRecommendations = 2; // 推荐数量
            List<Integer> recommendations = recommend(ratings, userId, numRecommendations);
            System.out.println("为用户 " + userId + " 推荐的物品: " + recommendations);
        }
        // 从数据库中获取评分数据的方法
        private static Map<Integer, Map<Integer, Integer>> fetchRatingsFromDatabase() {
            Map<Integer, Map<Integer, Integer>> ratings = new HashMap<>();
            try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASSWORD);
                 Statement stmt = conn.createStatement()) {
                String sql = "SELECT user_id, item_id, rating FROM ratings"; // 假设你的评分数据在ratings表中
                ResultSet rs = stmt.executeQuery(sql);
                while (rs.next()) {
                    int userId = rs.getInt("user_id");
                    int itemId = rs.getInt("item_id");
                    int rating = rs.getInt("rating");
                    ratings.computeIfAbsent(userId, k -> new HashMap<>()).put(itemId, rating);
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return ratings;
        }
        /**
         * 推荐方法,根据用户的评分数据和用户ID生成推荐物品。
         * <p>
         * 此方法基于用户间的相似度以及评分数据,综合计算出对指定用户的推荐物品列表。
         * </p>
         *
         * @param ratings            包含所有用户及其对应评分数据的映射。外层键为用户ID,内层键为物品ID,值为对应评分。
         * @param userId             目标用户的ID,用于生成针对该用户的推荐物品。
         * @param numRecommendations 期望生成的推荐物品数量,决定了最终返回推荐列表的长度。
         * @return 返回一个包含推荐物品ID的列表,列表长度由numRecommendations决定或者受限于可推荐物品数量(取较小值)。
         */
        public static List<Integer> recommend(Map<Integer, Map<Integer, Integer>> ratings, int userId, int numRecommendations) {
            // 创建一个Map,用于存储每个物品的加权评分。键为物品ID,值为加权评分(初始化为0.0)。
            // 加权评分会综合考虑其他用户与目标用户的相似度以及他们对物品的评分来计算。
            Map<Integer, Double> weightedRatings = new HashMap<>();
            // 创建一个Map,用于存储每个其他用户与目标用户的相似度得分。键为其他用户的ID,值为相似度得分(初始化为0.0)。
            Map<Integer, Double> similarityScores = new HashMap<>();
            // 计算与其他用户的相似度,并基于相似度和其他用户的评分计算物品的加权评分
            for (Map.Entry<Integer, Map<Integer, Integer>> entry : ratings.entrySet()) {
                // 获取当前遍历到的其他用户的ID
                int otherUserId = entry.getKey();
                // 如果当前用户就是目标用户,则跳过本次循环,不需要计算与自身的相似度
                if (otherUserId == userId) continue;
                // 调用calculateSimilarity方法计算当前其他用户与目标用户的相似度,并将结果存入similarityScores Map中
                double similarity = calculateSimilarity(ratings.get(userId), entry.getValue());
                similarityScores.put(otherUserId, similarity);
                // 遍历当前其他用户评分过的物品,计算这些物品的加权评分
                for (Map.Entry<Integer, Integer> item : entry.getValue().entrySet()) {
                    // 获取物品的ID
                    int itemId = item.getKey();
                    // 获取当前其他用户对该物品的评分
                    double rating = item.getValue();
                    // 如果目标用户没有对该物品进行过评分(即ratings.get(userId).get(itemId)为null),
                    // 则将该物品的加权评分加上当前其他用户对该物品的评分乘以相似度得分
                    if (ratings.get(userId).get(itemId) == null) {
                        weightedRatings.put(itemId, weightedRatings.getOrDefault(itemId, 0.0) + rating * similarity);
                    }
                }
            }
            // 将加权评分的Map转换为List<Map.Entry<Integer, Double>>,方便后续进行排序操作
            // 每个元素代表一个物品的ID及其对应的加权评分,以键值对形式存储在Entry中
            List<Map.Entry<Integer, Double>> sortedRecommendations = new ArrayList<>(weightedRatings.entrySet());
            // 使用lambda表达式自定义排序规则,按照加权评分从高到低对推荐物品进行排序
            // 这里通过比较Entry中的值(即加权评分)来确定顺序,b.getValue().compareTo(a.getValue())表示按照降序排列
            sortedRecommendations.sort((a, b) -> b.getValue().compareTo(a.getValue()));
            // 创建一个列表,用于存储最终要返回的推荐物品ID
            List<Integer> recommendedItems = new ArrayList<>();
            // 根据期望推荐的数量(取numRecommendations和实际可推荐物品数量的较小值),
            // 将排序后的推荐物品ID依次添加到recommendedItems列表中
            for (int i = 0; i < Math.min(numRecommendations, sortedRecommendations.size()); i++) {
                recommendedItems.add(sortedRecommendations.get(i).getKey());
            }
            return recommendedItems;
        }
        /**
         * 计算用户和其他用户之间相似度的方法。
         * <p>
         * 通过计算用户评分向量的点积以及各自向量的模长,来得出两个用户之间的相似度得分。
         * 使用余弦相似度的计算方式,衡量用户间在评分行为上的相似程度。
         * </p>
         *
         * @param userRatings      一个用户的评分数据映射,键为物品ID,值为对应的评分。
         * @param otherUserRatings 另一个用户的评分数据映射,与userRatings结构相同,用于对比计算相似度。
         * @return 返回一个双精度浮点数,表示两个用户之间的相似度得分,范围在0到1之间(包含0和1),值越接近1表示相似度越高。
         */
        private static double calculateSimilarity(Map<Integer, Integer> userRatings, Map<Integer, Integer> otherUserRatings) {
            // 初始化用于存储用户评分向量点积的变量,初始值为0.0,后续会根据共同评分的物品来累加计算
            double dotProduct = 0.0;
            // 初始化用于存储当前用户评分向量模长平方的变量,初始值为0.0,后续会根据当前用户的评分来累加计算
            double userMagnitude = 0.0;
            // 初始化用于存储其他用户评分向量模长平方的变量,初始值为0.0,后续会根据其他用户的评分来累加计算
            double otherUserMagnitude = 0.0;
            // 遍历当前用户的评分数据,计算与其他用户评分数据的点积以及各自向量的模长平方
            for (Map.Entry<Integer, Integer> entry : userRatings.entrySet()) {
                // 获取当前评分数据对应的物品ID
                int itemId = entry.getKey();
                // 如果其他用户也对该物品进行了评分(即otherUserRatings中包含该物品ID),则进行以下计算
                if (otherUserRatings.containsKey(itemId)) {
                    // 计算点积,将当前用户对该物品的评分与其他用户对该物品的评分相乘,并累加到dotProduct变量中
                    dotProduct += entry.getValue() * otherUserRatings.get(itemId);
                    // 计算当前用户评分向量模长的平方,将当前用户对该物品的评分进行平方,并累加到userMagnitude变量中
                    userMagnitude += Math.pow(entry.getValue(), 2);
                    // 计算其他用户评分向量模长的平方,将其他用户对该物品的评分进行平方,并累加到otherUserMagnitude变量中
                    otherUserMagnitude += Math.pow(otherUserRatings.get(itemId), 2);
                }
            }
            // 如果当前用户评分向量模长的平方为0或者其他用户评分向量模长的平方为0,
            // 说明至少有一个用户没有对任何共同物品进行评分,此时相似度为0,直接返回0(防止后续除法运算出现除以零的错误)
            if (userMagnitude == 0 || otherUserMagnitude == 0) {
                return 0;
            }
            // 根据余弦相似度的计算公式,返回两个用户之间的相似度得分。
            // 即点积除以两个用户评分向量模长的乘积(先分别对模长平方开方得到模长,再相乘)
            return dotProduct / (Math.sqrt(userMagnitude) * Math.sqrt(otherUserMagnitude));
        }
    }
    ​

    从数据库获取数据的方法可以根据自己的需求定义,这里只是方便演示就直接写了,代码采用的计算余弦的方法来计算用户相似度。

    如果想直接看效果的我们可以把上面代码中的main方法替换为下面的代码

     /**
         * main方法,用于测试推荐功能。
         * @param args
         */
        public static void main(String[] args) {
            // 创建一个包含所有用户评分数据的映射
            Map<Integer, Map<Integer, Integer>> ratings = new HashMap<>();
            // 添加一些模拟数据
            // 用户1对物品1评分5分,对物品2评分3分
            ratings.put(1, new HashMap<Integer, Integer>() {{
                put(1, 5);
                put(2, 3);
            }});
            // 用户2对物品1评分4分,对物品3评分2分
            ratings.put(2, new HashMap<Integer, Integer>() {{
                put(1, 4);
                put(3, 2);
            }});
            // 用户3对物品2评分4分,对物品3评分5分
            ratings.put(3, new HashMap<Integer, Integer>() {{
                put(2, 4);
                put(3, 5);
            }});
            // 用户4对物品1评分3分,对物品4评分2分
            ratings.put(4, new HashMap<Integer, Integer>() {{
                put(1, 3);
                put(4, 2);
            }});
            // 目标用户ID
            int userId = 1;
            // 期望生成的推荐物品数量
            int numRecommendations = 2;
            // 调用recommend方法生成推荐物品列表
            List<Integer> recommendedItems = recommend(ratings, userId, numRecommendations);
            // 打印推荐结果
            System.out.println("推荐给用户" + userId + "的物品ID列表: " + recommendedItems);
        }

    运行后我们可以看到控制台打印出的信息

    Springboot实现推荐系统的协同过滤算法

    这样就代表功能已经实现了。

    下面是基于余弦计算物品之间相似度的方法,实现和计算用户的差不多,感兴趣的可以看一下

    package com.bishe.utils.recommend;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    public class ItemCF {
        /**
         * 基于物品相似度的协同过滤推荐算法主方法。
         * 该方法根据用户对物品的评分数据,针对指定用户生成推荐物品列表。
         *
         * @param ratings 用户评分数据,外层键为用户ID,内层键为物品ID,值为对应评分。
         * @param userId  目标用户的ID,用于生成针对该用户的推荐列表。
         * @param numRecommendations 期望生成的推荐物品数量,决定了最终返回推荐列表的长度。
         * @return 返回一个包含推荐物品ID的列表,列表长度由numRecommendations决定或者受限于可推荐物品数量(取较小值)。
         */
        public static List<Integer> recommend(Map<Integer, Map<Injsteger, Integer>> ratings, int userId, int numRecommendations) {
            // 调用calculateItemSimilarities方法计算物品之间的相似度矩阵,
            // 这个矩阵将用于后续预测用户对未评分物品的评分,以生成推荐列表
            Map<Integer, Map<Integer, Double>> itemSimilarities = calculateItemSimilarities(ratings);
            // 获取目标用户(由userId指定)已评分的物品列表,
            // 以便后续基于这些已评分物品来预测对其他未评分物品的评分
            Map<Integer, Integer> userRatings = ratings.get(userId);
            List<Integer> ratedItems = new ArrayList<>(userRatings.keySet());
            // 用于存储预测的用户对未评分物品的评分,
            // 键为物品ID,值为预测的评分,后续会根据物品相似度等信息来计算这些评分
            Map<Integer, Double> predictedRatings = new HashMap<>();
            // 遍历所有用户及其评分数据,目的是针对目标用户未评分的物品进行评分预测
            for (Map.Entry<Integer, Map<Integer, Integer>> entry : ratings.entrySet()) {
                // 获取当前遍历到的其他用户的评分数据
                Map<Integer, Integer> otherUserRatings = entry.getValue();
                // 获取当前遍历到的其他用户的ID
                int otherUserId = entry.getKey();
                // 如果当前遍历到的用户就是目标用户,则跳过本次循环,不需要对自身已有的评分数据进行处理
                if (otherUserId == userId) cphpontinue;
                // 遍历当前其他用户评分过的物品,检查是否是目标用户未评分的物品,若是则进行评分预测
                for (Integer itemId : otherUserRatings.keySet()) {
                    if (!ratedItems.contains(itemId)) {
                        // 调用predictRating方法预测目标用户对当前未评分物品(itemId)的评分,
                        // 并将预测评分存入predictedRatings这个Map中
                        double predictedRating = predictRating(userRatings, itemSimilarities, itemId, otherUserRatings);
                        predictedRatings.put(itemId, predictedRating);
                    }
                }
            }
            // 将预测评分的Map转换为List<Map.Entry<Integer, Double>>形式,方便后续进行排序操作,
            // 每个元素代表一个物品的ID及其对应的预测评分,以键值对形式存储在Entry中
            List<Map.Entry<Integejsr, Double>> sortedPredictions = new ArrayList<>(predictedRatings.entrySet());
            // 使用lambda表达式自定义排序规则,按照预测评分从高到低对物品进行排序,
            // 这里通过比较Entry中的值(即预测评分)来确定顺序,b.getValue().compareTo(a.getValue())表示按照降序排列
            sortedPredictions.sort((a, b) -> b.getValue().compareTo(a.getValue()));
            // 创建一个列表,用于存储最终要返回的推荐物品ID
            List<Integer> recommendedItems = new ArrayList<>();
            // 根据期望推荐的数量(取numRecommendations和实际可推荐物品数量的较小值),
            // 将排序后的推荐物品ID依次添加到recommendedItems列表中
            for (int i = 0; i < Math.min(numRecommendations, sortedPredictions.size()); i++) {
                recommendedItems.add(sortedPredictions.get(i).getKey());
            }
            return recommendedItems;
        }
        /**
         * 计算物品之间相似度的方法,采用余弦相似度计算。
         * 该方法通过分析所有用户对物品的评分数据,构建出物品之间的相似度矩阵,
         * 矩阵中记录了每对物品之间的相似度得分,反映了物品在用户评分行为上的相似程度。
         *
         * @param ratings 用户评分数据,用于分析物品之间的相似程度。
         * @return 返回一个表示物品相似度的矩阵,外层键为物品ID,内层键为与之对比的物品ID,值为相似度得分。
         */
        private static Map<Integer, Map<Integer, Double>> calculateItemSimilarities(Map<Integer, Map<Integer, Integer>> ratings) {
            // 创建一个嵌套的Map结构,用于存储物品之间的相似度矩阵,
            // 外层键表示一个物品的ID,内层键表示与之对比的另一个物品的ID,值为它们之间的相似度得分
            Map<Integer, Map<Integer, Double>> itemSimilarities = new HashMap<>();
            // 遍历所有物品,外层循环每次确定一个物品(记为item1),用于与其他物品计算相似度
            for (Map.Entry<Integer, Map<Integer, Integer>> item1Entry : ratings.entrySet()) {
                // 获取当前遍历到的物品(item1)的ID
                int item1Id = item1Entry.getKey();
                // 获取当前遍历到的物品(item1)的用户评分数据
                Map<Integer, Integer> item1Ratings = item1Entry.getValue();
                // 创建一个Map,用于存储当前物品(item1)与其他物品的相似度得分,
                // 键为其他物品的ID,值为对应的相似度得分,后续会在循环中填充这个Map
                Map<Integer, Double> similaritiesForItem1 = new HashMap<>();
                // 内层循环再次遍历所有物品(记为item2),计算item1与每个item2之间的相似度
                for (Map.Entry<Integer, Map<Integer, Integer>> item2Entry : ratings.entrySet()) {
                    // 获取当前内层循环遍历到的物品(item2)的ID
                    int item2Id = item2Entry.getKey();
                    // 获取当前内层循环遍历到的物品(item2)的用户评分数据
                    Map<Integer, Integer> item2Ratings = item2Entry.getValue();
                    // 如果两个物品是同一个物品(即ID相同),则跳过本次相似度计算,因为自身与自身的相似度为1(无需计算)
                    if (item1Id == item2Id) continue;
                    // 调用calculateItemSimilarity方法计算当前两个物品(item1和item2)之间的相似度,
                    // 并将计算得到的相似度得分存入similaritiesForItem1这个Map中
                    double similarity = calculateItemSimilarity(item1Ratings, item2Ratings);
                    similaritiesForItem1.put(item2Id, similarity);
                }
                // 将当前物品(item1)与其他物品的相似度得分Map存入itemSimilarities这个总的相似度矩阵中,
                // 外层键为item1的ID,即完成了针对item1与其他所有物品相似度的计算和存储
                itemSimilarities.put(item1Id, similaritiesForItem1);
            }
            return itemSimilarities;
        }
        /**
         * 计算两个物品之间的相似度(采用余弦相似度)。
         * 通过分析两个物品各自对应的用户评分数据,按照余弦相似度的计算公式,
         * 得出反映它们在用户评分行为上相似程度的得分,范围在0到1之间(包含0和1),越接近1表示相似度越高。
         *
         * @param item1Ratings 第一个物品的用户评分数据,键为用户ID,值为对应评分。
         * @param item2Ratings 第二个物品的用户评分数据,结构与item1Ratings相同,用于对比计算相似度。
         * @return 返回一个双精度浮点数,表示两个物品之间的相似度得分,范围在0到1之间(包含0和1),值越接近1表示相似度越高。
         */
        private static double calculateItemSimilarity(Map<Integer, Integer> item1Ratings, Map<Integer, Integer> item2Ratings) {
            // 初始化用于存储两个物品评分向量点积的变量,初始值为0.0,后续会根据共同评分的用户来累加计算
            double dotProduct = 0.0;
            // 初始化用于存储第一个物品评分向量模长平方的变量,初始值为0.0,后续会根据第一个物品的用户评分来累加计算
            double item1Magnitude = 0.0;
            // 初始化用于存储第二个物品评分向量模长平方的变量,初始值为0.0,后续会根据第二个物品的用户评分来累加计算
            double item2Magnitude = 0.0;
            // 遍历第一个物品的用户评分数据,目的是找出与第二个物品共同评分的用户,并基于这些用户的评分计算相关数值
            for (Map.Entry<Integer, Integer> entry : item1Ratings.entrySet()) {
                // 获取当前评分数据对应的用户ID
                int userId = entry.getKey();
                // 如果第二个物品的用户评分数据中也包含当前用户(即共同评分的用户),则进行以下计算
                if (item2Ratings.containsKey(userId)) {
                    // 计算点积,将第一个物品当前用户的评分与第二个物品该用户的评分相乘,并累加到dotProduct变量中
                    dotProduct += entry.getValue() * item2Ratings.get(userId);
                    // 计算第一个物品评分向量模长的平方,将第一个物品当前用户的评分进行平方,并累加到item1Magnitude变量中
                    item1Magnitude += Math.pow(entry.getValue(), 2);
                    // 计算第二个物品评分向量模长的平方,将第二个物品当前用户的评分进行平方,并累加到item2Magnitude变量中
                    item2Magnitude += Math.pow(item2Ratings.get(userId), 2);
                }
            }
            // 如果第一个物品评分向量模长的平方为0或者第二个物品评分向量模长的平方为0,
            // 说明至少有一个物品没有被任何用户共同评分过,此时相似度为0,直接返回0(防止后续除法运算出现除以零的错误)
            if (item1Magnitude == 0 || item2Magnitude == 0) {
                return 0;
            }
            // 根据余弦相似度的计算公式,返回两个物品之间的相似度得分。
            // 即点积除以两个物品评分向量模长的乘积(先分别对模长平方开方得到模长,再相乘)
            return dotProduct / (Math.sqrt(item1Magnitude) * Math.sqrt(item2Magnitude));
        }
        /**
         * 预测用户对某个未评分物品的评分。
         * 通过结合用户已评分物品的评分、物品之间的相似度矩阵以及其他用户对该未评分物品的评分等信息,
         * 按照特定的计算公式来预测目标用户对该未评分物品的评分值。
     www.devze.com    *
         * @param userRatings 用户已有的评分数据,用于结合物品相似度来预测评分。
         * @param itemSimilarities 物品相似度矩阵,提供物品间的相似程度信息。
         * @param itemId 待预测评分的物品ID。
         * @param otherUserRatings 其他用户的评分数据,用于参考计算预测评分。
         * @return 返回预测的用户对该物品的评分值,为双精度浮点数。
         */
        private static double predictRating(Map<Integer, Integer> userRatings,
                                            Map<Integer, Map<Integer, Double>> itemSimilarities,
                                            int itemId,
                                            Map<Integer, Integer> otherUserRatings) {
            // 初始化分子变量,用于存储预测评分计算公式中的分子部分的值,初始值为0.0,后续会根据已评分物品的相关信息累加计算
            double numerator = 0.0;
            // 初始化分母变量,用于存储预测评分计算公式中的分母部分的值,初始值为0.0,后续会根据物品相似度等信息累加计算
            double denominator = 0.0;
            // 遍历用户已评分的物品,目的是结合这些已评分物品与待预测评分物品的相似度等信息来计算预测评分
            for (Map.Entry<Integer, Integer> ratingEntry : userRatings.entrySet()) {
                // 获取当前已评分物品的ID
                int ratedItemId = ratingEntry.getKey();
                // 获取当前已评分物品的评分
                double rating = ratingEntry.getValue();
                // 从物品相似度矩阵中获取当前已评分物品与待预测评分物品(itemId)之间的相似度
                double similarity = itemSimilarities.get(ratedItemId).get(itemId);
                // 将已评分物品的评分乘以相似度累加到分子变量中,按照预测评分的计算公式进行分子部分的累加
                numerator += rating * similarity;
                // 将相似度的绝对值累加到分母变量中,按照预测评分的计算公式进行分母部分的累加
                denominator += Math.abs(similarity);
            }
            // 如果分母变量的值为0,说明没有可参考的已评分物品与待预测物品有相似度关系,此时返回0作为预测评分
            if (denominator == 0) {
                return 0;
            }
            // 根据预测评分的计算公式(分子除以分母),返回预测的用户对该物品(itemId)的评分值
            return numerator / denominator;
        }
    }

    到此这篇关于Springboot实现推荐系统的协同过滤算法的文章就介绍到这了,更多相关Springboot协同过滤算法内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

    0

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    关注公众号