基于物品的协同过滤算法(细节分析)

Poblog 07月31日 2018


ItemCF 的一个优势就是可以提供推荐解释,即利用用户历史上喜欢的物品为现在的推荐结果进行解释

class Node:
   def __init__(self):
       self.weight = 0
       self.reason = dict()

def Recommend(user_id,train, W,K =3):
   
//user:'1'
    train:{'user':{goods...},...}
    W:{'goods1':{'goods2':like,...},...}
    //

   rank = dict()
   
//rank:user_id每个评价过的物品找出对应相似的物品
    //

   ru = train[user_id]
   
//ru:{'61': 1.0, '33': 1.0, ...}
      存放user评价过的电影
    //

   for i,pi in ru.items():
        
//i:'61'
  pi:1.0
//

       for j,wij in sorted(W[i].items(), \
                          key = operator.itemgetter(1), reverse = True)[0:K]:
           
//后面的公式找出物品i最相似的k个物品
   sorted:
   : [('59', 0.6040404496926219), ('60', 0.5457051563317492), ('462', 0.38018781261549794)]
      j:'59'  物品id
      wij:0.6040404496926219  相似度
   //

           if j in ru://已经出现过的不推荐
               continue
           if j not in rank:
               rank[j] = Node()
           rank[j].reason.setdefault(i,0)
           rank[j].weight += pi * wij
   
//计算推荐的物品的相似度
    //

           rank[j].reason[i] = pi * wij
   
//记录推荐j和i的关系有多大
   rank:
   {'59': }
    :
    reason:{'61': 0.6040404496926219,....} 推荐'59'和'61'的关系为0.6040404496926219
    weight:0.6040404496926219
    //

   return rank




 def Recommendation(users, train, W, K = 3):
    result = dict()
    for user in users:
        rank = Recommend(user,train,W,K)
        R = sorted(rank.items(), key = lambda student : student[1].weight, \
                   reverse = True)
        
//注意: key = lambda student : student[1].weight这里sort的排序规则变了
//

        result[user] = R
    return result



用户活跃度对物品相似度的影响

 IUF ( Inverse User Frequence ),活跃用户对物品相似度的贡献应该小于不活跃的用户,应该增加 IUF参数来修正物品相似度的计算公式

公式只是对活跃用户做了一种软性的惩罚,但对于很多过于活跃的用户,比如上面那位买了当当网 80% 图书的用户,为了避免相似度矩阵过于稠密,我们在实际计算中一般直接忽略他的兴趣列表,而不将其纳入到相似度计算的数据集中


和上面ItemCF算法不同的地方:

def ItemSimilarity(train):
    
//train:{'196': {'242': 1.0,'241': 1.0},
       '198': {'243': 1.0,'246': 1.0}
        ....}  训练集字典  user:{movieId1,....}
    //

    #calculate co-rated users between items
    C = dict()
    N = dict()
    for u, items in train.items():
      
//u:'196'
items:{'242': 1.0,....}
//

        for i in items:
    
//i:'242'
    //

            N.setdefault(i,0)
            N[i] += 1
    
//N:记录物品出现的总次数
    {'242': 1}
    //

            C.setdefault(i,{})
    
//C:记录物品-物品在同一用户列表出现次数
    {'242': {}}
    //

            for j in items:
                if i == j:
                    continue
                C[i].setdefault(j,0)
                C[i][j] += 1 / math.log(1+len(items) * 1.0)
                
//C:'242'和'393'共同出现在一个用户的列表中
  {'242': {'393': 0.65}}
  注:此处的计算公式和其他不同math.log(1+len(items) * 1.0)对过于活跃的用户进行了软惩罚
//

    #calculate finial similarity matrix W
    W = C.copy()
    for i, related_items in C.items():
       
//i:'242'  物品1
         related_items:{'393': 6, '381': 5, '251': 1, '655': 1, '663': 1, .....}  和物品有关联的物品列表
       //

        for j, cij in related_items.items():
   
//j:'393' 有关联的物品2
     cij:6.23   软惩罚之后的关联次数
   //

            W[i][j] = cij / math.sqrt(N[i] * N[j])
    
//W:赋值i和j的相似度  * N[j]的缘故是对热门物品进行惩罚避免推荐的全部是热门物品
    //

    return W
  


ItemCF-IUF 在准确率和召回率两个指标上和 ItemCF 相近,但 ItemCF-IUF 明显提高了推荐结果的覆盖率,降低了推荐结果的流行度, ItemCF-IUF 确实改进了ItemCF 的综合性能


