基于协同过滤实现个性化推荐

个性化推荐分为两种方式,一种是利用神经网络实现个性化推荐,另一种是使用协同过滤算法实现个性化推荐。协同过滤算法主要分为两步,第一步协同,比较两者的相似度,然后找出相似度较近的两者,第二步是过滤,过滤出相似两者相同的物品,其他的物品就是用户可能感兴趣的东西。

相似度计算公式

1)余弦相似度:结果 = 向量A · 向量B / (向量A的模长 * 向量B的模长)

余弦相似度公式

它计算的是两个向量在空间中的夹角大小, 值域为[-1, 1]: 1代表夹角为0°, 完全重叠/完全相似; -1代表夹角为180°, 完全相反方向/毫不相似

余弦相似度的问题是: 其计算严格要求"两个向量必须所有维度上都有数值",不能存在null的情况。

2)皮尔逊相关系数
皮尔逊相关系数的思路是, 我把这些null的维度都填上0,所以它可以看成数据标准化处理之后的余弦相似度
皮尔逊相关系数具有以下特点:

(1)、当X、Y相关系数结果为0时,X和Y两变量无关系。
(2)、当X的值增大(减小),Y值增大(减小),两个变量为正相关,相关系数在0.00与1.00之间。
(3)、当X的值增大(减小),Y值减小(增大),两个变量为负相关,相关系数在-1.00与0.00之间。
相关系数的绝对值越大,相关性越强,相关系数越接近于1或-1,相关度越强,相关系数越接近于0,相关度越弱。

通常情况下通过以下取值范围判断变量的相关强度:
0.8-1.0 极强相关
0.6-0.8 强相关
0.4-0.6 中等程度相关
0.2-0.4 弱相关
0.0-0.2 极弱相关或无相关

皮尔逊相关算法以及python实现

皮尔逊相关系数计算公式

##普通计算
def test(x, y):
   sum_xy = (x*y).mean()
   sum_x = x.mean()
   sum_y = y.mean()
   sum_x2 = (x*x).mean()
   sum_y2 = (y*y).mean()
   pc = (sum_xy-sum_x*sum_y)/np.sqrt((sum_x2-sum_x*sum_x)*(sum_y2-sum_y*sum_y))
   print(pc)
   
##pandas方式计算
def test2(x, y):
   data=pd.DataFrame({"x":x,"y":y})
   print(data.corr())

if __name__ == '__main__':
   x=np.array([1,3,5])
   y=np.array([1,2,4])
   test(x, y);
   test2(x, y);
基于用户的协同过滤算法(UserCF)

基本思路:
①首先整理用户A的感兴趣物品列表,例如物品A,物品B等
②计算计算用户A与其他用户之间的相似度。计算用户相似度我们可以使用余弦相似度皮尔逊相关系数
③计算得到与用户A相似度最近的K个用户,最后通过用户相似度叠加该用户感兴趣物品的程序,得出相似度最近的物品。
例如,下表是 用户-物品 评分表,0表示没有对物品进行过操作,根据此表数据列表用户A的推荐物品清单:

用户\物品 a b c d e
A 1 0 0 4 0
B 2 0 3 1 1
C 1 3 3 0 4
D 2 1 2 3 0

计算过程:
①首先计算各用户与A用户的相似度分别为:B:-0.12,C:-0.87,D:0.75
②然后相似度叠加到物品评分上,得出剩余物品的评分:b:-1.86, c:-1.47, e:-3.6
③综合以上评分,推荐的物品排序是 c > d > e

完整代码: 代码逻辑主要参照协同过滤和算法推荐,并作了进一步处理

import operator
from math import sqrt, pow


