协同过滤源代码在真实数据集上运行及优化

最近在做推荐算法相关研究,

先拿一个协同过滤代码练手。

网上找代码很容易,但是大多是讲原理的示例代码,在真实数据集上运行问题特别多。

以一个2w节点的开源数据集为例(baby.inter)

https://github.com/enoche/MMRec/

https://drive.google.com/drive/folders/13cBy1EA_saTUuXxVllKgtfci2A09jyaG?usp=sharing

列举一些我遇到的问题:

1.稀疏矩阵占用内存过大,使用coo_matrix可以极大减少占用

2.矩阵类型转换过程中维度经常出错,矩阵读取某列的方法和稀疏矩阵读某列的方法返回格式不同

3.使用多线程提速的过程中内存溢出,导致部分线程崩溃,运行结束后只得到一半结果。

4.获取结果的语句嵌套过多难以理解,例如:recommendations = unrated_items[np.argsort(-pred[unrated_items])][0:top_k]

5.格式转换不当导致内存溢出(64g内存的电脑啊,直接蓝屏了)

6.np.save np.savez不会用。

下面直接上代码

1.读取数据,这里数据的最后一位是训练集、验证集、测试集的划分标签。chatGPT写了几遍也不对,还是自己写的。

# 数据读取
def load_data(file_path):
    train_data_u = []
    train_data_i = []
    train_data_r = []
    val_data_u = []
    val_data_i = []
    val_data_r = []
    test_data_u = []
    test_data_i = []
    test_data_r = []

    with open(file_path) as file:
        next(file)  # 跳过第一行
        for line in file:
            line = line.strip().split("\t")
            userID = int(line[0])
            itemID = int(line[1])
            rating = float(line[2])
            x_label = line[4]
            
            if x_label == '0':
                train_data_u.append(userID)
                train_data_i.append(itemID)
                train_data_r.append(rating)
            elif x_label == '1':
                val_data_u.append(userID)
                val_data_i.append(itemID)
                val_data_r.append(rating)
            elif x_label == '2':
                test_data_u.append(userID)
                test_data_i.append(itemID)
                test_data_r.append(rating)

    num_users = max(train_data_u) + 1
    num_items = max(train_data_i) + 1
    
    train_data_matrix = coo_matrix((train_data_r, (train_data_u,train_data_i)), shape=(num_users, num_items))
    val_data_matrix = coo_matrix((val_data_r, (val_data_u,val_data_i)), shape=(num_users, num_items))
    test_data_matrix = coo_matrix((test_data_r, (test_data_u,test_data_i)), shape=(num_users, num_items))
    
    return train_data_matrix, val_data_matrix,test_data_matrix

2.训练模型,这里很多代码都只获取用户相似度,然后在预测过程中用户相似度乘评分矩阵得到相似用户的评分和。但是把矩阵乘法运算放在这里只需要计算1次,减少了约2w次的大规模矩阵乘法运算。

函数返回的矩阵每行表示某个用户对所有物品的评分。(这里把每个矩阵的维度都列举出来就能分析清楚了,我只是做笔记,懒得写。)矩阵每列表示一个商品。

# 模型训练
def train_model(data_matrix):
    similarity_matrix = cosine_similarity(data_matrix, dense_output=False)
    pred=similarity_matrix.dot(data_matrix)
    print(pred)
    return pred

3.预测用户评分,这里注意tocsr(),被坑了好久,coo_matrix可以直接进行点乘运算,但是不能做切片运算,切片出来某一行也和普通矩阵索引出来某行不一样。

然后就是非常复杂的排序运算,这里实在无法优化,也是整个代码最耗时的部分,因为要执行大约2w次。

def predict(ratings,pred, user_id, top_k):

    pred1=pred.tocsr()[user_id].toarray()[0]
    ratings1=ratings.tocsr()[user_id].toarray()[0]

    unrated_items = np.where(ratings1 == 0)[0]
    sort_index=np.argsort(-pred1[unrated_items])
    sort_items=unrated_items[sort_index]
    # 根据预测评分生成推荐列表
    recommendations =sort_items [0:top_k]
    return recommendations

4.评价函数,没什么说的,gpt写的直接拿来用

# 评估指标
def evaluate_recommendations(recommendations, test_matrix, k):
    num_users, num_items = test_matrix.shape
    recall = 0.0
    ndcg = 0.0
    precision = 0.0
    map_value = 0.0
    
    for user_id in range(num_users):
        test_ratings = test_matrix[user_id]
        if np.sum(test_ratings) > 0:
            relevant_items = np.where(test_ratings > 0)[0]
            
            recall += len(set(recommendations[user_id]) & set(relevant_items)) / len(relevant_items)
            
            dcg = 0.0
            idcg = np.log2(2)
            for i in range(k):
                item_id = recommendations[user_id][i]
                if item_id in relevant_items:
                    dcg += 1 / np.log2(i + 2)
            
            ndcg += dcg / idcg
            
            precision += len(set(recommendations[user_id]) & set(relevant_items)) / k
            
            ap = 0.0
            for i in range(k):
                item_id = recommendations[user_id][i]
                if item_id in relevant_items:
                    ap += len(set(recommendations[user_id]) & set(relevant_items)) / (i + 1)
            ap /= len(relevant_items)
            
            map_value += ap
    
    recall /= num_users
    ndcg /= num_users
    precision /= num_users
    map_value /= num_users
    
    return recall, ndcg, precision, map_value

