协同过滤推荐算法分为两类,分别是基于用户的协同过滤算法(user-based collaboratIve filtering),和基于物品的协同过滤算法(item-based collaborative filtering)。
1、相似度计算
欧式距离
from numpy import *
#欧氏距离
def EuclideanDistance(a,b):
return sqrt((a[0]-b[0])**2+(a[1]-b[1])**2)
print('a,b 二维欧式距离为:',EuclideanDistance((1,1),(2,2)))
曼哈顿距离
def ManhattanDistance(a,b):
return abs(a[0]-b[0])+abs(a[1]-b[1])
print('a,b 二维曼哈顿距离为:',ManhattanDistance((1,1),(2,2)))
切比雪夫距离
def ChebyshevDistance(a,b):
return max(abs(a[0]-b[0]),abs(a[1]-b[1]))
print('a,b二维切比雪夫距离:',ChebyshevDistance((1,2),(3,4)))
余弦距离
def CosineSimilarity(a,b):
cos=(a[0]*b[0]+a[1]*b[1])/(sqrt(a[0]**2+a[1]**2)*sqrt(b[0]**2+b[1]**2))
return cos
print('a,b 二维夹角余弦距离:',CosineSimilarity((1,1),(2,2)))
杰卡德
def JaccardSimilarityCoefficient(a,b):
set_a=set(a) #集合
set_b=set(b)
dis=float(len(set_a&set_b))/len(set_a|set_b) #交集/并集
return dis
print('a,b 杰卡德相似系数:',JaccardSimilarityCoefficient((1,2,3),(2,3,4)))
杰卡德距离
def JaccardSimilarityDistance(a,b):
set_a = set(a)
set_b = set(b)
dis = float(len( (set_a | set_b) - (set_a & set_b) ) )/ len(set_a | set_b)
return dis
print('a,b 杰卡德距离:', JaccardSimilarityDistance((1,2,3),(2,3,4)))# 杰卡德距离= 1 - J(A,B) =( |A∪B| - |A∩B| )/ |A∪B|
2.基于用户的协同过滤算法(user-based collaboratIve filtering)
基于用户的协同过滤算法是通过用户对商品或内容的喜欢(如商品购买,收藏,内容评论或分享),并对这些喜好进行度量和打分。根据不同用户对相同商品或内容的态度和偏好程度计算用户之间的关系。在有相同喜好的用户间进行商品推荐。简单的说就是如果A,B两个用户都购买了x,y,z三个产品,并且给出了5星的好评。那么A和B就属于同一类用户。可以将A买过的产品w也推荐给用户B。
第一步:计算两个用户的相似度,N(i)是用户u使用过的物品和用户v使用过的物品的交集
第二步:计算用户对物品的兴趣
读取文件,生成列表
file = open("data.csv",'r', encoding='UTF-8')#记得读取文件时加‘r’, encoding='UTF-8'
##读取data.csv中每行中除了名字的数据
data1=[]##存放每位用户购买的产品及权重
for line in file.readlines(): #readlines()方法读取整个文件所有行,保存在一个列表(list)变量中
#注意这里不是readline()#该方法每次读出一行内容
user_id_t,product_type,cnt_qz = line.strip().split(',')
data1.append((user_id_t,product_type,float(cnt_qz)))
训练数据集与测试数据集切分
def splitData(data,k,seed,M=9):
print("训练数据集与测试数据集切分...")
train,test = {},{}
random.seed(seed) #指定seed的话,每次后面的随机数产生的都是一样的顺序,np.random.seed(seed)
for user,item,record in data:
if random.randint(0,M) == k: ##随机数产生顺序一样,随机产生(0,m)之间的数,只有一个可以分给测试集,另外的m-1都分给训练集
test.setdefault(user,{})
test[user][item] = record
else:
train.setdefault(user,{})
train[user][item] = record
return train,test
trainData,testData = splitData(data1,3,47)
计算用户之间的相似度,采用惩罚热门商品和优化算法复杂度的算法,分子中的倒数惩罚了用户u和用户v共同兴趣列表中热门物品对他们相似度的影响。N(i)是对物品i有过行为的用户集合,越热门,N(i)越大。
def UserSimilarityBest():
print("开始计算用户之间的相似度 ...")
if os.path.exists("data/user_sim.json"):
print("用户相似度从文件加载 ...")
userSim = json.load(open("data/user_sim.json","r"))
else:
# 得到每个item被哪些user评价过
item_users = dict()
for u, items in trainData.items():
for i in items.keys():
item_users.setdefault(i,set())
if trainData[u][i] > 0:
item_users[i].add(u) #生成产品对应的用户,{产品:{用户1,用户2}}
# 构建倒排表
count = dict()
user_item_count = dict()
for i, users in item_users.items():#i是产品名称,users是用户集合
for u in users:
user_item_count.setdefault(u,0)
user_item_count[u] += 1 #用户对应的数量, #每个用户的个数{用户:数量累计}
count.setdefault(u,{})
for v in users:
count[u].setdefault(v, 0)##每个用户中其他用户{用户1:{用户2:相似度分子}}
if u == v:
continue
count[u][v] += 1 / math.log(1+len(users)) ##取对数,用户相似度的分子,{用户:{用户:相似度分子}}
# 构建相似度矩阵
userSim = dict()
for u, related_users in count.items():#遍历用户和用户
userSim.setdefault(u,{})
for v, cuv in related_users.items():#遍历用户和相似度
if u==v:
continue
userSim[u].setdefault(v, 0.0)
userSim[u][v] = cuv / math.sqrt(user_item_count[u] * user_item_count[v])#jaccard相似度{用户1:{用户2:相似度,用户3:相似度}}
json.dump(userSim, open('data/user_sim.json', 'w'))
return userSim
计算相似度和产品权重的值,根据得分的高低进行推荐
def recommend(self, user, k=5, nitems=10):
result = dict()
have_score_items = self.trainData.get(user, {})#获取用户、产品及得分
for v, wuv in sorted(self.users_sim[user].items(), key=lambda x: x[1], reverse=True)[0:k]: ##按照相似度排序,取前5个用户
for i, rvi in self.trainData[v].items():#取对应用户的产品名称及得分
if i in have_score_items:#如果产品在对应的用户中了,则跳过,循环下个电影
continue
result.setdefault(i, 0)
result[i] += wuv * rvi #得到对应的产品和相似度,余弦相似度*用户兴趣
return dict(sorted(result.items(), key=lambda x: x[1], reverse=True)[0:nitems])#获取相似度排名前10的产品
利用测试集进行测试,得到准确率
def precision(k=4, nitems=6):#默认,可以修改
print("开始计算准确率 ...")
hit = 0
precision = 0
for user in self.trainData.keys():#针对在训练集中出现的用户
tu = testData.get(user, {})#获取测试集用户已办理的产品
rank = recommend(user, k=k, nitems=nitems) #进行相似用户办理过产品的集合
for item, rate in rank.items():
if item in tu: #如果产品在测试集中办理的产品中+1
hit += 1
precision += nitems #每个用户已办理的产品数
return hit / (precision * 1.0) #推荐办理的产品数/已办理的产品数据
print("准确率为: {}".format(precision()))
3、基于物品的协同过滤算法(item-based collaborative filtering)
基于物品的协同过滤算法与基于用户的协同过滤算法很像,将商品和用户互换。通过计算不同用户对不同物品的评分获得物品间的关系。基于物品间的关系对用户进行相似物品的推荐。这里的评分代表用户对商品的态度和偏好。简单来说就是如果用户A同时购买了商品1和商品2,那么说明商品1和商品2的相关度较高。当用户B也购买了商品1时,可以推断他也有购买商品2的需求。
第一步:计算物品之间的相似度;
其中,|N(i)|是喜欢物品i的用户数,|N(j)|是喜欢物品j的用户数,|N(i)&N(j)|是同时喜欢物品i和物品j的用户数。
基于用户相似度的推荐是针对用户数较少,产品较多时;
UserCF算法:当物品数量远超用户数量时,可以考虑UserCF算法,例如:新闻类、短视频、热点多、社交性质重、需常更新数据的网站或APP;
第二步:根据物品的相似度和用户的历史行为给用户生成推荐列表;
ItemCF算法:当用户数量远远超过物品数量,可以考虑使用Item算法,例如:购物网站、技术博客、文章等不常更新、数据稳定的网站或APP;(小说APP)
读取数据
file = open("data.csv",'r', encoding='UTF-8')#记得读取文件时加‘r’, encoding='UTF-8'
##读取data.csv中每行中除了名字的数据
data1=[]##存放每位用户购买的产品及权重
for line in file.readlines(): #readlines()方法读取整个文件所有行,保存在一个列表(list)变量中
#注意这里不是readline()#该方法每次读出一行内容
user_id_t,product_type,cnt_qz = line.strip().split(',')
data1.append((user_id_t,product_type,float(cnt_qz)))
训练数据集与测试数据集切分
def splitData(data,k,seed,M=9):
print("训练数据集与测试数据集切分...")
train,test = {},{}
random.seed(seed) #指定seed的话,每次后面的随机数产生的都是一样的顺序,np.random.seed(seed)
for user,item,record in data:
if random.randint(0,M) == k: ##随机数产生顺序一样,随机产生(0,m)之间的数,只有一个可以分给测试集,另外的m-1都分给训练集
test.setdefault(user,{})
test[user][item] = record
else:
train.setdefault(user,{})
train[user][item] = record
return train,test
trainData,testData = splitData(data1,3,47)
计算产品之间的相似度
def ItemSimilarityBest(trainData):
print("开始计算产品之间的相似度")
if os.path.exists("item_sim.json"):
print("产品相似度从文件加载 ...")
itemSim = json.load(open("item_sim.json", "r"))
else:
itemSim = dict()
item_user_count = dict() # 得到每个物品有多少用户产生过行为
count = dict() # 共现矩阵
for user, item in trainData.items():#遍历每个用户及购买过的产品
print("user is {}".format(user))
for i in item.keys(): #每个物品遍历
item_user_count.setdefault(i, 0)
if float(trainData[str(user)][i]) > 0.0:
item_user_count[i] += 1 #每个物品有多少用户用过
for j in item.keys():
count.setdefault(i, {}).setdefault(j, 0)#i物品J物品共同被用的次数,且i不等j物品
if float(trainData[str(user)][i]) > 0.0 and float(trainData[str(user)][j]) > 0.0 and i != j:
count[i][j] += 1
# 共现矩阵 -> 相似度矩阵
for i, related_items in count.items():#
itemSim.setdefault(i, dict())
for j, cuv in related_items.items():#cuv是喜欢ij物品的交集
itemSim[i].setdefault(j, 0)
itemSim[i][j] = cuv / math.sqrt(item_user_count[i] * item_user_count[j]) #|N(i)|是喜欢物品i的用户数,|N(j)|是喜欢物品j的用户数,|N(i)&N(j)|是同时喜欢物品i和物品j的用户数。
json.dump(itemSim, open('item_sim.json', 'w'))
return itemSim
items_sim = ItemSimilarityBest(trainData)
计算相似度和产品权重的值,根据得分的高低进行推荐
def recommend( user, k=5, nitems=10): #nitem: 总共返回n个物品
result = dict()
u_items = trainData.get(user, {})#获取用户的产品及得分
for i, pi in u_items.items():#已办理的产品,遍历已办理的每个产品,获取其相似产品,再用相似产品的相似度*权重
if i not in items_sim.keys(): #以防测试集的产品不再这里面,测试的时候报错
continue
for j, wj in sorted(items_sim[i].items(), key=lambda x: x[1], reverse=True)[0:k]:#返回默认4个临近产品,根据相似度排序
if j in u_items:#如果已经办理过的,则剔除,跳到下一个
continue
result.setdefault(j, 0)#产品及相似度
result[j] += float(pi) * float(wj) #相似度及*兴趣累加,
return dict(sorted(result.items(), key=lambda x: x[1], reverse=True)[0:nitems])#每个相似的产品,获取前nitems个商品
对测试集进行测试,计算准确率
def precision( k=5,nitems=10):
print("开始计算准确率 ...")
hit = 0
precision = 0
for user in testData.keys():
u_items = testData.get(user, {})#获取用户产品及得分
result = recommend(user, k=k, nitems=nitems)#推荐产品及相似度
for item, rate in result.items():#
if item in u_items:#如果推荐的产品在测试集的已办理产品中,则+1
hit += 1
precision += nitems #一个用户10个产品,累计全部用户的产品
return hit / (precision * 1.0)#已办理的产品在推荐产品中/推荐产品的个数,算准确率
print("准确率为: {}".format(precision()))