大家好,這里是小羅畢設工作室。今天給大家帶來了一套完整的推薦系統: “基于Jaccard算法的用戶瀏覽歷史推薦商品系統”。 系統源碼后端實現是springboot,前端是vue3。?
視頻演示
基于Jaccard算法的用戶瀏覽歷史推薦商品系統實戰
圖片截圖
算法原理介紹
假設我們有兩個商品:商品A和商品B。商品A被用戶{小明,小紅,小張}瀏覽過,商品B被用戶{小紅,小張,小李}瀏覽過。
得到兩個用戶群體集合 : {小明,小紅,小張}? ??{小紅,小張,小李}? 。
我們通過Jaccard算法,將兩個集合先取交集得到 {小紅,小張} ,大小是2 , 在取并集得到?{小明,小紅,小張,小李},大小是4。
因此Jaccard相似度=2/4=0.5,這個值表示兩個商品的相似程度,值越接近1表示兩個商品越相似(被相同用戶群體瀏覽),系統會優先推薦相似度高的商品。
實現流程
基礎數據準備
首先我們需要準備用戶的行為數據表:
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
用戶推薦接口的實現
我將代碼的流程整理成了一個流程圖,首先我們需要構造一個Map結構數據:
?這些數據就是查詢用戶行為表,將所有的行為數據,按照商品分類,每一個商品都對應了一個用戶群體集合。
然后還是查詢用戶行為表 , 將當前用戶的行為數據查詢出來,進行下面流程:
?關鍵的核心點就是:將當前推薦用戶買過的商品id對應其他也買過的這個商品的用戶作為一個用戶集合,然后取所有商品對應的用戶群體集合(很多個),然后一個一個經過Jaccard算法計算相似度,把這個相似度最為評分,最后取出評分高的商品最為推薦商品。
講到這里,你應該能懂了,下面看下關鍵代碼實現:
/**完整代碼實現:gitcode巔抗目/hadluo2/springboot_vue.git* 基于用戶瀏覽歷史推薦物品* 主要步驟如下:* 1. 獲取用戶最近的瀏覽記錄,按時間倒序排列* 2. 獲取所有用戶的瀏覽記錄,構建物品-用戶倒排索引* 3. 計算用戶最近瀏覽物品與其他物品的相似度* 4. 考慮時間衰減因素,為每個候選物品計算最終得分* 5. 返回得分最高的N個物品作為推薦結果* @param userId 用戶ID* @param numRecommendations 推薦數量* @return 推薦的物品列表*/
public List<Item> recommendBasedOnBrowsingHistory(Long userId, int numRecommendations) {// 1. 獲取用戶最近的瀏覽記錄// 創建查詢條件:匹配用戶ID和瀏覽行為,按時間戳降序排序LambdaQueryWrapper<UserBehavior> wrapper = new LambdaQueryWrapper<>();wrapper.eq(UserBehavior::getUserId, userId).eq(UserBehavior::getBehaviorType, "VIEW").orderByDesc(UserBehavior::getTimestamp);List<UserBehavior> userViews = userBehaviorRepository.selectList(wrapper);// 如果用戶沒有瀏覽記錄,返回空列表if (CollectionUtils.isEmpty(userViews)) {return Collections.emptyList();}// 2. 獲取所有用戶的瀏覽記錄// 查詢所有用戶的瀏覽行為,用于后續計算物品相似度List<UserBehavior> allViews = userBehaviorRepository.selectList(new LambdaQueryWrapper<UserBehavior>().eq(UserBehavior::getBehaviorType, "VIEW"));// 3. 構建物品-用戶的倒排索引// key: 物品ID, value: 瀏覽過該物品的用戶ID集合Map<Long, Set<Long>> itemUserMap = new HashMap<>();for (UserBehavior view : allViews) {// computeIfAbsent: 如果key不存在,則創建一個新的HashSetitemUserMap.computeIfAbsent(view.getItemId(), k -> new HashSet<>()).add(view.getUserId());}// 4. 計算物品相似度和推薦得分// 存儲每個候選物品的最終得分Map<Long, Double> itemScores = new HashMap<>();// 獲取用戶已瀏覽過的物品ID集合,用于排除推薦Set<Long> userViewedItemIds = userViews.stream().map(UserBehavior::getItemId).collect(Collectors.toSet());// 遍歷用戶的每個瀏覽記錄for (UserBehavior userView : userViews) {// 獲取 瀏覽該物品的 所有用戶Set<Long> itemUsers = itemUserMap.get(userView.getItemId());if (itemUsers == null) continue;// 遍歷所有物品,計算與當前瀏覽物品的相似度for (Map.Entry<Long, Set<Long>> entry : itemUserMap.entrySet()) {Long otherItemId = entry.getKey();// 該用戶瀏覽過了這個商品id, 直接排除if (userViewedItemIds.contains(otherItemId)) continue;// 瀏覽過這個商品的 其他用戶Set<Long> otherItemUsers = entry.getValue();// 使用Jaccard相似度計算物品相似度// Jaccard相似度 = 兩個集合的交集大小 / 并集大小//完整代碼請見:gitcode巔抗目/hadluo2/springboot_vue.gitdouble similarity = calculateJaccardSimilarity(itemUsers, otherItemUsers);// 考慮時間衰減因素 相當于一個優化因子// 瀏覽時間越近,權重越大(1/1, 1/2, 1/3, ...)//完整代碼請見:gitcode巔抗目/hadluo2/springboot_vue.git// 累加相似度得分(考慮時間權重)itemScores.merge(otherItemId, similarity * timeWeight, Double::sum);}}// 5. 生成推薦結果// 對候選物品按得分降序排序,選擇前N個return itemScores.entrySet().stream().sorted(Map.Entry.<Long, Double>comparingByValue().reversed()).limit(numRecommendations).map(e -> itemRepository.selectById(e.getKey())).filter(Objects::nonNull).collect(Collectors.toList());
}
?代碼只貼了部分,原理已經教給大家。