基于物品余弦相似度的协同过滤算法

本文介绍了一种基于物品余弦相似度的推荐算法,通过计算物品间的相似度为用户推荐未接触过的可能感兴趣物品,详细阐述了算法流程,包括数据加载、数据划分、相似度计算及推荐效果评估。
import random
import math

class Dataset():

    def __init__(self, filePath):
        self.data = self.loadData(filePath)

    def loadData(self, filePath):
        print("加载数据...")
        data = []
        for line in open(filePath):
            # 获取前两个数据:userId及该user评价的电影的id
            data.append(tuple(map(int, line.strip().split('::')[:2])))
        print("数据加载完成")
        return data

    def splitData(self, M, k, seed=1):
        '''
        :params: data, 加载的所有(user, item)数据条目
        :params: M, 划分的份数,最后需要取M折的平均,用M次试验的平均值作为最后的评测指标
        :params: k, 本次是第几次划分,k~[0, M)
        :params: seed, random的种子数,对于不同的k应设置成一样的
        :return: train, test
        '''
        print("正在拆分数据...")
        train, test = [], []
        random.seed(seed)
        for userID, movieID in self.data:
            if random.randint(0, M) == k:
                test.append((userID, movieID))
            else:
                train.append((userID, movieID))
        print("数据已拆分成训练集和测试集")
        print("训练集长度:", train.__len__())
        print("测试集长度:", test.__len__())

        # 建立用户-物品倒排表:
        # 即对每个用户建立一个包含他所有评价过的电影列表
        # 处理成字典的形式,user->set(items),键为userId, 值为该用户全部所评价的电影列表
        def convert_dict(data):
            data_dict = {}
            for userId, movieId in data:
                if userId not in data_dict:
                    data_dict[userId] = set()
                data_dict[userId].add(movieId)
            data_dict = {k: list(data_dict[k]) for k in data_dict}
            return data_dict

        # p1 = {key: value for key, value in convert_dict(train).items() if key < 5}
        # print("p1",  p1)
        return convert_dict(train), convert_dict(test)

#评价指标的计算
class Metric():

    def __init__(self, train, test, GetRecommendation):
        '''
        :params: train, 训练数据
        :params: test, 测试数据
        :params: GetRecommendation, 为某个用户获取推荐物品的接口函数
        '''
        self.train = train
        self.test = test
        self.GetRecommendation = GetRecommendation
        self.recs = self.getRec()

    # 为test中的每个用户进行推荐
    def getRec(self):
        print("为测试集test中的每个用户进行推荐...")
        print("+"*20)
        recs = {}
        for user in self.test:
            rank = self.GetRecommendation(user)
            recs[user] = rank
            print("user=", user)
            print("recs[user]=", recs[user])
        # print("recs:", recs)
        return recs

    # 定义精确率指标计算方式
    def precision(self):
        print("计算精确率...")
        all, hit = 0, 0
        for user in self.test:
            test_items = set(self.test[user])
            rank = self.recs[user]
            for item, score in rank:
                if item in test_items:
                    hit += 1
            all += len(rank)
        return round(hit / all * 100, 2)

    # 定义召回率指标计算方式
    def recall(self):
        print("计算召回率...")
        all, hit = 0, 0
        for user in self.test:
            test_items = set(self.test[user])
            rank = self.recs[user]
            for item, score in rank:
                if item in test_items:
                    hit += 1
            all += len(test_items)
        return round(hit / all * 100, 2)

    # 定义覆盖率指标计算方式
    def coverage(self):
        print("计算覆盖率...")
        all_item, recom_item = set(), set()
        for user in self.test:
            for item in self.train[user]:
                all_item.add(item)
            rank = self.recs[user]
            for item, score in rank:
                recom_item.add(item)
        return round(len(recom_item) / len(all_item) * 100, 2)

    # 定义流行度指标计算方式
    def popularity(self):
        print("计算流行度...")
        # 计算物品的流行度
        item_pop = {}
        for user in self.train:
            for item in self.train[user]:
                if item not in item_pop:
                    item_pop[item] = 0
                item_pop[item] += 1

        num, pop = 0, 0
        for user in self.test:
            rank = self.recs[user]
            for item, score in rank:
                # 取对数,防止因长尾问题带来的被流行物品所主导
                pop += math.log(1 + item_pop[item])
                num += 1
        return round(pop / num, 6)

    def eval(self):
        metric = {'准确率': self.precision(),
                  '召回率': self.recall(),
                  '覆盖率': self.coverage(),
                  '流行度': self.popularity()}
        print('评价指标:', metric)
        print("-"*50)
        return metric


