传统推荐算法库使用--mahout初体验

news2024/11/27 22:36:59

文章目录

  • 前言
  • 环境准备
  • 调用
  • 混合
  • 总结

前言

郑重声明:本博文做法仅限毕设糊弄老师使用,不建议生产环境使用!!!
老项目缝缝补补又是三年,本来是打算直接重写写个社区然后给毕设使用的。但是怎么说呢,毕竟毕设的主角不是xx社区,这个社区是为我的编译器服务的,为了推广这个编译器,然后我才做了这个社区。然而不幸的是,开题答辩的时候,各位“专家”叫我以xx社区为主,听起来高级。于是没有办法,我只能强行做个社区,怎么做呢,照着以前写的社区抄,换个主题呗。但是重新写的成本太高了(一开始我是嫌弃白洞这个项目的部署成本比较高,因为里面确实集成了很多模块,有AI模块有传统微服务模块,当然开源的版本是没有这些东西的,毕竟还是要留点底裤的),但是重写实在难受,找了一圈想要找个开源的,结果都没有找到满意的,没办法,只能把白洞项目拿出来,然后做减法,加一个推荐系统。

推荐系统本来也是打算直接基于Java重写手写一个的,直接写个基于协同滤波的传统推荐算法。但是感谢开源,发现了个牛逼的框架mahout。这不就齐活了,我们直接糊弄糊弄毕设过去了就行了。借用某位大哥的话:你要搞清楚你的目的是什么,没有效益的事情少干。于是鄙人放弃了手写推荐系统,放弃了对netty重新封装。咱们有技术积累,但是没有能够产生实际效益的项目,所以不干,糊弄老师得了。

环境准备

这里的话,因为是糊弄毕设,所以我们是直接冷启动。用的是ItemCF,直接推荐博客。然后呢从100个用户里面数据里面推荐就行了,然后结果缓存起来,一天一推。多了没有,反正我用了这个玩意儿,现场查代码也没事,况且数据量根本就不够。

<!--        mahout推荐系统-->
        <dependency>
            <groupId>org.apache.mahout</groupId>
            <artifactId>mahout-mr</artifactId>
            <version>0.12.2</version>
        </dependency>

导入依赖先。

创建记录表:

CREATE TABLE `user_article_operation` (
	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
	`create_time` DATETIME NOT NULL COMMENT '操作时间,我们默认抓取比较新的数据来进行统计',
	`userid` BIGINT(20) NULL DEFAULT NULL,
	`article_id` BIGINT(20) NULL DEFAULT NULL,
	`operation` INT(11) NULL DEFAULT NULL COMMENT '0-点赞,1-收藏,2-fork(不同的类型,不同的评分)',
	PRIMARY KEY (`id`) USING BTREE,
	INDEX `key` (`userid`, `article_id`, `operation`) USING BTREE
)
COMMENT='用户对文章的操作表'
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
;

这里的话,我使用的是mybatis-plus创建对应的dao和mapper(这里会使用到比较复杂的sql,得手写)


@Data
@TableName("user_article_operation")
public class BlogRe {

    @TableId(value = "id",type = IdType.AUTO)
    private Long id;

    private Long userid;

    private Long articleId;

    private Integer operation;

    private Date createTime;

    @TableField(exist = false)
    private Integer value;

}

对应的xml文件是:


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">


    <mapper namespace="com.huterox.whitehole.whiteholeblog.dao.BlogReDao">

    <!--sql-->
    <select id="getAllUserPreference" resultType="com.huterox.whitehole.whiteholeblog.entity.surface.blogRe.BlogRe">
        SELECT
            userid,
            article_id,
            SUM(
                    CASE operation_type
                        WHEN 0 THEN 2
                        WHEN 1 THEN 3
                        WHEN 2 THEN 5
                        else 0 END
                ) AS "value"
        FROM
            user_article_operation
        GROUP BY user_id,article_id
        limit 100
    </select>
    </mapper>

调用

基本的环境准备好了,我们就得调用了。
这里的我的逻辑是,当用户登录了有数据,那么我就直接推荐,如果没有那就继续走默认,也就是按照热度进行推荐。
在我的项目里面最终是定位到了这里:
在这里插入图片描述
具体的推荐逻辑是这里:
在这里插入图片描述
所以我们在这里重点关注这里的实现就可以:

@Service
public class BlogReServiceImpl implements BlogReService {
    
    @Autowired
    BlogReDao blogReDao;

    @Autowired
    BlogReUserIdDao blogReUserIdDao;
    
    @Override
    public List<Long> recommend(String userId) throws TasteException {
        //注意这里我们限制了100个,我们从100个数据里面去拿到,然后做推荐
        List<BlogRe> userList = blogReDao.getAllUserPreference();
        //创建数据模型
        DataModel dataModel = this.createDataModel(userList);
        //获取用户相似程度
        UserSimilarity similarity = new UncenteredCosineSimilarity(dataModel);
        //获取用户邻居
        UserNeighborhood userNeighborhood = new NearestNUserNeighborhood(2, similarity, dataModel);
        //构建推荐器
        Recommender recommender = new GenericUserBasedRecommender(dataModel, userNeighborhood, similarity);
        //推荐2个
        BlogReUserId userMap = blogReUserIdDao.selectOne(new QueryWrapper<BlogReUserId>().eq("userid", userId));
        List<RecommendedItem> recommendedItems = recommender.recommend(userMap.getId(), 2);
        List<Long> itemIds = recommendedItems.stream().map(RecommendedItem::getItemID).collect(Collectors.toList());
        return itemIds;
    }

    private DataModel createDataModel(List<BlogRe> userArticleOperations) {
        FastByIDMap<PreferenceArray> fastByIdMap = new FastByIDMap<>();
        Map<Long, List<BlogRe>> map = userArticleOperations.stream().collect(Collectors.groupingBy(BlogRe::getUserid));
        Collection<List<BlogRe>> list = map.values();
        for (List<BlogRe> userPreferences : list) {
            GenericPreference[] array = new GenericPreference[userPreferences.size()];
            for (int i = 0; i < userPreferences.size(); i++) {
                BlogRe userPreference = userPreferences.get(i);
                GenericPreference item = new GenericPreference(userPreference.getUserid(), userPreference.getArticleId(), userPreference.getValue());
                array[i] = item;
            }
            fastByIdMap.put(array[0].getUserID(), new GenericUserPreferenceArray(Arrays.asList(array)));
        }
        return new GenericDataModel(fastByIdMap);
    }

}

这里写得很清楚了,当然具体的算法原理也不难,可以去翻翻我往期的博文。有Python手撸的版本。加上几个数据源设配器也能直接用了。核心算法原理很简单,不会就问GPT,只要数学没啥问题就懂,不懂,那就直接调用API也挺好。

这里注意的是:
这里要求用户ID是Long类型。
在这里插入图片描述
所以如果你和我的项目一样用户ID用的不是雪花这种算法,而是UUID,那么你得搞个中间的转换表。我这里没辙,所以只能强行加一个转换表:
在这里插入图片描述
当然我们这里还得记录操作。

        //记录一下操作
        BlogReUserId userMap = blogReUserIdDao.selectOne(new QueryWrapper<BlogReUserId>().eq("userid", userid));
        if(userMap==null){
            BlogReUserId blogReUserId = new BlogReUserId();
            blogReUserId.setUserid(userid);
            blogReUserIdDao.insert(blogReUserId);
        }
        BlogRe blogRe = new BlogRe();
        assert userMap != null;
        blogRe.setUserid(userMap.getId());
        blogRe.setArticleId(blogid);
        blogRe.setCreateTime(new Date());
        blogRe.setOperation(0);
        blogReDao.insert(blogRe);

这里看实际情况,反正我这就先这样操作了。

混合

之后的话就是做混合了
在我这里是直接这样了:

    @Override
    public PageUtils queryPageWithRem(Map<String, Object> params) throws Exception {

        //这里是携带推荐系统的
        PageUtils pageUtils = this.queryPage(params);
        if(params.get("rem").equals("1")){
            //触发满足使用推荐系统条件使用推荐系统
            if (params.get("userid")!=null){
                List<Long> blogIds = blogReService.recommend((String) params.get("userid"));
                List<BlogEntity> blogEntityList = this.list(new QueryWrapper<BlogEntity>().in("blogid", blogIds));
                //这个是按照热度推荐的
                List<BlogEntity> list = (List<BlogEntity>) pageUtils.getList();
                //将两者混合
                list.addAll(blogEntityList);
                pageUtils.setPageSize(list.size());
                pageUtils.setTotalCount(list.size());
            }
        }
        return pageUtils;
    }

