数据集:
ml-latest-small
协同过滤算法理论基础
相似度计算主要有三个经典算法:余弦定理相似性度量、欧氏距离相似度度量和杰卡德相似性度量。下面分别进行说明:
余弦定理相似性度量
三角形余弦定理公式:,由该公式可知角A越小,bc两边越近。当A为0度时,bc两边完全重合。
当bc两边为向量时,两个向量的余弦为:;
当存在多个向量时,多个向量的余弦为:
由上求余弦的向量公式,可得知当两个向量的夹角越小,两个向量方向越相近。当夹角为0时,两个向量方向完全重合。由此原理可以计算两个事物的相似度。比如,把一篇篇新闻提取成有效词语的向量,每一组有效词语向量的角度越小,则两篇新闻的相似程度越高。如下图,为了便于理解举个特例,假设新闻ABC三篇文章提取的关键字都是诗歌、李白、杜甫、王勃,并且对应文章出现的数量分别为[(诗歌,1)(李白,1)(杜甫,1)(王勃,1)]、[(诗歌,2)(李白,2)(杜甫,2)(王勃,2)]和[(诗歌,1)(李白,2)(杜甫,3)(王勃,4)]。从坐标图可以看出AB的相似度大于AC或者BC的相似度,AB的各关键字比例一样,而C相比AB更侧重诗人王勃的话题。
观察上图,你会发现新闻D和新闻A、B的相似度是一样的。这个就是余弦定理计算相似度的缺陷,因为余弦定理只关注向量的方向,并不关注向量的起始点。
欧氏距离相似性度量
与余弦定理通过方向度量相似度不同,欧氏距离是通过计算样本实际距离在度量相似度的。
二维平面上两点a(x1,y1)与b(x2,y2)间的欧氏距离:;
三维空间两点a(x1,y1,z1)与b(x2,y2,z2)间的欧氏距离:
两个n维向量a(x11,x12,…,x1n)与 b(x21,x22,…,x2n)间的欧氏距离:
把上例的几个点代入以上公式可以算出:AB欧氏距离为√4,AC欧氏距离为√14,AD欧氏距离为√10,BC欧氏距离为√6,BD欧氏距离为√10,CD欧氏距离√30。欧氏距离越短,n微向量越“近”,向量化的物品的相似度越高。通过比较,依旧是AB的相似度最高。
提示:对比余弦定理相似度度量与欧氏距离相似度度量:
欧氏距离能够体现个体数值特征的绝对差异,所以更多的用于需要从维度的数值大小中体现差异的分析,如使用用户行为指标分析用户价值的相似度或差异。
余弦距离更多的是从方向上区分差异,而对绝对的数值不敏感,更多的用于使用用户对内容评分来区分兴趣的相似度和差异,同时修正了用户间可能存在的度量标准不统一的问题(因为余弦距离对绝对数值不敏感)。
杰卡德相似性度量
两个集合的交集在该两个集合的并集所占的比例来度量两个集合的相似度。举例,新闻A和新闻B提取出词语集合的交集在新闻A和新闻B提取出词语集合的并集所占的比例就是AB的相似度。
协同过滤算法实现
思路步骤:
计算其他用户和目标用户的相似度(使用欧氏距离算法);
根据相似度的高低找出K个目标用户最相似的邻居;
在这些邻居喜欢的电影中,根据邻居与你的远近程度算出每个电影的推荐度;
根据每一件物品的推荐度高低给你推荐物品。
代码:
#-*- coding: utf-8 -*-
"""Created on Sat Jul 13 17:24:33 2019
@author: Erio"""
#协同过滤
importpandas as pdimportnumpy as npimporttensorflow as tf
ratings_df= pd.read_csv('C:/Users/lenovo/Desktop/ml-latest-small/ml-latest-small/ratings.csv')#print(ratings_df.tail())#tail命令用于输入文件中的尾部内容。tail命令默认在屏幕上显示指定文件的末尾5行。
movies_df= pd.read_csv('C:/Users/lenovo/Desktop/ml-latest-small/ml-latest-small/movies.csv')#print(movies_df.tail())
movies_df['movieRow'] =movies_df.index#生成一列‘movieRow’,等于索引值index#print(movies_df.tail())
movies_df= movies_df[['movieRow','movieId','title']]#筛选三列出来
movies_df.to_csv('C:/Users/lenovo/Desktop/ml-latest-small/ml-latest-small/moviesProcessed.csv', index=False, header=True, encoding='utf-8')#生成一个新的文件moviesProcessed.csv#print(movies_df.tail())
ratings_df= pd.merge(ratings_df, movies_df, on='movieId')#print(ratings_df.head())
ratings_df= ratings_df[['userId','movieRow','rating']]#筛选出三列
ratings_df.to_csv('C:/Users/lenovo/Desktop/ml-latest-small/ml-latest-small/ratingsProcessed.csv', index=False, header=True, encoding='utf-8')#导出一个新的文件ratingsProcessed.csv#print(ratings_df.head())
userNo= ratings_df['userId'].max() + 1
#userNo的最大值
movieNo = ratings_df['movieRow'].max() + 1
#movieNo的最大值
rating=np.zeros((movieNo,userNo))#创建一个值都是0的数据
flag =0
ratings_df_length=np.shape(ratings_df)[0]#查看矩阵ratings_df的第一维度是多少
for index,row inratings_df.iterrows():#interrows(),对表格ratings_df进行遍历
rating[int(row['movieRow']),int(row['userId'])] = row['rating']#将ratings_df表里的'movieRow'和'userId'列,填上row的‘评分’
flag += 1record= rating >0
record
record= np.array(record, dtype =int)#更改数据类型,0表示用户没有对电影评分,1表示用户已经对电影评分
record#print(record)
defnormalizeRatings(rating, record):
m, n=rating.shape#m代表电影数量,n代表用户数量
rating_mean = np.zeros((m,1))#每部电影的平均得分
rating_norm =np.zeros((m,n))#处理过的评分
for i inrange(m):
idx= record[i,:] !=0#每部电影的评分,[i,:]表示每一行的所有列
rating_mean[i] =np.mean(rating[i,idx])#第i行,评过份idx的用户的平均得分;
#np.mean() 对所有元素求均值
rating_norm[i,idx] -=rating_mean[i]#rating_norm = 原始得分-平均得分
returnrating_norm, rating_mean
rating_norm, rating_mean=normalizeRatings(rating, record)
rating_norm=np.nan_to_num(rating_norm)#对值为NaNN进行处理,改成数值0
rating_norm
rating_mean=np.nan_to_num(rating_mean)#对值为NaNN进行处理,改成数值0
rating_mean
num_features= 10X_parameters= tf.Variable(tf.random_normal([movieNo, num_features],stddev = 0.35))
Theta_parameters= tf.Variable(tf.random_normal([userNo, num_features],stddev = 0.35))#tf.Variables()初始化变量#tf.random_normal()函数用于从服从指定正太分布的数值中取出指定个数的值,mean: 正态分布的均值。stddev: 正态分布的标准差。dtype: 输出的类型
loss= 1/2 * tf.reduce_sum(((tf.matmul(X_parameters, Theta_parameters, transpose_b = True) - rating_norm) * record) ** 2) + 1/2 * (tf.reduce_sum(X_parameters ** 2) + tf.reduce_sum(Theta_parameters ** 2))#基于内容的推荐算法模型
optimizer= tf.train.AdamOptimizer(1e-4)#https://blog.csdn.net/lenbow/article/details/52218551
train =optimizer.minimize(loss)#Optimizer.minimize对一个损失变量基本上做两件事#它计算相对于模型参数的损失梯度。#然后应用计算出的梯度来更新变量。
#tf.summary的用法 https://www.cnblogs.com/lyc-seu/p/8647792.html
tf.summary.scalar('loss',loss)#用来显示标量信息
summaryMerged=tf.summary.merge_all()#merge_all 可以将所有summary全部保存到磁盘,以便tensorboard显示。
filename = './movie_tensorborad'writer=tf.summary.FileWriter(filename)#指定一个文件用来保存图。
sess =tf.Session()#https://www.cnblogs.com/wuzhitj/p/6648610.html
init =tf.global_variables_initializer()
sess.run(init)#运行
for i in range(5000):
_, movie_summary=sess.run([train, summaryMerged])#把训练的结果summaryMerged存在movie里
writer.add_summary(movie_summary, i)#把训练的结果保存下来
#评估模型
Current_X_parameters, Current_Theta_parameters =sess.run([X_parameters, Theta_parameters])#Current_X_parameters为用户内容矩阵,Current_Theta_parameters用户喜好矩阵
predicts = np.dot(Current_X_parameters,Current_Theta_parameters.T) +rating_mean#dot函数是np中的矩阵乘法,np.dot(x,y) 等价于 x.dot(y)
errors = np.sqrt(np.sum((predicts - rating)**2))#sqrt(arr) ,计算各元素的平方根#print(errors)
#3772.2766689811433
#这里user_id改为input就可以实现任意推荐了
user_id = 15sortedResult= predicts[:, int(user_id)].argsort()[::-1]#argsort()函数返回的是数组值从小到大的索引值; argsort()[::-1] 返回的是数组值从大到小的索引值
idx =0print('为该用户推荐的评分最高的10部电影是:'.center(80,'='))#center() 返回一个原字符串居中,并使用空格填充至长度 width 的新字符串。默认填充字符为空格。
for i insortedResult:print('评分: %.2f, 电影名: %s' % (predicts[i,int(user_id)],movies_df.iloc[i]['title']))#.iloc的用法:https://www.cnblogs.com/harvey888/p/6006200.html
idx += 1
if idx == 10:break
效果: