商城-商品推荐算法

算法说明

综合用户浏览(权重1)和购买记录(权重2)

进行归一化处理,消除品类数量差异

示例:购买电子产品的权重是浏览书籍的2倍

价格区间计算:

基于用户历史行为计算平均价格和标准差

使用高斯函数计算价格相似度,考虑价格分布的集中趋势

推荐评分:

综合品类匹配度(70%)和价格匹配度(30%)

使用流式处理进行高效计算和排序
优化方向:
冷启动问题:新用户无历史记录时,可结合热门商品推荐

实时更新:使用滑动窗口机制处理用户行为的时效性

深度学习:对于大规模数据可升级为神经网络模型

多维度特征:加入品牌、用户画像等更多特征

在计算商品相似度时,对于每个候选商品:

类别得分:该商品的每个类别的权重之和,根据用户偏好中的类别权重。例如,商品有类别A和B,用户偏好中A的权重是2,B的权重是3,那么得分是2+3=5。或者可能需要归一化处理,比如用户的总权重是某个值,然后计算相似度。

或者,使用余弦相似度,将用户偏好向量和商品向量表示为TF-IDF类型的权重,然后计算点积。

例如,将所有可能的类别收集为一个词汇表,然后用户偏好向量是每个类别的出现次数(或TF-IDF值),商品向量是该商品是否属于每个类别(01)。然后计算余弦相似度。

价格相似度可以单独计算,比如使用高斯核函数:exp(- (price_diff)^2 / (2 * sigma^2)),其中sigma是标准差,根据用户购买记录中的价格计算得出。

然后,总相似度是类别相似度乘以0.7 + 价格相似度乘以0.3,或者其他权重组合。

在代码中,可能需要先遍历所有用户的购买和浏览记录,统计类别出现的次数,计算每个类别的权重。购买次数和浏览次数可能需要不同的权重,比如购买一次算2分,浏览一次算1分。

然后,对于每个候选商品,计算其类别得分,即该商品所属类别在用户偏好中的权重之和。然后归一化这个得分,例如除以最大可能的得分,使其在01之间。

价格方面,计算该商品的价格与用户平均价格的差异,然后转化为相似度得分。例如,如果用户平均价格是100元,商品价格是120元,差异是20元,价格相似度可以是max(0, 1 - (20 / 100)) = 0.8,如果差异超过100,则得分为0。

或者,价格区间可以设定为用户购买记录中的最低和最高价格,然后如果商品价格在此区间内,得1分,否则按距离扣分。


处理冷启动的常见方法包括:

1. 推荐热门商品:当用户没有历史记录时,推荐销量或浏览量高的商品。

2. 基于人口统计学的推荐:比如根据用户的年龄、性别等信息,但当前User类没有这些属性,可能需要扩展。

3. 随机推荐:但可能不够精准。

4. 混合推荐:结合热门和随机推荐。

由于当前代码中的User类没有其他属性,可能采用推荐热门商品作为冷启动策略。需要修改推荐方法,当检测到用户没有历史记录时,切换到热门商品推荐。

新增功能说明:
冷启动用户判断:

通过isColdStartUser()方法检测用户是否没有浏览和购买记录

自动切换推荐策略

全局特征分析:

getGlobalCategoryPreferences():分析当前候选商品的品类分布

getGlobalPriceRange():计算全体候选商品的价格分布

混合推荐策略:

优先推荐高频品类商品(平台热门商品)

结合价格中位数附近商品(大众接受度高的价格区间)

权重调整为品类60% + 价格40%

动态适应机制:

根据当前可推荐商品实时计算特征

自动适应商品上下架变化
public class ProductRecommender {
    // 权重配置
    private static final double CATEGORY_WEIGHT = 0.7;
    private static final double PRICE_WEIGHT = 0.3;
    private static final double PRICE_RANGE_FACTOR = 0.3;

    // 冷启动权重
    private static final double COLD_START_CATEGORY_WEIGHT = 0.6;
    private static final double COLD_START_PRICE_WEIGHT = 0.4;

    // 添加缓存(使用WeakHashMap防止内存泄漏)
    private final Map<UserShopGoods, Map<String, Double>> userCategoryCache = new WeakHashMap<>();
    private final Map<UserShopGoods, double[]> userPriceRangeCache = new WeakHashMap<>();
    private final Map<List<ShopGoodsItem>, Map<String, Double>> globalCategoryCache = new WeakHashMap<>();
    private final Map<List<ShopGoodsItem>, double[]> globalPriceRangeCache = new WeakHashMap<>();

