基于邻域的协同过滤算法(一)

  两个月前我开始学习机器学习,这两个月期间学习了一些基础的机器学习算法及其Python实现。这周我刚开始学习《推荐系统实践》这本书,并打算以后定期将自己的学习情况做个简单的总结。这份总结是我这个博客上的第一篇文章。这周的学习的主要内容是基于邻域的协同过滤算法。基于用户行为分析的推荐算法是个性化推荐系统的重要算法,学术界一般将这种类型的算法称为协同过滤算法。

  协同过滤,从名字就可以看出这种方法的本质是先有协同(用户齐心协力)再有过滤(每个用户的推荐列表能够过滤掉他不喜欢的物品)。对协同过滤算法,学术届提出了了很多种方法,比如基于邻域的方法(neighborhood-based)、隐语义模型(laten factor model)、基于图的随机游走算法(random walk on graph)等。

  基于邻域的方法主要包括两种算法:基于用户的协同过滤算法(UserCF)和基于物品的协同过滤算法(ItemCF)。

基于用户的协同过滤算法(UserCF):

  UserCF的本质就是我要给你推荐物品,我先找到与你兴趣最相似的K个用户,然后把他们喜欢的而你却没产生过行为的物品推荐给你。当然不是把所有的他们喜欢的且你没产生过行为的都推荐给你,而是推荐你最感兴趣的 N个产品。根据上面这些描述,我们看到使用 UserCF需要两步:

  1. 找到与你兴趣相似的K个用户。
  2. 确定你最感兴趣的N个物品?

  在第一步中,我们需要去计算你与每个用户的兴趣相似度,然后按相似度降序排列,所对应的前K个用户就是我们要找的。

  对于用户u和v我们可以通过如下的Jaccard公式来计算:
wuv=N(u)N(v)N(u)N(v) ,

或者通过余弦相似度计算:
wuv=N(u)N(v)N(u)N(v) ,
计算相似度的时候,为了减少时间复杂度,需要建立一个物品-用户倒排表。因为当用户数很大时,直接从数据集很费时间,很多时间浪费在了计算 N(u)N(v)=0 上。另外,两个用户对冷门物品采取过同样的行为更能说明他们兴趣的相似度。所以书中指出 John S. Breese 在论文中提出如下计算相似度的公式:

wuv=iN(u)N(v)1ln(1+N(i))N(u)N(v).

这个公式通过 1ln(1+N(i)) 惩罚了用户u和用户v共同兴趣列表中热门物品对他们相似度的影响。
这里用字典格式来储存数据集和相似度,如:

train={'user1':['a', 'b', 'c'], 'user2':['a','b'],....,'userN':[...]}]
W={('user1', 'user2'): w12, ('user1', 'user3'): w13,.....}

  下面是计算相似度的代码

def UserSimilarity2(train, flag=1):
    item_users = dict()  
    for u, item in train.items():
        for i in item:
            if i not in item_users:
                item_users[i] = set()  # 生成一个集合
            item_users[i].add(u)
    C = dict()  # C为分子
    N = dict()  # N[u]表示用户u喜欢的物品个数
    ""
    #用户数很大时计算C耗时
    for u in train.keys():
        for v in train.keys():
            if u==v:
                continue
            else:
                if len(set(train[u] ) & set(train[v]))!=0:
                    C[(u, v)]=len(set(train[u] ) & set(train[v]))
    for u in train.keys():
        N[u]=len(train[u])
    print C
    print N
    ""
    for item, users in item_users.items():

        for u in users:
            if u not in N:
                N[u] = 1  # 如果用户u不在字典N里面,先创建
            else:
                N[u] += 1
            for v in users:
                if u != v:
                    if flag == 0:  # 修正前
                        if (u, v) not in C:
                            C[(u, v)] = 1
                        else:
                            C[(u, v)] += 1
                    elif flag == 1: # 修正后
                        if (u, v) not in C:
                            C[(u, v)] = 1 / log(1 + len(users))
                        else:
                            C[(u, v)] += 1 / log(1 + len(users))
    W = dict()
    for uv in C.keys():
        # pdb.set_trace()
        u = uv[0]
        v = uv[1]
        if u not in W:
            W[u] = set()
        # 添加与用户u相关的用户v,第二个意思是他们的权重Wuv
        W[u].add((v, C[uv] / sqrt(N[u] * N[v])))
    return W

