推荐系统项目--自我学习篇

123 阅读4分钟

LiCooer得到了一个任务,要求是根据要求进行推荐系统的开发,那么现在因为LiCooer太笨了,所以想了很久这个应该怎么写

LiCooer的思路

首先我们需要写个entity类吧(DTO)

想一想,我们写个最基础推荐系统,里面需要有物品吧,那我们弄简单一点,我就要userid,groupid,index,在这里这个index我们理解为权重
所以我们写一个对应entity类,当然我们写成DTO类同样是ok的,当然DTO和entity是有一定区别的,这些可以在网络上面查到资料

/**
 * 功能:一个对应的DTO类
 * 作者:LiCooer
 * 日期:2024/5/14 16:30
 */
public class RelateDTO {
    /** 用户id */
    private Integer useId;
    /** 商品id */
    private Integer ResumesId;
    /** 指数 */
    private Integer index;

    public RelateDTO(Integer useId, Integer resumesId, Integer index) {
        this.useId = useId;
        this.resumesId = resumesId;
        this.index = index;
    }
}

然后我们就要写算法代码对吧

我们在这里解释个问题,算法在Java当中是个什么形式,我们都知道API对吧,其实算法是一样,我们投对应符合参数的输入,然后调用算法就行
然后我们开始写算法
在这里,这个是个方法

Account currentUser = TokenUtils.getCurrentUser();
if (ObjectUtil.isEmpty(currentUser)) {
    // 没有用户登录
    return new ArrayList<>();
}

在这里,我们这部分其实很简单,我们取一个当前使用用户,然后我们就可以开始分析,如果这个用户不存在我们就返回一个新数组

List<Collect> allCollects = collectMapper.selectAll(null);

然后我们写这个表示我们的收藏信息 如果有人收藏了这份简历,那我们就可以记录对应行为

List<Comment> allComments = commentMapper.selectAll(null);

这里是对应评论

List<User> allUsers = userMapper.selectAll(null);

这里是对应用户信息

List<resume> allResumes = resumesMapper.selectAll(null);

这里是我们对应简历信息

// 定义一个存储每个简历和每个用户关系的List
List<RelateDTO> data = new ArrayList<>();
// 定义一个存储最后返回给前端的简历List
List<Resume> result = new ArrayList<>();

这是我们存储数据的数组

// 开始计算每个简历和每个用户之间的关系数据
for (Resumes resumes : allRemuses) {
    Integer resumeId = resumes.getId();
    for (User user : allUsers) {
        Integer userId = user.getId();
        int index = 1;
        // 1. 判断该用户有没有收藏该简历,收藏的权重我们给 1
        Optional<Collect> collectOptional = allCollects.stream().filter(x -> x.getReumsesId().equals(ResumesId) && x.getUserId().equals(userId)).findFirst();
        if (collectOptional.isPresent()) {
            index += 1;
        }
        // 4. 判断该用户有没有对该简历评论过,评论的权重我们给 2
        Optional<Comment> commentOptional = allComments.stream().filter(x -> x.getResumesId().equals(resumesId) && x.getUserId().equals(userId)).findFirst();
        if (commentOptional.isPresent()) {
            index += 2;
        }
        if (index > 1) {
            RelateDTO relateDTO = new RelateDTO(userId, resumeId, index);
            data.add(relateDTO);
        }
    }
}

在这里,我们写出了整个算法最基础的样子,我们有简易计算进行分析,然后表示一个index
然后我们就要开始写推荐算法的语料

List<Integer> resumesIds = UserCF.recommend(currentUser.getId(), data);
// 把商品id转换成商品
List<Resumes> recommendResult = resumesIds.stream().map(resumesId -> allResumes.stream()
        .filter(x -> x.getId().equals(resumesId)).findFirst().orElse(null))
        .limit(10).collect(Collectors.toList());

return recommendResult;

写完上述代码以后我们就可以开始写CF(协同过滤算法)使用类,在这里,我们讲解的是最基础的协同过滤算法,如果后面我们一直持续学习我们会知道这个推荐系统是一个十分复杂的任务

/**
 * 功能:CF(协同过滤算法使用类)
 * 作者:LiCooer
 * 日期:2024/5/15 11:11
 */
public class UserCF {

