推荐系统入门学习(二)【小白入门系列】

news2024/11/18 22:54:39

搭建你的第一个推荐系统

初识推荐系统

最初的推荐系统,作用是过滤垃圾邮件。今日机器学习算法的发展,朴素贝叶斯、神经网络已然成为了过滤垃圾邮件的好手。但是在30年前,算法还没有如今使用得广泛的时候,“找相同”就成为了过滤垃圾邮件的唯一选择。

简单的说,假设一个用户A和用户B都对许多相同邮件打上了“×”的标签,则当用户A对某一封邮件打上“×”的标签的时候,我们有理由相信,用户B也有很大可能会对这封邮件打上“×”的标签。

这便是基于用户行为来进行推荐服务,业界将这称为协同过滤算法。如今应用的最广泛的两种协同过滤算法分别是:

  • 基于用户行为的协同过滤算法:找出相似用户,假设A和B这两个用户相似,则用户A喜欢的物品就有理由推荐给用户B。
  • 基于物品属性的协同过滤算法:找出相似物品,假设A和B这两个物品相似,则用户对物品A产生了行为则很有可能也会对物品B产生行为。

相似性度量

那么首要的问题便是,相似性怎么度量?我们有以下选择方案

  • 余弦相似度
  • 皮尔逊相关系数

余弦相似度

思考一下,假设用户P1、P2对物品A、物品B、物品C都产生了行为,而用户P3对物品B、物品C、物品D产生了行为,试问P1和P2相似度更高还是和P3相似度更高?

我们最初可能会写一段代码:

# 假设现在我们有几个用户的数据,我想利用某种方法来计算他们之间的相关系数
# 表示用户person1对A物品产生行为,且他对A的喜欢度为5,其他以此类推
person1 = {"A": 5, "B": 4, "C": 3}  
person2 = {"A": 2, "B": 5, "D": 4}
person3 = {"B": 3, "C": 4, "E": 5}
person4 = {"A": 1, "C": 5, "D": 4}
person5 = {"A": 2, "B": 5, "C": 4}
person6 = {"A": 2, "B": 5, "C": 5}
person7 = {"A": 5, "B": 4, "C": 2}
# 简单的计算相似度的方法
def getCorr1(p1, p2):
    score = 0  # 得分
    # 遍历用户产生行为的物品
    for (item, rate) in p1.items():
        if item in p2.keys():
            score += 1  # 简单的,只要有相同,我们就加1
    return score

这个便是余弦相似度的前身,懂了上述代码之后,就可以很好的理解余弦相似度。

这里的u和v代表两个不同的用户,N则代表他们产生行为的物品

在这里插入图片描述

def getCorr2(p1, p2):
    num_intersection = len(set(p1) & set(p2))  # 求交集,先要转换成字典
    num_union_set = math.sqrt(len(p1) * len(p2) * 1.0)  # 求并集,最后开方
    return num_intersection / num_union_set  # 返回除的结果

细心的朋友可能会发现,这样计算,person1和person5计算的结果,person1和person7计算的结果是一样的啊!

那或许我们可以试试看皮尔逊相关系数

皮尔逊相关系数

皮尔逊相关系数是使用协方差除以两个变量的标准差得到的,当两个变量的方差都不为0时,相关系数才有意义。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

这里使用《推荐系统开发实战》中Netflix数据集的例子。数据集中的training_set.tar是官方训练集,而我们要做的是,将官方训练集划分成训练集和测试集,以测试算法的效果。

Netflix数据集是电影数据集,包含了很多电影的相关信息。而training_set.tar,解压之后的文件夹中包含着每部电影为后缀命名的文件,第一行为电影ID,后边的每行为用户评分行为。每列分别代表用户ID,评分值,时间。

在这里插入图片描述

我们用一个类来搭建推荐系统,这个类包含初始化函数、加载数据函数、随机选取用户函数、计算用户相似度函数、电影推荐函数、评估函数。

编写初始化函数

 # 初始化函数
 def __init__(self, file_path, seed, k, n_items):
     self.file_path = file_path  # 获取文件路径
     self.users = self.select_users()  # 获取随机获得的1000个用户
     self.seed = seed  # 获取随机数种子
     self.k = k  # 选取的近邻用户个数
     self.n_items = n_items  # 为每个用户推荐的电影数目
     self.train, self.test = self.load_and_split_data()  # 获取训练集和测试集

编写随机选取用户函数:因为数据量巨大,所以随机选取1000个用户来进行开发。就算是这样,耗时也得十几分钟…

def select_users(self):
    """
    获取所有用户,并随机选择1000个,然后返回
    :return: 返回值是列表形式的用户数据
    """
    # 加载的数据会以json格式保存下来,之后使用直接加载json格式的数据
    if os.path.exists("data/train.json") and os.path.exists("data/test.json"):
        print("数据存在,从文件中加载数据!")
        return list()
    else:
        print("数据不存在,随机获取1000个用户!")
        users = set()  # 用集合来存储所有用户
        # os.listdir返回一个列表,现在要做的是将该目录下所有的文件都加载进来
        for file in os.listdir(self.file_path):
            # 拼接成每一个文件的路径
            one_path = "{}/{}".format(self.file_path, file)
            # 以"只读"的形式打开文件
            with open(one_path, "r") as fp:
                # 读取所有行,fp.readlines()返回的是一个列表
                for line in fp.readlines():
                    # 如果某一行是以":"结尾,则跳过这一行,相当于是跳过第一行
                    if line.strip().endswith(":"):
                        continue
                    userID, _, _ = line.split(",")  # 按逗号进行切分,得到用户的ID
                    users.add(userID)  # 将用户的ID添加到用户的集合当中
        return random.sample(list(users), 1000)  # 从列表中随机返回1000个用户

编写加载数据函数

def load_and_split_data(self):
    """
    加载数据,并将数据划分为训练集和测试集
    :return:
    """
    # 训练集和测试集都以字典形式保存
    train = dict()
    test = dict()
    # 如果数据已经存在了,则直接加载
    if os.path.exists("data/train.json") and os.path.exists("data/test.json"):
        print("数据存在,从文件中加载数据!")
        # json.load表示从json文件中加载数据,加载之前需要先用open函数打开文件
        train = json.load(open("data/train.json"))
        test = json.load(open("data/test.json"))
    # 如果数据不存在,则要进行切分数据的工作
    else:
        print("数据不存在,读取数据以划分训练集和测试集!")
        random.seed(self.seed)  # 设置产生随机数的种子,保证每次试验产生的随机结果一致
        for file in os.listdir(self.file_path):
            one_path = "{}/{}".format(self.file_path, file)  # 取出每一个数据文件的完整路径
            print("当前路径: {}".format(one_path))
            with open(one_path, "r") as fp:
                movieID = fp.readline().split(":")[0]  # 读取第一行,随后切割,取出切割的第一个值,获得电影的ID
                # 遍历所有行
                for line in fp.readlines():
                    # 跳过第一行
                    if line.endswith(":"):
                        continue
                    userID, rate, _ = line.split(",")  # 获取用户的ID,及评分
                    # 判断用户是否在所选择的1000个用户当中
                    if userID in self.users:
                        print(userID, rate)
                        # 将用户均分成训练集和测试集
                        if random.randint(1, 50) == 1:
                            # 设置默认值,字典形式,评分向下取整||形式:{"user1":{"movie1":1,"movie2":2,...},...}
                            test.setdefault(userID, {})[movieID] = int(rate)
                        else:
                            train.setdefault(userID, {})[movieID] = int(rate)
        # json.dump表示将数据保存成json格式,第一个参数是要保存的数据,第二个参数的打开的文件(保存位置)
        print(train)
        print("---")
        print(test)
        # 如果data目录不存在,则先在当前目录下创建data目录,否则open()函数会报错
        if not os.path.exists("data"):
            os.mkdir("data")
        json.dump(train, open("data/train.json", "w"))
        json.dump(test, open("data/test.json", "w"))
    return train, test

