协同过滤

这篇博客介绍一下本人对协同过滤算法的一点粗浅的理解

协同过滤是一种经典的推荐算法,其主要功能是进行预测和推荐。协同过滤算法主要是通过对用户的历史数据进行挖掘,从而获取用户的偏好,进而对用户进行推荐。协同过滤算法主要分为两类:第一类是基于用户的协同过滤算法(user-based collaboratIve filtering),第二种是基于物品的协同过滤算法(item-based collaboratIve filtering)。接下来分别介绍这两种算法:

  • 基于用户的协同过滤算法(user-based collaboratIve filtering)

基于用户的协同过滤算法的核心思想就是根据用户的购买的历史数据找出和被推荐用户偏好相同的用户,根据这些被找出的用户的偏好来对被推荐用户进行商品的推荐。

  1. 寻找偏好相同的用户

假设存在如下的购买历史数据,表中为用户对商品的打分,例如我们现在要对用户2进行商品的推荐

 商品0商品1商品2商品3商品4
用户06.37.99.47.51.4
用户12.88.63.28.43.9
用户28.32.11.98.86.2
用户310.15.3.510.31.8
用户45.38.53.55.73. 

计算不同用户之间的余弦相似度(当然也可以采取其他指标来衡量用户之间的相似度,如欧氏距离,皮尔逊相关系数等),每一行作为描述用户的一个向量,为了避免余弦相似度对于数据大小的不敏感,我们采用修正余弦相似度,修正余弦相似度就是原始数据中每一列的值减去每一列的均值之后再求余弦相似度(如果是基于商品的话就是每一行减去每一行的均值之后再求余弦相似度),求得的余弦相似度矩阵如下所示:

 用户0用户1用户2用户3用户4
用户01-0.10743661-0.72897511-0.217028240.06820863
用户1-0.107436611-0.41094847-0.747491050.581322
用户2-0.72897511-0.4109484710.40040862-0.54275862
用户3-0.21702824-0.747491050.400408621-0.70154355
用户40.068208630.581322-0.54275862-0.701543551

从上表中可以看出和用户2最相似的前两个用户分别为用户3以及用户1

假设对于新一批商品,用户商品矩阵如下:

 商品0商品1商品2商品3商品4
用户07.92.55.84.62.8
用户12.75.2.36.10.
用户22.30.0.0.81.8
用户35.34.20.69.11.4
用户43.62.51.4.62.1

我们只需要看用户3以及用户1购买了的,但是用户2未购买的商品,从表中可以看出就是商品1和商品3了,然而,商品1和商品3哪一个更应该优先推荐给用户2呢?在这里我们这样计算:使用用户3和用户1与用户2的相似度作为权值,对评分进行加权求和再进行排序便得出了推荐顺序,计算过程如下:

score1 = 0.40040862 * 4.2 - 0.41094847 * 5 = -0.37302614600000017

score3 = 0.40040862 * 9.1 - 0.41094847 * 6.1 = 1.136932775

因此,我们对于用户2的推荐顺序为,优先推荐商品3,其次推荐商品1

到这里基于用户的协同过滤算法原理就说完了,接下来我们讨论基于商品的协同过滤算法

  • 基于物品的协同过滤算法(item-based collaboratIve filtering)

基于物品的协同过滤算法的核心思想就是,从新的商品中找出与用户已经购买的商品较为相似的商品,然后在推荐给用户。

先假设用户商品矩阵同上,如下所示:

 商品0商品1商品2商品3商品4
用户06.37.99.47.51.4
用户12.88.63.28.43.9
用户28.32.11.98.86.2
用户310.15.3.510.31.8
用户45.38.53.55.73. 

现在我们需要对用户2基于商品1来进行推荐

我们可以求得商品的修正余弦相似度矩阵,如下所示:

 商品0商品1商品2商品3商品4

商品0

1-0.67573792-0.475890850.52394413-0.2619477
商品1-0.6757379210.17905794-0.08555884-0.38554924
商品2-0.475890850.179057941-0.736125740.02310763
商品30.52394413-0.08555884-0.736125741-0.58185256
商品4-0.2619477-0.385549240.02310763-0.581852561

可以明显的看出,和商品1最相似的前2个(这里我们去前2个)商品为商品2和商品3,相似度依次为0.17905794和-0.08555884

现假设有新用户(用户A-C)对一批新商品(商品A-C)以及老商品进行了购买,对于老商品的购买,我们只关注对商品2和商品3的评分,则新用户对新商品(商品A-C)以及对老商品(商品2和商品3)的评分如下矩阵:

 商品A商品B商品C商品2商品3
用户A1.57319796-1.926802043.47319796-0.98441243-2.13518146
用户B-0.04562112-0.84562112-1.34562112-0.40900292.64586626
用户C-2.92988811-3.92988811-0.629888113.823560683.66610367

可以计算得到修正余弦相似度矩阵如下所示:

 商品A商品B商品C商品2商品3
商品A10.574770890.58667619-0.96447871-0.85518385
商品B0.574770891-0.18283381-0.72245596-0.56219773
商品C0.58667619-0.182833811-0.35192754-0.70337559
商品2-0.96447871-0.72245596-0.3519275410.75766642
商品3-0.85518385-0.56219773-0.703375590.757666421

由此我们可以得到商品A-C与商品2、商品3的相似度:

 商品A商品B商品C
商品2-0.96447871-0.72245596-0.35192754
商品3-0.85518385-0.56219773-0.70337559

我们只需要将用户2对商品2和商品3的评分作为权值对商品A-C的相似度分别进行加权求和,如下所示:

 用户2对商品2、3的评分商品A商品B商品C
商品21.9-0.96447871-0.72245596-0.35192754
商品38.8-0.85518385-0.56219773-0.70337559
加权求和 -9.358127429-6.320006348-6.8583675180000006

可以看出加权求和得到的推荐指数由高到低对应的商品分别为:商品B,商品C,商品A

由此我们便得出了对于用户2,基于商品1的推荐策略为,优先推荐商品B,其次为商品C,最后为商品A

至此,协同过滤算法以基本介绍完毕,下面是本人使用python编写的相关程序:

import numpy as np
from numpy import random as rd
import os
from copy import deepcopy
np.set_printoptions(linewidth=1000, suppress=True)
rd.seed(222)
# '用户-商品'矩阵为用户对商品的评分,0表示用户未购买此商品