数据不够的话可能推荐的数据是空的,所以得混合。之后缓存的话,是我直接在这个项目当中使用了SpringCache。当然最近搞项目的时候,我自己直接基于SpringAop写了个缓存注解实现,项目要求比较灵活,直接手写一个快。

总结

新年快乐~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1448546.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JavaScript中的Symbol:加密与安全性

JavaScript中的Symbol是一种唯一且不可变的数据类型&#xff0c;引入了一种新的基本数据类型&#xff0c;用于表示独一无二的标识符。在本文中&#xff0c;我们将深入介绍JavaScript中的Symbol&#xff0c;讨论如何将其应用于JS加密中&#xff0c;提供案例代码&#xff0c;并说…

每日一题(数字颠倒,单词倒排)

数字颠倒_牛客题霸_牛客网 (nowcoder.com) #include <stdio.h>int main() {char arr[100];gets(arr);int lenstrlen(arr);for(int ilen-1;i>0;i--){printf("%c",arr[i]);}return 0; } 单词倒排_牛客题霸_牛客网 (nowcoder.com) #include <stdio.h> #…

PLC_博图系列☞参数实例

PLC_博图系列☞参数实例 文章目录 PLC_博图系列☞参数实例背景介绍参数实例参数实例的工作原理创建参数实例将实例作为参数传送 关键字&#xff1a; PLC、 西门子、 博图、 Siemens 、 参数实例 背景介绍 这是一篇关于PLC编程的文章&#xff0c;特别是关于西门子的博图软件…

如何使用idea连通服务器上的Redis(详细版本)

这里我使用的是阿里云的服务器 打开阿里云的安全组&#xff0c;设置端口为6379 在redis.conf文件中&#xff0c;注释bind 127.0.0.1 将protected-mode设置为no&#xff0c;即关闭保护模式 更改服务器中的防火墙&#xff0c;放行6379端口 # 放行端口 firewall-cmd --zo…

Three.js学习7:dat.GUI 参数控制

每个学 Three.js 的都被安利了 dat.GUI 吧&#xff1f; 我也不例外&#xff01; 今天就来了学习下 dat.GUI&#xff0c;并记录下来自己的学习成果。 一、什么是 dat.GUI? dat.GUI 是一个轻量级的图形用户界面库&#xff08;GUI 组件&#xff09;&#xff0c;使用这个库可以…

【Zigbee课程设计系列文章】Zigbee开发环境搭建

【Zigbee课程设计系列文章】Zigbee开发环境搭建 前言IAR 下载安装Z-Stack协议栈安装 &#x1f38a;项目专栏&#xff1a;【Zigbee课程设计系列文章】&#xff08;附详细使用教程完整代码原理图完整课设报告&#xff09; 前言 &#x1f451;由于无线传感器网络&#xff08;也即…

C++提高编程(黑马笔记)

C提高编程 模版 特点&#xff1a; 只是一个框架&#xff0c;不可以直接使用通用并不是万能的 泛型主要利用模版 函数模版 语法&#xff1a; template<typename T> 函数# include<iostream> using namespace std;template<typename T> void MySwap(T&a…

unity 点击事件

目录 点击按钮&#xff0c;显示图片功能教程 第1步添加ui button&#xff0c;添加ui RawImage 第2步 添加脚本&#xff1a; 第3步&#xff0c;把脚本拖拽到button&#xff0c;点击button&#xff0c;设置脚本的变量&#xff0c; GameObject添加 Component组件 点击按钮&am…

Vue学习笔记(三)常用指令、生命周期

Vue学习笔记&#xff08;三&#xff09;常用指令 vue指令&#xff1a;html标签上带有 v- 前缀的特殊属性&#xff0c;不同的指令具有不同的含义&#xff0c;可以实现不同的功能。 常用指令&#xff1a; 指令作用v-for列表渲染&#xff0c;遍历容器的元素或者对象的属性v-bind…

例39:使用List控件

建立一个EXE工程&#xff0c;在窗体上放一个文本框&#xff0c;一个列表框和三个按钮输入如下的代码&#xff1a; Sub Form1_Command1_BN_Clicked(hWndForm As hWnd, hWndControl As hWnd)List1.AddItem(Text1.Text)End SubSub Form1_Command2_BN_Clicked(hWndForm As hWnd, h…

