基于物品的协同过滤算法的原理:
基于物品的协同过滤算法给用户推荐那些和他们之前喜欢的物品相似的物品。不过ItemCF算法并不利用物品的内容属性计算物品之间的相似度,它主要通过分析用户的行为记录计算用户之间的相似度,也就是说物品A和物品B具有很大的相似度是因为喜欢物品A的用户大都也喜欢物品B(这一点也是基于物品的协同过滤算法和基于内容的推荐算法最主要的区别)。同时,基于物品的协同过滤算法可以利用用户的历史行为给推荐结果提供推荐解释,用于解释的物品都是用户之前喜欢的或者购买的物品。
算法主要分为两步:
一、计算物品之间的相似度;
二、根据物品的相似度和用户的历史行为给用户生成推荐列表;
第一步计算物品相似度根据公式:
N(i)表示喜欢物品i的用户数,N(j)表示喜欢物品j的用户数,两个求交表示同时喜欢i和j的用户数。
上面公式说明如果两个物品同时属于很多用户的兴趣列表,那么它们很有可能属于同一个领域,即具有很大的相似度。
第二步是在生成相似度基础上给用户生成推荐列表,公式为:
Pui表示用户u对物品i的感兴趣程度。求和公式下N(u)表示用户u感兴趣的物品,S(j,k)表示与物品j最相似的物品的集合。Wij表示i与j的相似度。rui表示用户u对物品i的兴趣(对于隐反馈数据集,r=1)。
参考代码:
# -*- coding: utf-8 -*-
'''
Created on 2017年9月18日
@author: Jason.F
'''
import math
import random
import os
from itertools import islice
class ItemBasedCF:
def __init__(self, datafile = None):
self.datafile = datafile
self.readData()
self.splitData()
def readData(self,datafile = None):
self.datafile = datafile or self.datafile
self.data = []
file = open(self.datafile,'r')
for line in islice(file, 1, None): #file.readlines():
userid, itemid, record = line.split(',')
self.data.append((userid,itemid,float(record)))
def splitData(self,data=None,k=3,M=10,seed=10):
self.testdata = {}
self.traindata = {}
data = data or self.data
random.seed(seed)#生成随机数
for user,item,record in self.data:
self.traindata.setdefault(user,{})
self.traindata[user][item] = record #全量训练
if random.randint(0,M) == k:#测试集
self.testdata.setdefault(user,{})
self.testdata[user][item] = record
def ItemSimilarity(self, train = None):
train = train or self.traindata
self.itemSim = dict()
item_user_count = dict() #item_user_count{item: likeCount} the number of users who like the item
count = dict() #count{i:{j:value}} the number of users who both like item i and j
for user,item in train.items(): #initialize the user_items{user: items}
for i in item.keys():
item_user_count.setdefault(i,0)
item_user_count[i] += 1
for j in item.keys():
if i == j:
continue
count.setdefault(i,{})
count[i].setdefault(j,0)
count[i][j] += 1
for i, related_items in count.items():
self.itemSim.setdefault(i,dict())
for j, cuv in related_items.items():
self.itemSim[i].setdefault(j,0)
self.itemSim[i][j] = cuv / math.sqrt(item_user_count[i] * item_user_count[j] * 1.0)
def recommend(self,user,train = None, k = 10,nitem = 5):
train = train or self.traindata
rank = dict()
ru = train.get(user,{})
for i,pi in ru.items():
for j,wj in sorted(self.itemSim[i].items(), key = lambda x:x[1], reverse = True)[0:k]:
if j in ru:
continue
rank.setdefault(j,0)
rank[j] += pi*wj
return dict(sorted(rank.items(), key = lambda x:x[1], reverse = True)[0:nitem])
def testRecommend(self,user):
rank = self.recommend(user,k = 10,nitem = 5)
for i,rvi in rank.items():
items = self.traindata.get(user,{})
record = items.get(i,0)
print ("%5s: %.4f--%.4f" %(i,rvi,record))
if __name__ == "__main__":
ibc=ItemBasedCF(os.getcwd()+'\\ratings.csv')#初始化数据
ibc.ItemSimilarity()#计算物品相似度矩阵
ibc.testRecommend(user = "345") #单用户推荐
print ("%3s%20s%20s%20s%20s" % ('K',"recall",'precision','coverage','popularity'))
代码解读(代码中注释):
主要解释类中的函数及实现的功能
1.初始化函数。
def __init__(self, datafile = None):#初始化函数,传入文件路径参数
self.datafile = datafile #定义对象本身datafile
self.readData() #读取数据函数
self.splitData() #处理数据
2.读数据函数。
def readData(self,datafile = None):
self.datafile = datafile or self.datafile
self.data = [] #定义对象本身成员变量data
file = open(self.datafile,'r')
for line in islice(file, 1, None): #读取每一行的数据
userid, itemid, record = line.split(',') #每一行分开的到三个数据
self.data.append((userid,itemid,float(record))) #将每一行数据加入data字典中
3.处理数据,得到数据集。
def splitData(self,data=None,k=3,M=10,seed=10):
self.testdata = {}
self.traindata = {} #定义两个数据集,我们用到的主要为traindata
data = data or self.data
random.seed(seed)#生成随机数
for user,item,record in self.data:
self.traindata.setdefault(user,{})
self.traindata[user][item] = record #全量训练
if random.randint(0,M) == k:#测试集
self.testdata.setdefault(user,{})
self.testdata[user][item] = record
4.计算相似度,得到相似度矩阵。
def ItemSimilarity(self, train = None):
train = train or self.traindata #train是我们得到的数据集
self.itemSim = dict() #定义相似度矩阵
item_user_count = dict() #喜欢某物品的人数统计矩阵(对应公式的N(i))
count = dict() #同时喜欢i,j物品的人数矩阵(对应公式的N(i)&N(j))
for user,item in train.items(): #遍历每一个用户
for i in item.keys():
item_user_count.setdefault(i,0) #.setdefault()函数:如果键不存在于字典中,将会添加键并将值设为默认值
item_user_count[i] += 1 #统计喜欢i物品的人数
for j in item.keys():
if i == j:
continue
count.setdefault(i,{})
count[i].setdefault(j,0)
count[i][j] += 1 #统计同时喜欢两个物品的人数
for i, related_items in count.items():
self.itemSim.setdefault(i,dict())
for j, cuv in related_items.items():
self.itemSim[i].setdefault(j,0)
self.itemSim[i][j] = cuv / math.sqrt(item_user_count[i] * item_user_count[j] * 1.0) #通过公式计算得出相似度
5.推荐函数。通过传入的用户参数以及得到的相似度矩阵得到推荐的物品并返回。
def recommend(self,user,train = None, k = 10,nitem = 5):
train = train or self.traindata
rank = dict()
ru = train.get(user,{}) #取出与user相关的数据
for i,pi in ru.items(): #i为物品,pi为用户user对物品i的评分(对于隐反馈,此处pi=1)
for j,wj in sorted(self.itemSim[i].items(), key = lambda x:x[1], reverse = True)[0:k]: #根据相似度矩阵对物品i的相似物品进行排序,得到前K个物品
if j in ru:
continue
rank.setdefault(j,0)
rank[j] += pi*wj #根据公式计算得出用户user对物品j的感兴趣程度
return dict(sorted(rank.items(), key = lambda x:x[1], reverse = True)[0:nitem]) #返回nitem个推荐的物品
6.测试函数。
def testRecommend(self,user): #对传入的用户参数,打印出推荐的列表
rank = self.recommend(user,k = 10,nitem = 5) #调用推荐函数,返回一个rank字典
for i,rvi in rank.items():
items = self.traindata.get(user,{})
record = items.get(i,0)
print ("%5s: %.4f--%.4f" %(i,rvi,record)) #打印推荐结果
7.主函数。
if __name__ == "__main__":
ibc=ItemBasedCF(os.getcwd()+'\\ratings.csv')#初始化数据
ibc.ItemSimilarity()#计算物品相似度矩阵
ibc.testRecommend(user = "345") #单用户推荐
运行结果(运行环境为虚拟机上ipythonbook):
参考文献:
《推荐系统实战》