(一)协作型过滤 Collaborative Filtering
如果想了解商品、影片或网站的推荐性信息,最没有技术含量的方法是向朋友们询问,其中一部分人的品味会比其他人高一些,通过观察这些人是否通常也和我们一样喜欢同样的东西,可以逐步对这些情况有所了解。不过随着选择越来越多,想要通过询问一小群人来确定我们想要的东西,将会变得越来越不实际。因为他们可能并不了解所有的选择。这就是为什么人们要发展出一套被称作协作型过滤(collaborative filteriing)的技术。
一个协作型过滤算法通常是对一大群人进行搜索,并从中找到与我们品味相近的一小群人。算法会对这些人所偏爱的其他内容进行考察,并从中找出与我们品味相近的一小群人。算法会对这些人所偏爱的其他内容进行考察,并将他们组合起来构造出一个经过排名的推荐列表。有不同的方法可以帮助我们确定那些人与自己品味相近,并将他们的选择组合成列表。
(二)搜集偏好 Collecting Perferences
我们要做的第一件事情,是寻找一种表达不同人及其偏好的方法。在python中,达到这一目的的一种非常简单的方法是使用一个嵌套的字典。我们新建一个名为recommendations.py的文件,并加入如下代码来构造一个数据集:
#一个涉及影评者及其对几部影片评分情况的字典
critics={'Lisa Rose':{'Lady in the Water':2.5,'Snakes on a Plane':3.5,
'Just My Luck':3.0,'Superman Returns':3.5,
'You,Me and Dupree':2.5,'The Night Listener':3.0},
'Gene Seymour':{'Lady in the Water':3.0,'Snakes on a Plane':3.5,
'Just My Luck':1.5,'Superman Returns':5.0,
'The Nighr Listener':3.0,'You,Me and Dupree':3.5},
'Michael Phillips':{'Lady in the Water':2.5,'Snakes on a Plane':3.0,
'Superman Returns':3.5,'The Nighr Listener':4.0},
'Claudia Puig':{'Snakes on a Plane':3.5,'Just My Luck':3.0,
'The Night Listener':4.5,'Superman Returns':4.0,
'You,Me and Dupree':2.5},
'Mick LaSalle':{'Lady in the Water':3.0,'Snakes on a Plane':4.0,
'Just My Luck':2.0,'Superman Returns':3.0,
'The Night Listener':3.0,'You,Me and Dupree':2.0},
'Jack Matthews':{'Lady in the Water':3.0,'Snakes on a Plane':4.0,
'The Night Listener':3.0,'Superman Returns':5.0,
'You,Me and Dupree':3.5},
'Toby':{'Snakes on a Plane':4.5,'You,Me and Dupree':1.0,
'Superman Returns':4.0}}
本章中,我们将以交互式方式使用python,因此,应该先将recommendations.py保存起来,以便python的交互解释程序能够读取到它。我们也可以将文件保存在 python/Lib 目录下,
不过最为简单的作法,是在与我们保存文件的同一目录下启动python解释程序。
上述字典使用从1到5的评分,以此来体现包括本人在内的每位影评人对某一给定影片的喜爱程度。不管偏好是如何表达的,我们需要一种方法来将它们对应到数字。加入我们正在架构一个购物网站,不妨用数字1来表示有人过去曾购买过某件商品,用数字0表示未曾购买过任何商品。而对于一个新闻故事的投票网站,我们可以分别用数字-1,0和1来表达“不喜欢”,“没有投票”,“喜欢”,如表2-1所示:
表2-1:从用户行为到相应评价值的可能对应关系
音乐会门票 |
|
在线购物 |
|
网站推荐者 |
|
已购买 |
1 |
已购买 |
2 |
喜欢 |
1 |
未购买 |
0 |
已浏览 |
1 |
未投票 |
0 |
|
|
未购买 |
0 |
不喜欢 |
-1 |
对于算法实验和范例演示而言,使用字典也是很方便的。我们可以很容易地对字典进行查询和修改。启用Python,并输入以下几行命令:
from recommendations import critics
#从文件recommendations.py中导入字典critics
critics['Lisa Rose']['Lady in the Water']
#查询Lisa Rose对影片“Lady in the Water”的评分
critics['Toby']['Snakes on a Plane']=4.5
#增加Toby对影片“Snakes on a Plane”的评分
critics['Toby']
#查询Toby的所有信息
尽管可以将相当数量的人员偏好信息置于字典内(即内存中),但对于一个规模巨大的数据及而言,也许我们还是会希望将其存入数据库中。
(三)寻找相近的用户 Finding Similar Users
搜集完人们的偏好数据后,我们需要有一种方法来确定人们在品位方面的相似程度。为此,我们可以将每个人与所有其他人进行对比,并计算他们的相似度评价值。有若干种方法可以达到此目的,下面是两套计算相似度评价值的体系:欧几里得距离和皮尔逊相关度。
欧几里得度量定义欧几里得空间中点 x = (x1,...,xn) 和 y = (y1,...,yn) 之间的距离为
计算相似度评价值的一个非常简单的方法是使用欧几里得距离评价方法。它以经过人们一致评价的物品作为坐标轴,然后将参与评价的人绘制到图上,并考察他们彼此之间的距离远近,如图2-1所示:
图2-1: 处于“偏好空间”中的人们
该图显示了处于“偏好空间”中人们的分布情况。Toby在Snake轴线和Dupree周线上所标示的数值分别是4.5和1.0,两人在“偏好空间”中的距离越近,他们的兴趣偏好就越相似。因为这张图是二维的,所以在同一时间内只能看到两项评分,但这一规则对于更多数量的评分项而言也是实用的。
为了计算图上Toby和LaSalle之间的距离,我们可以计算出每一轴向上的差值,求平方后再相加,最后对总和取平方根。在Python中,我们可以用函数pow(n,2)对某数求平方,并使用sqrt函数求平方根:
from math import sqrtsqrt(pow(4.5-4,2)+pow(1-2,2))1.118033988749895
“(注: 1. pow() 方法返回 xy(x的y次方) 的值。以下是math模块pow()方法的语法:
import math
math.pow(x,y)
内置的pow()方法:pow(x,y[,z])
函数是计算x的y次方,如果z在存在,则再对结果进行取模,其结果等效于pow(x,y) %z注意:pow() 通过内置的方法直接调用,内置方法会把参数作为整型,而 math 模块则会把参数转换为 float。 参数:x -- 数值表达式; y -- 数值表达式; z -- 数值表达式 返回 xy(x的y次方) 的值。 pow(x,2) 等价于 x**y
2. sqrt()方法返回 x (x>0) 的平方根; math 模块中 sqrt 函数只能进行浮点数的运算
负数的平方根是虚数(以及复数,即实数和虚数之和),这个需要一个专门的函数 cmath (complex math,复数)的模块做处理:
import cmath
cmath.sqrt(-1)
结果为1j
)
上述算式可以计算出距离值,偏好越相似的人,其距离就越短。我们还需要一个函数来对偏好越相近的情况给出越大的值。为此,我们可以将函数值加1(这样就可以避免遇到被零整除的错误了),并取其倒数:
1/(1+sqrt(pow(4.5-4,2)+pow(1-2,2)))
结果:0.4721359549995794
这一新的函数总是返回介于0和1之间的值,返回1表示两人具有一样的偏好。我们将前述知识结合起来,就可以构造出用来计算相似度的函数。
将下列代码加入recommendations.py
from math import sqrt
#返回一个有关Person1和Person2的基于距离的相似度评价
def sim_distance(prefs,person1,person2):
#prefs是不同人的偏好的字典,person1和person2分别为字典prefs的两个关键值
#得到shared_items的列表
si={} #建立一个字典si,其中关键值为item,在此例中即为各个被评价的影片
for item in prefs[person1]:
if item in prefs[person2]:
si[item]=1
#如果两者没有共同之处,返回0
if len(si)==0:
return 0
#计算所有差值的平方和
sum_of_squares=sum([pow(prefs[person1][item]-prefs[person2][item],2) for
item in prefs[person1] if item in prefs[person2]])
return 1/(1+sqrt(sum_of_squares))
我们调用该函数,分别传入两个人的名字,并计算出相似度的评价值。
from imp import reload结果:0.29429805508554946
reload(recommendations)
recommendations.sim_distance(recommendations.critics,'Lisa Rose','Gene Seymour')
(注:reload():
在python中,每一个以 .py结尾的Python文件都是一个模块。其他的文件可以通过导入一个模块来读取该模块的内容。导入从本质上来讲,就是载入另一个文件,并能够读取那个文件的内容。一个模块的内容通过这样的属性能够被外部世界使用。
这种基于模块的方式使模块变成了Python程序架构的一个核心概念。更大的程序往往以多个模块文件的形式出现,并且导入了其他模块文件的工具。其中的一个模块文件被设计成主文件,或叫做顶层文件(就是那个启动后能够运行整个程序的文件)。
默认情况下,模块在第一次被导入之后,其他的导入都不再有效。如果此时在另一个窗口中改变并保存了模块的源代码文件,也无法更新该模块。这样设计的原因在于,导入是一个开销很大的操作(导入必须找到文件,将其编译成字节码,并且运行代码),以至于每个文件、每个程序运行不能够重复多于一次。
Python2 中可以直接使用reload(module)重载模块。Pyhton3中需要使用如下两种方式:
方式(1)
from imp方式(2)
imp.reload(module)
from imp import reload使用reload()时出现如下错误
reload(module)
>>> from imp import reload
>>> reload(recommendations)
Traceback (most recent call last):
File "<pyshell#86>", line 1, in <module>
reload(recommendations)
NameError: name 'recommendations' is not defined 原因是因为在reload某个模块的时候,需要先import来加载需要的模块,这时候再去reload就不会有问题,具体看下面代码:
>>> from imp import reload
>>> import recommendations
>>> reload(recommendations))
(四)皮尔逊相关度评价 Pearson Correlation Score
除了欧几里得距离,还有一种更复杂的方法来判断人们兴趣的相似度,那就是皮尔逊相关系数。 两个变量之间的相关系数越高,从一个变量去预测另一个变量的精确度就越高,这是因为相关系数越高,就意味着这两个变量的共变部分越多,所以从其中一个变量的变化就可越多地获知另一个变量的变化。如果两个变量之间的相关系数为1或-1,那么你完全可由变量X去获知变量Y的值。
· 当相关系数为0时,X和Y两变量无关系。
· 当X的值增大,Y也增大,正相关关系,相关系数在0.00与1.00之间
· 当X的值减小,Y也减小,正相关关系,相关系数在0.00与1.00之间
· 当X的值增大,Y减小,负相关关系,相关系数在-1.00与0.00之间
当X的值减小,Y增大,负相关关系,相关系数在-1.00与0.00之间
相关系数的绝对值越大,相关性越强,相关系数越接近于1和-1,相关度越强,相关系数越接近于0,相关度越弱。
假设有两个变量X、Y,那么两变量间的皮尔逊相关系数可通过以下公式计算:
该相关系数是判断两组数据与某一直线拟合程度的一种度量。对应的公式比欧几里得距离评价的计算公式要复杂,但它在数据不是很规范(normalized)的时候( 比如,影评者对影片的评价总是相对于平均水平偏离很大时),会倾向于给出更好的结果。为了形象地展现这一方法,我们可以在图上标示出两位评论者的评分情况,如下图2-2所示,Mick LaSalle 为《Superman》评了3分,而额Gene Seymour则评了5分,所以该影片被定位在图中的(3,5)处。
图2-2: 在散点图上比较两位影评者的评分结果
在图上,我们还可以看到一条直线。因为其绘制原则是尽可能靠近图上的所有坐标点,故被称为最佳拟合线(best_fit line)。如果两位评论者对所有影片的评论情况都相同,那么这条直线将成为对角线,并会与图上所有的坐标点都相交,从而得到一个结果为1的理想相关度评价。对于如上图所示的情况,由于评论者对部分影片的评分不尽相同,因而相关系数大概在0.4左右。图2-3展现了一个有着更高相关系数的例子,约为0.75:
图2-3: 具有较高相关度评价值的两位评论者
在采用皮尔逊方法进行评价是,我们可以从图上发现一个值得注意的地方,那就是它修正了“夸大分值(grade inflation)”的情况。在这张图中,虽然Jack Matthews总是倾向于给出比Lisa Rose更高的分支,但最终的直线仍然是拟合的,这是因为它们两者有着相对近似的偏好。如果某人总是倾向于给出比另一个人更好的分值,而两者的分值之差又始终保持一致,则他们依然可能会存在很好的相关性。此前提到的欧几里得距离评价方法,会因为一个人的评价始终比另一个人更为“严格”(从而导致评价始终相对偏低),而得出两者不相近的结论,即使他们的品味很相似也是如此。而这一行为是否就是我们想要的结果,则取决于具体的应用场景。 皮尔逊相关度评价算法首先会找出两位评论者都曾评价过的物品,然后计算两者的评分总和与平方和,并求得评分的乘积之和,最后,算法利用这些计算结果计算出皮尔逊相关系数。不同于距离度量法,这一共是不是分厂只管,但是通过除以将所有变量的变化值相乘后得到的结果,它的确能够告诉我们变量的总体变化情况。 为了使用这一公式,新建一个与recommendations.py中的sim_distance函数有同样签名的函数:
#返回p1和p2的皮尔逊相关系数
def sim_pearson(prefs,p1,p2):
#得到双方都曾评价过的物品列表
si={}
for item in prefs[p1]:
if item in prefs[p2]:
si[item]=1
#得到列元素的个数
n=len(si)
#如果两者没有共同之处,返回1
if n==0:
return 1
#对所有偏好求和
sum1=sum([prefs[p1][it] for it in si])
sum2=sum([prefs[p2][it] for it in si])
#求平方和
sum1sq=sum([pow(prefs[p1][it],2) for it in si])
sum2sq=sum([pow(prefs[p2][it],2) for it in si])
#求乘积之和
pSum=sum([prefs[p1][it]*prefs[p2][it] for it in si])
#计算皮尔逊评价值
num=pSum-(sum1*sum2/n)
den=sqrt((sum1sq-pow(sum1,2)/n)*(sum2sq-pow(sum2,2)/n))
if den==0:
return 0
r=num/den
return r
该函数将返回一个介于-1和1之间的数值:值为1时,两个人对同一样物品均有着一致的评价。与距离度量法不同,此处我们无需为达到正确的比率而对这一数值进行变换。以下代码求得图2-3中的相关评价值:
reload(recommendations)结果:0.39840953644479793
print(recommendations.sim_person(recommendations.critics,'Lisa Rose','Gene Seymour'))
(五)应该选用哪一种相似性度量方法 Which Similarity Metic Should You Use?
除了上述两种度量方法,还有许多方法可以衡量两组数据间的相似程度。哪种方法最优,取决于具体的应用。 本章剩余部分出现的函数均有一个可选的相似性参数,该参数指向一个实际的算法函数,这可以使针对算法的实验变得更加容易:我们可以指定sim_pearson或sim_distance作为相似性参数的取值。我们还可以使用许多其他的函数,如Jaccard系数或曼哈顿距离算法,作为相似度计算函数,只要它们满足如下条件:拥有同样的函数签名,以一个浮点数作为返回值,其数值越大代表相似度越大。 Jaccard 系数:又叫Jaccard相似性系数,用来比较样本集中的相似性和分散性的一个概率。Jaccard系数等于样本集交集与样本集合集的比值,即J = |A∩B| ÷ |A∪B|。即样本交集和样本并集的比值,两个文档的共同都有的词除以两个文档所有的词;
Jaccard 距离(Jaccard Distance) :是用来衡量两个集合差异性的一种指标,它是杰卡德相似系数的补集,被定义为1减去Jaccard相似系数。 曼哈顿距离(Manhattan Distance):
曼哈顿距离来源于城市区块距离,是将多个维度上的距离进行求和后的结果,即当上面的明氏距离中p=1时得到的距离度量公式,如下: