最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

前言

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

基本原理 

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

相似性度量

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

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

算法分類(lèi) 

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

原理:先找到與目標(biāo)用戶興趣相似的其他用戶(稱(chēng)為 “鄰居用戶”),然后根據(jù)這些鄰居用戶對(duì)物品的偏好來(lái)預(yù)測(cè)目標(biāo)用戶對(duì)未見(jiàn)過(guò)(或未評(píng)價(jià))物品的偏好。例如,目標(biāo)用戶 A 沒(méi)有看過(guò)電影 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é)同過(guò)濾(Item - based Collaborative Filtering)

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

計(jì)算方法

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

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

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

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

2、余弦相似度 

首先,在協(xié)同過(guò)濾中會(huì)構(gòu)建一個(gè)用戶 - 物品評(píng)分矩陣。假設(shè)我們有個(gè)用戶和個(gè)物品,矩陣中的元素表示用戶對(duì)物品的評(píng)分。例如,在電影推薦系統(tǒng)中,行代表用戶,列代表電影,矩陣中的每個(gè)元素就是用戶對(duì)電影的評(píng)分(如果用戶沒(méi)有評(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é)同過(guò)濾算法不需要對(duì)物品的內(nèi)容(如電影的劇情、商品的描述等)進(jìn)行分析,只依賴用戶的行為數(shù)據(jù),因此可以適用于各種類(lèi)型的物品推薦。可以發(fā)現(xiàn)新的興趣點(diǎn):能夠發(fā)現(xiàn)用戶潛在的興趣點(diǎn),例如用戶可能沒(méi)有意識(shí)到自己會(huì)喜歡某個(gè)物品,但通過(guò)協(xié)同過(guò)濾算法的推薦,用戶可能會(huì)發(fā)現(xiàn)并喜歡上這個(gè)新的物品。

缺點(diǎn) 

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

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

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

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

首先我們需要數(shù)據(jù)庫(kù)中需要有一張表,比如說(shuō)評(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;
/**
 * 該類(lèi)提供基于用戶評(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ù)庫(kù)地址
    private static final String USER = "your_username"; // 數(shù)據(jù)庫(kù)用戶名
    private static final String PASSWORD = "your_password"; // 數(shù)據(jù)庫(kù)密碼
    // 程序入口
    public static void main(String[] args) {
        // 從數(shù)據(jù)庫(kù)中獲取評(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ù)庫(kù)中獲取評(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)分來(lái)計(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)用戶,則跳過(guò)本次循環(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)分過(guò)的物品,計(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)用戶沒(méi)有對(duì)該物品進(jìn)行過(guò)評(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)行排序
        // 這里通過(guò)比較Entry中的值(即加權(quán)評(píng)分)來(lái)確定順序,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>
     * 通過(guò)計(jì)算用戶評(píng)分向量的點(diǎn)積以及各自向量的模長(zhǎng),來(lái)得出兩個(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)分的物品來(lái)累加計(jì)算
        double dotProduct = 0.0;
        // 初始化用于存儲(chǔ)當(dāng)前用戶評(píng)分向量模長(zhǎng)平方的變量,初始值為0.0,后續(xù)會(huì)根據(jù)當(dāng)前用戶的評(píng)分來(lái)累加計(jì)算
        double userMagnitude = 0.0;
        // 初始化用于存儲(chǔ)其他用戶評(píng)分向量模長(zhǎng)平方的變量,初始值為0.0,后續(xù)會(huì)根據(jù)其他用戶的評(píng)分來(lái)累加計(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,
        // 說(shuō)明至少有一個(gè)用戶沒(méi)有對(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)平方開(kāi)方得到模長(zhǎng),再相乘)
        return dotProduct / (Math.sqrt(userMagnitude) * Math.sqrt(otherUserMagnitude));
    }
}
?

從數(shù)據(jù)庫(kù)獲取數(shù)據(jù)的方法可以根據(jù)自己的需求定義,這里只是方便演示就直接寫(xiě)了,代碼采用的計(jì)算余弦的方法來(lái)計(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é)同過(guò)濾推薦算法主方法。
     * 該方法根據(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)分物品來(lái)預(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ù)物品相似度等信息來(lái)計(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)用戶,則跳過(guò)本次循環(huán),不需要對(duì)自身已有的評(píng)分?jǐn)?shù)據(jù)進(jìn)行處理
            if (otherUserId == userId) continue;
            // 遍歷當(dāng)前其他用戶評(píng)分過(guò)的物品,檢查是否是目標(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)行排序,
        // 這里通過(guò)比較Entry中的值(即預(yù)測(cè)評(píng)分)來(lái)確定順序,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ì)算。
     * 該方法通過(guò)分析所有用戶對(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相同),則跳過(guò)本次相似度計(jì)算,因?yàn)樽陨砼c自身的相似度為1(無(wú)需計(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è)物品之間的相似度(采用余弦相似度)。
     * 通過(guò)分析兩個(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)分的用戶來(lái)累加計(jì)算
        double dotProduct = 0.0;
        // 初始化用于存儲(chǔ)第一個(gè)物品評(píng)分向量模長(zhǎng)平方的變量,初始值為0.0,后續(xù)會(huì)根據(jù)第一個(gè)物品的用戶評(píng)分來(lái)累加計(jì)算
        double item1Magnitude = 0.0;
        // 初始化用于存儲(chǔ)第二個(gè)物品評(píng)分向量模長(zhǎng)平方的變量,初始值為0.0,后續(xù)會(huì)根據(jù)第二個(gè)物品的用戶評(píng)分來(lái)累加計(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,
        // 說(shuō)明至少有一個(gè)物品沒(méi)有被任何用戶共同評(píng)分過(guò),此時(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)平方開(kāi)方得到模長(zhǎng),再相乘)
        return dotProduct / (Math.sqrt(item1Magnitude) * Math.sqrt(item2Magnitude));
    }
    /**
     * 預(yù)測(cè)用戶對(duì)某個(gè)未評(píng)分物品的評(píng)分。
     * 通過(guò)結(jié)合用戶已評(píng)分物品的評(píng)分、物品之間的相似度矩陣以及其他用戶對(duì)該未評(píng)分物品的評(píng)分等信息,
     * 按照特定的計(jì)算公式來(lái)預(yù)測(cè)目標(biāo)用戶對(duì)該未評(píng)分物品的評(píng)分值。
     *
     * @param userRatings 用戶已有的評(píng)分?jǐn)?shù)據(jù),用于結(jié)合物品相似度來(lái)預(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)分物品的相似度等信息來(lái)計(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,說(shuō)明沒(méi)有可參考的已評(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ò)濾算法的文章就介紹到這了,更多相關(guān)Springboot協(xié)同過(guò)濾算法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • mybatisFlex各種鏈?zhǔn)絪ql寫(xiě)法小結(jié)

    mybatisFlex各種鏈?zhǔn)絪ql寫(xiě)法小結(jié)

    本文主要介紹了mybatisFlex各種鏈?zhǔn)絪ql寫(xiě)法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2025-04-04
  • 淺析Java中內(nèi)部鎖和顯示鎖的使用

    淺析Java中內(nèi)部鎖和顯示鎖的使用

    在java中鎖主要兩類(lèi),內(nèi)部鎖synchronized和顯示鎖java.util.concurrent.locks.Lock,本文將來(lái)和大家聊聊這兩者的相關(guān)知識(shí),需要的可以參考一下
    2023-06-06
  • 簡(jiǎn)單了解JAVA構(gòu)造方法

    簡(jiǎn)單了解JAVA構(gòu)造方法

    構(gòu)造方法作用就是對(duì)類(lèi)進(jìn)行初始化。 如果你沒(méi)有定議任何構(gòu)造方法的形式,程式會(huì)為你取一個(gè)不帶任何參數(shù)的構(gòu)造函數(shù),那么你產(chǎn)生類(lèi)的對(duì)像時(shí)只能用不帶參數(shù)的方法.下面小編和大家來(lái)一起學(xué)習(xí)一下吧
    2019-06-06
  • Spring Security 單點(diǎn)登錄簡(jiǎn)單示例詳解

    Spring Security 單點(diǎn)登錄簡(jiǎn)單示例詳解

    這篇文章主要介紹了Spring Security 單點(diǎn)登錄簡(jiǎn)單示例詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-02-02
  • Spring?Security?Oauth2整合JWT的詳細(xì)步驟和核心配置

    Spring?Security?Oauth2整合JWT的詳細(xì)步驟和核心配置

    這篇文章主要介紹了Spring?Security?Oauth2整合JWT的詳細(xì)步驟和核心配置,包括依賴引入、JWT工具類(lèi)、OAuth2和資源服務(wù)器配置以及SpringSecurity配置,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-11-11
  • Maven install 報(bào)錯(cuò)

    Maven install 報(bào)錯(cuò)"程序包不存在"問(wèn)題的解決方法

    這篇文章主要介紹了Maven install 報(bào)錯(cuò)"程序包不存在"問(wèn)題的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • pom.xml中解決Provides?transitive?vulnerable?dependency?maven:org.yaml:snakeyaml:1.33警告問(wèn)題

    pom.xml中解決Provides?transitive?vulnerable?dependency?mave

    這篇文章主要介紹了在pom.xml中如何解決Provides?transitive?vulnerable?dependency?maven:org.yaml:snakeyaml:1.33警告問(wèn)題,需要的朋友可以參考下
    2023-06-06
  • Java線程編程中的主線程講解

    Java線程編程中的主線程講解

    這篇文章主要介紹了Java線程編程中的主線程,是Java入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下
    2015-09-09
  • Java正則表達(dá)式學(xué)習(xí)之分組與替換

    Java正則表達(dá)式學(xué)習(xí)之分組與替換

    這篇文章主要給大家介紹了關(guān)于Java正則表達(dá)式學(xué)習(xí)之分組與替換的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • Java代碼編譯和反編譯的那些事兒

    Java代碼編譯和反編譯的那些事兒

    這篇文章主要給大家介紹了關(guān)于Java代碼編譯和反編譯的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05

最新評(píng)論

湖口县| 田阳县| 渑池县| 松溪县| 靖宇县| 肇州县| 深圳市| 澄迈县| 柳江县| 舞阳县| 八宿县| 宣恩县| 蛟河市| 镇沅| 故城县| 达尔| 若尔盖县| 太白县| 通山县| 霍州市| 绿春县| 天门市| 高阳县| 蓬溪县| 鹤壁市| 吴忠市| 蕉岭县| 安徽省| 合川市| 永修县| 正宁县| 诏安县| 县级市| 容城县| 建宁县| 柘荣县| 漳浦县| 柳江县| 彝良县| 锡林郭勒盟| 沙洋县|