基于userCF的推荐算法

本文介绍了使用协同过滤算法进行电影推荐的过程,包括数据划分、用户-用户相似度计算、推荐生成以及通过测试集评估推荐性能,主要关注召回率、精确率、覆盖率和流行度等指标。
摘要由CSDN通过智能技术生成

'''

1、将所有的用户-电影-评分条目8:2分成训练集和测试集;

2、根据训练集,构建用户-用户的协同过滤矩阵,找到每个用户和其他用户共同点评过的电影的数量:#{1: {3: 4, 4: 2, 5: 6, 2: 5}, 3: {1: 4, 4: 4, 5: 3,

2: 9}, 4: {1: 2, 3: 4, 2: 5, 5: 2}, 5: {1: 6, 2: 15, 3: 3, 4: 2}, 2: {1: 5, 5: 15, 3: 9, 4: 5}}  ;

3、根据训练集,统计训练集中每个用户点评的电影数量: {1: 45, 4: 20, 5: 155, 2: 100, 3: 41};

4、根据用户-用户的协同过滤矩阵和训练集中每个用户点评的电影数量,计算用户-用户的相似度矩阵:sim[u][v] = sim[u][v] / np.sqrt(num[u] * num[v]) ;

5、根据训练集,设置K个最高相似度用户,N个待推荐物品;

5、根据测试集,找到测试集的所有用户作为目标用户;

6、根据训练集中的目标用户 和 用户-用户的相似度矩阵,找到每个用户相似度最高的K个其他用户,倒序排列;

7、根据训练集进行推荐,使用80个相似度最高的其他用户的每个评价过的电影,且这些电影未在目标用户的训练集中被评价过,进行相似度累加,存入目标用户-电

影推荐表中;

8、根据训练集训练的结果,对每个目标用户进行 Top-N 推荐,将目标用户-电影推荐表中的前N项作为推荐的电影。

9、测试集的意义:代表了用户根据训练集的Top-N推荐电影,是否都在测试集中对这些电影进行评价了。

9、召回率计算:根据测试集,上一步中给目标用户推荐的Top-N电影是否在测试集的目标用户的电影评分表中,统计出总数,除以 测试集中所有目标用户评分的电

影总数。

10、准确率计算:根据测试集,上一步中给目标用户推荐的Top-N电影是否在测试集的目标用户的电影评分表中,统计出总数,除以 推荐给所有目标用户的总的

top-N电影。

11、计算覆盖率:通过训练集,训练出来的 给所有目标用户推荐的Top-N电影总数(去除了重复项),除以 训练集中 所有目标用户评过分的电影总数(去除了重复

项)。

12、计算流行度:首先遍历训练集,统计每个评过分的电影出现的总次数(有的电影总次数是多次),然后遍历目标用户-电影推荐表,统计给所有的目标用户推荐

的电影总数,统计每个评过分的电影出现的总次数的累加和: 用所有的目标用户推荐的电影总数 除以 每个评过分的电影出现的总次数的累加和,计算流行度。

'''

import pandas as pd

import numpy as np

from sklearn.model_selection import train_test_split

import os

from tqdm import tqdm

import math

import tensorflow as tf

import torch

def Recall(rec_dict, val_dict):

    """

        rec_dict: 推荐列表或评分列表,形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}

        val_dict: 用户实际的点击列表或评分列表(测试集),形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}

        召回率是评估推荐算法效果的指标之一,它衡量了推荐算法能够将用户实际点击的项目推荐出来的能力。

    """

    # 推荐列表中用户点击的项目数

    hit_items = 0

    # 所有的项目

    all_items = 0

    for user_id, items in val_dict.items():

        # 测试集中真实的点击列表

        real_set = items

        #print(real_set)

        # 推荐算法返回的推荐列表

        rec_set = rec_dict[user_id]

        #print(rec_set)

        # 当前用户推荐列表中有多少是实际点击的

        for item in rec_set:

            if item in real_set:

                hit_items += 1  # 求推荐的总数 \ 总的

                #print(hit_items,item)

                #print('')

        all_items += len(real_set)

        #print(all_items)

        #print('')

    print('Calc Recall...')

    print(hit_items)

    print(all_items)

    return round(hit_items / all_items * 100, 2)    #all_items 表示测试数据集中的中的物品数量,hit_items 表示当前系统推荐给 用户的 总的 N 个项目

   

def Precision(rec_dict, val_dict):

    """

    rec_dict: 推荐列表或评分列表,形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}

    val_dict: 用户实际的点击列表或评分列表(集),形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}

    """

    hit_items = 0

    all_items = 0

    for user_id, items in val_dict.items():

        real_set = items

        #print(real_set)

        rec_set = rec_dict[user_id]

        #print(rec_set)

        for item in rec_set:

            if item in real_set:

                hit_items += 1

                #print('*********')

                #print(hit_items,item)

                #print('')

        # 注意这里和 Recall 的区别,Recall 统计测试集里的样本,Precision 统计推荐列表中的样本

        all_items += len(rec_set)

        #print(all_items)

        #print('')

    print('Calc Precision...')

    print(hit_items)

    print(all_items)

    return round(hit_items / all_items * 100, 2)  #all_items 表示测试数据集中的项目数量,hit_items表示系统推荐给用户的 总共的 N 个物品

