Springboot實現(xiàn)推薦系統(tǒng)的協(xié)同過濾算法
前言
協(xié)同過濾算法(Collaborative Filtering)是一種在推薦系統(tǒng)中廣泛使用的算法,用于預測用戶對物品(如商品、電影、音樂等)的偏好,從而實現(xiàn)個性化推薦。下面是詳細介紹:
基本原理
基于用戶行為數(shù)據(jù):協(xié)同過濾算法的核心是利用用戶對物品的行為數(shù)據(jù),這些行為數(shù)據(jù)可以包括用戶的評分(如電影評分從 1 - 5 星)、購買記錄、瀏覽歷史、點贊 / 收藏等。通過分析這些數(shù)據(jù)來發(fā)現(xiàn)用戶之間的相似性或者物品之間的相似性。
相似性度量:
用戶相似性:如果兩個用戶對很多相同物品的評價(或行為)相似,那么這兩個用戶就具有較高的相似性。例如,用戶 A 和用戶 B 都對電影 1 打了 4 星,對電影 2 打了 3 星,對電影 3 打了 5 星,通過計算這種相似性,我們可以認為他們的觀影偏好相似。
物品相似性:如果很多用戶對兩個物品的評價(或行為)相似,那么這兩個物品就具有較高的相似性。比如,很多用戶既喜歡電影《泰坦尼克號》又喜歡電影《羅馬假日》,這兩部電影在用戶偏好上就具有一定的相似性。
算法分類
基于用戶的協(xié)同過濾(User - based Collaborative Filtering)
原理:先找到與目標用戶興趣相似的其他用戶(稱為 “鄰居用戶”),然后根據(jù)這些鄰居用戶對物品的偏好來預測目標用戶對未見過(或未評價)物品的偏好。例如,目標用戶 A 沒有看過電影 M,但是與 A 相似的用戶 B、C 都對電影 M 評價很高,那么就可以推測用戶 A 也可能喜歡電影 M。
案例:有A、B、C三個用戶,已知A四種水果都喜歡,C喜歡的兩種水果是A喜歡的,那么可以認為A和C是相似的,則可以把A喜歡的但不知道C是否喜歡的那兩種水果推薦給C.
基于物品的協(xié)同過濾(Item - based Collaborative Filtering)
原理:先計算物品之間的相似度,然后根據(jù)用戶已經(jīng)評價(或行為)過的物品與其他物品的相似度,來預測用戶對其他未評價(或未行為)物品的偏好。例如,用戶 A 喜歡電影《教父》,而電影《教父》和電影《美國往事》相似度很高,那么可以推測用戶 A 也可能喜歡電影《美國往事》。
計算方法
在協(xié)同過濾算法中,用于計算用戶或物品之間相似度的方法有很多種,下面我們將介紹兩種:
1、杰卡德相似系數(shù)(Jaccard Similarity Coefficient)
杰卡德相似系數(shù)主要用于衡量有限集合之間的相似性。在協(xié)同過濾的場景下,通常是將用戶對物品的行為(如是否購買、是否喜歡等)看作集合元素。對于兩個用戶 A 和 B,
也就是說,杰卡德相似系數(shù)是兩個用戶共同感興趣的物品數(shù)量與他們感興趣的所有物品數(shù)量之比
2、余弦相似度
首先,在協(xié)同過濾中會構建一個用戶 - 物品評分矩陣。假設我們有個用戶和個物品,矩陣中的元素表示用戶對物品的評分。例如,在電影推薦系統(tǒng)中,行代表用戶,列代表電影,矩陣中的每個元素就是用戶對電影的評分(如果用戶沒有評分可以用 0 或其他默認值表示)。對于兩個用戶 A 和 B
優(yōu)點
- 個性化推薦:能夠根據(jù)用戶的歷史行為為每個用戶提供個性化的推薦,推薦的物品與用戶的興趣緊密相關,提高了用戶發(fā)現(xiàn)感興趣物品的概率。
- 不需要物品內(nèi)容信息:與基于內(nèi)容的推薦算法不同,協(xié)同過濾算法不需要對物品的內(nèi)容(如電影的劇情、商品的描述等)進行分析,只依賴用戶的行為數(shù)據(jù),因此可以適用于各種類型的物品推薦。可以發(fā)現(xiàn)新的興趣點:能夠發(fā)現(xiàn)用戶潛在的興趣點,例如用戶可能沒有意識到自己會喜歡某個物品,但通過協(xié)同過濾算法的推薦,用戶可能會發(fā)現(xiàn)并喜歡上這個新的物品。
缺點
- 冷啟動問題:
- 用戶冷啟動:對于新用戶,由于沒有足夠的行為數(shù)據(jù),很難找到與其相似的用戶或者根據(jù)其行為來推薦物品。例如,一個新注冊的用戶還沒有對任何電影進行評分,就很難為他推薦合適的電影。
- 物品冷啟動:新加入的物品由于沒有用戶行為數(shù)據(jù),也很難被推薦出去。比如,一個新上映的電影,如果還沒有用戶對其進行評分或其他行為,就很難出現(xiàn)在推薦列表中。
- 數(shù)據(jù)稀疏問題:在實際應用中,用戶對物品的行為數(shù)據(jù)往往是非常稀疏的。例如,一個電商平臺有大量的商品和用戶,但每個用戶可能只購買或瀏覽了很少一部分商品,這就導致用戶 - 物品評分矩陣(或行為矩陣)中有大量的空白,影響了相似度計算的準確性和推薦質(zhì)量。
應用場景
- 電商平臺推薦:根據(jù)用戶的購買歷史、瀏覽記錄等推薦用戶可能感興趣的商品,如亞馬遜、淘寶等電商平臺都廣泛使用協(xié)同過濾算法來提高用戶的購買轉(zhuǎn)化率。
- 視頻和音樂平臺推薦:像 Netflix(視頻平臺)、Spotify(音樂平臺)等,根據(jù)用戶的觀看 / 收聽歷史、評分等行為推薦新的視頻或音樂。
- 社交平臺推薦:在社交網(wǎng)絡中推薦用戶可能感興趣的人、群組、內(nèi)容等,例如領英(LinkedIn)可能會根據(jù)用戶的職業(yè)興趣和關注的人來推薦新的人脈或行業(yè)內(nèi)容。
代碼實現(xiàn)
首先我們需要數(shù)據(jù)庫中需要有一張表,比如說評分表score,表中的字段如下,
然后我們就可以使用下面的代碼從這張表中獲取數(shù)據(jù),
? package com.bishe.utils.recommend; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 該類提供基于用戶評分數(shù)據(jù)生成推薦物品的相關功能。 */ 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ù)庫中獲取評分數(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ù)庫中獲取評分數(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ù)據(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ù)用戶的評分數(shù)據(jù)和用戶ID生成推薦物品。 * <p> * 此方法基于用戶間的相似度以及評分數(shù)據(jù),綜合計算出對指定用戶的推薦物品列表。 * </p> * * @param ratings 包含所有用戶及其對應評分數(shù)據(jù)的映射。外層鍵為用戶ID,內(nèi)層鍵為物品ID,值為對應評分。 * @param userId 目標用戶的ID,用于生成針對該用戶的推薦物品。 * @param numRecommendations 期望生成的推薦物品數(shù)量,決定了最終返回推薦列表的長度。 * @return 返回一個包含推薦物品ID的列表,列表長度由numRecommendations決定或者受限于可推薦物品數(shù)量(取較小值)。 */ public static List<Integer> recommend(Map<Integer, Map<Integer, Integer>> ratings, int userId, int numRecommendations) { // 創(chuàng)建一個Map,用于存儲每個物品的加權評分。鍵為物品ID,值為加權評分(初始化為0.0)。 // 加權評分會綜合考慮其他用戶與目標用戶的相似度以及他們對物品的評分來計算。 Map<Integer, Double> weightedRatings = new HashMap<>(); // 創(chuàng)建一個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(); // 如果當前用戶就是目標用戶,則跳過本次循環(huán),不需要計算與自身的相似度 if (otherUserId == userId) continue; // 調(diào)用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轉(zhuǎn)換為List<Map.Entry<Integer, Double>>,方便后續(xù)進行排序操作 // 每個元素代表一個物品的ID及其對應的加權評分,以鍵值對形式存儲在Entry中 List<Map.Entry<Integer, Double>> sortedRecommendations = new ArrayList<>(weightedRatings.entrySet()); // 使用lambda表達式自定義排序規(guī)則,按照加權評分從高到低對推薦物品進行排序 // 這里通過比較Entry中的值(即加權評分)來確定順序,b.getValue().compareTo(a.getValue())表示按照降序排列 sortedRecommendations.sort((a, b) -> b.getValue().compareTo(a.getValue())); // 創(chuàng)建一個列表,用于存儲最終要返回的推薦物品ID List<Integer> recommendedItems = new ArrayList<>(); // 根據(jù)期望推薦的數(shù)量(取numRecommendations和實際可推薦物品數(shù)量的較小值), // 將排序后的推薦物品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 一個用戶的評分數(shù)據(jù)映射,鍵為物品ID,值為對應的評分。 * @param otherUserRatings 另一個用戶的評分數(shù)據(jù)映射,與userRatings結構相同,用于對比計算相似度。 * @return 返回一個雙精度浮點數(shù),表示兩個用戶之間的相似度得分,范圍在0到1之間(包含0和1),值越接近1表示相似度越高。 */ private static double calculateSimilarity(Map<Integer, Integer> userRatings, Map<Integer, Integer> otherUserRatings) { // 初始化用于存儲用戶評分向量點積的變量,初始值為0.0,后續(xù)會根據(jù)共同評分的物品來累加計算 double dotProduct = 0.0; // 初始化用于存儲當前用戶評分向量模長平方的變量,初始值為0.0,后續(xù)會根據(jù)當前用戶的評分來累加計算 double userMagnitude = 0.0; // 初始化用于存儲其他用戶評分向量模長平方的變量,初始值為0.0,后續(xù)會根據(jù)其他用戶的評分來累加計算 double otherUserMagnitude = 0.0; // 遍歷當前用戶的評分數(shù)據(jù),計算與其他用戶評分數(shù)據(jù)的點積以及各自向量的模長平方 for (Map.Entry<Integer, Integer> entry : userRatings.entrySet()) { // 獲取當前評分數(shù)據(jù)對應的物品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(防止后續(xù)除法運算出現(xiàn)除以零的錯誤) if (userMagnitude == 0 || otherUserMagnitude == 0) { return 0; } // 根據(jù)余弦相似度的計算公式,返回兩個用戶之間的相似度得分。 // 即點積除以兩個用戶評分向量模長的乘積(先分別對模長平方開方得到模長,再相乘) return dotProduct / (Math.sqrt(userMagnitude) * Math.sqrt(otherUserMagnitude)); } } ?
從數(shù)據(jù)庫獲取數(shù)據(jù)的方法可以根據(jù)自己的需求定義,這里只是方便演示就直接寫了,代碼采用的計算余弦的方法來計算用戶相似度。
如果想直接看效果的我們可以把上面代碼中的main方法替換為下面的代碼
/** * main方法,用于測試推薦功能。 * @param args */ public static void main(String[] args) { // 創(chuàng)建一個包含所有用戶評分數(shù)據(jù)的映射 Map<Integer, Map<Integer, Integer>> ratings = new HashMap<>(); // 添加一些模擬數(shù)據(jù) // 用戶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; // 期望生成的推薦物品數(shù)量 int numRecommendations = 2; // 調(diào)用recommend方法生成推薦物品列表 List<Integer> recommendedItems = recommend(ratings, userId, numRecommendations); // 打印推薦結果 System.out.println("推薦給用戶" + userId + "的物品ID列表: " + recommendedItems); }
運行后我們可以看到控制臺打印出的信息
這樣就代表功能已經(jīng)實現(xiàn)了。
下面是基于余弦計算物品之間相似度的方法,實現(xiàn)和計算用戶的差不多,感興趣的可以看一下
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ù)用戶對物品的評分數(shù)據(jù),針對指定用戶生成推薦物品列表。 * * @param ratings 用戶評分數(shù)據(jù),外層鍵為用戶ID,內(nèi)層鍵為物品ID,值為對應評分。 * @param userId 目標用戶的ID,用于生成針對該用戶的推薦列表。 * @param numRecommendations 期望生成的推薦物品數(shù)量,決定了最終返回推薦列表的長度。 * @return 返回一個包含推薦物品ID的列表,列表長度由numRecommendations決定或者受限于可推薦物品數(shù)量(取較小值)。 */ public static List<Integer> recommend(Map<Integer, Map<Integer, Integer>> ratings, int userId, int numRecommendations) { // 調(diào)用calculateItemSimilarities方法計算物品之間的相似度矩陣, // 這個矩陣將用于后續(xù)預測用戶對未評分物品的評分,以生成推薦列表 Map<Integer, Map<Integer, Double>> itemSimilarities = calculateItemSimilarities(ratings); // 獲取目標用戶(由userId指定)已評分的物品列表, // 以便后續(xù)基于這些已評分物品來預測對其他未評分物品的評分 Map<Integer, Integer> userRatings = ratings.get(userId); List<Integer> ratedItems = new ArrayList<>(userRatings.keySet()); // 用于存儲預測的用戶對未評分物品的評分, // 鍵為物品ID,值為預測的評分,后續(xù)會根據(jù)物品相似度等信息來計算這些評分 Map<Integer, Double> predictedRatings = new HashMap<>(); // 遍歷所有用戶及其評分數(shù)據(jù),目的是針對目標用戶未評分的物品進行評分預測 for (Map.Entry<Integer, Map<Integer, Integer>> entry : ratings.entrySet()) { // 獲取當前遍歷到的其他用戶的評分數(shù)據(jù) Map<Integer, Integer> otherUserRatings = entry.getValue(); // 獲取當前遍歷到的其他用戶的ID int otherUserId = entry.getKey(); // 如果當前遍歷到的用戶就是目標用戶,則跳過本次循環(huán),不需要對自身已有的評分數(shù)據(jù)進行處理 if (otherUserId == userId) continue; // 遍歷當前其他用戶評分過的物品,檢查是否是目標用戶未評分的物品,若是則進行評分預測 for (Integer itemId : otherUserRatings.keySet()) { if (!ratedItems.contains(itemId)) { // 調(diào)用predictRating方法預測目標用戶對當前未評分物品(itemId)的評分, // 并將預測評分存入predictedRatings這個Map中 double predictedRating = predictRating(userRatings, itemSimilarities, itemId, otherUserRatings); predictedRatings.put(itemId, predictedRating); } } } // 將預測評分的Map轉(zhuǎn)換為List<Map.Entry<Integer, Double>>形式,方便后續(xù)進行排序操作, // 每個元素代表一個物品的ID及其對應的預測評分,以鍵值對形式存儲在Entry中 List<Map.Entry<Integer, Double>> sortedPredictions = new ArrayList<>(predictedRatings.entrySet()); // 使用lambda表達式自定義排序規(guī)則,按照預測評分從高到低對物品進行排序, // 這里通過比較Entry中的值(即預測評分)來確定順序,b.getValue().compareTo(a.getValue())表示按照降序排列 sortedPredictions.sort((a, b) -> b.getValue().compareTo(a.getValue())); // 創(chuàng)建一個列表,用于存儲最終要返回的推薦物品ID List<Integer> recommendedItems = new ArrayList<>(); // 根據(jù)期望推薦的數(shù)量(取numRecommendations和實際可推薦物品數(shù)量的較小值), // 將排序后的推薦物品ID依次添加到recommendedItems列表中 for (int i = 0; i < Math.min(numRecommendations, sortedPredictions.size()); i++) { recommendedItems.add(sortedPredictions.get(i).getKey()); } return recommendedItems; } /** * 計算物品之間相似度的方法,采用余弦相似度計算。 * 該方法通過分析所有用戶對物品的評分數(shù)據(jù),構建出物品之間的相似度矩陣, * 矩陣中記錄了每對物品之間的相似度得分,反映了物品在用戶評分行為上的相似程度。 * * @param ratings 用戶評分數(shù)據(jù),用于分析物品之間的相似程度。 * @return 返回一個表示物品相似度的矩陣,外層鍵為物品ID,內(nèi)層鍵為與之對比的物品ID,值為相似度得分。 */ private static Map<Integer, Map<Integer, Double>> calculateItemSimilarities(Map<Integer, Map<Integer, Integer>> ratings) { // 創(chuàng)建一個嵌套的Map結構,用于存儲物品之間的相似度矩陣, // 外層鍵表示一個物品的ID,內(nèi)層鍵表示與之對比的另一個物品的ID,值為它們之間的相似度得分 Map<Integer, Map<Integer, Double>> itemSimilarities = new HashMap<>(); // 遍歷所有物品,外層循環(huán)每次確定一個物品(記為item1),用于與其他物品計算相似度 for (Map.Entry<Integer, Map<Integer, Integer>> item1Entry : ratings.entrySet()) { // 獲取當前遍歷到的物品(item1)的ID int item1Id = item1Entry.getKey(); // 獲取當前遍歷到的物品(item1)的用戶評分數(shù)據(jù) Map<Integer, Integer> item1Ratings = item1Entry.getValue(); // 創(chuàng)建一個Map,用于存儲當前物品(item1)與其他物品的相似度得分, // 鍵為其他物品的ID,值為對應的相似度得分,后續(xù)會在循環(huán)中填充這個Map Map<Integer, Double> similaritiesForItem1 = new HashMap<>(); // 內(nèi)層循環(huán)再次遍歷所有物品(記為item2),計算item1與每個item2之間的相似度 for (Map.Entry<Integer, Map<Integer, Integer>> item2Entry : ratings.entrySet()) { // 獲取當前內(nèi)層循環(huán)遍歷到的物品(item2)的ID int item2Id = item2Entry.getKey(); // 獲取當前內(nèi)層循環(huán)遍歷到的物品(item2)的用戶評分數(shù)據(jù) Map<Integer, Integer> item2Ratings = item2Entry.getValue(); // 如果兩個物品是同一個物品(即ID相同),則跳過本次相似度計算,因為自身與自身的相似度為1(無需計算) if (item1Id == item2Id) continue; // 調(diào)用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; } /** * 計算兩個物品之間的相似度(采用余弦相似度)。 * 通過分析兩個物品各自對應的用戶評分數(shù)據(jù),按照余弦相似度的計算公式, * 得出反映它們在用戶評分行為上相似程度的得分,范圍在0到1之間(包含0和1),越接近1表示相似度越高。 * * @param item1Ratings 第一個物品的用戶評分數(shù)據(jù),鍵為用戶ID,值為對應評分。 * @param item2Ratings 第二個物品的用戶評分數(shù)據(jù),結構與item1Ratings相同,用于對比計算相似度。 * @return 返回一個雙精度浮點數(shù),表示兩個物品之間的相似度得分,范圍在0到1之間(包含0和1),值越接近1表示相似度越高。 */ private static double calculateItemSimilarity(Map<Integer, Integer> item1Ratings, Map<Integer, Integer> item2Ratings) { // 初始化用于存儲兩個物品評分向量點積的變量,初始值為0.0,后續(xù)會根據(jù)共同評分的用戶來累加計算 double dotProduct = 0.0; // 初始化用于存儲第一個物品評分向量模長平方的變量,初始值為0.0,后續(xù)會根據(jù)第一個物品的用戶評分來累加計算 double item1Magnitude = 0.0; // 初始化用于存儲第二個物品評分向量模長平方的變量,初始值為0.0,后續(xù)會根據(jù)第二個物品的用戶評分來累加計算 double item2Magnitude = 0.0; // 遍歷第一個物品的用戶評分數(shù)據(jù),目的是找出與第二個物品共同評分的用戶,并基于這些用戶的評分計算相關數(shù)值 for (Map.Entry<Integer, Integer> entry : item1Ratings.entrySet()) { // 獲取當前評分數(shù)據(jù)對應的用戶ID int userId = entry.getKey(); // 如果第二個物品的用戶評分數(shù)據(jù)中也包含當前用戶(即共同評分的用戶),則進行以下計算 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(防止后續(xù)除法運算出現(xiàn)除以零的錯誤) if (item1Magnitude == 0 || item2Magnitude == 0) { return 0; } // 根據(jù)余弦相似度的計算公式,返回兩個物品之間的相似度得分。 // 即點積除以兩個物品評分向量模長的乘積(先分別對模長平方開方得到模長,再相乘) return dotProduct / (Math.sqrt(item1Magnitude) * Math.sqrt(item2Magnitude)); } /** * 預測用戶對某個未評分物品的評分。 * 通過結合用戶已評分物品的評分、物品之間的相似度矩陣以及其他用戶對該未評分物品的評分等信息, * 按照特定的計算公式來預測目標用戶對該未評分物品的評分值。 * * @param userRatings 用戶已有的評分數(shù)據(jù),用于結合物品相似度來預測評分。 * @param itemSimilarities 物品相似度矩陣,提供物品間的相似程度信息。 * @param itemId 待預測評分的物品ID。 * @param otherUserRatings 其他用戶的評分數(shù)據(jù),用于參考計算預測評分。 * @return 返回預測的用戶對該物品的評分值,為雙精度浮點數(shù)。 */ private static double predictRating(Map<Integer, Integer> userRatings, Map<Integer, Map<Integer, Double>> itemSimilarities, int itemId, Map<Integer, Integer> otherUserRatings) { // 初始化分子變量,用于存儲預測評分計算公式中的分子部分的值,初始值為0.0,后續(xù)會根據(jù)已評分物品的相關信息累加計算 double numerator = 0.0; // 初始化分母變量,用于存儲預測評分計算公式中的分母部分的值,初始值為0.0,后續(xù)會根據(jù)物品相似度等信息累加計算 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; } // 根據(jù)預測評分的計算公式(分子除以分母),返回預測的用戶對該物品(itemId)的評分值 return numerator / denominator; } }
到此這篇關于Springboot實現(xiàn)推薦系統(tǒng)的協(xié)同過濾算法的文章就介紹到這了,更多相關Springboot協(xié)同過濾算法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
解決Mybatis在IDEA中找不到mapper映射文件的問題
這篇文章主要介紹了解決Mybatis在IDEA中找不到mapper映射文件的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10SpringBoot3中token攔截器鏈的設計與實現(xiàn)步驟
本文介紹了spring boot后端服務開發(fā)中有關如何設計攔截器的思路,文中通過代碼示例和圖文講解的非常詳細,具有一定的參考價值,需要的朋友可以參考下2024-03-03