本文详细说明机器学习实战这本书中的示例:餐厅菜肴推荐引擎的算法
5.1 推荐未尝过的菜肴
推荐系统的工作过程是:给定一个用户,系统会为此用户返回N个最好的推荐菜。为了实现这一点,则需要做到:
- 寻找用户没有评级的菜肴,即在用户-物品矩阵中的0值;
- 在用户没有评级的所有物品中,对每个物品预计一个可能的评级分数。这就是说,我们认为用户可能对物品的打分(这就是相似度计算的初衷);
- 对这些物品的评分从高到底进行排序,返回前N个物品。
基于物品相似度的推荐引擎代码如下:
# 用来计算在给定相似度计算方法的条件下,用户对物品的估计评分值
# 参数:数据矩阵、用户编号、物品编号、相似度计算方法,矩阵采用图1和图2的形式
# 即行对应用户、列对应物品
def standEst(dataMat, user, simMeas, item) :
# 首先得到数据集中的物品数目
n = shape(dataMat)[1]
# 对两个用于计算估计评分值的变量进行初始化
simTotal = 0.0; ratSimTotal = 0.0
# 遍历行中的每个物品
for j in range(n) :
userRating = dataMat[user,j]
# 如果某个物品评分值为0,意味着用户没有对该物品评分,跳过
if userRating == 0 : continue
# 寻找两个用户都评级的物品,变量overLap给出的是两个物品当中已经被评分的那个元素
overLap = nonzero(logical_and(dataMat[:, item].A>0, dataMat[:, j].A>0))[0]
# 若两者没有任何重合元素,则相似度为0且中止本次循环
if len(overLap) == 0 : similarity = 0
# 如果存在重合的物品,则基于这些重合物品计算相似度
else : similarity = simMeas(dataMat[overLap, item], dataMat[overLap, j])
# print 'the %d and %d similarity is : %f' % (item, j, similarity)
# 随后相似度不断累加
simTotal += similarity
ratSimTotal += similarity * userRating
if simTotal == 0 : return 0
# 通过除以所有的评分总和,对上述相似度评分的乘积进行归一化。这使得评分值在0-5之间,
# 而这些评分值则用于对预测值进行排序
else : return ratSimTotal/simTotal
# 推荐引擎,会调用standEst()函数,产生最高的N个推荐结果。
# simMeas:相似度计算方法
# estMethod:估计方法
def recommend(dataMat, user, N=3, simMeas=cosSim, estMethod=standEst) :
# 寻找未评级的物品,对给定用户建立一个未评分的物品列表
unratedItems = nonzero(dataMat[user, :].A==0)[1]
# 如果不存在未评分物品,退出函数,否则在所有未评分物品上进行循环
if len(unratedItems) == 0 : return 'you rated everything'
itemScores = []
for item in unratedItems :
# 对于每个未评分物品,通过调用standEst()来产生该物品的预测评分。
estimatedScore = estMethod(dataMat, user, simMeas, item)
# 该物品的编号和估计得分值会放在一个元素列表itemScores
itemScores.append((item, estimatedScore))
# 寻找前N个未评级物品
return sorted(itemScores, key=lambda jj : jj[1], reverse=True)[:N]
我们以一个更大的用户-菜肴矩阵来详细的说明算法的流程,用户-菜肴矩阵如下图:
模拟算法过程如下:
1.进入recommend函数,设置user=Brett=0,dataMat为用户-菜肴矩阵
2.计算unratedItems,先看dataMat[user, :].A==0得出的是布尔值,所以我们观察矩阵,
dataMat[0, :].A 是 [2,0,0,4,4,0,0,0,0,0,0]
dataMat[0, :].A==0 得出的列表是 [F,T,T,F,F,T,T,T,T,T,T],F表示false,t表示true
nonzero(dataMat[user, :].A==0)得出的数组是array([1,2,5,6,7,8,9,10],dtype=int64)
unratedItems =nonzero(dataMat[user, :].A==0)[0]得出的数组是[1,2,5,6,7,8,9,10]
发现 len(unratedItems) != 0
执行 itemScores = []
item为[1,2,5,6,7,8,9,10]中的其中一个,首先取item为1
执行estimatedScore = estMethod(dataMat, Brett, simMeas,1)
3.进入standEst函数,设置user=Brett,dataMat为用户-菜肴矩阵,item为1
执行 n = shape(dataMat)[1],n=11
simTotal = 0.0; ratSimTotal = 0.0
执行for j in range(n) :取j为0
执行userRating = dataMat[user,j],可以看出userRating = dataMat[0, 0]=2
执行overLap = nonzero(logical_and(dataMat[:, item].A>0, dataMat[:, j].A>0))[0]
dataMat[:, item].A >0= dataMat[:, 1].A>0=[ F,F,F,T,T,F,F,F,F,F,T ]
dataMat[:, j].A >0= dataMat[: , 0].A>0 = [ T.F.F.T.T.F.T.F.F.F.T ]
logical_and(dataMat[:, item].A>0, dataMat[:, j].A>0)=[F,F,F,T,T,F,F,F,F,F,T],这样就使得两个评分为0的用户的值为False
overLap = nonzero(logical_and(dataMat[:, item].A>0, dataMat[:, j].A>0))[0] = [3,4,10]
可以看出 len(overLap) != 0
执行similarity = simMeas(dataMat[overLap, 1], dataMat[overLap, 0]),
即将(3,5,1),(3,5,1)这两个向量余弦化,得:
similarity = (3*3+5*5+1*1)/[sqrt(3^2+5^2+1^2)*sqrt(3^2+5^2+1^2)]=1
simTotal = 0+1=1, ratSimTotal = 0+1*2=24.然后再次执行for j in range(n) :取j为1
执行userRating = dataMat[user,j],可以看出userRating = dataMat[0, 1]=0
执行if userRating == 0 : continue ,这里就规避了未被评分的物品的影响
5.依次类推
得出 ratSimTotal/simTotal
6.再返回recommend函数,执行itemScores.append((item, estimatedScore))=itemScores.append((1, estimatedScore))等,
计算出每个未评级物品的相应得分
执行并返回 sorted(itemScores, key=lambda jj : jj[1], reverse=True)[:N] ,寻找前N个未评级物品
7.结果分析,得分越高,说明给用户推荐该物品,用户喜欢的可能性就越大。 整个算法其实就是在做一件事情,计算所有未评级物品与已评级物品的相似度,并打分。