fromnumpyimportrandomdefsplit_data(data,M,k,seed):"""切割训练集,防止过拟合data:训练集M:切割训练集的份数k:测试集的索引seed:随机数种子"""test=[]train=[]random.seed(seed)foruser,itemindata:ifrandom.randint(0,M)==k:test.append([user,item])else:train.append([user,item])returntrain,test二、评测指标2.1准确率/召回率对用户\(u\)推荐\(N\)个物品,记作\(R(u)\),用户\(u\)在测试集上喜欢的物品集合为\(T(u)\),可以使用准确率/召回率评测推荐算法的精度。
准确率公式为:
其中\(R(u)\)是用户在训练集上的行为给用户作出的推荐列表。
召回率公式为:
其中\(T(u)\)是用户在测试集上的行为给用户作出的推荐列表。
准确率描述最终的推荐列表中有多少比例是发生过的用户-物品评分记录;召回率描述有多少比例的用户-物品评分记录包含在最终的推荐列表中。
defrecall_(train,test,N):"""计算召回率"""hit=0all_=0foruserintrain.keys():tu=test[user]rank=get_recommendation(user,N)foritem,puiinrank:ifitemintu:hit+=1all_+=len(tu)returnhit/(all_*1.)defprecision(train,test,N):"""计算准确率"""hit=0all_=0foruserintrain.keys():tu=test[user]rank=get_recommendation(user,N)foritem,puiinrank:ifitemintu:hit+=1all_+=Nreturnhit/(all_*1.)2.2覆盖率覆盖率反映了推荐算法发掘长尾的能力,覆盖率越高,说明推荐算法越能够将长尾中的物品推荐给用户。
defpopularity(train,test,N):"""计算新颖度"""item_popularity=dict()foruser,itemsintrain.items():foriteminitems.keys():ifitemnotinitem_popularity:item_popularity[item]=0item_popularity[item]+=1ret=0n=0foruserintrain.keys():rank=get_recommendation(user,N)foritem,puiinrank:#物品的流行度分布满足长尾分布,对流行度取对数后,流行度的平均值更稳定ret+=math.log(1+item_popularity[item])n+=1ret/=n*1.returnret三、基于领域的算法基于领域的算法分为两大类,一类是基于用户的协同过滤算法,另一类是基于物品的协同过滤算法。
在一个基于用户的协同过滤算法的在线个性化推荐系统中,当一个用户A需要个性化推荐时,一般需要以下两个步骤:
对于步骤1,我们需要计算两个用户的兴趣相似程度,协同过滤算法主要利用用户的行为的相似度计算用户间兴趣的相似度,可以使用以下两种方法计算用户间的兴趣相似度(其中\(u\)和\(v\)表示不同的用户,\(N(u)\)表示用户\(u\)曾经拥有正反馈的物品集合;\(N(v)\)表示用户\(v\)曾经拥有正反馈的物品集合。):
假设上图为某个网站用户行为记录,UserCF(基于用户的协同过滤算法)时使用余弦相似度计算用户A和用户B的兴趣相似度为:
同理我们可以计算出用户A和用户C、D的相似度为:
defuser_similarity(train):"""计算用户间的余弦相似度"""W=dict()foruintrain.keys():forvintrain.keys():ifu==v:continueW[u][v]=len(train[u]&train[v])W[u][v]/=math.sqrt(len(train[u])*len(train[v])*1.)returnW对于上面的余弦相似度计算,有着很大的计算开销。因为很多用户之间并没有对同样的物品产生过行为,即有时候\(|N(u)\bigcap{N(v)}|=0\).因此我们可以考虑首先计算\(|N(u)\bigcap{N(v)}|\neq0\)的用户对\((u,v)\),然后再对这些用户进行余弦相似度计算,即下面改进的余弦相似度计算。
改进的余弦相似度计算,需要以下三个步骤:
defuser_similarity(train):"""计算用户之间的相似度"""#构建物品-用户的倒排表item_users=dict()foru,itemsintrain.items():foriinitems.keys():ifinotinitem_users:item_users[i]=set()item_users[i].add(u)#计算用户之间的共同有过行为的物品C=dict()N=dict()fori,usersinitem_users.items():foruinusers:N[u]+=1forvinusers:ifu==v:continueC[u][v]=+=1#计算最后的余弦相似度矩阵WW=dict()foru,related_usersinC.items():forv,cuvinrelated_users.items():W[u][v]=cuv/math.sqrt(N[u]*N[v])returnWFile"
其中\(S(u,K)\)包含和用户\(u\)兴趣最接近的\(K\)个用户,\(N(i)\)是对物品\(i\)有过行为的用户集合,\(w_{uv}\)是用户\(u\)和用户\(v\)的兴趣相似度,\(r_{vi}\)代表用户\(v\)对物品\(i\)的兴趣,因为使用的是单一行为的隐反馈数据,因此所有的\(r_{vi}=1\)。
defrecommend(user,train,W):"""实现UserCF算法"""rank=dict()interacted_items=train[user]forv,wuvinsorted(W[u].items,key=itemgetter(1),reverse=True)[0:K]:fori,rviintrain[v].items():ifiininteracted_items:#在继续之前我们应该筛选用户间的行为rank[i]+=wuv*rvireturnrank3.1.2User-IIF推荐算法如果两个用户都买过《新华字典》,并不能证明两个人兴趣相似,因为中国绝大多数人都买过《新华字典》,但是两个用户都买过冷门商品,如《机器学习》,则可以认为两个用户的兴趣相似。因此我们可以通过如下公式计算用户间的兴趣相似度:
基于上述用户间兴趣相似度则可以改造UserCF算法为User-IIF算法。
defuser_similarity(train):"""计算用户之间的相似度"""#构建物品-用户的倒排表item_users=dict()foru,itemsintrain.items():foriinitems.keys():ifinotinitem_users:item_users[i]=set()item_users[i].add(u)#计算用户之间的共同有过行为的物品C=dict()N=dict()fori,usersinitem_users.items():foruinusers:N[u]+=1forvinusers:ifu==v:continueC[u][v]=+=1/math.log(1+len(users))#计算最后的余弦相似度矩阵WW=dict()foru,related_usersinC.items():forv,cuvinrelated_users.items():W[u][v]=cuv/math.sqrt(N[u]*N[v])returnW3.2基于物品的协同过滤算法基于物品的协同过滤算法的推荐系统主要分为两步:
由于基于物品的协同过滤算法的思想是——购买了该商品的用户也会经常购买其他的商品。根据这句话,可以用下面的公式定义物品的相似度:
其中\(N(i)\)是喜欢物品\(i\)的用户数,\(|N(i)\bigcap{N(j)|}\)是同时喜欢物品\(i\)和物品\(j\)的用户数。
ItemCF算法的思想是,假设每个用户的兴趣都局限在某几个方面。如果两个物品属于一个用户的兴趣列表,那么这两个物品可能就属于有限的几个领域;如果两个物品属于很多用户的兴趣列表,则它们可能就属于同一个领域,因而有很大的相似度。
ItemCF算法类似于UserCF算法,也需要建立用户-物品倒排表,建立该表流程如下: