返回介绍

10.1 协同过滤

发布于 2024-01-26 22:17:31 字数 14931 浏览 0 评论 0 收藏 0

在2012年初,爆出了这样一则新闻故事:一位男子进入一家Target[1]商店,挥舞着手中的一叠优惠券,这些都是Target邮寄给他还在读高中的女儿的。他来的目的是谴责经理,因为这套优惠券都是诸如婴儿服装、配方奶和幼儿家具这类商品专享的。

听到顾客的投诉,经理再三道歉。他感觉很糟糕,想在几天后通过电话跟进,解释这是怎么回事。这个时候,反而是这位父亲在电话里进行了道歉。看来他的女儿确实是怀孕了。她的购物习惯泄露了她的这个秘密。

出卖这位女生的算法很可能是,至少部分是,基于协同过滤。

那么,什么是协同过滤?

协同过滤(collaborative filtering)是基于这样的想法,在某处总有和你趣味相投的人。假设你和趣味相投的人们评价方式都非常类似,而且你们都已经以这种方式评价了一组特定的项目,此外,你们每个人对其他人尚未评价的项目也有过评价。正如已经假设的那样,你们的口味是类似的,因此可以从趣味相投的人们那里,提取具有很高评分而你尚未评价的项目,作为给你的推荐,反之亦然。在某种程度上,这点和数字化配对非常相像,但结果是你喜欢的歌曲或产品,而不是与异性的约会。

对于怀孕的高中生这个案例,当她购买了无味的乳液、棉球和维生素补充剂[2]之后,她可能就和那些稍后继续购买婴儿床和尿布的人匹配上了。

10.1.1 基于用户的过滤

让我们通过一个例子来看看实践中这是如何运作的。

我们将从被称为效用矩阵(utility matrx)的东西开始。它和词条-文档矩阵相类似,不过这里我们表示的是产品和用户,而不再是词条和文档。

这里,我们假设有顾客A到D,以及他们所评分的一组产品,评分从0到5,如表10-1所示。

表10-1

Snarky's Potato Chips

SoSo SmoothLotion

DufflyBeer

BetterTapWater

XXLargeLivin' Football Jersey

SnowyCottonBalls

Disposos'Diapers

A

4

5

3

5

B

4

4

5

C

2

2

1

D

5

3

5

4

之前我们看到,当想要查找类似的项目时,可以使用余弦相似度。让我们在这里试试。我们将为用户A发现最相似的其他顾客。由于这里的向量是稀疏的,包含了许多未评分的项目,我们将在这些缺失的地方输入一些默认值。这里填入0。我们从用户A和用户B的比较开始。

from sklearn.metrics.pairwise import cosine_similarity 
cosine_similarity(np.array([4,0,5,3,5,0,0]).reshape(1,-1),\ 
                     np.array([0,4,0,4,0,5,0]).reshape(1,-1))  

上述代码生成图10-1的输出。

图10-1

我们可以看到,这两者没有很高的相似性,这是有道理的,因为他们没有多少共同的评分。

现在来看看用户C与用户A的比较。

cosine_similarity(np.array([4,0,5,3,5,0,0]).reshape(1,-1),\
                     np.array([2,0,2,0,1,0,0]).reshape(1,-1))

上述代码生成图10-2的输出。

图10-2

这里,我们看到他们有很高的相似度(记住1是完美的相似度), 尽管他们对同样产品的评价有所不同。为什么得到了这样的结果?[3]问题在于我们对没有评分的产品,选择使用0分。它表示强烈的(负的)一致性。在这种情况下,0不是中性的。

那么,如何解决这个问题?

我们可以做的是重新生成每位用户的评分,并使得平均分变为0或中性,而不是为缺失值简单地使用0。我们拿出每位用户的评分,将其减去该用户所有打分的平均值。例如,对于用户A,他打分的平均值为17/4,或4.25。然后我们从用户A提供的每个单独评分中减去这个值。

一旦完成,我们继续找到其他用户的平均值,从他们的每个评分中减去该均值,直到对每位用户完成该项操作。

这个过程后将产生表10-2。请注意,每行的用户评分总和为0 (这里忽略四舍五入带来的问题)。

表10-2

Snarky's Potato Chips

SoSo SmoothLotion

