基于向量模型的推荐系统案例(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
目录
相关文章
|
18天前
|
Java Linux Windows
如何查看已安装的 Java 版本
要查看已安装的 Java 版本,打开命令提示符或终端,输入 `java -version`,回车后即可显示当前系统中 Java 的版本信息。
|
18天前
|
Ubuntu Java Linux
如何检查 Java 版本是否兼容
要检查Java版本是否兼容,可在命令行输入“java -version”查看当前安装的Java版本,然后对比目标应用所需的Java版本,确保其满足要求。
|
1月前
|
机器学习/深度学习 搜索推荐 大数据
深度解析:如何通过精妙的特征工程与创新模型结构大幅提升推荐系统中的召回率,带你一步步攻克大数据检索难题
【10月更文挑战第2天】在处理大规模数据集的推荐系统项目时,提高检索模型的召回率成为关键挑战。本文分享了通过改进特征工程(如加入用户活跃时段和物品相似度)和优化模型结构(引入注意力机制)来提升召回率的具体策略与实现代码。严格的A/B测试验证了新模型的有效性,为改善用户体验奠定了基础。这次实践加深了对特征工程与模型优化的理解,并为未来的技术探索提供了方向。
92 2
深度解析:如何通过精妙的特征工程与创新模型结构大幅提升推荐系统中的召回率,带你一步步攻克大数据检索难题
|
1月前
|
缓存 Java Maven
java: 警告: 源发行版 11 需要目标发行版 11 无效的目标发行版: 11 jdk版本不符,项目jdk版本为其他版本
如何解决Java项目中因JDK版本不匹配导致的编译错误,包括修改`pom.xml`文件、调整项目结构、设置Maven和JDK版本,以及清理缓存和重启IDEA。
46 1
java: 警告: 源发行版 11 需要目标发行版 11 无效的目标发行版: 11 jdk版本不符,项目jdk版本为其他版本
|
23天前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
15 1
|
1月前
|
机器学习/深度学习 算法 搜索推荐
django调用矩阵分解推荐算法模型做推荐系统
django调用矩阵分解推荐算法模型做推荐系统
26 4
|
1月前
|
Java Docker 容器
java版本学习网站又添加了一个libgdx模块
java版本学习网站之前添加了docker,想了想还是再把libgdx添加进去吧。
29 3
|
1月前
|
机器学习/深度学习 搜索推荐 TensorFlow
使用Python实现智能电子商务推荐系统:深度学习模型详解
使用Python实现智能电子商务推荐系统:深度学习模型详解
170 4
|
1月前
|
Java Maven Spring
查看springboot版本支持最高的java版本
截至最近更新,Spring Boot 3.0及以上版本支持的最高Java版本为Java 17。鉴于技术的不断演进,建议直接参考Spring Boot的官方文档获取最准确的支持信息,因为这些版本兼容性可能会随着新版本的发布而有所变化。选择与你的Spring Boot版本相匹配的Java版本,可以确保充分利用框架特性,同时保证项目的稳定性和前瞻性。
46 0
|
2月前
|
Java
java版本详解
java版本详解

热门文章

最新文章