推薦演算法在商城系統實踐

来源:https://www.cnblogs.com/waynaqua/archive/2023/04/09/17300654.html
-Advertisement-
Play Games

一、簡介 本文博主給大家講解如何在自己開源的電商項目newbee-mall-pro中應用協同過濾演算法來達到給用戶更好的購物體驗效果。 newbee-mall-pro項目地址: 源碼地址:https://github.com/wayn111/newbee-mall-pro 線上地址:http://12 ...


一、簡介

本文博主給大家講解如何在自己開源的電商項目newbee-mall-pro中應用協同過濾演算法來達到給用戶更好的購物體驗效果。

newbee-mall-pro項目地址:


二、協同過濾演算法

協同過濾演算法是一種基於用戶或者物品的相似度來推薦商品的方法,它可以有效地解決商城系統中的信息過載問題。協同過濾演算法的實踐主要包括以下幾個步驟:

  1. 數據收集和預處理。這一步需要從商城系統中獲取用戶的行為數據,如瀏覽、購買、評價等,然後進行一些必要的清洗和轉換,以便後續的分析和計算。
  2. 相似度計算。這一步需要根據用戶或者物品的特征或者行為,採用合適的相似度度量方法,如餘弦相似度、皮爾遜相關係數、Jaccard指數等,來計算用戶之間或者物品之間的相似度矩陣。
  3. 推薦生成。這一步需要根據相似度矩陣和用戶的歷史行為,採用合適的推薦策略,如基於鄰域的方法、基於模型的方法、基於矩陣分解的方法等,來生成針對每個用戶的個性化推薦列表。
  4. 推薦評估和優化。這一步需要根據一些評價指標,如準確率、召回率、覆蓋率、多樣性等,來評估推薦系統的效果,並根據反饋信息和業務需求,進行一些參數調整和演算法優化,以提高推薦系統的性能和用戶滿意度。

在原有的商城首頁為你推薦欄目是使用後臺配置的商品列表,基於人為配置。在項目商品用戶持續增長的情況下,不一定能給用戶推薦用戶可能想要的商品。

因此在v2.4.1版本中,商城首頁為你推薦欄目添加了協同過濾演算法。按照UserCF基於用戶的協同過濾、ItemCF基於物品的協同過濾。 實現了兩種不同的推薦邏輯。

  • UserCF:基於用戶的協同過濾。當一個用戶A需要個性化推薦的時候,我們可以先找到和他有相似興趣的其他用戶,然後把那些用戶喜歡的,而用戶A沒有聽說過的物品推薦給A。
    userCF.png
    假設用戶 A 喜歡物品 A、物品 C,用戶 B 喜歡物品 B,用戶 C 喜歡物品 A 、物品 C 和物品 D;從這些用戶的歷史喜好信息中,我們可以發現用戶 A 和用戶 C 的口味和偏好是比較類似的,同時用戶 C 還喜歡物品 D,那麼我們可以推斷用戶 A 可能也喜歡物品 D,因此可以將物品 D 推薦給用戶 A。具體代碼在 ltd.newbee.mall.recommend.core.UserCF 中。

  • itemCF:基於物品的協同過濾。預先根據所有用戶的歷史偏好數據計算物品之間的相似度,然後把與用戶喜歡的物品相類似的物品推薦給用戶。 
    itemCF.png
    假如用戶A喜歡物品A和物品C,用戶B喜歡物品A、物品B和物品C,用戶C喜歡物品A,從這些用戶的歷史喜好中可以認為物品A與物品C比較類似,喜歡物品A的都喜歡物品C,基於這個判斷用戶C可能也喜歡物品C,所以推薦系統將物品C推薦給用戶C。 具體代碼在 ltd.newbee.mall.recommend.core.ItemCF 中。

三、推薦演算法代碼實踐

3.1 數據收集和預處理

newbee-mall-pro中,我們基於用戶下單的商品數據進行收集和預處理。

/**
 * 根據所有用戶購買商品的記錄進行數據手機
 *
 * @return List<RelateDTO>
 */
@Override
public List<RelateDTO> getRelateData() {
    List<RelateDTO> relateDTOList = new ArrayList<>();
    // 獲取所有訂單以及訂單關聯商品的集合
    List<Order> newBeeMallOrders = orderDao.selectOrderIds();
    List<Long> orderIds = newBeeMallOrders.stream().map(Order::getOrderId).toList();
    List<OrderItemVO> newBeeMallOrderItems = orderItemDao.selectByOrderIds(orderIds);
    Map<Long, List<OrderItemVO>> listMap = newBeeMallOrderItems.stream()
            .collect(Collectors.groupingBy(OrderItemVO::getOrderId));
    Map<Long, List<OrderItemVO>> goodsListMap = newBeeMallOrderItems.stream()
            .collect(Collectors.groupingBy(OrderItemVO::getGoodsId));
    // 遍歷訂單,生成預處理數據
    for (Order newBeeMallOrder : newBeeMallOrders) {
        Long orderId = newBeeMallOrder.getOrderId();
        for (OrderItemVO newBeeMallOrderItem : listMap.getOrDefault(orderId, Collections.emptyList())) {
            Long goodsId = newBeeMallOrderItem.getGoodsId();
            Long categoryId = newBeeMallOrderItem.getCategoryId();
            RelateDTO relateDTO = new RelateDTO();
            ...
            relateDTOList.add(relateDTO);
        }
    }
    return relateDTOList;
}

3.2 相似度計算

在推薦演算法中,相似度建立是一個非常重要的過程,它標志著演算法準不准確,能不能給用戶帶來好的推薦體驗。在newbee-mall-pro中,我們將用戶之間下單的商品進行相似度計算,因為如果兩個用戶購買了同一個商品,那麼我們認為這兩個用戶之間是存在聯繫並且都存在付費行為。

// 遍歷訂單商品
for (OrderItemVO newBeeMallOrderItem : listMap.getOrDefault(orderId, Collections.emptyList())) {
    Long goodsId = newBeeMallOrderItem.getGoodsId();
    Long categoryId = newBeeMallOrderItem.getCategoryId();
    RelateDTO relateDTO = new RelateDTO();
    relateDTO.setUserId(newBeeMallOrder.getUserId());
    relateDTO.setProductId(goodsId);
    relateDTO.setCategoryId(categoryId);
    // 通過計算商品購買次數,來建立相似度
    List<OrderItemVO> list = goodsListMap.getOrDefault(goodsId, Collections.emptyList());
    int sum = list.stream().mapToInt(OrderItemVO::getGoodsCount).sum();
    relateDTO.setIndex(sum);
    relateDTOList.add(relateDTO);
}

通過餘弦相似度演算法計算用戶與商品之間的相似度,從而為用戶推薦最相似的商品。當兩個用戶購買了同一個商品時,我們就認為兩個用戶產生了關聯,因此針對兩個用戶購買的同一個商品進行相似度計算,來建立用戶之間的相似度。

餘弦相似度是一種用於衡量兩個向量之間的相似度的方法,它通過計算兩個向量的夾角的餘弦值來得到。在商城系統中,餘弦相似度可以用於實現基於內容的推薦演算法,即根據用戶的歷史購買或瀏覽行為,為用戶推薦與其興趣相似的商品。具體來說,可以將每個商品表示為一個特征向量,例如商品的類別、價格、評分等,然後將每個用戶表示為一個偏好向量,例如用戶購買或瀏覽過的商品的特征向量的加權平均。這樣,就可以利用餘弦相似度來計算用戶和商品之間的相似度,從而為用戶推薦最相似的商品。

計算相關係數,傳入用戶ID或者物品ID,計算相似度

/**
 * 計算相關係數併排序
 *
 * @param key  基於用戶協同代表用戶id,基於物品協同代表武平id
 * @param map  預處理數據集
 * @param type 類型0基於用戶推薦使用餘弦相似度 1基於物品推薦使用餘弦相似度
 * @return Map<Double, Long>
 */
public static Map<Double, Long> computeNeighbor(Long key, 
                          Map<Long, List<RelateDTO>> map, int type) {
    Map<Double, Long> distMap = new TreeMap<>();
    List<RelateDTO> items = map.get(key);
    map.forEach((k, v) -> {
        // 排除此用戶
        if (!k.equals(key)) {
            // 計算關係繫數
            double coefficient = relateDist(v, items, type);
            distMap.put(coefficient, k);
        }
    });
    return distMap;
}

