一、简介

本文博主给我们讲解如安在自己开源的电商项目newbee-mall-pro中运用协同过滤算法来到达给用户更好的购物体会效果。

newbee-mall-pro项目地址:

  • 源码地址:github.com/wayn111/new…
  • 在线地址:http://121.4.124.33/newbeemall

二、协同过滤算法

协同过滤算法是一种依据用户或许物品的类似度来引荐产品的办法,它能够有用地处理商城体系中的信息过载问题。协同过滤算法的实践主要包括以下几个步骤:

  1. 数据搜集和预处理。这一步需求从商城体系中获取用户的行为数据,如阅读、购买、点评等,然后进行一些必要的清洗和转化,以便后续的分析和核算。
  2. 类似度核算。这一步需求依据用户或许物品的特征或许行为,选用适宜的类似度衡量办法,如余弦类似度、皮尔逊相联系数、Jaccard指数等,来核算用户之间或许物品之间的类似度矩阵。
  3. 引荐生成。这一步需求依据类似度矩阵和用户的前史行为,选用适宜的引荐策略,如依据邻域的办法、依据模型的办法、依据矩阵分化的办法等,来生成针对每个用户的个性化引荐列表。
  4. 引荐评价和优化。这一步需求依据一些点评方针,如准确率、召回率、覆盖率、多样性等,来评价引荐体系的效果,并依据反馈信息和事务需求,进行一些参数调整和算法优化,以进步引荐体系的功能和用户满意度。

在原有的商城主页为你引荐栏目是运用后台装备的产品列表,依据人为装备。在项目产品用户持续增长的情况下,不一定能给用户引荐用户或许想要的产品。

因而在v2.4.1版本中,商城主页为你引荐栏目添加了协同过滤算法。按照UserCF依据用户的协同过滤、ItemCF依据物品的协同过滤。完成了两种不同的引荐逻辑。

  • UserCF:依据用户的协同过滤。当一个用户A需求个性化引荐的时候,咱们能够先找到和他有类似爱好的其他用户,然后把那些用户喜爱的,而用户A没有听说过的物品引荐给A。

    推荐算法在商城系统实践
    假定用户 A 喜爱物品 A、物品 C,用户 B 喜爱物品 B,用户 C 喜爱物品 A、物品 C 和物品 D;从这些用户的前史喜爱信息中,咱们能够发现用户 A 和用户 C 的口味和偏好是比较类似的,同时用户 C 还喜爱物品 D,那么咱们能够推断用户 A 或许也喜爱物品 D,因而能够将物品 D 引荐给用户 A。详细代码在ltd.newbee.mall.recommend.core.UserCF中。

  • itemCF:依据物品的协同过滤。预先依据一切用户的前史偏好数据核算物品之间的类似度,然后把与用户喜爱的物品相类似的物品引荐给用户。

    推荐算法在商城系统实践
    假设用户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、点击率、查找率、下单产品排行等数据来按某种热度排序来引荐给用户。

总结

到这里,本文所分享引荐算法在商城体系实践就悉数介绍完了,期望对我们完成引荐体系落地有所帮助,喜爱的朋友们能够点赞加重视。

公众号【waynblog】每周更新博主最新技术文章,欢迎我们重视

本文正在参加「金石方案」