def Coverage(rec_dict, train_dict):

    #这是一个计算推荐系统覆盖率的函数

    """

    rec_dict: 推荐列表或评分列表,形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}

    train_dict: 用户实际的点击列表或评分列表(训练集),形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}

    函数的主要逻辑是遍历rec_dict中的每个用户,然后将该用户在train_dict中的所有物品添加到all_items集合中。

    接着,将该用户在rec_dict中的所有物品添加到hit_items集合中, 再求比值。

    """

    hit_items = set()

    all_items = set()

    for user_id in rec_dict:

        for item in train_dict[user_id]:

            all_items.add(item)

            #print(all_items)

           

            #print('')

        for item in rec_dict[user_id]:   #这里在统计总的推荐电影数量时,去除了重复项

            hit_items.add(item)

            #print(hit_items)

           

            #print('')

        print(hit_items)

    print('Calc Coverage...')

    print(len(hit_items))

    print(len(all_items))

    return round(len(hit_items) / len(all_items) * 100, 2)

def Popularity(rec_dict, train_dict):

    """

    rec_dict: 推荐列表或评分列表,形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}

    train_dict: 用户实际的点击列表或评分列表(训练集),形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}

    这段代码是一个计算推荐列表的流行度的函数。

   

    最后,将pop除以推荐物品的总数num,并保留三位小数返回结果。

    """

    pos_item = {}

    #函数首先遍历训练集train_dict,统计每个物品出现的次数,保存在pos_item字典中。

    for user_id in train_dict:

        #print(user_id)

        for item in train_dict[user_id]:

           # print(train_dict[user_id])

            if item not in pos_item:

                pos_item[item] = 0

            pos_item[item] += 1

        #print(pos_item)

        #第一次循环:{260: 1, 1029: 1, 1028: 1, 1287: 1, 1545: 1, 1035: 1, 783: 1, 527: 1, 2321: 1, 914: 1,

        #531: 1, 661: 1, 919: 1, 3105: 1, 1193: 1, 3114: 1, 1961: 1, 1836: 1, 1197: 1,

        #48: 1, 2355: 1, 1207: 1, 1097: 1, 3408: 1, 720: 1,

        #594: 1, 2398: 1, 608: 1, 2018: 1, 2918: 1, 2791: 1, 2028: 1, 2797: 1, 1907: 1, 2294: 1, 1270: 1, 1022: 1, 2687: 1}

    #然后遍历推荐列表rec_dict,对每个推荐的物品,根据其在训练集中出现的次数计算其流行度,并累加到pop变量中。

    pop, num = 0, 0

    for user_id in rec_dict:

        for item in rec_dict[user_id]:

            #print(pos_item[item])

            #print(math.log(pos_item[item] + 1))

            pop += math.log(pos_item[item] + 1) # 由于物品流行度满足长尾分布,取对数可以使得平均值更加稳定            

            num += 1

    print('Calc Popularity...')

    print(pop)

    print(num)    

    return round(pop / num, 3)

def rec_eval(val_rec_items, val_user_items, trn_user_items):   #四种评价指标

    print('Recall:',Recall(val_rec_items, val_user_items))  

    print('Precision',Precision(val_rec_items, val_user_items))

    print('Coverage',Coverage(val_rec_items, trn_user_items))

    print('Popularity',Popularity(val_rec_items, trn_user_items))

def get_data(data_path):

   

    col_names = ["user_id", "movie_id", "rating", "timestamp"]

    ratings = pd.read_csv(os.path.join(data_path, "ratings.dat"), sep="::", engine="python", names=col_names)

    print(ratings)

   

    # 划分训练集和测试集

    train_data, val_data, _, _ = train_test_split(ratings, ratings, test_size=0.2)

   

    train_data = train_data.groupby("user_id")["movie_id"].apply(list).reset_index()

   

    val_data = val_data.groupby("user_id")["movie_id"].apply(list).reset_index()

   

    # 将数组构造成字典的形式{user_id: [item_id1, item_id2,...,item_idn]}

    train_user_item = {}

    val_user_item = {}

   

    for user_id, movie in zip(*(list(train_data["user_id"]), list(train_data["movie_id"]))):

        train_user_item[user_id] = set(movie)

   

    for user_id, movie in zip(*(list(val_data["user_id"]), list(val_data["movie_id"]))):

        val_user_item[user_id] = set(movie)

    print(train_user_item[1])  #{1, 2692, 260, 1028, 1545, 1035, 527, 783, 2321, 914, 531, 150, 919, 1566, 3105, 2340, 1961,

    #938, 3114, 1962, 1836, 1193, 2355, 1207, 1721, 1097, 2762, 588, 3408, 720, 594, 595, 2398, 1246, 608, 2018, 2918, 745, 2028, 2797, 2294, 1270, 2687}

    return train_user_item, val_user_item

