首页 » 机器学习实战 » 机器学习实战全文在线阅读

《机器学习实战》14.4 基于协同过滤的推荐引擎

关灯直达底部

近十年来,推荐引擎对因特网用户而言已经不是什么新鲜事物了。Amazon会根据顾客的购买历史向他们推荐物品,Netflix会向其用户推荐电影,新闻网站会对用户推荐新闻报道,这样的例子还有很多很多。当然,有很多方法可以实现推荐功能,这里我们只使用一种称为协同过滤(collaborative filtering)的方法。协同过滤是通过将用户和其他用户的数据进行对比来实现推荐的。

这里的数据是从概念上组织成了类似图14-2所给出的矩阵形式。当数据采用这种方式进行组织时,我们就可以比较用户或物品之间的相似度了。这两种做法都会使用我们很快就介绍到的相似度的概念。当知道了两个用户或两个物品之间的相似度,我们就可以利用已有的数据来预测未知的用户喜好。例如,我们试图对某个用户喜欢的电影进行预测,推荐引擎会发现有一部电影该用户还没看过。然后,它就会计算该电影和用户看过的电影之间的相似度,如果其相似度很高,推荐算法就会认为用户喜欢这部电影。

在上述场景下,唯一所需要的数学方法就是相似度的计算,这并不是很难。接下来,我们首先讨论物品之间的相似度计算,然后讨论在基于物品和基于用户的相似度计算之间的折中。最后,我们介绍推荐引擎成功的度量方法。

14.4.1 相似度计算

我们希望拥有一些物品之间相似度的定量方法。那么如何找出这些方法呢?倘若我们面对的是食品销售网站,该如何处理?或许可以根据食品的配料、热量、某个烹调类型的定义或者其他类似的信息进行相似度的计算。现在,假设该网站想把业务拓展到餐具行业,那么会用热量来描述一个叉子吗?问题的关键就在于用于描述食品的属性和描述餐具的属性有所不同。倘若我们使用另外一种比较物品的方法会怎样呢?我们不利用专家所给出的重要属性来描述物品从而计算它们之间的相似度,而是利用用户对它们的意见来计算相似度。这就是协同过滤中所使用的方法。它并不关心物品的描述属性,而是严格地按照许多用户的观点来计算相似度。图14-3给出了由一些用户及其对前面给出的部分菜肴的评级信息所组成的矩阵。

图14-3 用于展示相似度计算的简单矩阵

我们计算一下手撕猪肉和烤牛肉之间的相似度。一开始我们使用欧氏距离来计算。手撕猪肉和烤牛肉的欧氏距离为:

而手撕猪肉和鳗鱼饭的欧氏距离为:

在该数据中,由于手撕猪肉和烤牛肉的距离小于手撕猪肉和鳗鱼饭的距离,因此手撕猪肉与烤牛肉比与鳗鱼饭更为相似。我们希望,相似度值在0到1之间变化,并且物品对越相似,它们的相似度值也就越大。我们可以用相似度=1/(1+距离)这样的算式来计算相似度。当距离为0时,相似度为1.0。如果距离真的非常大时,相似度也就趋近于0。

第二种计算距离的方法是皮尔逊相关系数(Pearson correlation)。我们在第8章度量回归方程的精度时曾经用到过这个量,它度量的是两个向量之间的相似度。该方法相对于欧氏距离的一个优势在于,它对用户评级的量级并不敏感。比如某个狂躁者对所有物品的评分都是5分,而另一个忧郁者对所有物品的评分都是1分,皮尔逊相关系数会认为这两个向量是相等的。在NumPy中,皮尔逊相关系数的计算是由函数corrcoef进行的,后面我们很快就会用到它了。皮尔逊相关系数的取值范围从-1到+1,我们通过0.5 + 0.5*corrcoef这个函数计算,并且把其取值范围归一化到0到1之间。

另一个常用的距离计算方法就是余弦相似度(cosine similarity),其计算的是两个向量夹角的余弦值。如果夹角为90度,则相似度为0;如果两个向量的方向相同,则相似度为1.0。同皮尔逊相关系数一样,余弦相似度的取值范围也在-1到+1之间,因此我们也将它归一化到0到1之间。计算余弦相似度值,我们采用的两个向量AB夹角的余弦相似度的定义如下:

其中,||A||、||B||表示向量A、B的2范数,你可以定义向量的任一范数,但是如果不指定范数阶数,则都假设为2范数。向量[4,2,2]的2范数为:

同样,NumPy的线性代数工具箱中提供了范数的计算方法linalg.norm

接下来我们将上述各种相似度的计算方法写成Python中的函数。打开svdRec.py文件并加入下列代码。

程序清单14-1 相似度计算

from numpy import *from numpy import linalg as ladef ecludSim(inA,inB):    return 1.0/(1.0 + la.norm(inA - inB))def pearsSim(inA,inB):    if len(inA) < 3 : return 1.0    return 0.5+0.5*corrcoef(inA, inB, rowvar = 0)[0][1]def cosSim(inA,inB):    num = float(inA.T*inB)    denom = la.norm(inA)*la.norm(inB)    return 0.5+0.5*(num/denom)  

程序中的3个函数就是上面提到的几种相似度的计算方法。为了便于理解,NumPy的线性代数工具箱linalg被作为la导入,函数中假定inAinB都是列向量。perasSim函数会检查是否存在3个或更多的点。如果不存在,该函数返回1.0,这是因为此时两个向量完全相关。

下面我们对上述函数进行尝试。在保存好文件svdRec.py之后,在Python提示符下输入如下命令:

>>> reload(svdRec)<module /'svdRec/' from /'svdRec.pyc/'>>>> myMat=mat(svdRec.loadExData)>>> svdRec.ecludSim(myMat[:,0],myMat[:,4])0.12973190755680383>>> svdRec.ecludSim(myMat[:,0],myMat[:,0])1.0  

欧氏距离看上去还行,那么接下来试试余弦相似度:

>>> svdRec.cosSim(myMat[:,0],myMat[:,4])0.5>>> svdRec.cosSim(myMat[:,0],myMat[:,0])1.0000000000000002      

余弦相似度似乎也行,就再试试皮尔逊相关系数:

>>> svdRec.pearsSim(myMat[:,0],myMat[:,4])0.20596538173840329>>> svdRec.pearsSim(myMat[:,0],myMat[:,0])1.0   

上面的相似度计算都是假设数据采用了列向量方式进行表示。如果利用上述函数来计算两个行向量的相似度就会遇到问题(我们很容易对上述函数进行修改以计算行向量之间的相似度)。这里采用列向量的表示方法,暗示着我们将利用基于物品的相似度计算方法。后面我们会阐述其中的原因。

14.4.2 基于物品的相似度还是基于用户的相似度?

我们计算了两个餐馆菜肴之间的距离,这称为基于物品(item-based)的相似度。另一种计算用户距离的方法则称为基于用户(user-based)的相似度。回到图14-3,行与行之间比较的是基于用户的相似度,列与列之间比较的则是基于物品的相似度。到底使用哪一种相似度呢?这取决于用户或物品的数目。基于物品相似度计算的时间会随物品数量的增加而增加,基于用户的相似度计算的时间则会随用户数量的增加而增加。如果我们有一个商店,那么最多会有几千件商品。在撰写本书之际,最大的商店大概有100 000件商品。而在Netflix大赛中,则会有480 000个用户和17 700部电影。如果用户的数目很多,那么我们可能倾向于使用基于物品相似度的计算方法。

对于大部分产品导向的推荐引擎而言,用户的数量往往大于物品的数量,即购买商品的用户数会多于出售的商品种类。

14.4.3 推荐引擎的评价

如何对推荐引擎进行评价呢?此时,我们既没有预测的目标值,也没有用户来调查他们对预测的满意程度。这里我们就可以采用前面多次使用的交叉测试的方法。具体的做法就是,我们将某些已知的评分值去掉,然后对它们进行预测,最后计算预测值和真实值之间的差异。

通常用于推荐引擎评价的指标是称为最小均方根误差(Root Mean Squared Error,RMSE)的指标,它首先计算均方误差的平均值然后取其平方根。如果评级在1星到5星这个范围内,而我们得到的RMSE为1.0,那么就意味着我们的预测值和用户给出的真实评价相差了一个星级。