欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Springboot實(shí)現(xiàn)推薦系統(tǒng)的協(xié)同過濾算法

 更新時(shí)間:2025年05月08日 14:20:36   作者:ueanaIU瀟瀟子  
協(xié)同過濾算法是一種在推薦系統(tǒng)中廣泛使用的算法,用于預(yù)測(cè)用戶對(duì)物品(如商品、電影、音樂等)的偏好,從而實(shí)現(xiàn)個(gè)性化推薦,下面給大家介紹Springboot實(shí)現(xiàn)推薦系統(tǒng)的協(xié)同過濾算法,感興趣的朋友一起看看吧

前言

協(xié)同過濾算法(Collaborative Filtering)是一種在推薦系統(tǒng)中廣泛使用的算法,用于預(yù)測(cè)用戶對(duì)物品(如商品、電影、音樂等)的偏好,從而實(shí)現(xiàn)個(gè)性化推薦。下面是詳細(xì)介紹: 

基本原理 

基于用戶行為數(shù)據(jù):協(xié)同過濾算法的核心是利用用戶對(duì)物品的行為數(shù)據(jù),這些行為數(shù)據(jù)可以包括用戶的評(píng)分(如電影評(píng)分從 1 - 5 星)、購買記錄、瀏覽歷史、點(diǎn)贊 / 收藏等。通過分析這些數(shù)據(jù)來發(fā)現(xiàn)用戶之間的相似性或者物品之間的相似性。

相似性度量

用戶相似性:如果兩個(gè)用戶對(duì)很多相同物品的評(píng)價(jià)(或行為)相似,那么這兩個(gè)用戶就具有較高的相似性。例如,用戶 A 和用戶 B 都對(duì)電影 1 打了 4 星,對(duì)電影 2 打了 3 星,對(duì)電影 3 打了 5 星,通過計(jì)算這種相似性,我們可以認(rèn)為他們的觀影偏好相似。

物品相似性:如果很多用戶對(duì)兩個(gè)物品的評(píng)價(jià)(或行為)相似,那么這兩個(gè)物品就具有較高的相似性。比如,很多用戶既喜歡電影《泰坦尼克號(hào)》又喜歡電影《羅馬假日》,這兩部電影在用戶偏好上就具有一定的相似性。

算法分類 

基于用戶的協(xié)同過濾(User - based Collaborative Filtering)

原理:先找到與目標(biāo)用戶興趣相似的其他用戶(稱為 “鄰居用戶”),然后根據(jù)這些鄰居用戶對(duì)物品的偏好來預(yù)測(cè)目標(biāo)用戶對(duì)未見過(或未評(píng)價(jià))物品的偏好。例如,目標(biāo)用戶 A 沒有看過電影 M,但是與 A 相似的用戶 B、C 都對(duì)電影 M 評(píng)價(jià)很高,那么就可以推測(cè)用戶 A 也可能喜歡電影 M。

案例:有A、B、C三個(gè)用戶,已知A四種水果都喜歡,C喜歡的兩種水果是A喜歡的,那么可以認(rèn)為A和C是相似的,則可以把A喜歡的但不知道C是否喜歡的那兩種水果推薦給C.

基于物品的協(xié)同過濾(Item - based Collaborative Filtering)

原理:先計(jì)算物品之間的相似度,然后根據(jù)用戶已經(jīng)評(píng)價(jià)(或行為)過的物品與其他物品的相似度,來預(yù)測(cè)用戶對(duì)其他未評(píng)價(jià)(或未行為)物品的偏好。例如,用戶 A 喜歡電影《教父》,而電影《教父》和電影《美國往事》相似度很高,那么可以推測(cè)用戶 A 也可能喜歡電影《美國往事》。

計(jì)算方法

在協(xié)同過濾算法中,用于計(jì)算用戶或物品之間相似度的方法有很多種,下面我們將介紹兩種:

1、杰卡德相似系數(shù)(Jaccard Similarity Coefficient) 

杰卡德相似系數(shù)主要用于衡量有限集合之間的相似性。在協(xié)同過濾的場(chǎng)景下,通常是將用戶對(duì)物品的行為(如是否購買、是否喜歡等)看作集合元素。對(duì)于兩個(gè)用戶 A 和 B,

也就是說,杰卡德相似系數(shù)是兩個(gè)用戶共同感興趣的物品數(shù)量與他們感興趣的所有物品數(shù)量之比 

2、余弦相似度 

首先,在協(xié)同過濾中會(huì)構(gòu)建一個(gè)用戶 - 物品評(píng)分矩陣。假設(shè)我們有個(gè)用戶和個(gè)物品,矩陣中的元素表示用戶對(duì)物品的評(píng)分。例如,在電影推薦系統(tǒng)中,行代表用戶,列代表電影,矩陣中的每個(gè)元素就是用戶對(duì)電影的評(píng)分(如果用戶沒有評(píng)分可以用 0 或其他默認(rèn)值表示)。對(duì)于兩個(gè)用戶 A 和 B

優(yōu)點(diǎn) 

  • 個(gè)性化推薦:能夠根據(jù)用戶的歷史行為為每個(gè)用戶提供個(gè)性化的推薦,推薦的物品與用戶的興趣緊密相關(guān),提高了用戶發(fā)現(xiàn)感興趣物品的概率。
  • 不需要物品內(nèi)容信息:與基于內(nèi)容的推薦算法不同,協(xié)同過濾算法不需要對(duì)物品的內(nèi)容(如電影的劇情、商品的描述等)進(jìn)行分析,只依賴用戶的行為數(shù)據(jù),因此可以適用于各種類型的物品推薦。可以發(fā)現(xiàn)新的興趣點(diǎn):能夠發(fā)現(xiàn)用戶潛在的興趣點(diǎn),例如用戶可能沒有意識(shí)到自己會(huì)喜歡某個(gè)物品,但通過協(xié)同過濾算法的推薦,用戶可能會(huì)發(fā)現(xiàn)并喜歡上這個(gè)新的物品。

缺點(diǎn) 

  • 冷啟動(dòng)問題:
    • 用戶冷啟動(dòng):對(duì)于新用戶,由于沒有足夠的行為數(shù)據(jù),很難找到與其相似的用戶或者根據(jù)其行為來推薦物品。例如,一個(gè)新注冊(cè)的用戶還沒有對(duì)任何電影進(jìn)行評(píng)分,就很難為他推薦合適的電影。
    • 物品冷啟動(dòng):新加入的物品由于沒有用戶行為數(shù)據(jù),也很難被推薦出去。比如,一個(gè)新上映的電影,如果還沒有用戶對(duì)其進(jìn)行評(píng)分或其他行為,就很難出現(xiàn)在推薦列表中。
  • 數(shù)據(jù)稀疏問題:在實(shí)際應(yīng)用中,用戶對(duì)物品的行為數(shù)據(jù)往往是非常稀疏的。例如,一個(gè)電商平臺(tái)有大量的商品和用戶,但每個(gè)用戶可能只購買或?yàn)g覽了很少一部分商品,這就導(dǎo)致用戶 - 物品評(píng)分矩陣(或行為矩陣)中有大量的空白,影響了相似度計(jì)算的準(zhǔn)確性和推薦質(zhì)量。

應(yīng)用場(chǎng)景 

  • 電商平臺(tái)推薦:根據(jù)用戶的購買歷史、瀏覽記錄等推薦用戶可能感興趣的商品,如亞馬遜、淘寶等電商平臺(tái)都廣泛使用協(xié)同過濾算法來提高用戶的購買轉(zhuǎn)化率。
  • 視頻和音樂平臺(tái)推薦:像 Netflix(視頻平臺(tái))、Spotify(音樂平臺(tái))等,根據(jù)用戶的觀看 / 收聽歷史、評(píng)分等行為推薦新的視頻或音樂。
  • 社交平臺(tái)推薦:在社交網(wǎng)絡(luò)中推薦用戶可能感興趣的人、群組、內(nèi)容等,例如領(lǐng)英(LinkedIn)可能會(huì)根據(jù)用戶的職業(yè)興趣和關(guān)注的人來推薦新的人脈或行業(yè)內(nèi)容。

代碼實(shí)現(xiàn) 

首先我們需要數(shù)據(jù)庫中需要有一張表,比如說評(píng)分表score,表中的字段如下,

然后我們就可以使用下面的代碼從這張表中獲取數(shù)據(jù),

?
package com.bishe.utils.recommend;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * 該類提供基于用戶評(píng)分?jǐn)?shù)據(jù)生成推薦物品的相關(guān)功能。
 */
public class UserCF {
  private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database"; // 數(shù)據(jù)庫地址
    private static final String USER = "your_username"; // 數(shù)據(jù)庫用戶名
    private static final String PASSWORD = "your_password"; // 數(shù)據(jù)庫密碼
    // 程序入口
    public static void main(String[] args) {
        // 從數(shù)據(jù)庫中獲取評(píng)分?jǐn)?shù)據(jù)
        Map<Integer, Map<Integer, Integer>> ratings = fetchRatingsFromDatabase();
        int userId = 1; // 要推薦的用戶ID
        int numRecommendations = 2; // 推薦數(shù)量
        List<Integer> recommendations = recommend(ratings, userId, numRecommendations);
        System.out.println("為用戶 " + userId + " 推薦的物品: " + recommendations);
    }
    // 從數(shù)據(jù)庫中獲取評(píng)分?jǐn)?shù)據(jù)的方法
    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"; // 假設(shè)你的評(píng)分?jǐn)?shù)據(jù)在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;
    }
    /**
     * 推薦方法,根據(jù)用戶的評(píng)分?jǐn)?shù)據(jù)和用戶ID生成推薦物品。
     * <p>
     * 此方法基于用戶間的相似度以及評(píng)分?jǐn)?shù)據(jù),綜合計(jì)算出對(duì)指定用戶的推薦物品列表。
     * </p>
     *
     * @param ratings            包含所有用戶及其對(duì)應(yīng)評(píng)分?jǐn)?shù)據(jù)的映射。外層鍵為用戶ID,內(nèi)層鍵為物品ID,值為對(duì)應(yīng)評(píng)分。
     * @param userId             目標(biāo)用戶的ID,用于生成針對(duì)該用戶的推薦物品。
     * @param numRecommendations 期望生成的推薦物品數(shù)量,決定了最終返回推薦列表的長(zhǎng)度。
     * @return 返回一個(gè)包含推薦物品ID的列表,列表長(zhǎng)度由numRecommendations決定或者受限于可推薦物品數(shù)量(取較小值)。
     */
    public static List<Integer> recommend(Map<Integer, Map<Integer, Integer>> ratings, int userId, int numRecommendations) {
        // 創(chuàng)建一個(gè)Map,用于存儲(chǔ)每個(gè)物品的加權(quán)評(píng)分。鍵為物品ID,值為加權(quán)評(píng)分(初始化為0.0)。
        // 加權(quán)評(píng)分會(huì)綜合考慮其他用戶與目標(biāo)用戶的相似度以及他們對(duì)物品的評(píng)分來計(jì)算。
        Map<Integer, Double> weightedRatings = new HashMap<>();
        // 創(chuàng)建一個(gè)Map,用于存儲(chǔ)每個(gè)其他用戶與目標(biāo)用戶的相似度得分。鍵為其他用戶的ID,值為相似度得分(初始化為0.0)。
        Map<Integer, Double> similarityScores = new HashMap<>();
        // 計(jì)算與其他用戶的相似度,并基于相似度和其他用戶的評(píng)分計(jì)算物品的加權(quán)評(píng)分
        for (Map.Entry<Integer, Map<Integer, Integer>> entry : ratings.entrySet()) {
            // 獲取當(dāng)前遍歷到的其他用戶的ID
            int otherUserId = entry.getKey();
            // 如果當(dāng)前用戶就是目標(biāo)用戶,則跳過本次循環(huán),不需要計(jì)算與自身的相似度
            if (otherUserId == userId) continue;
            // 調(diào)用calculateSimilarity方法計(jì)算當(dāng)前其他用戶與目標(biāo)用戶的相似度,并將結(jié)果存入similarityScores Map中
            double similarity = calculateSimilarity(ratings.get(userId), entry.getValue());
            similarityScores.put(otherUserId, similarity);
            // 遍歷當(dāng)前其他用戶評(píng)分過的物品,計(jì)算這些物品的加權(quán)評(píng)分
            for (Map.Entry<Integer, Integer> item : entry.getValue().entrySet()) {
                // 獲取物品的ID
                int itemId = item.getKey();
                // 獲取當(dāng)前其他用戶對(duì)該物品的評(píng)分
                double rating = item.getValue();
                // 如果目標(biāo)用戶沒有對(duì)該物品進(jìn)行過評(píng)分(即ratings.get(userId).get(itemId)為null),
                // 則將該物品的加權(quán)評(píng)分加上當(dāng)前其他用戶對(duì)該物品的評(píng)分乘以相似度得分
                if (ratings.get(userId).get(itemId) == null) {
                    weightedRatings.put(itemId, weightedRatings.getOrDefault(itemId, 0.0) + rating * similarity);
                }
            }
        }
        // 將加權(quán)評(píng)分的Map轉(zhuǎn)換為L(zhǎng)ist<Map.Entry<Integer, Double>>,方便后續(xù)進(jìn)行排序操作
        // 每個(gè)元素代表一個(gè)物品的ID及其對(duì)應(yīng)的加權(quán)評(píng)分,以鍵值對(duì)形式存儲(chǔ)在Entry中
        List<Map.Entry<Integer, Double>> sortedRecommendations = new ArrayList<>(weightedRatings.entrySet());
        // 使用lambda表達(dá)式自定義排序規(guī)則,按照加權(quán)評(píng)分從高到低對(duì)推薦物品進(jìn)行排序
        // 這里通過比較Entry中的值(即加權(quán)評(píng)分)來確定順序,b.getValue().compareTo(a.getValue())表示按照降序排列
        sortedRecommendations.sort((a, b) -> b.getValue().compareTo(a.getValue()));
        // 創(chuàng)建一個(gè)列表,用于存儲(chǔ)最終要返回的推薦物品ID
        List<Integer> recommendedItems = new ArrayList<>();
        // 根據(jù)期望推薦的數(shù)量(取numRecommendations和實(shí)際可推薦物品數(shù)量的較小值),
        // 將排序后的推薦物品ID依次添加到recommendedItems列表中
        for (int i = 0; i < Math.min(numRecommendations, sortedRecommendations.size()); i++) {
            recommendedItems.add(sortedRecommendations.get(i).getKey());
        }
        return recommendedItems;
    }
    /**
     * 計(jì)算用戶和其他用戶之間相似度的方法。
     * <p>
     * 通過計(jì)算用戶評(píng)分向量的點(diǎn)積以及各自向量的模長(zhǎng),來得出兩個(gè)用戶之間的相似度得分。
     * 使用余弦相似度的計(jì)算方式,衡量用戶間在評(píng)分行為上的相似程度。
     * </p>
     *
     * @param userRatings      一個(gè)用戶的評(píng)分?jǐn)?shù)據(jù)映射,鍵為物品ID,值為對(duì)應(yīng)的評(píng)分。
     * @param otherUserRatings 另一個(gè)用戶的評(píng)分?jǐn)?shù)據(jù)映射,與userRatings結(jié)構(gòu)相同,用于對(duì)比計(jì)算相似度。
     * @return 返回一個(gè)雙精度浮點(diǎn)數(shù),表示兩個(gè)用戶之間的相似度得分,范圍在0到1之間(包含0和1),值越接近1表示相似度越高。
     */
    private static double calculateSimilarity(Map<Integer, Integer> userRatings, Map<Integer, Integer> otherUserRatings) {
        // 初始化用于存儲(chǔ)用戶評(píng)分向量點(diǎn)積的變量,初始值為0.0,后續(xù)會(huì)根據(jù)共同評(píng)分的物品來累加計(jì)算
        double dotProduct = 0.0;
        // 初始化用于存儲(chǔ)當(dāng)前用戶評(píng)分向量模長(zhǎng)平方的變量,初始值為0.0,后續(xù)會(huì)根據(jù)當(dāng)前用戶的評(píng)分來累加計(jì)算
        double userMagnitude = 0.0;
        // 初始化用于存儲(chǔ)其他用戶評(píng)分向量模長(zhǎng)平方的變量,初始值為0.0,后續(xù)會(huì)根據(jù)其他用戶的評(píng)分來累加計(jì)算
        double otherUserMagnitude = 0.0;
        // 遍歷當(dāng)前用戶的評(píng)分?jǐn)?shù)據(jù),計(jì)算與其他用戶評(píng)分?jǐn)?shù)據(jù)的點(diǎn)積以及各自向量的模長(zhǎng)平方
        for (Map.Entry<Integer, Integer> entry : userRatings.entrySet()) {
            // 獲取當(dāng)前評(píng)分?jǐn)?shù)據(jù)對(duì)應(yīng)的物品ID
            int itemId = entry.getKey();
            // 如果其他用戶也對(duì)該物品進(jìn)行了評(píng)分(即otherUserRatings中包含該物品ID),則進(jìn)行以下計(jì)算
            if (otherUserRatings.containsKey(itemId)) {
                // 計(jì)算點(diǎn)積,將當(dāng)前用戶對(duì)該物品的評(píng)分與其他用戶對(duì)該物品的評(píng)分相乘,并累加到dotProduct變量中
                dotProduct += entry.getValue() * otherUserRatings.get(itemId);
                // 計(jì)算當(dāng)前用戶評(píng)分向量模長(zhǎng)的平方,將當(dāng)前用戶對(duì)該物品的評(píng)分進(jìn)行平方,并累加到userMagnitude變量中
                userMagnitude += Math.pow(entry.getValue(), 2);
                // 計(jì)算其他用戶評(píng)分向量模長(zhǎng)的平方,將其他用戶對(duì)該物品的評(píng)分進(jìn)行平方,并累加到otherUserMagnitude變量中
                otherUserMagnitude += Math.pow(otherUserRatings.get(itemId), 2);
            }
        }
        // 如果當(dāng)前用戶評(píng)分向量模長(zhǎng)的平方為0或者其他用戶評(píng)分向量模長(zhǎng)的平方為0,
        // 說明至少有一個(gè)用戶沒有對(duì)任何共同物品進(jìn)行評(píng)分,此時(shí)相似度為0,直接返回0(防止后續(xù)除法運(yùn)算出現(xiàn)除以零的錯(cuò)誤)
        if (userMagnitude == 0 || otherUserMagnitude == 0) {
            return 0;
        }
        // 根據(jù)余弦相似度的計(jì)算公式,返回兩個(gè)用戶之間的相似度得分。
        // 即點(diǎn)積除以兩個(gè)用戶評(píng)分向量模長(zhǎng)的乘積(先分別對(duì)模長(zhǎng)平方開方得到模長(zhǎng),再相乘)
        return dotProduct / (Math.sqrt(userMagnitude) * Math.sqrt(otherUserMagnitude));
    }
}
?