    // 修改后的特征计算方法
    private Map<String, Double> getCategoryPreferences(UserShopGoods user) {
        return userCategoryCache.computeIfAbsent(user, k -> computeCategoryPreferences(user));
    }

    private double[] getPriceRange(UserShopGoods user) {
        return userPriceRangeCache.computeIfAbsent(user, k -> computePriceRange(user));
    }

    private Map<String, Double> getGlobalCategoryPreferences(List<ShopGoodsItem> items) {
        return globalCategoryCache.computeIfAbsent(items, k -> computeGlobalCategoryPreferences(items));
    }

    private double[] getGlobalPriceRange(List<ShopGoodsItem> items) {
        return globalPriceRangeCache.computeIfAbsent(items, k -> computeGlobalPriceRange(items));
    }


    /**
     * 主推荐方法
     * @param user 目标用户
     * @param allItems 所有候选商品
     * @param topN 推荐数量
     * @return 推荐商品列表(按得分降序)
     */

    public List<ShopGoodsItem> recommend(UserShopGoods user, List<ShopGoodsItem> allItems, int topN) {
        // 过滤已购买商品
        Set<ShopGoodsItem> purchasedItems = new HashSet<>(user.getPurchaseHistory());
        List<ShopGoodsItem> candidates = allItems.stream()
                .filter(item -> !purchasedItems.contains(item))
                .collect(Collectors.toList());

        if (candidates.isEmpty()) return Collections.emptyList();

        // 冷启动处理
        if (isColdStartUser(user)) {
            return coldStartRecommend(candidates, topN);
        }
        // 常规推荐逻辑
        else {
            // 获取用户偏好特征
            Map<String, Double> categoryPrefs = getCategoryPreferences(user);
            double[] priceRange = getPriceRange(user);

            // 计算并排序推荐得分
            return candidates.parallelStream()
                    .map(item -> new AbstractMap.SimpleEntry<>(item,
                            calculateScore(item, categoryPrefs, priceRange)))
                    .sorted((e1, e2) -> Double.compare(e2.getValue(), e1.getValue()))
                    .limit(topN)
                    .map(Map.Entry::getKey)
                    .collect(Collectors.toList());
        }
    }

    // 冷启动用户判断
    private boolean isColdStartUser(UserShopGoods user) {
        return user.getBrowseHistory().isEmpty()
                && user.getPurchaseHistory().isEmpty();
    }

    //===================== 常规推荐逻辑 =====================//
    /**
     * 计算用户品类偏好(带权重)
     * 购买记录权重=2,浏览记录权重=1
     */
    private Map<String, Double> computeCategoryPreferences(UserShopGoods user) {
        Map<String, Double> preferences = new HashMap<>();

        // 处理购买记录(高权重)
        user.getPurchaseHistory().forEach(item ->
                item.getCategories().forEach(cat ->
                        preferences.merge(cat, 2.0, Double::sum)));

        // 处理浏览记录(低权重)
        user.getBrowseHistory().forEach(item ->
                item.getCategories().forEach(cat ->
                        preferences.merge(cat, 1.0, Double::sum)));

        // 归一化处理(最大值为基准)
        double max = preferences.values().stream()
                .max(Double::compare)
                .orElse(1.0);
        preferences.replaceAll((k, v) -> v / max);

        return preferences;
    }

    /**
     * 计算用户价格区间
     * 基于浏览和购买记录计算平均值±标准差*系数
     */
    private double[] computePriceRange(UserShopGoods user) {

        DoubleStream priceStream =DoubleStream.concat(
                user.getPurchaseHistory().stream().mapToDouble(a->a.getPrice().doubleValue()),
                user.getBrowseHistory().stream().mapToDouble(a->a.getPrice().doubleValue())
        ).filter(p -> p > 0);


        // 使用收集器一次性计算总和、平方和及数量
        double[] stats = priceStream.collect(
                () -> new double[3], // [sum, sumSquares, count]
                (acc, p) -> {
                    acc[0] += p;        // 累加价格总和
                    acc[1] += p * p;    // 累加价格平方和
                    acc[2]++;           // 累加计数
                },
                (acc1, acc2) -> {       // 合并并行流结果
                    acc1[0] += acc2[0];
                    acc1[1] += acc2[1];
                    acc1[2] += acc2[2];
                }
        );

        long count = (long) stats[2];
        if (count == 0) {
            return new double[]{0, Double.MAX_VALUE}; // 无有效价格时返回默认范围
        }
        double avg = stats[0] / count;
        double variance = (stats[1] / count) - (avg * avg);
        double stdDev = Math.sqrt(variance);

        return new double[]{
                Math.max(0, avg - stdDev * PRICE_RANGE_FACTOR),
                avg + stdDev * PRICE_RANGE_FACTOR
        };
    }

