基于向量模型的推荐系统案例(java版本)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 基于向量模型的推荐系统案例(java版本)

我们在使用各类型的软件的时候,总是能在各大app中获取到推荐信息的数据,而且会发现推荐的信息数据还比较适合个人的口味,例如说某些共同兴趣爱好的好友推荐,某些好听的音乐推荐等等。


在进行推荐系统的核心算法介绍之前,我们需要先来回顾一下以前所学过的数学知识内容。


欧几里得距离


二维的欧几里得距离:


例如下图所示,在这样的一个简单的二维空间图里面,根据对于a点的坐标和b点的坐标进行二维空间距离的计算,假设p为点a到点b的欧式距离,那么可以根据勾股定理来计算出两点之间的向量距离为:


网络异常,图片无法展示
|

网络异常,图片无法展示
|


三维空间的欧几里得距离:


除了常见的二维空间之外,常用于的计算场景还有可能是基于三维空间运算的。


网络异常,图片无法展示
|


在这种场景下,假设计算A点和B点之间的距离为p,那么计算可以得出p的值为:


网络异常,图片无法展示
|


在了解了这些基本的知识点之后,我们再结合实际的应用场景来展开应用。

例如说一个电影影评网站,需要加入一个推荐喜欢观看同类电影的好友功能。


首先模拟出一个具体的数据场景:


网络异常,图片无法展示
|


1对该电影进行过评价,0没有对该电影进行过评价


有了这样的一个数据统计场景之后,我们可以根据对电影是否有共同评价进行共同兴趣爱好的匹配推荐。但是这种场景下也有一定的缺陷,那就是对于电影的评价有好有坏,需要将共同喜爱同一类电影的用户进行匹配推荐,将不喜欢同一类电影的用户进行匹配推荐就属于推荐失误的场景了。


改进点


在用户评论里面加入对于电影的打分功能,我们将打分等级也进行一个分类


网络异常,图片无法展示
|


那么我们将这里的打分等级和上述的电影评价相互结合之后便可得出下表:


网络异常,图片无法展示
|


根据上述的这张表,我们再回顾到本文开始时候所说的二维和三维空间里面的欧几里得距离计算。


假设A点的坐标为A(a1,a2…),B点坐标为B(b1,b2…)


二维空间距离计算:


网络异常,图片无法展示
|


三维空间距离计算:


网络异常,图片无法展示
|


类比一维、二维、三维的表示方法,n 维空间中的某个位置,我们可以写作(X1X1,X2X2,X3X3,…,XKXK)。这种表示方法我们称之为向量。


n维空间的距离计算:


网络异常,图片无法展示
|


那么集合上边的具体应用场景,我们便可以展开相应的计算了:


首先罗列出每个用户的空间坐标


小明(5,-1,-1,4,-1,-1,3,-1,1)(当前用户)

小王(4,-1,3,2,5,-1,-1,5,-1)

小东(-1,5,-1,-1,2,2,-1,-1,2)

小红(2,5,-1,3,3,-1,4,5,-1)

小乔(-1,-1,-1,-1,-1,-1,-1,5,-1)

小芳(-1,4,-1,3,3,5,5,-1,4)


然后再通过计算的时候,假设当前用户是小明,那么我们再进行用户匹配推荐的时候需要计算各个点和小明的欧几里得距离:


套用以下公式:


网络异常,图片无法展示
|


计算出小王和各个人之间的向量差值,值越小,即表示两者之间的相似度越高。

计算出来小王相对于小明的向量差为:


网络异常,图片无法展示
|


小东相对于小明的向量差为:


网络异常,图片无法展示
|


等等…


说了这么多,还是用实际的代码案例来进行讲解会好些

首先是 网站会员,电影信息,影评 三种基本模型


import lombok.AllArgsConstructor;
import lombok.Data;
/**
 * @author idea
 * @date 2019/5/4
 * @Version V1.0
 */
@Data
@AllArgsConstructor
public class MemberPO {
    private int id;
    private String memberName;
}
复制代码


import lombok.AllArgsConstructor;
import lombok.Data;
/**
 * @author idea
 * @date 2019/5/4
 * @Version V1.0
 */
@Data
@AllArgsConstructor
public class MoviePO {
    private int id;
    private String movieName;
}
复制代码


import lombok.AllArgsConstructor;
import lombok.Data;
/**
 * @author idea
 * @date 2019/5/4
 * @Version V1.0
 */
@Data
@AllArgsConstructor
public class MovieReviewPO {
    private int movieId;
    private int memberId;
    private int reviewScore;
}
复制代码


为了方便,这里的数据暂时用模拟的形式展示,忽略了从数据库读取的环节:


import com.sise.model.MoviePO;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
 * @author idea
 * @date 2019/5/4
 * @Version V1.0
 */
@Service
public class MovieService {
    public static List<MoviePO> MOVIE_LIST = new ArrayList<>();
    static {
        List<String> movieNames = Arrays.asList("绿皮书", "复仇者联盟", "月光男孩", "海边的曼彻斯特",
                "盗梦空间", "记忆碎片", "致命魔术", "流浪地球", "正义联盟");
        int id = 0;
        for (String movieName : movieNames) {
            MOVIE_LIST.add(new MoviePO(id++, movieName));
        }
    }
    /**
     * 根据名称获取用户信息
     *
     * @param name
     * @return
     */
    public MoviePO getMovieByName(String name) {
        return MOVIE_LIST.stream().filter(moviePO -> {
            return moviePO.getMovieName().equals(name);
        }).findFirst().get();
    }
}
复制代码


import com.sise.model.MemberPO;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
 * @author idea
 * @date 2019/5/4
 * @Version V1.0
 */
@Service
public class MemberService {
    public static List<MemberPO> MEMBER_LIST = new ArrayList<>();
    static {
        List<String> memberNameS = Arrays.asList("小明", "小王", "小东", "小红", "小乔", "小芳");
        int id = 0;
        for (String memberName : memberNameS) {
            MEMBER_LIST.add(new MemberPO(id++, memberName));
        }
    }
    /**
     * 根据名称获取用户信息
     *
     * @param name
     * @return
     */
    public MemberPO getMemberByName(String name) {
        return MEMBER_LIST.stream().filter(memberPO -> {
            return memberPO.getMemberName().equals(name);
        }).findFirst().get();
    }
}
复制代码


用户对电影打分的数据是存储在了Redis里面的,这里的为了方便,所以创建了一个mock使用的测试接口:


首先需要配置好SpringBoot和RedisTemplate,这部分的配置比较简单,这里暂时就先省略了。


电影评论service


import com.sise.model.MemberPO;
import com.sise.model.MoviePO;
import com.sise.model.MovieReviewPO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.*;
/**
 * @author idea
 * @date 2019/5/4
 * @Version V1.0
 */
@Service
@Slf4j
public class MovieReviewService {
    @Resource
    private RedisTemplate<String, MovieReviewPO> redisTemplate;
    public void mockData(MemberPO memberPO, MoviePO moviePO, Integer score) {
        Map<Object, Object> scoreMap = redisTemplate.opsForHash().entries(String.valueOf(memberPO.getId()));
        if (scoreMap == null) {
            scoreMap = new HashMap<>();
        }
        scoreMap.put(moviePO.getId(), score);
        redisTemplate.opsForHash().putAll(String.valueOf(memberPO.getId()), scoreMap);
        log.info("[MovieReviewService]保存信息成功!");
    }
    /**
     * 获取到list类型的统计数目
     *
     * @param memberId
     * @return
     */
    public List<Integer> getScoreList(int memberId) {
        Map<Object, Object> scoreMap = redisTemplate.opsForHash().entries(String.valueOf(memberId));
        List<Integer> result = new ArrayList();
        Map<Integer, Integer> sortMap = new TreeMap<Integer, Integer>(
                new Comparator<Integer>() {
                    @Override
                    public int compare(Integer obj1, Integer obj2) {
                        // 降序排序
                        return obj2.compareTo(obj1);
                    }
                });
        for (Object key : scoreMap.keySet()) {
            Integer movieIndex = (Integer) key;
            Integer score = (Integer) scoreMap.get(key);
            sortMap.put(movieIndex, score);
        }
        for (Object key : sortMap.keySet()) {
            result.add(sortMap.get(key));
        }
        return result;
    }
}
复制代码


然后是mock评论数据的接口


import com.sise.model.MemberPO;
import com.sise.model.MoviePO;
import com.sise.service.MemberService;
import com.sise.service.MovieReviewService;
import com.sise.service.MovieService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
 * @author idea
 * @date 2019/5/4
 * @Version V1.0
 */
@RestController
public class MockDataController {
    @Autowired
    private MovieReviewService movieReviewService;
    @Autowired
    private MemberService memberService;
    @Autowired
    private MovieService movieService;
    @GetMapping(value = "/mockData")
    public String mockData() {
        List<String> list =
                MovieService.MOVIE_LIST
                        .stream()
                        .map(moviePO -> moviePO.getMovieName())
                        .collect(Collectors.toList());
        //不同的用户打分程度匹配不一致
        List<Integer> score = Arrays.asList(-1, 4, -1, 3, 3, 5, 5, -1, 4);
        String name="小芳";
        int index = 0;
        for (String movieName : list) {
            this.mockData(name, movieName, score.get(index));
            index++;
        }
        return "success";
    }
    private void mockData(String memberName, String movieName, int score) {
        MemberPO memberPO = memberService.getMemberByName(memberName);
        MoviePO moviePO = movieService.getMovieByName(movieName);
        movieReviewService.mockData(memberPO, moviePO, score);
        System.out.println(memberPO.toString() + " " + moviePO.toString());
    }
}
复制代码


有了基本的测试数据之后,便可以来对核心的向量计算模块进行编写代码了:


import com.sise.model.MemberPO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
/**
 * 推荐的核心部分
 *
 * @author linhao
 * @date 2019/5/4
 * @Version V1.0
 */