從數(shù)據(jù)庫獲取數(shù)據(jù)的方法可以根據(jù)自己的需求定義,這里只是方便演示就直接寫了,代碼采用的計(jì)算余弦的方法來計(jì)算用戶相似度。

如果想直接看效果的我們可以把上面代碼中的main方法替換為下面的代碼

 /**
     * main方法,用于測(cè)試推薦功能。
     * @param args
     */
    public static void main(String[] args) {
        // 創(chuàng)建一個(gè)包含所有用戶評(píng)分?jǐn)?shù)據(jù)的映射
        Map<Integer, Map<Integer, Integer>> ratings = new HashMap<>();
        // 添加一些模擬數(shù)據(jù)
        // 用戶1對(duì)物品1評(píng)分5分,對(duì)物品2評(píng)分3分
        ratings.put(1, new HashMap<Integer, Integer>() {{
            put(1, 5);
            put(2, 3);
        }});
        // 用戶2對(duì)物品1評(píng)分4分,對(duì)物品3評(píng)分2分
        ratings.put(2, new HashMap<Integer, Integer>() {{
            put(1, 4);
            put(3, 2);
        }});
        // 用戶3對(duì)物品2評(píng)分4分,對(duì)物品3評(píng)分5分
        ratings.put(3, new HashMap<Integer, Integer>() {{
            put(2, 4);
            put(3, 5);
        }});
        // 用戶4對(duì)物品1評(píng)分3分,對(duì)物品4評(píng)分2分
        ratings.put(4, new HashMap<Integer, Integer>() {{
            put(1, 3);
            put(4, 2);
        }});
        // 目標(biāo)用戶ID
        int userId = 1;
        // 期望生成的推薦物品數(shù)量
        int numRecommendations = 2;
        // 調(diào)用recommend方法生成推薦物品列表
        List<Integer> recommendedItems = recommend(ratings, userId, numRecommendations);
        // 打印推薦結(jié)果
        System.out.println("推薦給用戶" + userId + "的物品ID列表: " + recommendedItems);
    }