DufflyBeer

BetterTapWater

XXLargeLivin' Football Jersey

SnowyCottonBalls

Disposos'Diapers

A

- 0 .25

0 .75

-1.25

0 .75

B

- 0 .33

- 0 .33

0 .66

C

0 .33

0 .33

- 0 .66

D

0 .75

-1.25

0 .75

- 0 .25

让我们在新的数据集上尝试余弦相似度。再次将用户A和用户B、C进行比较。

首先,A和B之间的比较如下。

cosine_similarity(np.array([-.25,0,.75,-1.25,.75,0,0])\
                    .reshape(1,-1),\ 
                     np.array([0,-.33,0,-.33,0,.66,0])\
                     .reshape(1,-1))

上述代码生成图10-3的输出。

图10-3

现在,我们试试看A和C。

cosine_similarity(np.array([-.25,0,.75,-1.25,.75,0,0])\
                     .reshape(1,-1),\ 
                     np.array([.33,0,.33,0,-.66,0,0])\ 
                     .reshape(1,-1))

上述代码生成图10-4的输出。

图10-4

我们可以看到,A和B之间的相似度略有增加,而A和C之间的相似度显著下降。这正是我们所希望的。

这种中心化的过程除了帮助我们处理缺失值之外,还有其他好处,例如帮助我们处理不同严苛程度的打分者,现在每位打分者的平均分都是0了。注意,这个公式等价于Pearson相关系数,取值落在−1和1之间。

让我们现在采用这个框架,使用它来预测产品的评分。我们将示例限制为三位用户X、Y和Z,我们将预测X尚未评价,而和X非常相似的Y和Z已经评过的产品,对于X而言会得到多少分。

我们先从每位用户的基本评分开始,如表10-3所示。

表10-3

Snarky's Potato Chips

SoSo SmoothLotion

DufflyBeer

BetterTapWater

XXLargeLivin' Football Jersey

SnowyCottonBalls

Disposos'Diapers

X

4

3

4

Y

3.5

2.5

4

4

Z

4

3.5

4.5

4.5

接下来,我们将中心化这些评分,如表10-4所示。

表10-4

Snarky's Potato Chips

SoSo SmoothLotion

DufflyBeer

BetterTapWater

XXLargeLivin' Football Jersey

SnowyCottonBalls

Disposos'Diapers

X

0 .33

- 0 .66

0 .33

?

Y

0

-1

0 .5

0 .5

Z

- 0 .125

- 0 .625

0 .375

0 .375

现在,我们想知道用户X会给Disposos' Diapers打多少分。我们可以根据用户评分中心化之后的余弦相似度获得权重,并通过这些权重对用户Y和用户Z的评分进行加权计算。

让我们先得到用户Y和X的相似度。

user_x = [0,.33,0,-.66,0,33,0] 
user_y = [0,0,0,-1,0,.5,.5] 
     cosine_similarity(np.array(user_x).reshape(1,-1),\ 
     np.array(user_y). reshape(1,-1))

上述代码生成图10-5的输出。

图10-5

现在计算用户Z和X的相似度。

user_x = [0,.33,0,-.66,0,33,0] 
user_z = [0,-.125,0,-.625,0,.375,.375] 

cosine_similarity(np.array(user_x).reshape(1,-1),\ 
                     np.array(user_z).reshape(1,-1))

上述代码生成图10-6的输出。

图10-6

因此,我们现在有一个用户X和用户Y之间的相似度(0.42447212),以及用户A和用户Z之间的相似度(0.46571861)。

将它们整合起来,我们通过每位用户与X之间的相似度,对每位用户的评分进行加权,然后除以总相似度。

(0.42447212 × (4) +0.46571861 × (4.5)) / (0.42447212 +0.46571861) = 4.26

我们可以看到用户X对Disposos' Diapers 的预估评分为4.26(不低啊,最好发张优惠券!)。

10.1.2 基于项目的过滤

到目前为止,我们只了解了基于用户的协同过滤,但还有一个可用的方法。在实践中,这种方法远优于基于用户的过滤[4],它被称为基于项目的过滤。这是它的工作原理:每个被评分项目与所有其他项目相比较,找到最相似的项,而不是根据评分历史将每位用户和所有其他用户相匹配。同时,也是使用中心化余弦相似度。