@Service
public class RecommendService {
    @Autowired
    private MovieReviewService movieReviewService;
    /**
     * 计算两个用户之间的爱好相似度
     *
     * @param currentMemberId
     * @param compareMemberId
     * @return double  degree 相似度
     */
    public double countSimilarityDegree(int currentMemberId, int compareMemberId) {
        List<Integer> currentIndexList = movieReviewService.getScoreList(currentMemberId);
        List<Integer> compareMemberList = movieReviewService.getScoreList(compareMemberId);
        //两个人的评分统计是相同个数的
        if (currentIndexList.size() == compareMemberList.size()) {
            int total = MovieService.MOVIE_LIST.size();
            int result = 0;
            //计算向量的和
            for (int i = 0; i < total; i++) {
                int x1 = currentIndexList.get(i);
                int x2 = compareMemberList.get(i);
                result = result + (int) Math.pow((x1 - x2), 2);
            }
            double degree = Math.sqrt(result);
            return degree;
        }
        return 0;
    }
     /**
     * 计算爱好相似的用户 从高往底
     *
     * @param currentMemberId
     * @return List 
     */
    public List countSimilarityList(int currentMemberId) {
        List<Integer> idList = MemberService.MEMBER_LIST
                .stream()
                .filter(memberPO -> memberPO.getId() != currentMemberId)
                .map(MemberPO::getId)
                .collect(Collectors.toList());
        Map<Integer, Double> hashMap = new HashMap<>();
        for (Integer memberId : idList) {
            double degree = countSimilarityDegree(currentMemberId, memberId);
            hashMap.put(memberId, degree);
        }
        //这里将map.entrySet()转换成list
        List<Map.Entry<Integer, Double>> list = new ArrayList<>(hashMap.entrySet());
        //然后通过比较器来实现排序
        Collections.sort(list,new Comparator<Map.Entry<Integer, Double>>() {
            //升序排序
            @Override
            public int compare(Map.Entry<Integer, Double> o1,
                               Map.Entry<Integer, Double> o2) {
                return o2.getValue().compareTo(o1.getValue());
            }
        });
        return list;
    }
}
复制代码


测试所用的接口


import com.sise.service.RecommendService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * @author idea
 * @date 2019/5/4
 * @Version V1.0
 */
@RestController
public class RecommendController {
    @Autowired
    private RecommendService recommendService;
    @GetMapping(value = "count")
    public List countDegree(int curId) {
        return recommendService.countSimilarityList(curId);
    }
}
复制代码


通常我们会给用户相似度设置一个阈值,当相似程度超过该阈值的时候,就会被引入到好友推荐列表中做成推荐人名单。


推荐系统这个典型案例的思路让我们明白了向量的强大之处,这也是数据结构和算法所具有的魅力,利用向量空间来计算出欧几里得距离,从而解决掉如此复杂的问题。

上述的代码案例只能说是一个简单的模型,真实生产中的实践可要比这复杂得多,比如说针对于初期应用程序的基础数据量不足的情况下,使用这类方式来做推荐功能可能会有点牵强,因此还是需要在落地实践中不断的尝试和探索。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
1天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
8 2
|
6天前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
8 1
|
19天前
|
Java 数据库
案例一:去掉数据库某列中的所有英文,利用java正则表达式去做,核心:去掉字符串中的英文
这篇文章介绍了如何使用Java正则表达式从数据库某列中去除所有英文字符。
33 15
|
18天前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
【10月更文挑战第8天】本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
26 5
|
20天前
|
机器学习/深度学习 算法 搜索推荐
django调用矩阵分解推荐算法模型做推荐系统
django调用矩阵分解推荐算法模型做推荐系统
16 4
|
23天前
|
分布式计算 NoSQL Java
Hadoop-32 ZooKeeper 分布式锁问题 分布式锁Java实现 附带案例和实现思路代码
Hadoop-32 ZooKeeper 分布式锁问题 分布式锁Java实现 附带案例和实现思路代码
37 2
|
1天前
|
Java Linux Windows
如何查看已安装的 Java 版本
要查看已安装的 Java 版本,打开命令提示符或终端,输入 `java -version`,回车后即可显示当前系统中 Java 的版本信息。
|
1天前
|
Ubuntu Java Linux
如何检查 Java 版本是否兼容
要检查Java版本是否兼容,可在命令行输入“java -version”查看当前安装的Java版本,然后对比目标应用所需的Java版本,确保其满足要求。
|
15天前
|
Java Maven Spring
查看springboot版本支持最高的java版本
截至最近更新,Spring Boot 3.0及以上版本支持的最高Java版本为Java 17。鉴于技术的不断演进,建议直接参考Spring Boot的官方文档获取最准确的支持信息,因为这些版本兼容性可能会随着新版本的发布而有所变化。选择与你的Spring Boot版本相匹配的Java版本,可以确保充分利用框架特性,同时保证项目的稳定性和前瞻性。
28 0
|
20天前
|
机器学习/深度学习 搜索推荐 算法
推荐系统的矩阵分解和FM模型
推荐系统的矩阵分解和FM模型
17 0