5.主函数,写主函数一部分是c语言的习惯,另一部分是随时可以用return打断,否则当执行到thread.join时代码无法ctrl+c打断,只能直接关控制台。(哦,我都是记事本写代码,在控制台执行,没有IDE)

def printTime():
    print(f"[{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}]")


def main():        
    print('start')
    printTime()    
    # 数据路径
    file_path = "..\\MMRec-master\\data\\baby\\baby.inter"

    # 加载数据
    train_matrix, val_matrix,test_matrix = load_data(file_path)

    # 训练
    pred = train_model(train_matrix)
    print(pred.toarray()[0])
    # 生成推荐
    top_k = 10
    recommendations = {}
    recommendations[0]=(predict(train_matrix,pred,0, top_k))
    print(recommendations[0])
    print(len(recommendations))

    #return
    # 创建一个线程列表
    threads = []
    num_threads=16
    # 将用户数量按照线程数进行划分
    user_per_thread = test_matrix.shape[0] // num_threads
    # 定义一个函数,用于并行计算推荐结果
    def calculate_recommendations(start, end):
        print(start,end)
        for user_id in range(start, end):
            recommendations[user_id] = predict(train_matrix, pred, user_id, top_k)#2.82G
            if(user_id%500==0):
                print(user_id)
                
    for i in range(num_threads):
        start = i * user_per_thread
        end = (i+1) * user_per_thread
        
        # 创建并启动一个新线程
        t = threading.Thread(target=calculate_recommendations, args=(start, end))
        t.start()
        
        threads.append(t)

    # 等待所有线程完成
    for t in threads:
        t.join()
       
    start=  int( test_matrix.shape[0] // num_threads)*num_threads
    end=test_matrix.shape[0]
    calculate_recommendations(start, end)
      
    printTime()
    np.savez('data.npz',recommendations=recommendations,test_matrix=test_matrix)
    '''data=np.load('data.npz',allow_pickle=True)
    recommendations=data['recommendations'].item();
    test_matrix=data['test_matrix'].item();'''

    # 评估指标
    recall, ndcg, precision, map_value = evaluate_recommendations(recommendations, test_matrix.toarray(), top_k)
    printTime()
    print("Recall =", recall)
    print("NDCG =", ndcg)
    print("Precision =", precision)
    print("MAP =", map_value)
    
main()

6.上面的np.savez是因为在花费了11个小时运行完代码后在评分环节可能崩溃,为了避免重新运行11个小时,第一时间保存结果。

后记:

这个代码在单线程第一次跑通时占用2.8g内存,11个小时。

后来4个线程,2.8*4+2.8+2=16,刚好让一台16g内存的电脑随时崩溃,4个子线程,1个主线程,2g系统占用。

后来换了一台电脑8个线程占用1个半小时,内存占用25g。

现在这版同时开16个线程,只需要20秒,

我去挑战更大的数据集了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PSIM仿真DSP28335ADC功能并使用SCI串口模块输出曲线

在使用PSIM 2022 软件仿真DSP28335单片机时,发现里面还有SCI串口打印模块,在仿真软件里面可以看到串口数据,但是将代码下载到单片机上之后,使用串口助手却看不到任何数据,经过一番探索终于发现,串口不是这样…

【linux】串口工具

1. PuTTY - 尽管PuTTY更为人所知作为SSH和Telnet客户端,但它也有串口连接能力。 - 在Debian上可以通过命令 sudo apt-get install putty 来安装PuTTY。 2. Minicom - Minicom是一个字符界面的串行通信程序,经常用于调试串口和模拟器。 - 可以通过…

webserver 之 线程同步 线程池(半同步半反应堆)

目录 🐂前言 🍑B / S 模型 🐘线程同步机制 🌼概念 (1)RAII (2)信号量 (3)互斥量 (4)条件变量 🌼功能 &#xf…

python丰富的任务进度显示

pip install txdpy 安装 txdpy from txdpy import progbar 导入 progbar progbar()函数传入一个可遍历对象,返可迭代对象 from txdpy import progbar from random import uniform from time import sleepfor i in progbar(range(4651)):print(f第{i}条任务)…

Ubuntu Desktop 隐藏 / 显示文件和文件夹

Ubuntu Desktop 隐藏 / 显示文件和文件夹 1. GUI hot key2. Show hidden and backup filesReferences 1. GUI hot key Ctrl H: 隐藏 / 显示文件和文件夹 2. Show hidden and backup files Edit -> Preferences -> Views References [1] Yongqiang Cheng, https://yo…

Linux:vim的相关知识

目录 vim 是一个较为常见的编译文件的命令操作。 三种模式的区分的作用如下: 命令模式: 插入模式: 进入插入模式的标志:左下角有INSERT 底行模式: 命令模式的常见命令: 底行模式常见命令&#xff1…

【UE】在控件蓝图中通过时间轴控制材质参数变化

效果 步骤 1. 新建一个控件蓝图和一个材质 2. 打开材质,设置材质域为用户界面,混合模式设置为“半透明” 在材质图表中添加两个参数来控制材质的颜色和不透明度 3. 对材质创建材质实例 4. 打开控件蓝图,在画布面板中添加一个图像控件 将刚…

QT发送request请求

时间记录:2024/1/23 一、使用步骤 (1)pro文件中添加network模块 (2)创建QNetworkAccessManager网络管理类对象 (3)创建QNetworkRequest网络请求对象,使用setUrl方法设置请求url&am…

什么是BMC

BMC全称为Baseboard Management Controller(基板管理控制器),是一种独立于服务器操作系统和主处理器的专用微控制器,它内置在服务器、网络设备和其他复杂电子系统的主板上。BMC主要负责监控和管理系统硬件的状态,并提供…

模型之气体的行为

气体的行为 “探索气体动理论:分子运动与温度的统计关系” 气体动理论由丹尼尔•伯努利在1738年提出,后来又由麦克斯韦、玻尔兹曼等人在19世纪后半叶推进。根据这种理论,气体是由运动着的分子组成的,气体的许多性质——如温度和…

“深入理解RabbitMQ交换机的原理与应用“

深入理解RabbitMQ交换机的原理与应用 引言1. RabbitMQ交换机简介介绍1.1 什么是RabbitMQ?1.1.1 消息中间件的作用1.1.2 RabbitMQ的特点和优势 1.2 RabbitMQ的基本概念1.2.1 队列1.2.2 交换机1.2.3 路由键 1.3 交换机的作用和分类1.3.1 直连交换机(direct…

【MySQL进阶】锁

文章目录 锁概述全局锁语法特点 表级锁表锁意向锁 行级锁行锁间隙锁&临键锁 面试了解数据库的锁吗?介绍一下间隙锁InnoDB中行级锁是怎么实现的?数据库在什么情况下会发生死锁?说说数据库死锁的解决办法 锁 概述 锁机制:数据库…

C++进阶--哈希的应用之位图和布隆过滤器

哈希的应用之位图和布隆过滤器 一、位图1.1 位图(bitset)的提出1.2 位图的概念1.3 位图的模拟实现1.3.1 位图的底层结构1.3.2 位图的成员函数1.3.2.1 位图的构造1.3.2.2 位图的插入:set1.3.2.3 位图的删除:reset1.3.2.4 位图的查找…

LLM for Kernel Fuzzing

KernelGPT: Enhanced Kernel Fuzzing via Large Language Models 1.Introduction2.Background2.1.Kernel and Device Drivers2.2.Kernel Fuzzing2.2.1.Syzkaller规约2.2.2.规约生成 3.Approach3.1.Driver Detection3.2.Specification Generation3.2.1.Command Value3.2.2.Argum…

鸿蒙自定义刷新组件使用

前言 DevEco Studio版本:4.0.0.600 1、RefreshLibrary_HarmonyOS.har,用于HarmonyOS "minAPIVersion": 9, "targetAPIVersion": 9, "apiReleaseType": "Release", "compileSdkVersion": "3.…

【Redis】Redis如何实现key的过期删除

​ 🍎个人博客:个人主页 🏆个人专栏:Redis ⛳️ 功不唐捐,玉汝于成 ​ 目录 前言 正文 结语 我的其他博客 前言 在当今信息时代,数据的快速存储和高效检索成为了软件系统设计中的核心需求。Redis作为…

idea中git提交代码更改作者名字

代码提交远程的时候显示的是上一个离职同事的用户名,有两种方法进行更改 在C盘【C:\Users\Administrator】中找到.gitconfig文件 进行更改 打开文件 将姓名以及邮箱改成自己的即可 [user] name xxxxx email xxxxx163.com 如图所示 命令行更改 在Termi…

ESP32-WROVER-DEV连接W5500实现有线网络

目的:ESP32-WROVER-DEV相机模块连接W5500模块,实现有线网络的连接。 开发环境:Arduino 2.1.1 可以实现的功能:可以使用web的ping访问,ESP32的LED IO2闪烁。 硬件连接如下图: 模块硬件的导线连接 W5500 …

SpringBoot项目整合MybatisPlus并使用SQLite作为数据库

文章目录 SQLite介绍搭建项目创建项目修改pom.xml SQLite查看SQLite是否安装创建数据库创建数据表IDEA连接SQLitenavicat连接SQLite数据库 后端增删改查接口实现MybatisX生成代码不会生成看这个UserUserMapperUserMapper.xml controller创建配置文件application.yaml启动类Incr…

行测-判断:2.类比推理

行测-判断:2.类比推理 给出一组相关的词,要求通过观察分析,在备选答案中找出一组与之在逻辑关系上最为贴近或相似的词。 1. 语义关系★★ 1.1 近义关系,反义关系 C,反义词 B,BD 都是近义词,考…