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

来源: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
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...