这个代码中 flag=0对应的是第一个没有修正的相似度计算公式,flag=1对应的就是修正后的相似度计算公式。
计算出相似度后,下来要做的就是找出与你相似度高的前K个用户,然后去把他们喜欢的你没发生过行为的物品(记为 Itemj )却很感兴趣的物品推荐给你。

  第二步和第一步的思路差不多,定性问题定量化。计算你对 Itemj 的感兴趣程度,然后降序排列,选出感兴趣程度最高的前N个推荐给你。
这里,感兴趣程度是用下面的公式来度量的:

p(u,i)=vS(u,K)N(i)wuvrvi

S(u,K) 就是那K个用户的集合, N(i) 是对物品i有过行为的用户集合, wuv 就不说了, rvi 是用户v对i物品的兴趣。简单的说,v对i产生了行为 rvi 就是1,没产生就是0,当然这里的 rvi 只有这两种值。

  下面是实现推荐的代码:

def Recommend(user, train, W, N, K=10):
    rank = dict()
    interacted_items = train[user]
    for v, wuv in sorted(W[user], key=lambda x: x[1], reverse=True)[0:K]:  # 相似度最大的K个用户
        for i in train[v]:  # v产生过行为的物品
            if i not in interacted_items:  
                if i not in rank:
                    rank[i] = wuv * 1
                else:
                    rank[i] += wuv * 1

    rank = sorted(rank.items(), key=lambda x: x[1], reverse=True)
    rank = rank[:N]
    return rank

这里的K是一个很关键的参数,K的选取会影响最后的推荐结果。这本书中K对TopN推荐的好坏,是用这几个指标来衡量的:准确率,召回率,覆盖率 和 流行度。
记U为系统的用户集合,R(u)为训练集上得出的推荐列表, T(u)为测试集上的行为列表。
推荐结果的准确率可定义为:

Precision=uU|R(u)T(u)|uU|R(u)|

召回率被定义为
Recall=uU|R(u)T(u)|uU|T(u)|

def PrecisionRecall(train, test, N):
    hit = 0 #两者的分子
    n_precision=0 # 准确率的分母
    n_recall = 0 #召回率的分母
    W = UserSimilarity2(train)
    for user in train.keys():
        try:  # 计算分子分母
            te_user_items = test[user]
            recomRank = Recommend(user, train, W, N)
            for recom_item, w in recomRank:
                if recom_item in te_user_items:
                    hit += 1
            n_precision += N
            n_recall+=len(te_user_items)
        except:
            pass
    return [hit / (1.0*n_precision), hit/(1.0*n_recall)]

覆盖率的计算公式为:

Coverage=uUR(u)I

这里 I 所有物品的集合。那么覆盖率就是推荐出去的物品占总物品的比率,商家们会关心的指标。下面是计算覆盖率的代码:

def Coverage(train, N):
    recommend_items = set() #分子
    all_items = set() #分母
    W = UserSimilarity2(train)
    for user in train.keys():
        for item in train[user]:
            all_items.add(item)
        rank = Recommend(user, train, W, N)
        for item in rank[0]:
            recommend_items.add(item)
    return len(recommend_items) / (len(all_items) * 1.0)

这本书上,目前看的这一章,我没有看到流行度的具体定义,但从给的代码看,似乎平均流行度是这么定义的:

p=uUln(1+R(u))U

具体代码是这样的:

 def Popularity(train,N):
    item_popularity=dict()
    for user, items in train.items():
        for item in items:
            if item not in item_popularity:
                item_popularity[item]=0
            item_popularity[item]+=1
    ret=0
    n=0
    W = UserSimilarity2(train, flag=1)
    for user in train.keys():
        rank=Recommend(user, train, W, N, K=10)
        for item, pui in rank:
            ret +=log(1+ item_popularity[item])
            n +=1
    ret /=n*1.0
    return ret

你可能感兴趣的:(推荐系统)