運(yùn)行后我們可以看到控制臺(tái)打印出的信息

這樣就代表功能已經(jīng)實(shí)現(xiàn)了。

下面是基于余弦計(jì)算物品之間相似度的方法,實(shí)現(xiàn)和計(jì)算用戶的差不多,感興趣的可以看一下

package com.bishe.utils.recommend;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ItemCF {
    /**
     * 基于物品相似度的協(xié)同過濾推薦算法主方法。
     * 該方法根據(jù)用戶對(duì)物品的評(píng)分?jǐn)?shù)據(jù),針對(duì)指定用戶生成推薦物品列表。
     *
     * @param ratings 用戶評(píng)分?jǐn)?shù)據(jù),外層鍵為用戶ID,內(nèi)層鍵為物品ID,值為對(duì)應(yīng)評(píng)分。
     * @param userId  目標(biāo)用戶的ID,用于生成針對(duì)該用戶的推薦列表。
     * @param numRecommendations 期望生成的推薦物品數(shù)量,決定了最終返回推薦列表的長(zhǎng)度。
     * @return 返回一個(gè)包含推薦物品ID的列表,列表長(zhǎng)度由numRecommendations決定或者受限于可推薦物品數(shù)量(取較小值)。
     */
    public static List<Integer> recommend(Map<Integer, Map<Integer, Integer>> ratings, int userId, int numRecommendations) {
        // 調(diào)用calculateItemSimilarities方法計(jì)算物品之間的相似度矩陣,
        // 這個(gè)矩陣將用于后續(xù)預(yù)測(cè)用戶對(duì)未評(píng)分物品的評(píng)分,以生成推薦列表
        Map<Integer, Map<Integer, Double>> itemSimilarities = calculateItemSimilarities(ratings);
        // 獲取目標(biāo)用戶(由userId指定)已評(píng)分的物品列表,
        // 以便后續(xù)基于這些已評(píng)分物品來預(yù)測(cè)對(duì)其他未評(píng)分物品的評(píng)分
        Map<Integer, Integer> userRatings = ratings.get(userId);
        List<Integer> ratedItems = new ArrayList<>(userRatings.keySet());
        // 用于存儲(chǔ)預(yù)測(cè)的用戶對(duì)未評(píng)分物品的評(píng)分,
        // 鍵為物品ID,值為預(yù)測(cè)的評(píng)分,后續(xù)會(huì)根據(jù)物品相似度等信息來計(jì)算這些評(píng)分
        Map<Integer, Double> predictedRatings = new HashMap<>();
        // 遍歷所有用戶及其評(píng)分?jǐn)?shù)據(jù),目的是針對(duì)目標(biāo)用戶未評(píng)分的物品進(jìn)行評(píng)分預(yù)測(cè)
        for (Map.Entry<Integer, Map<Integer, Integer>> entry : ratings.entrySet()) {
            // 獲取當(dāng)前遍歷到的其他用戶的評(píng)分?jǐn)?shù)據(jù)
            Map<Integer, Integer> otherUserRatings = entry.getValue();
            // 獲取當(dāng)前遍歷到的其他用戶的ID
            int otherUserId = entry.getKey();
            // 如果當(dāng)前遍歷到的用戶就是目標(biāo)用戶,則跳過本次循環(huán),不需要對(duì)自身已有的評(píng)分?jǐn)?shù)據(jù)進(jìn)行處理
            if (otherUserId == userId) continue;
            // 遍歷當(dāng)前其他用戶評(píng)分過的物品,檢查是否是目標(biāo)用戶未評(píng)分的物品,若是則進(jìn)行評(píng)分預(yù)測(cè)
            for (Integer itemId : otherUserRatings.keySet()) {
                if (!ratedItems.contains(itemId)) {
                    // 調(diào)用predictRating方法預(yù)測(cè)目標(biāo)用戶對(duì)當(dāng)前未評(píng)分物品(itemId)的評(píng)分,
                    // 并將預(yù)測(cè)評(píng)分存入predictedRatings這個(gè)Map中
                    double predictedRating = predictRating(userRatings, itemSimilarities, itemId, otherUserRatings);
                    predictedRatings.put(itemId, predictedRating);
                }
            }
        }
        // 將預(yù)測(cè)評(píng)分的Map轉(zhuǎn)換為L(zhǎng)ist<Map.Entry<Integer, Double>>形式,方便后續(xù)進(jìn)行排序操作,
        // 每個(gè)元素代表一個(gè)物品的ID及其對(duì)應(yīng)的預(yù)測(cè)評(píng)分,以鍵值對(duì)形式存儲(chǔ)在Entry中
        List<Map.Entry<Integer, Double>> sortedPredictions = new ArrayList<>(predictedRatings.entrySet());
        // 使用lambda表達(dá)式自定義排序規(guī)則,按照預(yù)測(cè)評(píng)分從高到低對(duì)物品進(jìn)行排序,
        // 這里通過比較Entry中的值(即預(yù)測(cè)評(píng)分)來確定順序,b.getValue().compareTo(a.getValue())表示按照降序排列
        sortedPredictions.sort((a, b) -> b.getValue().compareTo(a.getValue()));
        // 創(chuàng)建一個(gè)列表,用于存儲(chǔ)最終要返回的推薦物品ID
        List<Integer> recommendedItems = new ArrayList<>();
        // 根據(jù)期望推薦的數(shù)量(取numRecommendations和實(shí)際可推薦物品數(shù)量的較小值),
        // 將排序后的推薦物品ID依次添加到recommendedItems列表中
        for (int i = 0; i < Math.min(numRecommendations, sortedPredictions.size()); i++) {
            recommendedItems.add(sortedPredictions.get(i).getKey());
        }
        return recommendedItems;
    }
    /**
     * 計(jì)算物品之間相似度的方法,采用余弦相似度計(jì)算。
     * 該方法通過分析所有用戶對(duì)物品的評(píng)分?jǐn)?shù)據(jù),構(gòu)建出物品之間的相似度矩陣,
     * 矩陣中記錄了每對(duì)物品之間的相似度得分,反映了物品在用戶評(píng)分行為上的相似程度。
     *
     * @param ratings 用戶評(píng)分?jǐn)?shù)據(jù),用于分析物品之間的相似程度。
     * @return 返回一個(gè)表示物品相似度的矩陣,外層鍵為物品ID,內(nèi)層鍵為與之對(duì)比的物品ID,值為相似度得分。
     */
    private static Map<Integer, Map<Integer, Double>> calculateItemSimilarities(Map<Integer, Map<Integer, Integer>> ratings) {
        // 創(chuàng)建一個(gè)嵌套的Map結(jié)構(gòu),用于存儲(chǔ)物品之間的相似度矩陣,
        // 外層鍵表示一個(gè)物品的ID,內(nèi)層鍵表示與之對(duì)比的另一個(gè)物品的ID,值為它們之間的相似度得分
        Map<Integer, Map<Integer, Double>> itemSimilarities = new HashMap<>();
        // 遍歷所有物品,外層循環(huán)每次確定一個(gè)物品(記為item1),用于與其他物品計(jì)算相似度
        for (Map.Entry<Integer, Map<Integer, Integer>> item1Entry : ratings.entrySet()) {
            // 獲取當(dāng)前遍歷到的物品(item1)的ID
            int item1Id = item1Entry.getKey();
            // 獲取當(dāng)前遍歷到的物品(item1)的用戶評(píng)分?jǐn)?shù)據(jù)
            Map<Integer, Integer> item1Ratings = item1Entry.getValue();
            // 創(chuàng)建一個(gè)Map,用于存儲(chǔ)當(dāng)前物品(item1)與其他物品的相似度得分,
            // 鍵為其他物品的ID,值為對(duì)應(yīng)的相似度得分,后續(xù)會(huì)在循環(huán)中填充這個(gè)Map
            Map<Integer, Double> similaritiesForItem1 = new HashMap<>();
            // 內(nèi)層循環(huán)再次遍歷所有物品(記為item2),計(jì)算item1與每個(gè)item2之間的相似度
            for (Map.Entry<Integer, Map<Integer, Integer>> item2Entry : ratings.entrySet()) {
                // 獲取當(dāng)前內(nèi)層循環(huán)遍歷到的物品(item2)的ID
                int item2Id = item2Entry.getKey();
                // 獲取當(dāng)前內(nèi)層循環(huán)遍歷到的物品(item2)的用戶評(píng)分?jǐn)?shù)據(jù)
                Map<Integer, Integer> item2Ratings = item2Entry.getValue();
                // 如果兩個(gè)物品是同一個(gè)物品(即ID相同),則跳過本次相似度計(jì)算,因?yàn)樽陨砼c自身的相似度為1(無需計(jì)算)
                if (item1Id == item2Id) continue;
                // 調(diào)用calculateItemSimilarity方法計(jì)算當(dāng)前兩個(gè)物品(item1和item2)之間的相似度,
                // 并將計(jì)算得到的相似度得分存入similaritiesForItem1這個(gè)Map中
                double similarity = calculateItemSimilarity(item1Ratings, item2Ratings);
                similaritiesForItem1.put(item2Id, similarity);
            }
            // 將當(dāng)前物品(item1)與其他物品的相似度得分Map存入itemSimilarities這個(gè)總的相似度矩陣中,
            // 外層鍵為item1的ID,即完成了針對(duì)item1與其他所有物品相似度的計(jì)算和存儲(chǔ)
            itemSimilarities.put(item1Id, similaritiesForItem1);
        }
        return itemSimilarities;
    }
    /**
     * 計(jì)算兩個(gè)物品之間的相似度(采用余弦相似度)。
     * 通過分析兩個(gè)物品各自對(duì)應(yīng)的用戶評(píng)分?jǐn)?shù)據(jù),按照余弦相似度的計(jì)算公式,
     * 得出反映它們?cè)谟脩粼u(píng)分行為上相似程度的得分,范圍在0到1之間(包含0和1),越接近1表示相似度越高。
     *
     * @param item1Ratings 第一個(gè)物品的用戶評(píng)分?jǐn)?shù)據(jù),鍵為用戶ID,值為對(duì)應(yīng)評(píng)分。
     * @param item2Ratings 第二個(gè)物品的用戶評(píng)分?jǐn)?shù)據(jù),結(jié)構(gòu)與item1Ratings相同,用于對(duì)比計(jì)算相似度。
     * @return 返回一個(gè)雙精度浮點(diǎn)數(shù),表示兩個(gè)物品之間的相似度得分,范圍在0到1之間(包含0和1),值越接近1表示相似度越高。
     */
    private static double calculateItemSimilarity(Map<Integer, Integer> item1Ratings, Map<Integer, Integer> item2Ratings) {
        // 初始化用于存儲(chǔ)兩個(gè)物品評(píng)分向量點(diǎn)積的變量,初始值為0.0,后續(xù)會(huì)根據(jù)共同評(píng)分的用戶來累加計(jì)算
        double dotProduct = 0.0;
        // 初始化用于存儲(chǔ)第一個(gè)物品評(píng)分向量模長(zhǎng)平方的變量,初始值為0.0,后續(xù)會(huì)根據(jù)第一個(gè)物品的用戶評(píng)分來累加計(jì)算
        double item1Magnitude = 0.0;
        // 初始化用于存儲(chǔ)第二個(gè)物品評(píng)分向量模長(zhǎng)平方的變量,初始值為0.0,后續(xù)會(huì)根據(jù)第二個(gè)物品的用戶評(píng)分來累加計(jì)算
        double item2Magnitude = 0.0;
        // 遍歷第一個(gè)物品的用戶評(píng)分?jǐn)?shù)據(jù),目的是找出與第二個(gè)物品共同評(píng)分的用戶,并基于這些用戶的評(píng)分計(jì)算相關(guān)數(shù)值
        for (Map.Entry<Integer, Integer> entry : item1Ratings.entrySet()) {
            // 獲取當(dāng)前評(píng)分?jǐn)?shù)據(jù)對(duì)應(yīng)的用戶ID
            int userId = entry.getKey();
            // 如果第二個(gè)物品的用戶評(píng)分?jǐn)?shù)據(jù)中也包含當(dāng)前用戶(即共同評(píng)分的用戶),則進(jìn)行以下計(jì)算
            if (item2Ratings.containsKey(userId)) {
                // 計(jì)算點(diǎn)積,將第一個(gè)物品當(dāng)前用戶的評(píng)分與第二個(gè)物品該用戶的評(píng)分相乘,并累加到dotProduct變量中
                dotProduct += entry.getValue() * item2Ratings.get(userId);
                // 計(jì)算第一個(gè)物品評(píng)分向量模長(zhǎng)的平方,將第一個(gè)物品當(dāng)前用戶的評(píng)分進(jìn)行平方,并累加到item1Magnitude變量中
                item1Magnitude += Math.pow(entry.getValue(), 2);
                // 計(jì)算第二個(gè)物品評(píng)分向量模長(zhǎng)的平方,將第二個(gè)物品當(dāng)前用戶的評(píng)分進(jìn)行平方,并累加到item2Magnitude變量中
                item2Magnitude += Math.pow(item2Ratings.get(userId), 2);
            }
        }
        // 如果第一個(gè)物品評(píng)分向量模長(zhǎng)的平方為0或者第二個(gè)物品評(píng)分向量模長(zhǎng)的平方為0,
        // 說明至少有一個(gè)物品沒有被任何用戶共同評(píng)分過,此時(shí)相似度為0,直接返回0(防止后續(xù)除法運(yùn)算出現(xiàn)除以零的錯(cuò)誤)
        if (item1Magnitude == 0 || item2Magnitude == 0) {
            return 0;
        }
        // 根據(jù)余弦相似度的計(jì)算公式,返回兩個(gè)物品之間的相似度得分。
        // 即點(diǎn)積除以兩個(gè)物品評(píng)分向量模長(zhǎng)的乘積(先分別對(duì)模長(zhǎng)平方開方得到模長(zhǎng),再相乘)
        return dotProduct / (Math.sqrt(item1Magnitude) * Math.sqrt(item2Magnitude));
    }
    /**
     * 預(yù)測(cè)用戶對(duì)某個(gè)未評(píng)分物品的評(píng)分。
     * 通過結(jié)合用戶已評(píng)分物品的評(píng)分、物品之間的相似度矩陣以及其他用戶對(duì)該未評(píng)分物品的評(píng)分等信息,
     * 按照特定的計(jì)算公式來預(yù)測(cè)目標(biāo)用戶對(duì)該未評(píng)分物品的評(píng)分值。
     *
     * @param userRatings 用戶已有的評(píng)分?jǐn)?shù)據(jù),用于結(jié)合物品相似度來預(yù)測(cè)評(píng)分。
     * @param itemSimilarities 物品相似度矩陣,提供物品間的相似程度信息。
     * @param itemId 待預(yù)測(cè)評(píng)分的物品ID。
     * @param otherUserRatings 其他用戶的評(píng)分?jǐn)?shù)據(jù),用于參考計(jì)算預(yù)測(cè)評(píng)分。
     * @return 返回預(yù)測(cè)的用戶對(duì)該物品的評(píng)分值,為雙精度浮點(diǎn)數(shù)。
     */
    private static double predictRating(Map<Integer, Integer> userRatings,
                                        Map<Integer, Map<Integer, Double>> itemSimilarities,
                                        int itemId,
                                        Map<Integer, Integer> otherUserRatings) {
        // 初始化分子變量,用于存儲(chǔ)預(yù)測(cè)評(píng)分計(jì)算公式中的分子部分的值,初始值為0.0,后續(xù)會(huì)根據(jù)已評(píng)分物品的相關(guān)信息累加計(jì)算
        double numerator = 0.0;
        // 初始化分母變量,用于存儲(chǔ)預(yù)測(cè)評(píng)分計(jì)算公式中的分母部分的值,初始值為0.0,后續(xù)會(huì)根據(jù)物品相似度等信息累加計(jì)算
        double denominator = 0.0;
        // 遍歷用戶已評(píng)分的物品,目的是結(jié)合這些已評(píng)分物品與待預(yù)測(cè)評(píng)分物品的相似度等信息來計(jì)算預(yù)測(cè)評(píng)分
        for (Map.Entry<Integer, Integer> ratingEntry : userRatings.entrySet()) {
            // 獲取當(dāng)前已評(píng)分物品的ID
            int ratedItemId = ratingEntry.getKey();
            // 獲取當(dāng)前已評(píng)分物品的評(píng)分
            double rating = ratingEntry.getValue();
            // 從物品相似度矩陣中獲取當(dāng)前已評(píng)分物品與待預(yù)測(cè)評(píng)分物品(itemId)之間的相似度
            double similarity = itemSimilarities.get(ratedItemId).get(itemId);
            // 將已評(píng)分物品的評(píng)分乘以相似度累加到分子變量中,按照預(yù)測(cè)評(píng)分的計(jì)算公式進(jìn)行分子部分的累加
            numerator += rating * similarity;
            // 將相似度的絕對(duì)值累加到分母變量中,按照預(yù)測(cè)評(píng)分的計(jì)算公式進(jìn)行分母部分的累加
            denominator += Math.abs(similarity);
        }
        // 如果分母變量的值為0,說明沒有可參考的已評(píng)分物品與待預(yù)測(cè)物品有相似度關(guān)系,此時(shí)返回0作為預(yù)測(cè)評(píng)分
        if (denominator == 0) {
            return 0;
        }
        // 根據(jù)預(yù)測(cè)評(píng)分的計(jì)算公式(分子除以分母),返回預(yù)測(cè)的用戶對(duì)該物品(itemId)的評(píng)分值
        return numerator / denominator;
    }
}

