2022-10-31-基于用户的协同过滤推荐算法实现+MAE+RMSE的求解+项目代码+运行结果图(一)

简介: 2022-10-31-基于用户的协同过滤推荐算法实现+MAE+RMSE的求解+项目代码+运行结果图

推荐算法学习笔记

协同过滤推荐算法测评指标RMSE均方根误差

推荐系统笔记:

一、为什么需要推荐系统

为了解决互联网时代下的信息超载问题。

二、搜索引擎和推荐系统的区别

· 分类目录,是将著名网站分门别类,从而方便用户根据类别查找公司。

· 搜索引擎,用户通过输入关键字,查找自己需要的信息。

· 推荐系统,和搜索引擎一样,是一种帮助用户快速发展有用信息的工具。通过分析用户的历史行为,给用户的兴趣建模,从而主动给用户推荐能够满足他们兴趣和需求的信息。并且,推荐系统能够很好的发掘物品的长尾,挑战传统的2/8原则(80%的销售额来自20%的热门品牌)。

三、技术角度来看,搜索引擎和推荐系统的区别

1)搜索引擎,注重搜索结果之间的关系和排序;

2)推荐系统,需要研究用户的兴趣模型,利用社交网络的信息进行个性化的计算;

3)搜索引擎,由用户主导,需要输入关键词,自行选择结果。如果结果不满意,需要修改关键词,再次搜索;

4)推荐系统,由系统主导,根据用户的浏览顺序,引导用户发现自己感兴趣的信息;

四、推荐系统的定义

推荐系统通过发掘用户的行为,找到用户的个性化需求,从而将长尾物品准确推荐给需要它的用户,帮助用户找到他们感兴趣但很难发现的物品。

高质量的推荐系统会使用户对系统产生依赖,因此,推荐系统不仅能为用户提供个性化服务,还能与用户建立长期稳定的关系,提高用户忠诚度,防止用户流失。

五、推荐系统评测

一般推荐系统的参与方有3个:

  • 用户
  • 物品提供商
  • 推荐系统提供网站

因此,评测一个推荐系统时,需要考虑3方的利益,一个好的推荐系统是能够令三方共赢的系统。

六、三个实验方法的优缺点

1)离线实验

离线实验的方法的步骤如下:

a)通过日志系统获得用户行为数据,并按照一定格式生成一个标准的数据集;

b)将数据集按照一定的规则分成训练集和测试集;

c)在训练集上训练用户兴趣模型,在测试集上进行预测;

d)通过事先定义的离线指标,评测算法在测试集上的预测结果。

从以上步骤看出,离线实验的都是在数据集上完成的。意味着,它不需要一个实际的系统作为支撑,只需要有一个从日志中提取的数据集即可。

离线实验的优点是:

不需要有对实际系统的控制权;

不需要用户参与实践;

速度快,可以测试大量算法;

缺点是:

数据集的稀疏性限制了适用范围,例如一个数据集中没有包含某用户的历史行为,则无法评价对该用户的推荐结果;

评价结果的客观性,无法得到用户主观性的评价;

难以找到离线评价指标和在线真实反馈(如 点击率、转化率、点击深度、购买客单价、购买商 品类别等)之间的关联关系;

2)用户调查

用户调查需要一些真实的用户,让他们在需要测试的推荐系统上完成一些任务。在他们完成任务时,需要观察和记录用户的行为,并让他们回答一些问题。

最后,我们通过分析他们的行为和答案,了解测试系统的性能。

用户调查的优点是:

可以获得用户主观感受的指标,出错后容易弥补;

缺点是:

招募测试用户代价较大;

无法组织大规模的测试用户,统计意义不足;

3)在线实验

在完成离线实验和用户调查之后,可以将系统上线做AB测试,将它和旧算法进行比较。

在线实验最常用的评测算法是【A/B测试】,它通过一定的规则将用户随机分成几组,对不同组的用户采用不同的算法,然后通过统计不同组的评测指标,比较不同算法的好坏。

它的核心思想是:

a) 多个方案并行测试;

b) 每个方案只有一个变量不同;

c) 以某种规则优胜劣汰。

其中第2点暗示了A/B 测试的应用范围:A/B测试必须是单变量。

对于推荐系统的评价中,唯一变量就是–推荐算法

AB测试的优点是:

可以公平获得不同算法实际在线时的性能指标,包括商业上关注的指标;

缺点是:

周期较长,必须进行长期的实验才能得到可靠的结果;

大型网站做AB测试,可能会因为不同团队同时进行各种测试对结果造成干扰,所以切分流量是AB测试中的关键。

不同的层以及控制这些层的团队,需要从一个统一的地方获得自己AB测试的流量,而不同层之间的流量应该是正交的。