    /**
     * 计算商品推荐得分
     * 得分 = 品类得分 * 权重 + 价格得分 * 权重
     */
    private double calculateScore(ShopGoodsItem item,
                                  Map<String, Double> categoryPrefs,
                                  double[] priceRange) {
        // 品类得分:所有相关类别的平均偏好值
        double categoryScore = item.getCategories().stream()
                .mapToDouble(cat -> categoryPrefs.getOrDefault(cat, 0.0))
                .average()
                .orElse(0);

        // 价格得分:基于高斯分布函数
        double priceScore = calculatePriceScore(item.getPrice().doubleValue(), priceRange);

        return (CATEGORY_WEIGHT * categoryScore)
                + (PRICE_WEIGHT * priceScore);
    }

    //===================== 冷启动推荐逻辑 =====================//
    /**
     * 冷启动推荐策略
     * 基于全局商品特征进行推荐
     */
    private List<ShopGoodsItem> coldStartRecommend(List<ShopGoodsItem> candidates, int topN) {
        Map<String, Double> globalCats = getGlobalCategoryPreferences(candidates);
        double[] globalPriceRange = getGlobalPriceRange(candidates);

        return candidates.stream()
                .map(item -> new AbstractMap.SimpleEntry<>(
                        item,
                        calculateColdStartScore(item, globalCats, globalPriceRange)))
                .sorted((e1, e2) -> Double.compare(e2.getValue(), e1.getValue()))
                .limit(topN)
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());
    }

    /**
     * 计算全局品类偏好(出现频率)
     */
    private Map<String, Double> computeGlobalCategoryPreferences(List<ShopGoodsItem> items) {
        Map<String, Double> freq = new HashMap<>();
        items.forEach(item ->
                item.getCategories().forEach(cat ->
                        freq.merge(cat, 1.0, Double::sum)));

        // 归一化处理
        double max = freq.values().stream()
                .max(Double::compare)
                .orElse(1.0);
        freq.replaceAll((k, v) -> v / max);
        return freq;
    }

    /**
     * 计算全局价格区间
     */
    private double[] computeGlobalPriceRange(List<ShopGoodsItem> items) {
        DoubleSummaryStatistics stats = items.stream()
                .mapToDouble(p->p.getPrice().doubleValue())
                .summaryStatistics();

        if (stats.getCount() == 0) return new double[]{0, Double.MAX_VALUE};

        double stdDev = Math.sqrt(items.stream()
                .mapToDouble(p -> Math.pow(p.getPrice().doubleValue() - stats.getAverage(), 2))
                .average()
                .orElse(0));

        return new double[]{
                Math.max(0, stats.getAverage() - stdDev * PRICE_RANGE_FACTOR),
                stats.getAverage() + stdDev * PRICE_RANGE_FACTOR
        };
    }

    /**
     * 冷启动评分计算
     */
    private double calculateColdStartScore(ShopGoodsItem item,
                                           Map<String, Double> globalCats,
                                           double[] priceRange) {
        double catScore = item.getCategories().stream()
                .mapToDouble(k->globalCats.getOrDefault(k,0.0))
                .average()
                .orElse(0);

        double priceScore = calculatePriceScore(item.getPrice().doubleValue(), priceRange);

        return (COLD_START_CATEGORY_WEIGHT * catScore)
                + (COLD_START_PRICE_WEIGHT * priceScore);
    }

    //===================== 公共工具方法 =====================//
    /**
     * 价格得分计算(高斯函数)
     * 当price接近区间中点时得分接近1,偏离越大得分越低
     */
    private double calculatePriceScore(double price, double[] range) {
        double mid = (range[0] + range[1]) / 2;
        double width = range[1] - range[0];
        double variance = Math.pow(width / 4, 2);

        if (variance == 0) return 1.0; // 单点区间直接满分

        // 标准差控制曲线陡峭程度
        double exponent = -Math.pow(price - mid, 2) / (2 * variance);
        return Math.exp(exponent);
    }

    //===================== 测试用例 =====================//
    public static void main(String[] args) {
        // 创建测试商品
        List<ShopGoodsItem> allItems = Arrays.asList(
                new ShopGoodsItem("A1",  new HashSet<>(Arrays.asList("Electronics", "Smart Home")), BigDecimal.valueOf(299.99)),
                new ShopGoodsItem("B2", new HashSet<>(Arrays.asList("Books", "Education")), BigDecimal.valueOf(19.99)),
                new ShopGoodsItem("C3", new HashSet<>(Arrays.asList("Electronics", "Accessories")), BigDecimal.valueOf(89.99)),
                new ShopGoodsItem("D4", new HashSet<>(Arrays.asList("Fashion", "Men")), BigDecimal.valueOf(129.99)),
                new ShopGoodsItem("E5", new HashSet<>(Arrays.asList("Home", "Kitchen")), BigDecimal.valueOf(49.99))
        );

        // 创建活跃用户(有历史行为)
        UserShopGoods activeUser = new UserShopGoods(
                Arrays.asList(allItems.get(1)), // 浏览过B2
                        Arrays.asList(allItems.get(0)) // 购买过A1
                );

        // 创建冷启动用户
        UserShopGoods newUser = new UserShopGoods(Collections.emptyList(), Collections.emptyList());

        ProductRecommender recommender = new ProductRecommender();

        System.out.println("===== 活跃用户推荐 =====");
        List<ShopGoodsItem> rec1 = recommender.recommend(activeUser, allItems, 100);
        rec1.forEach(System.out::println);

        System.out.println("\n===== 冷启动用户推荐 =====");
        List<ShopGoodsItem> rec2 = recommender.recommend(newUser, allItems, 100);
        rec2.forEach(System.out::println);
    }
}