計算兩個用戶間的相關係數

/**
 * 計算兩個序列間的相關係數
 *
 * @param xList
 * @param yList
 * @param type  類型0基於用戶推薦使用餘弦相似度 1基於物品推薦使用餘弦相似度 2基於用戶推薦使用皮爾森繫數計算
 * @return
 */
private static double relateDist(List<RelateDTO> xList, 
                              List<RelateDTO> yList, Integer type) {
    List<Integer> xs = Lists.newArrayList();
    List<Integer> ys = Lists.newArrayList();
    xList.forEach(x -> yList.forEach(y -> {
        if (type == 0) {
            // 基於用戶推薦時如果兩個用戶購買的商品相同,則計算相似度
            if (x.getProductId().longValue() == y.getProductId().longValue()) {
                xs.add(x.getIndex());
                ys.add(y.getIndex());
            }
        } else if (type == 1) {
            // 基於物品推薦時如果兩個用戶id相同,則計算相似度
            if (x.getUserId().longValue() == y.getUserId().longValue()) {
                xs.add(x.getIndex());
                ys.add(y.getIndex());
            }
        }
    }));
    if (ys.size() == 0 || xs.size() == 0) {
        return 0d;
    }
    // 餘弦相似度計算
    return cosineSimilarity(xs, ys);
}

餘弦相似度計算

/**
 * 來計算向量之間的餘弦相似度,
 * 也就是計算兩個用戶或者兩個物品之間的相似度
 * @param xs
 * @param xs
 * @return double
 */
private static double cosineSimilarity(List<Integer> xs, 
                                                List<Integer> ys) {
    double dotProduct = 0;
    double norm1 = 0;
    double norm2 = 0;
    for (int i = 0; i < xs.size(); i++) {
        Integer x = xs.get(i);
        Integer y = ys.get(i);
        dotProduct += x * y;
        norm1 += Math.pow(x, 2);
        norm2 += Math.pow(y, 2);
    }
    return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
}

3.3 推薦生成

基於用戶協同的推薦生成,我們可以先找到和目標用戶有相似興趣的其他用戶,然後把其他用戶喜歡的,而目標用戶沒有買過的物品推薦給目標用戶。

public class UserCF {
    /**
     * 物用戶協同推薦
     *
     * @param userId 用戶ID
     * @param num    返回數量
     * @param list   預處理數據
     * @return 商品id集合
     */
    public static List<Long> recommend(Long userId, Integer num,
                                       List<RelateDTO> list, Integer type) {
        // 對每個用戶的購買商品記錄進行分組
        Map<Long, List<RelateDTO>> userMap = list.stream()
                .collect(Collectors.groupingBy(RelateDTO::getUserId));
        // 獲取其他用戶與當前用戶的關係值
        Map<Double, Long> userDisMap = CoreMath.computeNeighbor(userId, userMap, type);
        List<Long> similarUserIdList = new ArrayList<>();
        List<Double> values = new ArrayList<>(userDisMap.keySet());
        values.sort(Collections.reverseOrder());
        List<Double> scoresList = values.stream().limit(3).toList();
        // 獲取關係最近的用戶
        for (Double aDouble : scoresList) {
            similarUserIdList.add(userDisMap.get(aDouble));
        }
        List<Long> similarProductIdList = new ArrayList<>();
        for (Long similarUserId : similarUserIdList) {
            // 獲取相似用戶購買商品的記錄
            List<Long> collect = userMap.get(similarUserId).stream()
                    .map(RelateDTO::getProductId).toList();
            // 過濾掉重覆的商品
            List<Long> collect1 = collect.stream()
                    .filter(e -> !similarProductIdList.contains(e)).toList();
            similarProductIdList.addAll(collect1);
        }
        // 當前登錄用戶購買過的商品
        List<Long> userProductIdList = userMap.getOrDefault(userId,
                        Collections.emptyList()).stream().map(RelateDTO::getProductId).toList();
        // 相似用戶買過,但是當前用戶沒買過的商品作為推薦
        List<Long> recommendList = new ArrayList<>();
        for (Long similarProduct : similarProductIdList) {
            if (!userProductIdList.contains(similarProduct)) {
                recommendList.add(similarProduct);
            }
        }
        Collections.sort(recommendList);
        return recommendList.stream().distinct().limit(num).toList();
    }
}