总结:一般来说,一个新的推荐算法最终上线,需要完成上述的3个实验。

  1. 首先,通过离线实验证明它在很多离线指标上优于现有的算法;
  2. 其次,通过用户调查确定用户满意度不低于现有的算法;
  3. 最后,通过在线AB测试确定它在我们关心的指标上优于现有的算法;

关于均方根误差(RMSE):

RMSE是一个重要的预测评分的准确度指标,所谓预测评分的准确度衡量的是算法评测的评分与用户的实际评分之间的贴合度

RMSE越小表示误差越小,推荐系统的性能就越好

其中的平方根是RMSE加大了对预测不准的用户物品评分的惩罚(平方项的惩罚),因而对系统的评测更加苛刻。但是要注意研究表明,如果评分系统是基于整数建立的(即用户给的评分都是整数),那么对预测结果取整数会降低MAE的误差。


项目代码

一、实现原理和步骤

1、使用movielens数据集(943个用户,1682部电影,80000条评分数据);

2、构建用户-电影评分矩阵;

二维矩阵,横坐标为943的用户的id,纵坐标为1682部电影的id,其中在用户打过分 的电影的下方标出对应的分数。

3、数据统计分析,可以直观地打印出比如电影的评分分布情况,可以明显的看出打高分的 数量和打低分的数量。

4、输入用户id(1-943);

5、基于用户的协同过滤推荐算法;

1、根据用户历史行为信息构建用户-项目评分矩阵,用户历史行为信息包括项目评分、浏览历史、收藏历史、喜好标签等,本项目以单一的项目评分为例

2、根据用户-项目评分矩阵计算用户之间的相似度。计算相似度常用的方法有余弦算法、修正余弦算法、皮尔森算法等等,该项目为余弦算法。

3、根据用户之间的相似度得到目标用户的最近邻居KNN。KNN的筛选常用的有两种方式,一种是设置相似度阀值(给定一个相似度的下限,大于下限的相似度为最近邻居),一种是根据与目标用户相似度的高低来选择前N个最近邻居(本项目以前N个为例)。相似度排序可用经典冒泡排序法。

4、预测项目评分并进行推荐。

6、计算推荐算法测评指标rmse值。

7、冷启动推荐(扩展,暂未实现);

在该推荐系统中用所拥有的用户-电影-评分数据集进行训练测试所产生的一个推荐算法进行一个冷启动推荐,冷启动推荐的场景分别为刚注册的新用户在没有用户使用系统历史数据的情况下进行一个合适的推荐,和刚刚上线一个新电影的时候在没有用户观看记录数据的情况下将该电影推荐给可能对该电影感兴趣的用户。

二、代码实现

项目目录:

Application:算法主运行算法

Base:基础常量接口

ComputeSimilarity:比较两个用户相似度的类

GetScore:获取预测评分

PearsonCorrelation:余弦算法/皮尔森算法

ProduceSimilarityMatrix:得到用户相似度矩阵

ReadFile:读取movielens

u1.base:训练集

ui.test:测试集

Application:

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
/**
 * 协同过滤推荐算法运行主方法
 * @author line
 *
 */