编写计算用户相似度函数:使用皮尔逊相关系数进行计算

def pearson(self, rating1, rating2):
    """
    计算皮尔逊相关系数,来表示用户之间的相似度
    :param rating1:
    :param rating2:
    :return: 返回的是相似度度量值
    """
    # 计算皮尔逊相关系数所需要的
    sum_xy = 0  # 用户1和用户2对相同电影的评分相乘
    sum_x = 0  # 用户1和用户2相同电影评分中,用户1对这些电影的评分总合
    sum_y = 0  # 用户1和用户2相同电影评分中,用户2对这些电影的评分总合
    sum_x2 = 0  # 用户1和用户2相同电影评分中,用户1对这些电影的评分的平方的总和
    sum_y2 = 0  # 用户1和用户2相同电影评分中,用户2对这些电影的评分的平方的总和
    num = 0
    # 循环遍历用户的数据
    for key in rating1.keys():
        # 如果两个用户同时对电影评分了
        if key in rating2.keys():
            num += 1  # 记录两个用户评分过的相同的电影的数量
            x = rating1[key]  # 得到用户1对这部电影的评分
            y = rating2[key]  # 得到用户2对这部电影的评分
            sum_xy += x * y
            sum_x += x
            sum_y += y
            sum_x2 += math.pow(x, 2)
            sum_y2 += math.pow(y, 2)
    # 如果两个用户没有一同评分过的电影,则相关系数为0
    if num == 0:
        return 0
    # 计算皮尔逊相关系数的分母,分母要分开计算,因为如果两个变量的方差为0,则就没有意义了
    denominator = math.sqrt(sum_x2 - math.pow(sum_x, 2) / num) * math.sqrt(sum_y2 - math.pow(sum_y, 2) / num)
    # 如果相关系数的分母是0,则没有意义
    if denominator == 0:
        return 0
    else:
        return (sum_xy - (sum_x * sum_y) / num) / denominator

测试1:测试用户相关系数

# 调用相关系数测验 这里的411705和452261都是train数据集中的用户ID(根据自己train中的数据),
# 得出结果是0.2981122155377652
# First是类实例化完成的对象
print(FirstS.pearson(FirstS.train['411705'], FirstS.train['452261']))

编写电影推荐函数

def recommend(self, userID):
    """
    为用户ID为userID的用户进行电影推荐
    :param userID:
    :return: 返回值是用字典dict()存储的电影
    """
    neighborUser = dict()  # 用字典形式存储用户距离(相关系数)
    # 遍历训练集的所有用户
    for user in self.train.keys():
        # 保证不是同一个用户
        if userID != user:
            distance = self.pearson(self.train[userID], self.train[user])  # 计算相关系数
            neighborUser[user] = distance  # 用字典存储用户之间的相关系数(距离)
    # 例:dict_items([('小明', 1), ('小北', 2)]) ,k相当于就是(元组),k[1]则是取出评分来进行排序
    newNeighborUser = sorted(neighborUser.items(), key=lambda k: k[1], reverse=True)  # 根据距离来进行字典排序
    movies = dict()  # 用字典来存储推荐的电影以及推荐分
    # 之前已经按照升序排序了,这里取出评分最高的k个来,即挑选出k个近邻用户个数
    for (sim_user, sim) in newNeighborUser[:self.k]:
        # sim_user是ID,sim是用户相似度
        # 遍历该用户的所有电影
        for movieID in self.train[sim_user].keys():
            movies.setdefault(movieID, 0)  # 如果该电影之前还不存在,则设置默认值0
            # 计算用户对电影的感兴趣程度,使用"距离"即用户相关系数 * 另一个用户对该电影的评分,总和要加起来
            movies[movieID] += sim * self.train[sim_user][movieID]
    # 将推荐出来的电影,按照推荐分(感兴趣程度)进行排序
    newMovies = sorted(movies.items(), key=lambda k: k[1], reverse=True)
    return newMovies

测试2:测试电影推荐结果