class UserCf():
   # 获得初始化数据
   def __init__(self, data):
       self.data = data;

   # 计算两个用户的皮尔逊相关系数
   def pearson(self, user1, user2):
       sumXY = 0.0;
       n = 0;
       sumX = 0.0;
       sumY = 0.0;
       sumX2 = 0.0;
       sumY2 = 0.0;
       try:
           for movie1, score1 in user1.items():
               # 此处选择的是两个用户之间相同的部门,为了解决空数据的问题,与上面的计算公式略有不同
               if movie1 in user2.keys():
                   n += 1;
                   sumXY += score1 * user2[movie1]
                   sumX += score1;
                   sumY += user2[movie1]
                   sumX2 += pow(score1, 2)
                   sumY2 += pow(user2[movie1], 2)

           ## 利用上图中第三个公式求解
           ##协方差:cov(X,Y) = E(XY)-E(X)E(Y) = SUM(XY)/n-[SUM(X)/n*SUM(Y)/n]
           molecule = sumXY - (sumX * sumY) / n;
           ##方差:sqrt(SUM(X2)/n-[SUM(X)/n]2)
           denominator = sqrt((sumX2 - pow(sumX, 2) / n) * (sumY2 - pow(sumY, 2) / n))
           r = molecule / denominator
       except Exception as e:
           print("异常信息:", e.message)
           return None
       return r

   # 计算与当前用户的距离,获得最临近的用户
   def nearstUser(self, username, n=1):
       distances = {};  # 用户,相似度
       for otherUser, items in self.data.items():  # 遍历整个数据集
           if otherUser not in username:  # 非当前的用户
               distance = self.pearson(self.data[username], self.data[otherUser])  # 计算两个用户的相似度
               distances[otherUser] = distance
       sortedDistance = sorted(distances.items(), key=operator.itemgetter(1), reverse=True);  # 最相似的N个用户
       print("排序后的用户相似度为:", sortedDistance)
       return sortedDistance[:n]

   # l利用用户相似度和评分给电影排序
   def recomand(self, username, n=1):
       recommand = {};  # 待推荐的电影
       for user, score in dict(self.nearstUser(username, n)).items():  # 最相近的n个用户
           for movies, scores in self.data[user].items():
               if movies not in self.data[username].keys():  # 当前用户没有看过
                   ## 此处添加了用户相似度与评分之间的叠加关系
                   if movies not in recommand.keys():
                       recommand[movies] = scores * score
                   else:
                       recommand[movies] += scores * score

       return sorted(recommand.items(), key=operator.itemgetter(1), reverse=True);  # 对推荐的结果按照电影评分排序


if __name__ == '__main__':
   users = {'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 Night 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 Night 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}
            }

   userCf = UserCf(data=users)
   recommandList = userCf.recomand('Toby', 10)
   print("最终推荐:%s" % recommandList)

基于物品实现协同过滤

基本思路:
①首先整理出用户物品的关系表。即用户-物品列表,记录了每个用户喜爱的物品
②统计各物品的喜欢人数,整理出物品-物品共现矩阵,即共同喜欢两个物品的人数
③利用余弦相似度计算物品-物品之间的相似度,并且通过叠加用户对该物品的感兴趣程度,得出相似度最近的物品。

NOTE:对于异常值的处理:限制"用户-物品倒排表"中物品数量
比如,商城网站,假定一般用户一天内只会购买10件商品,如果存在用户一天内购买了超过100件商品,说明这个用户可能是批发商,此类用户数据需要排除或者添加惩罚参数。因为此类用户的行为数据不仅增加计算量,而且会干扰其他用户的推荐数据,比如相似度计算时,热门物品与所有人都有关联,那么这些物品与其他物品的相似度都会比较大,那么计算出来的推荐结果总是热门。

IUF公式

IUF(Inverse User Frequence),即用户活跃度对数的倒数的参数,John S. Breese认为活跃用户对物品相似度的贡献应该小于不活跃的用户,他提出应该增加IUF参数来修正物品相似度的计算公式。为了避免相似度矩阵过于稠密,我们在实际计算中一般直接忽略他的兴趣列表,而不将其纳入到相似度计算的数据集中

表格1-1,用户-物品列表

用户 喜爱的物品
A {a,b,d}
B {b,c,e, f}
C {c,d}
D {b,c,d}
E {a,d, f}
F {a,b, c, d, e, f}(非正常用户,不参与计算)

表格1-2,物品的共现矩阵

物品 a b c d e f
a 0 1 0 2 0 1
b 1 0 2 2 1 1
c 0 2 0 2 1 1
d 2 2 2 0 0 1
e 0 1 1 0 0 1
f 1 1 1 1 1 0

表格1-3,物品的感兴趣人数列表

物品 感兴趣人数
a 2
b 3
c 3
d 4
e 1
f 2

表格1-4,物品相似度,利用余弦相似度公式 W=N(ab)/√(N(a)*N(b))

物品 a b c d e f
a 0 1/√6 0 2/√8 0 1/2
b 1/√6 0 2/3 2/√12 1/√3 1/√6
c 0 2/3 0 2/√12 1/√3 1/√6
d 2/√8 2/√12 2/√12 0 0 1/√8
e 0 1/√3 1/√3 0 0 1/√2
f 1/2 1/√6 1/√6 1/√8 1/√2 0

表格1-5,计算用户A之外的c,e,f的分数

