从技术角度分析推荐系统案例

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 从技术角度分析推荐系统案例


1.Java 性能优化:教你提高代码运行的效率


2. Java问题排查工具清单


3.记住:永远不要在MySQL中使用UTF-8


4. Springboot启动原理解析


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


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


欧几里得距离


二维的欧几里得距离:


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


image.png



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

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


image.png


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


image.png


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


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


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


image.png


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


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


改进点


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


image.png


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


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


二维空间距离计算:


image.png


三维空间距离计算:


image.png


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

n维空间的距离计算:



image.png


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


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


小明(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)


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


套用以下公式:


image.png


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

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


image.png


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


image.png


等等….


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

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

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
目录
相关文章
|
4月前
|
人工智能 自然语言处理 NoSQL
对谈Concured首席技术官:利用AI和MongoDB打造个性化内容推荐系统
内容无处不在。无论消费者寻找什么或所处任何行业,找到内容并不困难;关键在于如何找到对应的内容。
1638 0
|
11月前
|
存储 监控 搜索推荐
【业务架构】业务驱动的推荐系统相关技术总结
【业务架构】业务驱动的推荐系统相关技术总结
93 0
|
1月前
|
搜索推荐 前端开发 数据可视化
【优秀python web毕设案例】基于协同过滤算法的酒店推荐系统,django框架+bootstrap前端+echarts可视化,有后台有爬虫
本文介绍了一个基于Django框架、协同过滤算法、ECharts数据可视化以及Bootstrap前端技术的酒店推荐系统,该系统通过用户行为分析和推荐算法优化,提供个性化的酒店推荐和直观的数据展示,以提升用户体验。
|
4月前
|
机器学习/深度学习 数据采集 人工智能
构建一个基于AI的推荐系统的技术探索
【5月更文挑战第23天】本文探讨了构建基于AI的推荐系统的关键技术,包括数据收集、预处理、特征工程、推荐算法(如协同过滤、内容过滤、深度学习)及结果评估。通过理解用户行为和偏好,推荐系统能提供个性化建议。实现步骤涉及确定业务需求、设计数据方案、预处理、算法选择、评估优化及系统部署。随着技术进步,未来推荐系统将更加智能。
|
18天前
|
数据采集 机器学习/深度学习 人工智能
利用AI技术实现个性化新闻推荐系统
【8月更文挑战第31天】 本文将介绍如何利用AI技术实现一个个性化的新闻推荐系统。我们将使用Python语言和一些常用的机器学习库,如scikit-learn和pandas,来构建一个简单的推荐系统。这个系统可以根据用户的阅读历史和兴趣偏好,为他们推荐相关的新闻文章。我们将从数据预处理、特征提取、模型训练和结果评估等方面进行详细的讲解。
|
2月前
|
机器学习/深度学习 搜索推荐 算法
深度学习在推荐系统中的应用:技术解析与实践
【7月更文挑战第6天】深度学习在推荐系统中的应用为推荐算法的发展带来了新的机遇和挑战。通过深入理解深度学习的技术原理和应用场景,并结合具体的实践案例,我们可以更好地构建高效、准确的推荐系统,为用户提供更加个性化的推荐服务。
|
2月前
|
存储 搜索推荐 算法
`surprise`是一个用于构建和分析推荐系统的Python库。
`surprise`是一个用于构建和分析推荐系统的Python库。
|
3月前
|
机器学习/深度学习 人工智能 搜索推荐
构建基于AI的个性化新闻推荐系统:技术探索与实践
【6月更文挑战第5天】构建基于AI的个性化新闻推荐系统,通过数据预处理、用户画像构建、特征提取、推荐算法设计及结果评估优化,解决信息爆炸时代用户筛选新闻的难题。系统关键点包括:数据清洗、用户兴趣分析、表示学习、内容及协同过滤推荐。实践案例证明,结合深度学习的推荐系统能提升用户体验,未来系统将更智能、个性化。
|
4月前
|
算法 搜索推荐 数据挖掘
MATLAB模糊C均值聚类FCM改进的推荐系统协同过滤算法分析MovieLens电影数据集
MATLAB模糊C均值聚类FCM改进的推荐系统协同过滤算法分析MovieLens电影数据集
|
10月前
|
缓存 搜索推荐 NoSQL
150 混合推荐系统案例(项目开发)
150 混合推荐系统案例(项目开发)
78 0