# 为用户411705进行电影推荐 得出结果 [('17292', 10.062177826491071), ('2660', 6.6388284833811735), ('15755', 6.252865699153531)..
# 第一个结果是电影的ID,第二个结果是推荐分
print(FirstS.recommend("411705"))

编写评估函数

def evaluate(self, num=30):
    """
    评估:针对测试集,为其推荐的电影占本身有行为电影的比例
    :param num: 表示为随机的30个用户推荐电影,并查看其准确率
    :return:
    """
    print("开始计算准确率!")
    precisions = list()  #
    random.seed(10)  # 设置随机数种子
    # 随机在测试集中获取30个用户
    for userID in random.sample(self.test.keys(), num):
        hit = 0
        result = self.recommend(userID)[:self.n_items]  # 为每个用户推荐电影,n_items是推荐的电影数,取出排名靠前的
        # 遍历推荐列表
        for (item, rate) in result:
            # 如果推荐出来的电影,在测试集中,有被操作过,则记录加一
            if item in self.test[userID]:
                hit += 1
        precisions.append(hit / n_items)  # 操作过的/推荐出来的 = 准确率
    # 计算准确率的平均值 并返回
    return sum(precisions) / len(precisions)

测试3:测试推荐结果

# 测试推荐效果, 使用准确率 结果 0.0033333333333333335 可以通过修改参数,如选取的近邻用户个数,来进行模型调优!
print(FirstS.evaluate())

总结

本文介绍了协同过滤算法的两种分类,以及使用余弦相似度度量和基于皮尔逊相关系数度量的用户协同过滤算法。祝学习愉快!

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

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

相关文章

大型网络游戏任务系统的架构与设计

在网路游戏中做任务已经成为游戏很重要的一个核心功能和玩法,如何做好一个灵活可扩展的任务系统的架构与设计,今天来给大家分享一些我们的设计经验。接下来我把整个的任务系统分成以下6个模块:任务配置表设计与管理;游戏任务的解锁与生成;任务完成判定;任…

将本地的word文档转为markdown(带图片)发布到CSDN