def item_user_list(train_user_item):

    print("建立倒排表....")

    items_users = {}

    for user_id, items in tqdm(train_user_item.items()):

        for item in items:

            if item not in items_users:

                items_users[item] = set()

            items_users[item].add(user_id)

    print(items_users)

    return items_users

def CollaborativeFilterMatrix(train_user_item, items_users):

    CFMatrix = {}

    num = {}

    print("构建协同过滤矩阵....")

    # 遍历所有的项目,统计用户两两之间交互的项目数

    for item, users in tqdm(items_users.items()):

       

        # 首先统计每个用户交互的项目个数

        for u in users:

            if u not in num:

                num[u] = 0

            num[u] += 1  #{1: 41, 3: 42, 4: 19, 5: 152, 2: 107}

            #print(num)

           

            # 统计每个用户与其它用户共同交互的项目个数

            if u not in CFMatrix:

                CFMatrix[u] = {}

            for v in users:

                if v != u:

                    if v not in CFMatrix[u]:

                        CFMatrix[u][v] = 0

                    CFMatrix[u][v] += 1

    #{1: {3: 4, 4: 2, 5: 6, 2: 5}, 3: {1: 4, 4: 4, 5: 3, 2: 9}, 4: {1: 2, 3: 4, 2: 5, 5: 2}, 5: {1: 6, 2: 15, 3: 3, 4: 2}, 2: {1: 5, 5: 15, 3: 9, 4: 5}}

    print(CFMatrix)  

    print(num)            

    return CFMatrix, num

def ComputeSimilarity(CFMatrix, num):

    sim = CFMatrix

    print("构建用户相似度矩阵....")

    for u, other_user in tqdm(CFMatrix.items()):

        for v, score in other_user.items():

            sim[u][v] = sim[u][v] / np.sqrt(num[u] * num[v])

            #print(sim)

       #这里得到的是一个相似度值的协同过滤矩阵

    return sim

                   

def RecForUser(sim, train_user_item, val_user_item, K, N):

    print("给测试用户进行推荐....")

    items_rank = {}

    '''    这段代码是对用户评分数据进行处理的过程。具体来说,它使用了一个循环来遍历每个用户和他们评分的电影集合。

    根据80个相似度最高的用户,将目标用户未评分的电影推荐前N个给目标用户。

    然后,它创建了一个空列表来存储每个用户的电影评分。接下来,它根据用户的相似度分数对相似用户进行排序,并选择前80个最高相似度的用户。

    然后,它遍历这些相似用户的评分电影,并将它们添加到当前用户的电影评分列表中。

    如果某个电影已经在当前用户的列表中,则跳过该电影。最后,根据相似度分数将电影的评分累加到当前用户的电影评分中。

    '''

    for u, _ in tqdm(val_user_item.items()):  #得到用户,每个用户评分的电影集合

        items_rank[u] = {}  #创建目标用户的空列表

        #针对用户的相似度用户按相似度分数排序,取前80个最高的相似度用户,倒序排列

        for v, score in sorted(sim[u].items(), key=lambda x:x[1], reverse=True)[:K]:

            for item in train_user_item[v]: #取出前80个用户的评分电影

                if item in train_user_item[u]:  #如果80个用户评分的电影在训练集的目标用户-电影评分表中,不做操作

                    continue

                else:

                    if item not in items_rank[u]:

                        items_rank[u][item] = 0  #创建测试用户的目标用户-未评分电影矩阵

                    items_rank[u][item] += score

    print('')

    #print(items_rank)

    print('')

    print("为每个用户进行Top-N推荐....")

    items_rank = {k: sorted(v.items(), key=lambda x: x[1], reverse=True)[:N] for k, v in items_rank.items()}

    #print(items_rank)

    items_rank = {k: set([x[0] for x in v]) for k, v in items_rank.items()} # 将输出整合成合适的格式输出

    print('')

    print(items_rank)

    return items_rank  

if __name__ == "__main__":

    if torch.cuda.is_available():

        device = torch.device("cuda")  # 使用GPU

    else:

        device = torch.device("cpu")

    #with tf.device('/GPU:0'):

    root_path = './data/ml-1m/'

    train_user_item, val_user_item = get_data(root_path)

    items_users = item_user_list(train_user_item)

    CFMatrix, num = CollaborativeFilterMatrix(train_user_item, items_users)

    sim = ComputeSimilarity(CFMatrix, num)

    rec_items = RecForUser(sim, train_user_item, val_user_item, K=80,N=10)  #根据训练集,最相似的前80个用户,进行TOP-10的推荐  

    rec_eval(rec_items, val_user_item, train_user_item)

   

"""

        推荐模型评估:

        Recall: 10.26

        Precision 33.99

        Coverage 19.41

        Popularity 7.228

"""

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值