商品实体类

@Data
@EqualsAndHashCode(callSuper = false)
public class ShopGoodsItem  implements Serializable  {
    private final String id;
    private final Set<String> categories;
    private final BigDecimal price;


}

@Data
public class UserShopGoods {
	// 浏览记录
    private final List<ShopGoodsItem> browseHistory;
    // 购买记录
    private final List<ShopGoodsItem> purchaseHistory;

    public UserShopGoods(List<ShopGoodsItem> browseHistory, List<ShopGoodsItem> purchaseHistory) {
        this.browseHistory = Collections.unmodifiableList(new ArrayList<>(browseHistory));
        this.purchaseHistory = Collections.unmodifiableList(new ArrayList<>(purchaseHistory));
    }
}
### B2B电子商务平台中推荐算法的实现方法 #### 推荐算法概述 近年来,随着深度学习技术的发展,推荐系统迎来了新的变革。这些先进的模型可以从原始数据中自动提取特征,从而显著提升推荐系统的准确性和鲁棒性[^1]。 #### 数据预处理 为了构建有效的推荐系统,在实际应用前需对收集到的数据进行清洗和转换。这通常涉及去除噪声、填补缺失值以及标准化数值范围等操作。对于B2B场景而言,还需特别关注企业间的交易历史记录及其关联属性。 #### 特征工程 针对特定业务需求定制化地创建新变量来增强预测能力至关重要。例如,在分析供应商与采购商之间的关系时,可以考虑加入诸如合作年限、订单频率等因素作为额外维度;同时利用自然语言处理技术解析产品描述文档以获取更多语义层面的信息支持分类任务。 #### 模型选择 目前主流的方法包括但不限于基于矩阵分解的技术(如SVD++)、协同过滤法(User-based/Item-based CF)及神经网络架构下的序列建模方案(RNN/LSTM)。值得注意的是,当面对冷启动问题——即缺乏足够的交互行为样本用于初始化估计权重向量的情况下,则可尝试引入内容感知机制辅助决策制定过程。 #### 训练优化策略 考虑到计算资源消耗较大这一现实约束条件,建议采用增量式更新方式而非全量重新训练整个框架结构。此外,通过调整超参数配置文件中的正则项系数λ控制过拟合风险水平,并借助早停法则防止过度迭代造成性能下降现象发生。 #### 应用实例探讨 以某知名跨境电商为例,其成功实现了个性化商品推送服务并取得了良好反响。具体做法是在原有基础上融入了多源异构信息融合的思想,综合考量用户偏好标签体系、浏览轨迹日志流等多个方面因素共同作用下完成精准匹配工作。 ```python import pandas as pd from sklearn.model_selection import train_test_split from surprise import Dataset, Reader, SVDpp from surprise.model_selection import cross_validate # 加载数据集 data = pd.read_csv('transactions.csv') reader = Reader(rating_scale=(0, 5)) dataset = Dataset.load_from_df(data[['user_id', 'item_id', 'rating']], reader) # 划分训练集测试集 trainset, testset = train_test_split(dataset.build_full_trainset().build_testset(), test_size=0.2) # 初始化SVD++ algo = SVDpp() # 执行交叉验证评估指标表现情况 cross_validate(algo, dataset, measures=['RMSE'], cv=5, verbose=True) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值