public class Application implements Base {
    public static void main(String[] args) {
        // 输入userId,并获取
        System.out.println("请输入一个用户Id(1、2、3……943)");
        Scanner scanner = new Scanner(System.in);
        //获取得到输入的userId
        int userId = scanner.nextInt();
        // 从文件中读取数据
        int[][] user_movie_base = new int[PREFROWCOUNT][COLUMNCOUNT];
        //读取文件中的数据
        user_movie_base = new ReadFile().readFile(BASE);
        //产生相似度矩阵
        double[] similarityMatrix = new ProduceSimilarityMatrix().produceSimilarityMatrix(user_movie_base, userId);
        // 知道每个用户之间的相似度值之后,开始获取每隔相似值对应的userId,然后和相似值关联,再根据相似值排序,即得到相似爱好的userId,然后再输出相似推荐的商品
        int[] id = new int[KNEIGHBOUR];//存放K个最近邻userId
        //产生一个临时相似度矩阵变量,是为了相似度排序时和userid对应
        double[] tempSimilarity = new double[similarityMatrix.length];
        for (int j = 0; j < tempSimilarity.length; j++) {
            tempSimilarity[j] = similarityMatrix[j];
        }
        Arrays.sort(tempSimilarity);//排序,升序
        int flag = 0;//临时变量
        double[] similarity = new double[KNEIGHBOUR];//保存前K个相似度,从大到小
        for (int m = tempSimilarity.length - 1; m >= tempSimilarity.length - KNEIGHBOUR; m--) {
            for(int j = 0; j < similarityMatrix.length; j++) {
                if (similarityMatrix[j] == tempSimilarity[m] && similarityMatrix[j] != 0.0){
                    similarity[flag] = tempSimilarity[m];
                    id[flag]=j;//保存前K个相似度的userid
                    flag++;
                }
            }
        }
        System.out.println("相似度最近的" + KNEIGHBOUR + "个用户是:");
        System.out.print("近邻用户");
        System.out.printf("%25s","相似度");//格式化输出"%25s"是占多少位
        System.out.printf("%30s\n","推荐产品");
        Map<Integer, Double> map = new HashMap<Integer, Double>();//存放每件商品的id和期望值,是键值对关系,即一对一
        for (int i = 0; i < KNEIGHBOUR; i++) {//按照k值得大小来循环
            // 前k个近邻用户的推荐产品
            int user_id = id[i];//数组id中的userid根据相似度大小顺序已经排好,从大到小
            int[] items = user_movie_base[user_id];// 获取源数据K个邻近用户userid的所有评分
            String str = "";
            for (int j = 0; j < COLUMNCOUNT; j++) {//循环每件商品,如果相邻用户对某件商品的评分不为0,而目标用户的评分为0,该商品就为推荐商品
                if ((items[j] != 0) && (user_movie_base[userId - 1][j] == 0)){
                    str += " " + (j + 1);//将推荐商品的id保存在一个字符串中,可以直接输出
                    //此时,可以通过循环计算某一件推荐商品的评分用户的相似度期望
                    //开始计算期望,将相同商品的相似度相加,并保存在map集合中
                    if(map.containsKey(j + 1)){//如果一件商品的值,已经保存在map集合的键中(键是唯一的,即不会和其他的数值一样),那么键对应的值,就会改变,加上该商品不用用户的相似度
                        double d = map.get(j+1);
                        d+=similarity[i];
                        map.put(j+1,d);//修改map中的值
                    }else{
                        map.put(j+1, similarity[i]);//如果没有保存一件商品的id,那么开始保存
                    }
                }
            }
            System.out.print(id[i] + 1);
            System.out.printf("%16s\t" ,String.format("%.2f",similarity[i]*100)+"%");//输出的同时格式化数据
            System.out.println(str);//输出每个用户的推荐商品
        }
        //选择最好的推荐商品,期望加权
        //循环map集合的键
        Map<Integer,Double> map2 = new HashMap<Integer, Double>(); //保存商品id和加权期望,因为还要对加权期望排序,要和商品id对应
        double s1 = 0;
        double s2 = 0;
        Set<Integer> set = map.keySet();//获取map集合中的所有键,输出是一个set集合
        for(int key : set){//循环map中的所有键
            for (int i = 0; i < KNEIGHBOUR; i++) {
                int score = user_movie_base[id[i]][key-1];//map中的键是商品id,i是userid,获取评分
                s1+=score*map.get(key);
                s2+=score;
            }
            map2.put(key, s1/s2);//保存加权期望值,和商品id对应
        }
        Object[] arr = map2.values().toArray();//获取map2中所有的值,也就是每件商品的加权期望
        Arrays.sort(arr);//升序排列,调用系统数据包中的函数,自动排列数组
        set = map2.keySet();//获取商品id
        int max=0;//最佳推荐项目id
        for(int key : set){//循环商品id,根据最大的加权期望,找到商品id
            if(map2.get(key)==arr[arr.length-1]){
                max = key;
                break;
            }
        }
        System.out.println("最值得推荐的商品是:"+max);
// 误差率
        int[][] test = new ReadFile().readFile(TEST); // 462个用户的实际评分
        double[][] similarityMatrix2 = new ProduceSimilarityMatrix().produceSimilarityMatrix(user_movie_base);//获取任意两行之间的相似度矩阵
        double[][] matrix = new GetScore().getScore(user_movie_base,similarityMatrix2);
        double[] mae = new ProduceMAE().produceMAE(matrix, test);
        double Mae = 0.0, MAE = 0.0;//平均绝对误差,通过两大组数据的相似度矩阵对比而来
        for (int k = 0; k < mae.length; k++) {
            Mae += mae[k];
        }
        MAE = Mae / TESTROWCOUNT;
        System.out.println("MAE=:" + MAE);
        double RMSE=new ProduceMSE().produceMSE(matrix,test);
        System.out.println("MSE=:" + RMSE);
    }
} 

Base

/**
 * 基础静态文件数据
 * @author line
 *
 */
public interface Base {
    public static final int KNEIGHBOUR = 10; //number of neighbors最近邻个数
    public static final int COLUMNCOUNT = 1682; //number of items 项目总数
    public static final int PREFROWCOUNT = 943; //number of users in base训练集上的用户数目
    public static final int TESTROWCOUNT = 462; //number of users in test测试集上的用户数目
    public static final String BASE = "./ml-100k/u1.base";//训练集
    public static final int BASE_LINE = 80000;//base数据集的行数
    public static final String TEST = "./ml-100k/u1.test";//测试集
    public static final int TEST_LINE = 20000;//test数据集的行数
    public static final String BASE_GENRE = "./ml-100k/u.user";//用户属性集
    public static final String BASE_ITEMS_GENRE = "./ml-100k/u.item";//用户属性集
    public static final int ITEMS_GENRE_LINE = 19;//test数据集的行数
} 

ComputeSimilarity

import java.util.ArrayList;
import java.util.List;
/**
 * 从两行数据中,获取需要对比的要求数据
 * @author line
 *
 */
public class ComputeSimilarity {
    public double computeSimilarity(int[] item1,int[] item2) {
        List<Integer> list1 = new ArrayList<Integer>();//因为不知道两行userid的评分是否有效即都不为0,所以定义集合来储存不知道的有效评分
        List<Integer> list2 = new ArrayList<Integer>();
        for (int i = 0; i < item1.length; i++) {
            if(item1[i] != 0 || item2[i] !=0) {//如果相同列上有0就舍去
                list1.add(new Integer(item1[i]));//因为合格数据个数不确定,所以用集合表示
                list2.add(new Integer(item2[i]));
            }
        }
        return new PearsonCorrelation().pearsonCorrelation(list1,list2);//返回相似度值
    }
} 


目录
相关文章
|
1月前
|
存储 算法 程序员
C 语言递归算法:以简洁代码驾驭复杂逻辑
C语言递归算法简介:通过简洁的代码实现复杂的逻辑处理,递归函数自我调用解决分层问题,高效而优雅。适用于树形结构遍历、数学计算等领域。
|
2月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
65 1
|
2月前
|
存储 缓存 算法
通过优化算法和代码结构来提升易语言程序的执行效率
通过优化算法和代码结构来提升易语言程序的执行效率
|
2月前
|
算法
分享一些提高二叉树遍历算法效率的代码示例
这只是简单的示例代码,实际应用中可能还需要根据具体需求进行更多的优化和处理。你可以根据自己的需求对代码进行修改和扩展。
|
2月前
|
算法 测试技术 开发者
在Python开发中,性能优化和代码审查至关重要。性能优化通过改进代码结构和算法提高程序运行速度,减少资源消耗
在Python开发中,性能优化和代码审查至关重要。性能优化通过改进代码结构和算法提高程序运行速度,减少资源消耗;代码审查通过检查源代码发现潜在问题,提高代码质量和团队协作效率。本文介绍了一些实用的技巧和工具,帮助开发者提升开发效率。
52 3
|
2月前
|
分布式计算 Java 开发工具
阿里云MaxCompute-XGBoost on Spark 极限梯度提升算法的分布式训练与模型持久化oss的实现与代码浅析
本文介绍了XGBoost在MaxCompute+OSS架构下模型持久化遇到的问题及其解决方案。首先简要介绍了XGBoost的特点和应用场景,随后详细描述了客户在将XGBoost on Spark任务从HDFS迁移到OSS时遇到的异常情况。通过分析异常堆栈和源代码,发现使用的`nativeBooster.saveModel`方法不支持OSS路径,而使用`write.overwrite().save`方法则能成功保存模型。最后提供了完整的Scala代码示例、Maven配置和提交命令,帮助用户顺利迁移模型存储路径。
|
3月前
|
存储 缓存 算法
如何通过优化算法和代码结构来提升易语言程序的执行效率?
如何通过优化算法和代码结构来提升易语言程序的执行效率?
|
3月前
|
搜索推荐
插入排序算法的讲解和代码
【10月更文挑战第12天】插入排序是一种基础的排序算法,理解和掌握它对于学习其他排序算法以及数据结构都具有重要意义。你可以通过实际操作和分析,进一步深入了解插入排序的特点和应用场景,以便在实际编程中更好地运用它。
|
3月前
|
机器学习/深度学习 人工智能 算法
【玉米病害识别】Python+卷积神经网络算法+人工智能+深度学习+计算机课设项目+TensorFlow+模型训练
玉米病害识别系统,本系统使用Python作为主要开发语言,通过收集了8种常见的玉米叶部病害图片数据集('矮花叶病', '健康', '灰斑病一般', '灰斑病严重', '锈病一般', '锈病严重', '叶斑病一般', '叶斑病严重'),然后基于TensorFlow搭建卷积神经网络算法模型,通过对数据集进行多轮迭代训练,最后得到一个识别精度较高的模型文件。再使用Django搭建Web网页操作平台,实现用户上传一张玉米病害图片识别其名称。
82 0
【玉米病害识别】Python+卷积神经网络算法+人工智能+深度学习+计算机课设项目+TensorFlow+模型训练
|
3月前
|
缓存 分布式计算 监控
优化算法和代码需要注意什么
【10月更文挑战第20天】优化算法和代码需要注意什么
36 0

热门文章

最新文章