商城-商品推荐算法

算法说明

综合用户浏览(权重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));
    }
}
<think>好的,我现在需要帮用户介绍如何用JavaScript封装商品推荐算法。首先,我需要明确用户的需求。他们可能想要了解推荐算法的基本概念以及在JS中的实现方法。可能用户是前端开发者,或者全栈,希望在自己的应用中集成推荐功能。 接下来,我应该考虑推荐算法的常见类型,比如基于内容的推荐、协同过滤、关联规则等。但用户提到的是封装,所以可能需要更具体的实现步骤,比如基于用户行为的协同过滤或者简单的相似度计算。 用户可能对算法原理不太熟悉,所以需要先简要解释推荐算法的基本思路,比如协同过滤中的用户-商品矩阵,或者基于商品相似度的推荐。然后,再给出具体的代码示例,说明如何用JS实现这些逻辑。 需要注意JS处理大数据时的性能问题,但可能用户的应用场景数据量不大,所以示例可以简化。比如使用欧氏距离或余弦相似度计算商品之间的相似度,然后根据用户的历史行为推荐相似商品。 还要考虑代码的结构,如何封装成函数或类,提高复用性。比如创建一个Recommendation类,包含计算相似度、生成推荐的方法。同时,数据结构的设计,比如商品特征数组或用户购买记录。 可能用户需要实际的代码示例,所以需要编写一段结构清晰、注释详细的JS代码,并解释关键部分,比如相似度计算函数、推荐逻辑等。同时,提醒注意事项,比如数据预处理、性能优化、冷启动问题等。 最后,确保回答符合之前的系统指令:使用正确的中文,数学公式用$...$,独立公式用$$...$$,结构清晰。检查是否有遗漏的要点,比如协同过滤的分类(用户基和物品基),或者不同的相似度计算方法。</think>让我们从JavaScript封装商品推荐算法的实现原理和步骤进行解析: 一、推荐算法核心原理 1. 常用算法类型: - 协同过滤(用户基/物品基) - 内容相似度推荐 - 关联规则(Apriori算法- 混合推荐模型 2. 核心数学公式: 物品相似度计算常用余弦相似度: $$ \text{similarity}(A,B) = \frac{\sum_{i=1}^{n} A_i \times B_i}{\sqrt{\sum_{i=1}^{n} A_i^2} \times \sqrt{\sum_{i=1}^{n} B_i^2}} $$ 二、JavaScript实现步骤 ```javascript class ProductRecommender { constructor(products, userBehavior) { this.products = products; // 商品特征数组 this.userBehavior = userBehavior; // 用户行为数据 } // 计算余弦相似度 cosineSimilarity(vecA, vecB) { const dotProduct = vecA.reduce((sum, a, i) => sum + a * vecB[i], 0); const magnitudeA = Math.sqrt(vecA.reduce((sum, a) => sum + a*a, 0)); const magnitudeB = Math.sqrt(vecB.reduce((sum, b) => sum + b*b, 0)); return dotProduct / (magnitudeA * magnitudeB); } // 生成推荐列表 generateRecommendations(targetProductId, count = 5) { const target = this.products.find(p => p.id === targetProductId); const similarities = this.products.map(product => ({ id: product.id, score: this.cosineSimilarity(target.features, product.features) })); return similarities .filter(item => item.id !== targetProductId) .sort((a, b) => b.score - a.score) .slice(0, count); } } ``` 三、实际应用示例 ```javascript // 商品特征数据(示例特征:价格、销量、评分、类别) const products = [ {id: 1, features: [199, 150, 4.5, 1]}, {id: 2, features: [299, 80, 4.8, 1]}, {id: 3, features: [599, 30, 4.2, 2]} ]; // 用户行为数据(浏览、收藏、购买记录) const userBehavior = { viewed: [1, 3], purchased: [2] }; // 使用示例 const recommender = new ProductRecommender(products, userBehavior); console.log(recommender.generateRecommendations(1)); ``` 四、算法优化方向 1. 特征工程: - 归一化处理:$$ x' = \frac{x - \min}{\max - \min} $$ - 特征加权(重要特征乘权重系数) 2. 混合推荐策略: ```javascript function hybridRecommendation(userId) { const cf = collaborativeFiltering(userId); const cb = contentBased(userId); return [...cf, ...cb].sort((a,b) => b.score - a.score); } ``` 五、注意事项 1. 冷启动问题处理: - 新用户推荐热门商品 - 新商品使用标签匹配推荐 2. 性能优化: ```javascript // 使用Web Worker进行离线计算 const worker = new Worker('recommendation-worker.js'); ``` 3. 实时性保障: - 建立用户行为事件监听 - 使用IndexedDB存储用户行为数据 六、扩展应用场景 1. 可集成推荐类型: ```javascript recommendBy = { similarProducts: (productId) => {...}, frequentlyBoughtTogether: (productId) => {...}, personalizedRecommend: (userId) => {...} } ``` 这种封装方式可以实现平均时间复杂度为$O(n)$的实时推荐,适用于中小型电商系统。实际部署时建议结合Redis等缓存技术优化计算效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值