物品 分数
c 1*0+1*2/3+1*2/√12 = 1.24
e 1*0+1*1/√3+1*0 = 0.577
f 1*1/2+1*1/√6+1*1/√8 = 1.26

所以推荐的顺序是:f > c > e,但是f属于是热门产品,因为它和所有的物品都有关联关系,所以在分数时,只选择与该物品最相似的物品,防止分数叠加造成的影响。

完整代码: 代码逻辑主要参照协同过滤和算法推荐,并作了进一步处理

# -*- coding: UTF-8 -*-
from math import sqrt
import operator

#1.构建用户-->物品的倒排
def loadData(files):
   data ={}
   for line in files:
       user,score,item=line.split(",")
       data.setdefault(user,{})
       data[user][item]=score
   print("----1.1.用户:物品的倒排----")
   print(data)

   # 排除非正常用户的干扰
   for user in list(data.keys()):
       if len(data[user]) > 5:
           del data[user]

   print("----1.2.用户:物品的倒排----")
   print(data)
   return data

def similarity(data):
   # 构造物品的共现矩阵
   N={} #喜欢物品i的总人数
   C={} #喜欢物品i也喜欢物品j的人数
   for user,item in data.items():
       for i,score in item.items():
           N.setdefault(i,0)
           N[i]+=1
           C.setdefault(i,{})
           for j,scores in item.items():
               if j not in i:
                   C[i].setdefault(j,0)
                   C[i][j]+=1

   print("---2.构造的共现矩阵---")
   print ('N:',N)
   print ('C:',C)

   #2.2 计算物品与物品的相似矩阵
   W={}
   for i,item in C.items():
       W.setdefault(i,{})
       for j,item2 in item.items():
           W[i].setdefault(j,0)
           W[i][j]=C[i][j]/sqrt(N[i]*N[j])

   print("---3.构造的相似矩阵---")
   print(W)
   return W

#3.根据用户的历史记录,给用户推荐物品
def recommandList(data,W,user,k=3,N=10):
   rank={}
   for i,score in data[user].items(): #获得用户user历史记录,如A用户的历史记录为{'a': '1', 'b': '1', 'd': '1'}
>     # 此处k的作用是只选择与该物品最相似的产品,防止热门数据的干扰
       for j,w in sorted(W[i].items(),key=operator.itemgetter(1),reverse=True)[0:k]:  #获得与物品i相似的k个物品
           if j not in data[user].keys(): #该相似的物品不在用户user的记录里
               rank.setdefault(j,0)
               rank[j]+=float(score) * w

   print("---4.推荐----")
   print(sorted(rank.items(),key=operator.itemgetter(1),reverse=True)[0:N])
   return sorted(rank.items(),key=operator.itemgetter(1),reverse=True)[0:N]

if __name__=='__main__':
   # 用户,兴趣度,物品
   uid_score_bid = ['A,1,a', 'A,1,b', 'A,1,d', 'B,1,b', 'B,1,c', 'B,1,e', 'B,1,f', 'C,1,c', 'C,1,d', 'D,1,b', 'D,1,c', 'D,1,d',
                    'E,1,a', 'E,1,d', 'E,1,f', 'F,1,a', 'F,1,b', 'F,1,c', 'F,1,d', 'F,1,e', 'F,1,f']
   data=loadData(uid_score_bid)   #获得数据
   W=similarity(data) #计算物品相似矩阵
   recommandList(data,W,'A',3,10) #推荐

基于用户的协同过滤推荐算法与基于项目的协同过滤推荐算法比较

基于用户的协同过滤推荐算法:可以帮助用户发现新的商品,适用于用户比较少的场景。
基于物品的协同过滤推荐算法:准确性好,便于离线计算,但推荐结果一般是用户喜欢的商品,不会带给用户惊喜性,适用于物品不太多的场景。

一般新闻,博客网站的用户可能不太关心自己比较感兴趣的区域,而是热点数据。而且这种网站数据更新十分频繁,物品数据量大。所以这种场景比较适合使用基于用户的过滤算法。
而在商城或者评分网站,物品数量更新没这么频繁,而且此类网站用户热衷于寻找自己感兴趣的物品,所以更适合基于物品的过滤算法。

参照:物品协同过滤算法(ItemCF)原理以及案例实战(附完整 Python 代码)
皮尔逊相关系数python实现
推荐系统一基本流程讲解
java开发实现协同过滤算法 协同过滤和算法推荐
基于用户的协同过滤算法(java)
ItemCF - 限制"用户-物品倒排表"中物品数量

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容