【数据结构】链表OJ面试题5《链表的深度拷贝》(题库+解析)

1.前言 前五题在这http://t.csdnimg.cn/UeggB 后三题在这http://t.csdnimg.cn/gbohQ 给定一个链表&#xff0c;判断链表中是否有环。http://t.csdnimg.cn/Rcdyc 给定一个链表&#xff0c;返回链表开始入环的第一个结点。 如果链表无环&#xff0c;则返回 NULLhttp://t.cs…

电路设计(17)——乒乓球竞赛计分仪的proteus仿真

1.设计要求 设计、制作一台包括有“抛币选边”&#xff1b;“热身定时”&#xff1b;竞赛计分等电路的乒乓球竞赛计分仪&#xff0c;要求在同一块万能板上完成制作。 乒乓球开赛前&#xff0c;裁判员会叫双方有关运动员到身边商定采用抛硬币办法选边&#xff08;或选先发球&…

【开源】SpringBoot框架开发食品生产管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 加工厂管理模块2.2 客户管理模块2.3 食品管理模块2.4 生产销售订单管理模块2.5 系统管理模块2.6 其他管理模块 三、系统展示四、核心代码4.1 查询食品4.2 查询加工厂4.3 新增生产订单4.4 新增销售订单4.5 查询客户 五、…

【Web】NSSCTF Round#18 Basic个人wp(部分)

目录 ①门酱想玩什么呢&#xff1f; ②Becomeroot ①门酱想玩什么呢&#xff1f; 先试一下随便给个链接 不能访问远程链接&#xff0c;结合评论区功能&#xff0c;不难联想到xss&#xff0c;只要给个评论区链接让门酱访问就可 我们研究下评论区 从评论区知道&#xff0c;要…

【JavaEE】----SpringBoot的创建和使用

目录 1.Spring与SpringBoot的区别&#xff08;面试&#xff09; 2. SpringBoot的创建 3.SpringBoot创建时的问题及解决 4.SpringBoot的目录学习 5.创建一个SpringBoot 项目并且启动 1.Spring与SpringBoot的区别&#xff08;面试&#xff09; Spring 的诞⽣是为了简化 Java 程…

【一周年】我的创作纪念日

今天&#xff0c;是我成为创作者的第366天&#xff0c;不知不觉&#xff0c;来CSDN已经一年啦~ 在这个特殊的日子&#xff0c;也给大家讲讲我的创作故事。 一、机缘 起初&#xff0c;刚认识CSDN时&#xff0c;我的高中生涯刚结束&#xff0c;顺利从一名懵懂的高中生变身为一名懵…

JavaScript中的常见算法

一.排序算法 1.冒泡排序 冒泡排序比较所有相邻的两个项&#xff0c;如果第一个比第二个大&#xff0c;则交换它们。元素项向上移动至 正确的顺序&#xff0c;就好像气泡升至表面一样。 function bubbleSort(arr) {const { length } arrfor (let i 0; i < length - 1; i)…

C语言—数组一维(含冒泡排序)

1.用数组存储10个整型数&#xff0c;通过键盘输入一个数&#xff0c;找出该数在数组中的下标值&#xff1b; /*1.用数组存储10个整型数&#xff0c;通过键盘输入一个数&#xff0c;找出该数在数组中的下标值&#xff1b;*/#include <stdio.h>int main(void) {int nums[…

静态时序分析:SDC约束命令set_clock_uncertainty

相关阅读 静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html?spm1001.2014.3001.5482 set_clock_uncertainty是用来指定设计中时钟周期的不确定性&#xff0c;不确定性指的是对那些会对时钟周期造成的负面影响。这些不确定性可能来源于时钟抖动(clo…

mysql8.0.36主从复制(读写分离)配置教程

1、关闭防火墙 使用命令行关闭防火墙 在Ubuntu系统中&#xff0c;可以使用以下命令关闭防火墙&#xff1a; sudo ufw disable执行该命令后&#xff0c;系统会提示是否要关闭防火墙&#xff0c;确认后即可关闭防火墙。 查看防火墙状态 使用以下命令可以查看防火墙当前的状…