# 1. 基于物品余弦相似度的推荐
def ItemCF(train, K, N):
    '''
    :params: train, 训练数据集
    :params: K, 超参数,设置取TopK相似物品数目
    :params: N, 超参数,设置取TopN推荐物品数目
    :return: GetRecommendation, 推荐接口函数
    '''
    # 计算物品相似度矩阵
    sim = {}
    num = {}
    for user in train:
        movieIds = train[user]
        for i in range(len(movieIds)):
            u = movieIds[i]  # 获取movieId
            if u not in num:  # 对相同的电影计数
                num[u] = 0
            num[u] += 1
            if u not in sim:  # 将movieId加入相似度矩阵,作为行头
                sim[u] = {}
            for j in range(len(movieIds)):
                if j == i: continue  # 矩阵对角线
                # 将出现的movieId加入相似度矩阵
                v = movieIds[j]
                if v not in sim[u]:
                    sim[u][v] = 0
                sim[u][v] += 1
    for u in sim:
        for v in sim[u]:
            sim[u][v] /= math.sqrt(num[u] * num[v])

    # 按照相似度排序
    sorted_item_sim = {k: list(sorted(v.items(), key=lambda x: x[1], reverse=True)) \
                       for k, v in sim.items()}

    # 获取接口函数
    def GetRecommendation(user):
        items = {}
        seen_items = set(train[user])
        for item in train[user]:
            for u, _ in sorted_item_sim[item][:K]:
                if u not in seen_items:
                    if u not in items:
                        items[u] = 0
                    items[u] += sim[item][u]
        recs = list(sorted(items.items(), key=lambda x: x[1], reverse=True))[:N]
        return recs

    return GetRecommendation

class Experiment():
    def __init__(self, M, K, N, filePath='D:/python/testProjects/test_01/data/ml-1m/ratings.dat'):
        '''
        :params: M, 进行多少次实验
        :params: K, TopK相似物品的个数
        :params: N, TopN推荐物品的个数
        :params: filePath, 数据文件路径
        '''
        self.M = M
        self.K = K
        self.N = N
        self.filePath = filePath

    # 定义单次实验
    def worker(self, train, test):
        '''
        :params: train, 训练数据集
        :params: test, 测试数据集
        :return: 各指标的值
        '''
        print("正在进行计算...")
        getRecommendation = ItemCF(train, self.K, self.N)
        metric = Metric(train, test, getRecommendation)
        return metric.eval()

    # 多次实验取平均
    def run(self):
        metrics = {'准确率': 0, '召回率': 0,
                   '覆盖率': 0, '流行度': 0}
        dataset = Dataset(self.filePath)
        for ii in range(self.M):
            train, test = dataset.splitData(self.M, ii)
            print('第{}次试验:'.format(ii+1))
            metric = self.worker(train, test)
            metrics = {k: metrics[k] + metric[k] for k in metrics}
        metrics = {k: metrics[k] / self.M for k in metrics}
        print('平均结果 (M={}, K={}, N={}): {}'.format(self.M, self.K, self.N, metrics))


'''
:params: M, 进行多少次实验
:params: K, TopK相似物品的个数
:params: N, TopN推荐物品的个数
'''
if __name__ == '__main__' :
    M, K, N = 1, 30, 5
    cf_exp = Experiment(M, K, N)
    print("总共进行的试验次数:", M)
    print("TopK相似物品的个数:", K)
    print("TopN推荐物品的个数:", N)
    print("-"*50)
    cf_exp.run()

'''
基于物品的协同过滤算法:
    给用户推荐那些他们之前喜欢的物品相似的物品
    通过分析用户的行为记录计算物品之间的相似度
    即:物品A和B具有很高的相似度,是因为喜欢物品A的用户都喜欢物品B
'''

 