文章目录一、前言二、word转为markdown三、自动发布本地md文件一、前言 背景:早期很多学习笔记都是记在word上(那一年,还不知道有markdown),现在想将早期的一些资料都发布到CSDN上(本地学习资料我都懒得打…

数据中心灾备通用基础

1.灾备的定义 没有灾备会带来的问题 一旦数据中心的数据遭到破坏,随之而来的是很多棘手的问题 灾备可以提供全面数据保护 IT行业中的备份 在当今IT行业中,数据量急剧增长,并且新的法规不断出台,对备份时间、恢复时间目标&#…

Linux应用编程---4.pthread_create函数

Linux应用编程—4.pthread_create函数 ​ 之前学习了进程有关的东西,现在学习如何创建一个线程。 4.1 pthread_create()函数详情 ​ 线程创建函数是:pthread_create()。在Linux终端下,输入man pthread_create,查看函数定义以及…

Python基础(二十三):面向对象之继承介绍

文章目录 面向对象之继承介绍 一、继承的概念 二、单继承 三、多继承

IntelliJ IDEA安装教程(超详细)

✅作者简介:CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1🏆 📃个人主页:hacker707的csdn博客 🔥系列专栏:实用工具 💬个人格言:不断的翻越一座又一…

Java学习(44)new关键字

new关键字 对象实例化 实例化对象的过程可以分为两部分: (1) 声明对象:Cat one (2) 实例化对象:new Cat()JVM可以被理解为Java程序与操作系统之间的桥梁 每次使用new关键字,就相当于在内存中开辟了一块新的存储空间。 package c…

CPU概述

CPU 概述 1. CPU 基础 CPU 的概念 CPU 是最重要的计算机组件,由控制器和运算器组成,通过总线与其它设备连接。 CPU 与南北桥 所谓的桥,都是 cpu 和其它设备连接这样的一个桥梁。分为南北桥。 什么是南桥? 南桥主要是用来连接…

【北邮果园大三上】运筹学期中后

运筹学后半段 第五章 动态规划 最优化原理,可以归结为一个递推公式 现实应用:比如最优路径、资源分配、生产计划和库存等 5.1 动态规划的最优化原理及其算法 5.1.1 求解多阶段决策过程的方法 例如:最短路径问题 求A到B的最短路径&#xff…

SpringBoot3.0集成SpringDoc2.0

SpringBoot3.0集成SpringDoc1. pom配置2. OpenApi配置类3. 使用4. SpringDoc注解与SpringFox对比5. 演示项目这里为什么要使用SpringDoc呢?是因为集成SpringFox只支持SpringBoot2.x, 而基于Swagger的SpringDoc的社区现在十分活跃,代码也在不断…

excel 设置双坐标轴柱状图

思路:通过添加辅助列来调整。 步骤1 选中数据,插入柱状图,这时候会得到原始的柱状图 选中“设计”-“更改图表类型”-“组合”,调整图表类型均为柱状图,可以得到初始双坐标轴图表…

Linux应用编程---7.有名管道

Linux应用编程—7.有名管道 7.1 mkfifo函数详情 ​ 无名管道用于具有亲缘关系的进程之间通讯,比如:父子进程、兄弟进程。有名管道用于非亲缘关系进程之间的通讯。创建有名管道使用到的库函数是:mkfifo(),这里强调是库函数的原因…

Elasticsearch连续剧之基础篇

目录一、前言二、Elasticsearch数据结构三、安装1.配置最大可创建文件数大小2.由于ES不能以root用户运行,我们需要创建一个非root用户,此处创建一个名为es的用户3.安装es4.启动es服务四、常用操作1.创建没有结构的索引2.创建有结构的索引3.删除索引4.文档…

【迅为iMX6Q】开发板 u-boot 2020.04 RTL8211E 以太网驱动适配

相关参考 【迅为iMX6Q】开发板 u-boot 2015.04 SD卡 启动 【迅为iMX6Q】开发板 u-boot 2020.04 SD卡 启动 【迅为iMX6Q】开发板 u-boot 2022.04 SD卡 启动 开发环境 win10 64位 VMware Workstation Pro 16 ubuntu 22.04 【迅为imx6q】开发板, 2G DDR 目标 …

SAP Webservice 发布外网SAP端代理配置

前言 SAP Webservice接口发布外网的需求不算少见,一般是通过代理形式替换Webservice地址中的域名,但是完整的Webservice接口发布外网还需要以下步骤(如果调用方在不做这些配置的情况下可以正常调用,则无需处理。) 1. 配…

gravity 安装部署

gravity是由膜拜公司自主研发的不同数据库间数据同步工具。 官方介绍:https://github.com/moiot/gravity/blob/master/README-cn.md Gravity 是一款数据复制组件,提供全量、增量数据同步,以及向消息队列发布数据更新。 DRC 的设计目标是&a…

有电脑就可以干的工作有哪些?试试这些项目

现在几乎人人都有手机,吃饭时候看,睡觉的时候看,上个厕所也在看,有次坐地铁,发现很多上学的初中生、小学生都拿着手机,手机档次还挺高,都是最新的苹果、华为款的手机。反之再看看使用电脑的人又…

Linux——信号知识归纳(上)

一.信号概念(一).大致认识信号是操作系统控制进程的一种方式,比如ctrl C、栈溢出程序崩溃、kill -9命令等底层都是操作系统发送信号给进程执行特定操作。因此,所有信号就底层而言都是操作系统发出的。同时,进程接收到信…

Java设计模式中桥接模式是什么/桥接模式有什么用,怎么运用桥接模式

继续整理记录这段时间来的收获,详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用! 5.5 桥接模式 5.5.1 定义 将抽象与实现分离,使他们可以独立变化,即用组合关系来代替继承关系实现,从而降低抽象与实现两个可变…

相参雷达(CSDN_0004_20220909)

文章编号:CSDN_0004_20220909 雷达信号处理中的一个基本操作是对样本进行积累,以提高SNR。积累的方式包括相参积累和非相参积累。相参积累是对复数信号(既包括幅度又包括相位)相加积累;非相参积累是对信号的幅度&#…