物品相似度的归一化

Karypis 在研究中发现如果将 ItemCF 的相似度矩阵按最大值归一化,可以提高推荐的准确率,研究表明,如果已经得到了物品相似度矩阵 w ,那么可以用如下公式得到归一化之后的相似度矩阵 w'
归一化的好处不仅仅在于增加推荐的准确度,它还可以提高推荐的覆盖率和多样性.


ItemCF 算法和 ItemCF-Norm 算法的离线实验性能。从实验结果可以看到,归一化确实能提高 ItemCF 的性能,其中各项指标都有了比较明显的提高


ItemCF-Norm 代码改进 思路(未验证,效率待提高)
   W=ItemSimilarity(train);
   for i, related_items in W.items():
      for j, cij in related_items.items():
          W[i][j] = cij / max(W[j]);



UserCF和ItemCF的综合比较
  UserCF:给用户推荐那些和他有共同兴趣爱好的用户喜欢的物品,着重于反映和用户兴趣相似的小群体的热点(更社会化:反映了用户所在的小型兴趣群体中物品的热门程度)
  ItemCF: 给用户推荐那些和他之前喜欢的物品类似的物品,着重于维系用户的历史兴趣(更加个性化:反映了用户自己的兴趣传承)


  新闻网站:UserCF--因为很少有用户只看某个话题的新闻。
          另一个原因:新闻的更新非常快,每时每刻都有新内容出现,而 ItemCF 需要维护一张物品相关度的表,如果物品更新很快,那么这张表也需要很快更新,这在技术上很难实现,物品相关度表都只能做到一天一次更新,这在新闻领域是不可以接受的, UserCF 对于新用户也需要更新相似度表,但在新闻网站中,物品的更新速度远远快于新用户的加入速度

 图书、电子商务和电影网站:ItemCF---用户的兴趣是比较固定和持久的,越是资深的技术人员,他们看的书就越可能不热门,用户大都不太需要流行度来辅助他们判断一个物品的好坏,而是可以通过自己熟悉领域的知识自己判断物品的质量,网站的物品更新速度不会特别快,一天一次更新物品相似度矩阵对它们来说不会造成太大的损失,是可以接受的


  UserCF 需要维护一个用户相似度的矩阵,而 ItemCF 需要维护一个物品相似度矩阵

  实际的互联网中,用户数目往往非常庞大,而在图书、电子商务网站中,物品的数目则是比较少的,物品的相似度相对于用户的兴趣一般比较稳定

  UserCF和ItemCF优缺点的对比


  为什么原始 ItemCF 算法的覆盖率和新颖度都不高?

  亚马逊网的研究人员在设计 ItemCF 算法之初发现 ItemCF 算法计算出的图书相关表存在一个问题,就是很多书都和《哈利波特》相关。也就是说,购买任何一本书的人似乎都会购买《哈利波特》。后来他们研究发现,主要是因为《哈利波特》太热门了,确实是购买任何一本书的人几乎都会购买它。

  哈利波特问题有几种解决方案:
  可以在分母上加大对热门物品的惩罚:通过提高 α ,就可以惩罚热门的 j 。

   α 只有在取值为 0.5 时才会导致最高的准确率和召回率,而无论 α < 0.5 或者 α > 0.5 都不会带来这两个指标的提高。但是,如果看覆盖率和平均流行度就可以发现, α 越大,覆盖率就越高,并且结果的平均热门程度会降低。


  通过这种方法可以在适当牺牲准确率和召回率的情况下显著提升结果的覆盖率和新颖性


  上述方法还不能彻底地解决哈利波特问题

  每个用户一般都会在不同的领域喜欢一种物品


 看新闻联播是父辈每天的必修课,他们每天基本就看新闻联播,而且每天不看别的新闻,就看这一种新闻。此外,他们很多都是电视剧迷,都会看央视一套 8 点的电视剧。
 最终结果就是黄金时间的电视剧都和新闻联播相似,而新闻联播和其他新闻的相似度很低

 上面的问题换句话说就是,两个不同领域的最热门物品之间往往具有比较高的相似度

 这个时候,仅仅靠用户行为数据是不能解决这个问题的,因为用户的行为表示这种物品之间应该相似度很高。此时,我们只能依靠引入物品的内容数据解决这个问题,比如对不同领域的物品降低权重等