    /**
     * 方法描述: 推荐简历id列表
     *
     * @param userId 当前用户
     * @param list   用户简历评分数据
     * @return {@link List<Integer>}
     */
    public static List<Integer> recommend(Integer userId, List<RelateDTO> list) {
        // 按用户分组
        Map<Integer, List<RelateDTO>> userMap = list.stream().collect(Collectors.groupingBy(RelateDTO::getUseId));
        // 获取其他用户与当前用户的关系值
        Map<Integer, Double> userDisMap = CoreMath.computeNeighbor(userId, userMap, 0);
        if (CollectionUtil.isEmpty(userDisMap)) {
            return Collections.emptyList();
        }
        // 获取关系最近的用户
        double maxValue = Collections.max(userDisMap.values());
        Set<Integer> userIds = userDisMap.entrySet().stream().filter(e -> e.getValue() == maxValue).map(Map.Entry::getKey).collect(Collectors.toSet());
        // 取关系最近的用户
        Integer nearestUserId = userIds.stream().findAny().orElse(null);
        if (nearestUserId == null) {
            return Collections.emptyList();
        }
        // 最近邻用户看过简历列表
        List<Integer> neighborItems = userMap.get(nearestUserId).stream().map(RelateDTO::getResumesId).collect(Collectors.toList());
        // 指定用户看过简历列表
        List<Integer> userItems = userMap.get(userId).stream().map(RelateDTO::getGoodsId).collect(Collectors.toList());
        // 找到最近邻看过,但是该用户没看过的简历
        neighborItems.removeAll(userItems);
        return neighborItems;
    }
}
    

然后我们写最核心的算法,怎么计算他们之间相似度呢?

public class CoreMath {


    /**
     * 计算相关系数并排序
     */
    public static Map<Integer, Double> computeNeighbor(Integer key, Map<Integer, List<RelateDTO>> map, int type) {
        Map<Integer, Double> distMap = new TreeMap<>();
        List<RelateDTO> userItems = map.get(key);
        if (CollectionUtil.isNotEmpty(userItems)) {
            map.forEach((k, v) -> {
                //排除此用户
                if (!k.equals(key)) {
                    //关系系数
                    double coefficient = relateDist(v, userItems, type);
                    //关系距离
                    double distance = Math.abs(coefficient);
                    distMap.put(k, distance);
                }
            });
        }
        return distMap;
    }


    /**
     * 计算两个序列间的相关系数
     */
    private static double relateDist(List<RelateDTO> xList, List<RelateDTO> yList, int type) {
        List<Integer> xs = new ArrayList<>();
        List<Integer> ys = new ArrayList<>();
        xList.forEach(x -> yList.forEach(y -> {
            if (type == 0) {
                if (x.getGoodsId().equals(y.getGoodsId())) {
                    xs.add(x.getIndex());
                    ys.add(y.getIndex());
                }
            } else {
                if (x.getUseId().equals(y.getUseId())) {
                    xs.add(x.getIndex());
                    ys.add(y.getIndex());
                }
            }
        }));
        return getRelate(xs, ys);
    }

    /**
     * 方法描述: 皮尔森(pearson)相关系数计算
     * @param xs x集合
     * @param ys y集合
     * @Return {@link double}
     */
    public static double getRelate(List<Integer> xs, List<Integer> ys) {
        int n = xs.size();
        //至少有两个元素
        if (n < 2) {
            return 0D;
        }
        double Ex = xs.stream().mapToDouble(x -> x).sum();
        double Ey = ys.stream().mapToDouble(y -> y).sum();
        double Ex2 = xs.stream().mapToDouble(x -> Math.pow(x, 2)).sum();
        double Ey2 = ys.stream().mapToDouble(y -> Math.pow(y, 2)).sum();
        double Exy = IntStream.range(0, n).mapToDouble(i -> xs.get(i) * ys.get(i)).sum();
        double numerator = Exy - Ex * Ey / n;
        double denominator = Math.sqrt((Ex2 - Math.pow(Ex, 2) / n) * (Ey2 - Math.pow(Ey, 2) / n));
        if (denominator == 0) {
            return 0D;
        }
        return numerator / denominator;
    }

}

在上面,我们用了对应的算法方式去进行求解,这里需要有对应了解的前置知识,就是协同过滤的算法数学支持,这个我们后面会讲
注:本文仅供自己学习了解,不做任何商业用途,本文参照B站学习武哥聊编程