### 基于余弦相似度协同过滤推荐算法实现 #### 1. 协同过滤推荐算法概述 协同过滤推荐算法是一种通过分析用户的历史行为数据来预测用户可能感兴趣的物品的方法[^1]。它主要包括两种形式:基于用户的协同过滤(User-Based Collaborative Filtering, User-CF)和基于物品协同过滤(Item-Based Collaborative Filtering, Item-CF)。其中,基于物品协同过滤在电商系统中更为常用,因为它能够更好地应对新用户冷启动问题,并且计算复杂度相对较低。 #### 2. 余弦相似度简介 余弦相似度用于衡量两个向量之间的夹角大小,通常用来表示两者的相似程度。对于电商系统的商品推荐场景,可以将每个用户或每件商品看作一个多维空间中的向量,其维度对应于其他用户/商品的行为记录。具体而言,如果 $ u_i $ 和 $ u_j $ 是两位用户的评分向量,则它们的余弦相似度定义为: $$ \text{cosine\_similarity}(u_i, u_j) = \frac{\sum_{k=1}^{n} (r_{ik} \cdot r_{jk})}{\sqrt{\sum_{k=1}^{n} r_{ik}^2} \cdot \sqrt{\sum_{k=1}^{n} r_{jk}^2}} $$ 这里 $ r_{ik} $ 表示用户 $ i $ 对第 $ k $ 个物品的评分[^3]。 #### 3. 实现步骤说明 以下是基于余弦相似度协同过滤推荐算法的具体实现过程: - **构建用户-物品交互矩阵** 首先需要建立一个二维数组或稀疏矩阵,行代表用户,列代表物品,单元格值为该用户对该物品的评分或其他互动指标(如点击次数、购买频率等)。由于实际应用中可能存在大量缺失值,因此需考虑如何填充这些空白区域。 - **计算相似度矩阵** 使用上述提到的余弦相似度公式分别计算所有成对用户之间或者所有成对物品之间的相似度得分,形成一个新的方阵——即相似度矩阵。注意,在大规模数据集上直接求解可能会非常耗时,所以往往采用一些优化策略比如局部敏感哈希(LSH)技术降低运算成本。 - **寻找最近邻居集合** 根据预先设定好的阈值挑选出最接近目标实体的一批对象构成近邻列表。例如当执行的是item-cf版本时,就找出与待测项目关联最强的一些产品作为备选池子成员。 - **生成最终推荐列表** 结合之前获得的相关性和权重参数综合评估各个候选项的价值,按照降序排列给出前若干名建议给定客户体验尝试消费可能性较高的那些货品实例[^3]。 #### 4. Python代码示例 下面展示了一个简化版的Python程序片段演示整个流程操作逻辑: ```python import numpy as np from sklearn.metrics.pairwise import cosine_similarity def build_user_item_matrix(data): """ 构建用户-物品矩阵 """ users = list(set([d['user'] for d in data])) items = list(set([d['item'] for d in data])) matrix = np.zeros((len(users), len(items))) user_to_idx = {users[i]:i for i in range(len(users))} item_to_idx = {items[j]:j for j in range(len(items))} for entry in data: user_id = user_to_idx[entry['user']] item_id = item_to_idx[entry['item']] rating = entry['rating'] matrix[user_id][item_id] = rating return matrix, users, items def calculate_cosine_similarities(matrix): """ 计算余弦相似度 """ similarities = cosine_similarity(matrix.T) return similarities data = [ {'user': 'A', 'item': 'X', 'rating': 5}, {'user': 'B', 'item': 'Y', 'rating': 3}, ... ] matrix, _, _ = build_user_item_matrix(data) similarities = calculate_cosine_similarities(matrix) print(similarities) ``` 此脚本首先创建了一个人工模拟的小型数据集`data[]`,接着调用了辅助函数完成从原始资料到结构化表格转换的任务;之后借助第三方库sklearn快速完成了核心部分—也就是针对转置后的输入表单运用内置方法得出各要素间的亲密度数值结果.
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值