让我们来看看它是如何工作的。

再次,我们有一个效用矩阵。这一次,我们将看看用户对歌曲的评分。每一列是一位用户,而每一行是一首歌曲,如表10-5所示。

表10-5

U1

U2

U3

U4

U5

S1

2

4

5

S2

3

3

S3

1

5

4

S4

4

4

4

S5

3

5

现在,假设我们想知道U3对于S5的评分。这里,我们会根据用户对歌曲的评分来寻找类似的歌曲,而不是寻找类似的用户。

让我们来看一个例子。

首先,我们从每行歌曲的中心化开始,并计算其他每首歌曲和目标歌曲(即S5)的余弦相似度,参见表10-6。

表10-6

U1

U2

U3

U4

U5

CntrdCoSim

S1

-1.66

0 .33

1.33

0 .98

S2

0

0

0

S3

-2.33

1.66

0 .66

0 .72

S4

0

0

0

0

S5

-1

?

1

1

你可以看到,最右边的列是其他每行相对行S5的中心化余弦相似度。

接下来需要选择一个数字,k,这是我们为预测U3对歌曲的评分,所要使用的最近邻居数量。在这个简单的例子中,我们使用k = 2。

我们可以看到对于歌曲S5,S1和S3是和它最相似的,所以我们将使用U3对这两首歌的评分(分别为4和5)。

现在让我们计算评分。

 (0.98 × (4) +0.72 × (5)) / (0.98 +0.72) = 4.42

因此,通过基于项目的协同过滤,我们可以看到U3很可能给S5打出高分4.42。

之前,我提到基于用户的过滤不如基于项目的过滤有效。这是为什么呢?

很有可能,你的朋友和你有共同的爱好,但是你们每个人都有自己喜欢,而别人毫无兴趣的领域。

例如,也许你们都喜欢“权力的游戏”这部电视剧,但你的朋友也喜欢Norwegian death metal重金属乐队。而你死也不愿意听这种音乐。如果你们在许多方面类似——除了death metal——那么基于用户的推荐,你仍然会看到很多关于乐队的推荐,其名称都包括火焰、斧头、头骨和大头棒这样的字眼。使用基于项目的过滤,很可能会避免让你看到这些推荐。

让我们用快速的代码示例,来总结这个问题的讨论。

首先,我们将创建DataFrame示例。

import pandas as pd 
import numpy as np 
from sklearn.metrics.pairwise import cosine_similarity 

df = pd.DataFrame({'U1':[2 , None, 1, None, 3], 'U2': [None, 3, None, 4, 
None],\
                      'U3': [4, None, 5, 4, None], 'U4': [None, 3, None, 4, 
None], 'U5': [5, None, 4, None, 5]}) 

df.index = ['S1', 'S2', 'S3', 'S4', 'S5'] 

df

上述代码生成图10-7的输出。

图10-7

我们现在将创建一个函数,它将读取用户和项目的评分矩阵。对于给定的项目和用户,该函数将返回基于协同过滤的预测评分。

def get_sim(ratings, target_user, target_item, k=2): 
centered_ratings = ratings.apply(lambda x: x - x.mean(), axis=1) 
csim_list = [] 
for i in centered_ratings.index: 
csim_list.append(cosine_similarity(np.nan_to_num(centered_ratings.loc[i,:].
values).reshape(1, -1), 
np.nan_to_num(centered_ratings.loc[target_item,:]).reshape(1, -1)).item()) 
new_ratings = pd.DataFrame({'similarity': csim_list, 'rating': 
ratings[target_user]}, index=ratings.index) 
top = new_ratings.dropna().sort_values('similarity', 
ascending=False)[:k].copy() 
top['multiple'] = top['rating'] * top['similarity'] 
result = top['multiple'].sum()/top['similarity'].sum() 
     return result

现在可以传入我们的值,并获得用户对项目的预测评分。

get_sim(df, 'U3', 'S5', 2)

上述代码生成图10-8的输出。

图10-8

我们可以看到这与之前的分析相符。

到目前为止,我们在进行比较时,将用户和项目作为整个的实体,但是现在,让我们继续了解另一种方法,它将我们的用户和项目分解为所谓的特征集合。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文