到此這篇關(guān)于Springboot實(shí)現(xiàn)推薦系統(tǒng)的協(xié)同過濾算法的文章就介紹到這了,更多相關(guān)Springboot協(xié)同過濾算法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解Java類加載機(jī)制中的雙親委派模型

    詳解Java類加載機(jī)制中的雙親委派模型

    Java的雙親委派模型是一種類加載機(jī)制,它用于保證Java類的安全性和穩(wěn)定性,在這個(gè)模型中,當(dāng)一個(gè)類需要被加載時(shí),Java虛擬機(jī)會(huì)先檢查自己是否已經(jīng)加載了該類,本文就給大家講解一下Java類加載機(jī)制中的雙親委派模型,需要的朋友可以參考下
    2023-09-09
  • 解決Mybatis在IDEA中找不到mapper映射文件的問題

    解決Mybatis在IDEA中找不到mapper映射文件的問題

    這篇文章主要介紹了解決Mybatis在IDEA中找不到mapper映射文件的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-10-10
  • java利用時(shí)間格式生成唯一文件名的方法

    java利用時(shí)間格式生成唯一文件名的方法

    這篇文章主要介紹了java利用時(shí)間格式生成唯一文件名的方法,需要的朋友可以參考下
    2017-01-01
  • java為什么不建議用equals判斷對(duì)象相等

    java為什么不建議用equals判斷對(duì)象相等

    本文主要介紹了java為什么不建議用equals判斷對(duì)象相等,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • 解決idea web 配置相對(duì)路徑問題

    解決idea web 配置相對(duì)路徑問題

    這篇文章主要介紹了idea web 配置相對(duì)路徑問題的解決方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-06-06
  • SpringBoot3中token攔截器鏈的設(shè)計(jì)與實(shí)現(xiàn)步驟

    SpringBoot3中token攔截器鏈的設(shè)計(jì)與實(shí)現(xiàn)步驟

    本文介紹了spring boot后端服務(wù)開發(fā)中有關(guān)如何設(shè)計(jì)攔截器的思路,文中通過代碼示例和圖文講解的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下
    2024-03-03
  • Mybatis查詢記錄條數(shù)的實(shí)例代碼

    Mybatis查詢記錄條數(shù)的實(shí)例代碼

    這篇文章主要介紹了Mybatis查詢記錄條數(shù)的實(shí)例代碼,需要的朋友可以參考下
    2017-08-08
  • java短網(wǎng)址服務(wù)(TinyURL)生成算法

    java短網(wǎng)址服務(wù)(TinyURL)生成算法

    這篇文章主要為大家詳細(xì)介紹了java短網(wǎng)址服務(wù)生成算法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-08-08
  • java實(shí)現(xiàn)微信支付結(jié)果通知

    java實(shí)現(xiàn)微信支付結(jié)果通知

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)微信支付結(jié)果通知,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-01-01
  • Java使用HttpClient詳細(xì)示例

    Java使用HttpClient詳細(xì)示例

    這篇文章介紹了Java使用HttpClient的詳細(xì)示例,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-12-12

最新評(píng)論