class CF_Algorithm(object):

    def __init__(self, type, data, u_i_index, k):
        """

        :param data:用户商品矩阵,用于计算有用户间相似度或者是物品间相似度,0轴表示用户,1轴表示商品
        :param type:指定为"u"表示基于用户的协同过滤算法,指定为"i"表示基于物品的协同过滤算法
        :param u_i_index:指定被找最近邻的用户或商品的索引,如果u_i_index为物品索引,则判断是否为用户推荐u_i_index号商品
        :param k:相似度前k大的商品或用户
        """
        print("总共有%s个用户(用户编号为%s~%s)和%s种商品(商品编号为%s~%s)" % (data.shape[0], 0, data.shape[0] - 1, data.shape[1], 0, data.shape[1] - 1))
        print("'用户-商品'矩阵为:\n", data)
        self.k = k
        self.type = type.lower()
        self.u_i_index = u_i_index
        assert self.type in ["i", "u"], "协同过滤算法指定类别:u:基于用户,i:基于商品"
        self.data = data.astype(np.float64)
        self.data_copy = deepcopy(self.data)
        if self.type == "u":
            self.data = self.data - np.mean(self.data, axis=0, keepdims=True)
        else:
            self.user_index_recomended = int(input("请输入被推荐商品的用户编号[%s-%s]:" % (0, self.data.shape[0] - 1)))
            assert 0 <= self.user_index_recomended <= self.data.shape[0] - 1, "用户编号超出范围"
            assert self.data[self.user_index_recomended][self.u_i_index] != 0, "用户%s未购买商品%s,因此不能基于商品%s做推荐" % (self.user_index_recomended, self.u_i_index, self.user_index_recomended)
            self.data = self.data - np.mean(self.data, axis=1, keepdims=True)
        if self.type == "u":
            assert 0 <= self.u_i_index <= (self.data.shape[0] - 1), "指定被找最近邻的用户索引越界, 范围为[%d,%d]" % (0, self.data.shape[0] - 1)
        else:
            assert 0 <= self.u_i_index <= (self.data.shape[1] - 1), "指定被找最近邻的商品索引越界, 范围为[%d,%d]" % (0, self.data.shape[1] - 1)

    def calc_similarity(self):
        # 用来计算相似度矩阵
        mid_mat = None
        if self.type == "u":
            mid_mat = np.dot(self.data, self.data.T)
        else:
            mid_mat = np.dot(self.data.T, self.data)
        self.similarity_mat = np.zeros(mid_mat.shape, np.float64)
        for i in range(mid_mat.shape[0]):
            norm_i = None
            if self.type == "u":
                norm_i = np.linalg.norm(self.data[i, :])
            else:
                norm_i = np.linalg.norm(self.data[:, i])
            for j in range(i + 1):
                norm_j = None
                if self.type == "u":
                    norm_j = np.linalg.norm(self.data[j, :], ord=2)
                else:
                    norm_j = np.linalg.norm(self.data[:, j], ord=2)
                self.similarity_mat[i, j] = mid_mat[i, j] / (norm_i * norm_j)
        self.similarity_mat = (self.similarity_mat + self.similarity_mat.T) / (np.ones(mid_mat.shape) + np.eye(mid_mat.shape[0]))
        if self.type == "i":
            print("商品修正余弦相似度矩阵为:\n", self.similarity_mat)
        else:
            print("用户修正余弦相似度矩阵为:\n", self.similarity_mat)
        return self.similarity_mat

    def get_k_nearest(self):
        sort_index = np.argsort(self.similarity_mat[self.u_i_index, :])[::-1]
        self.k_nearest_index = sort_index[:self.k + 1][1:]
        if self.type == "u":
            print("和%s号用户最相似的前%s个用户的索引为:\n" % (self.u_i_index, self.k), self.k_nearest_index)
        else:
            print("和%s号商品最相似的前%s个商品的索引为:\n" % (self.u_i_index, self.k), self.k_nearest_index)
            # self.k_neareest_score记录了self.user_index_recomended用户对离self.u_i_index索引的物品最近的self.k个物品的评分
            self.k_nearest_score = self.data_copy[self.user_index_recomended, self.k_nearest_index]
        self.simi = self.similarity_mat[self.u_i_index, self.k_nearest_index]
        print("相似度依次为:", self.simi)

    def recommend(self, data_new, new_user_score_nearest):
        """

        :param data_new: 当self.type=u时为新用户对新物品的评分矩阵,即‘用户-商品’矩阵,当self.type=i时为新商品的‘用户-商品’矩阵
        :param new_user_score_nearest: 为新用户对最近的self.k个商品的评分矩阵
        :return:
        """
        print("新商品的'用户-商品'矩阵(%s个新商品,商品编号为%s~%s):\n" % (data_new.shape[1], 0, data_new.shape[1] - 1), data_new)
        if self.type == "u":
            good_bool_not_buy = data_new[self.u_i_index] == 0
            if not np.any(good_bool_not_buy):
                print("无需推荐,所有的新商品%s号用户均已购买评价" % self.u_i_index)
                return
            good_index_not_buy = np.array(list(range(data_new.shape[1])))[good_bool_not_buy]
            nearest_new_good_score = data_new[self.k_nearest_index][:, good_bool_not_buy]
            recommend_sort = good_index_not_buy[np.argsort(np.sum(self.simi.reshape(-1, 1) * nearest_new_good_score, axis=0))[::-1]]
            print("针对用户%s,推荐指数由高到低的商品编号为:\n" % self.u_i_index, recommend_sort)
        else:
            data_new_add = np.concatenate([data_new, new_user_score_nearest], axis=1)
            data_new_add = data_new_add - np.mean(data_new_add, axis=1, keepdims=True)
            print("将与%s号商品最相似的%s个商品的评分追加到新的'用户-商品'矩阵最后得到(商品编号为[%s-%s]):\n" % (self.u_i_index, self.k, 0, data_new.shape[1] + 1), data_new_add)
            med_data = np.dot(data_new_add.T, data_new_add)
            new_simi_mat = np.zeros(med_data.shape, dtype=np.float)
            for i in range(med_data.shape[0]):
                for k in range(i + 1):
                    new_simi_mat[i, k] = med_data[i, k] / (np.linalg.norm(data_new_add[:, i]) * np.linalg.norm(data_new_add[:, k]))
            new_simi_mat = (new_simi_mat + new_simi_mat.T) / (np.eye(new_simi_mat.shape[0]) + np.ones(new_simi_mat.shape, dtype=np.float))
            print("新商品追加最近的%s个商品的修正余弦相似度矩阵:\n" % self.k, new_simi_mat)
            # nearest_simi_with_new_good记录了新商品和最近的k个商品的相似度
            nearest_simi_with_new_good = new_simi_mat[-2:, :-2]
            print("新商品和最近的%s个商品的相似度:\n" % self.k, nearest_simi_with_new_good)
            print("用户%s对最近的%s个商品的评分:\n" % (self.user_index_recomended, self.k), self.k_nearest_score)
            recomend_order_big_to_small = np.arange(nearest_simi_with_new_good.shape[1])[np.argsort(np.sum(self.k_nearest_score.reshape(-1, 1) * nearest_simi_with_new_good, axis=0))[::-1]]
            print("用户%s基于商品%s对于新商品的推荐指数从高到低依次为:\n" % (self.user_index_recomended, self.u_i_index), recomend_order_big_to_small)


def main():
    cf = CF_Algorithm("i", np.around(rd.uniform(1, 11, (5, 5)), 1), 1, 2)
    cf.calc_similarity()
    cf.get_k_nearest()
    cf.recommend(np.around(np.array(rd.randint(0, 11, (3, 3)), dtype=np.float) / rd.uniform(1, 2, (3, 3)), 1), rd.uniform(0, 11, (3, cf.k)))


if __name__ == "__main__":
    main()

 

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值