基於物品協同的推薦生成,找出與目標用戶購買過的商品中最相似的前幾個商品中目標用戶也沒有買過的商品推薦給用戶。

public class ItemCF {

    /**
     * 物品協同推薦
     *
     * @param userId 用戶ID
     * @param num    返回數量
     * @param list   預處理數據
     * @return 商品id集合
     */
    public static List<Long> recommend(Long userId, Integer num, 
                                        List<RelateDTO> list) {
        // 按物品分組
        Map<Long, List<RelateDTO>> userMap = list.stream()
                .collect(Collectors.groupingBy(RelateDTO::getUserId));
        List<Long> userProductItems = userMap.get(userId).stream()
                .map(RelateDTO::getProductId).toList();
        Map<Long, List<RelateDTO>> itemMap = list.stream()
                .collect(Collectors.groupingBy(RelateDTO::getProductId));
        List<Long> similarProductIdList = new ArrayList<>();
        Multimap<Double, Long> itemTotalDisMap = TreeMultimap.create();
        for (Long itemId : userProductItems) {
            // 獲取其他物品與當前物品的關係值
            Map<Double, Long> itemDisMap = CoreMath.computeNeighbor(itemId, itemMap, 1);
            itemDisMap.forEach(itemTotalDisMap::put);
        }

        List<Double> values = new ArrayList<>(itemTotalDisMap.keySet());
        values.sort(Collections.reverseOrder());
        List<Double> scoresList = values.stream().limit(num).toList();
        // 獲取關係最近的用戶
        for (Double aDouble : scoresList) {
            Collection<Long> longs = itemTotalDisMap.get(aDouble);
            for (Long productId : longs) {
                if (!userProductItems.contains(productId)) {
                    similarProductIdList.add(productId);
                }
            }
        }
        return similarProductIdList.stream().distinct().limit(num).toList();
    }
}

3.4 推薦評估和優化

newbee-mall-pro中可以針對為你推薦欄目中推薦的商品做曝光率、點擊率、下單數等作為監控指標來評估推薦效果。

四、用戶協同和物品協同應用場景

用戶協同和物品協同都是兩種常用的推薦系統演算法,它們分別利用用戶之間和物品之間的相似度來給用戶提供個性化的推薦。用戶協同和物品協同的應用場景有以下幾種:

  • 用戶協同適用於用戶數量相對較少,用戶興趣相對穩定,物品數量相對較多,物品更新頻率較高的場景。例如,電影推薦、音樂推薦、圖書推薦等。
  • 物品協同適用於用戶數量相對較多,用戶興趣相對多變,物品數量相對較少,物品更新頻率較低的場景。例如,新聞推薦、廣告推薦、社交網路推薦等。
  • 用戶協同和物品協同也可以結合起來,形成混合推薦系統,以提高推薦的準確性和覆蓋率。例如,電商平臺可以根據用戶的購買歷史和評價,以及物品的屬性和銷量,綜合使用用戶協同和物品協同來給用戶推薦商品。

商城系統使用用戶協同還是物品協同,這是一個需要根據具體情況進行選擇的問題。用戶協同是指根據用戶之間的相似度,為用戶推薦他們可能感興趣的物品。物品協同是指根據物品之間的相似度,為用戶推薦與他們已經購買或瀏覽過的物品相似的物品。兩種方法各有優缺點,需要綜合考慮商城系統的目標、規模、數據量、稀疏度等因素。一般來說,如果商城系統的目標是增加用戶的多樣性和探索性,那麼用戶協同可能更合適,因為它可以為用戶提供更廣泛的選擇。如果商城系統的目標是增加用戶的滿意度和忠誠度,那麼物品協同可能更合適,因為它可以為用戶提供更精準的推薦

在一般商城系統中,初期用戶數量少可以使用用戶協同,後期用戶數遠超商品數,使用物品協同會更好些,這兩者也可以結合使用。推薦演算法是不會一成不變的,它需要根據某些指標數據不斷優化調整升值甚至重構使用另外的演算法。

五、冷啟動問題

商城協同演算法冷啟動問題是指在商城系統中,當新用戶或新商品加入時,由於缺乏足夠的交互數據,導致協同過濾演算法無法為其提供準確的推薦結果。

newbee-mall-pro就是指新用戶還未下單

這種問題會影響商城的用戶體驗和轉化率,因此需要有效的解決方案。一種常見的方法是使用流行度演算法。

利用基於流行度的演算法非常簡單粗暴,類似於各大新聞、微博熱榜、商城等,根據PV、UV、點擊率、搜索率、下單商品排行等數據來按某種熱度排序來推薦給用戶。

總結

到這裡,本文所分享推薦演算法在商城系統實踐就全部介紹完了,希望對大家實現推薦系統落地有所幫助,喜歡的朋友們可以點贊加關註

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • cargo、crates.io 本章內容 通過 release profile 來自定義構建 在https://crates.io/上發佈庫 通過 workspaces 組織大工程 從 https://crates.io/來安裝庫 使用自定義命令擴展 cargo 一、通過 release profi ...
  • 今天在學習springMVC的json數據綁定時,需要使用到jquery發送ajax請求。但是當我通過是<script>標簽引入了jquery.js。 但是當我訪問該jsp的時候就是不顯示頁面的內容 我一直以為時SpringMVC的servelt攔截器攔截了靜態資源,但是我過濾了靜態資源還是不顯示。 ...
  • 概念 異常處理的概念起源於早期的編程語言,如 LISP、PL/I 和 CLU。這些編程語言首次引入了異常處理機制,以便在程式執行過程中檢測和處理錯誤情況。異常處理機制隨後在 Ada、Modula-3、C++、Python、Java 等編程語言中得到了廣泛採用和發展。在 Java 中,異常處理是提供一 ...
  • 之前一直以為固態硬碟各方面都比機械硬碟性能高,所以首選固態硬碟,直到看了極客時間-深入淺出電腦組成原理中硬碟相關章節的內容,才發現固態硬碟原來是有缺點的,所以這裡來做一個總結。 機械硬碟(HDD) 機械硬碟由以下幾個部分組成: 盤面:盤面(碟片)上有一層磁性塗層,數據就是存儲在這個磁性的塗層上,一 ...
  • 網路分層結構 電腦網路體系大致分為三種,OSI七層模型、TCP/IP四層模型和五層模型。一般面試的時候考察比較多的是五層模型。最全面的Java面試網站 五層模型:應用層、傳輸層、網路層、數據鏈路層、物理層。 應用層:為應用程式提供交互服務。在互聯網中的應用層協議很多,如功能變數名稱系統DNS、HTTP協議 ...
  • 當今的軟體開發需要使用許多不同的工具和技術來確保代碼質量和穩定性。PMD是一個流行的靜態代碼分析工具,可以幫助開發者在編譯代碼之前發現潛在的問題。在本文中,我們將討論如何在Gradle中使用PMD,並介紹一些最佳實踐。 什麼是PMD? PMD是一個用於Java代碼的靜態代碼分析工具。它可以幫助開發者 ...
  • demo軟體園每日更新資源,請看到最後就能獲取你想要的: 1.完善版手游導航源碼app軟體 APP手機軟體 應用商城下載類網站佈局規整,利於用戶體驗 瀏覽網站看到一款帶後臺的app軟體手游類源碼,後臺功能強大,界面美觀,適用於app軟體,手機軟體下載,手游類導航網, 其他行業也可以把數據刪掉,添加自 ...
  • 文章目錄一、前言一、方式1:spring 官方創建 springboot項目1、打開線上的 spring initializr2、選擇項目的語言、版本、依賴等3、 解壓源碼包,並使用IDEA打開4、測試介面二、方式2:社區idea安裝Spring插件1、添加插件三、方式3:(麻煩)手動maven 創 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...