电影推荐系统详细代码注释

el/2024/2/25 22:45:50

注:此博文是转载于它处,注释部分也为转载

# -*- coding: utf8 -*-
'''
Created on 2015-06-22
@author: Lockvictor
'''
import sys, random, math
import os
from operator import itemgetter
random.seed(0)
class ItemBasedCF():''' TopN recommendation - ItemBasedCF '''def __init__(self):self.trainset = {}self.testset = {}#此处依然无法输出print("类型=",type(self.trainset))self.n_sim_movie = 20#训练集用的电影数量self.n_rec_movie = 10#推荐电影数量self.movie_sim_mat = {}#初始化为字典self.movie_popular = {}#初始化为字典self.movie_count = 0print >> sys.stderr, 'Similar movie number = %d' % self.n_sim_movieprint >> sys.stderr, 'Recommended movie number = %d' % self.n_rec_movie#def __init__(self)解释#初始化7个变量,四个字典,三个整形#self代表this指针#意思是,这些变量是归这个类管辖的#__init__是构造函数,用来初始化,写法固定@staticmethoddef loadfile(filename):''' load a file, return a generator. '''print("loadfile filename=", filename)fp = open(filename, 'r')for i, line in enumerate(fp):yield line.strip('\r\n')if i % 100000 == 0:print >> sys.stderr, 'loading %s(%s)' % (filename, i)fp.close()print >> sys.stderr, 'load %s succ' % filename#def loadfile(filename)解释#这里的filename指的是ratings.dat#line代表数据集中每行的内容#i是个计数器,表示当前读到第几行了,每当读取到100000行的整数倍#输出语句报个信儿。#@staticmethod表示这个函数可以定义在类的外面#enumerate是为了配合i而存在的,#也就是说,这个for循环原本可以简化为:#for line in (fp)#yield line.strip('\r\n')#line.strip('\r\n')#表示删除每行的回车符的ASCII编码#yield是加强版的return,类似于C语言里面的升级版return#可以返回多个元素,这里估计是返回多个属性的意思吧
##################################################def generate_dataset(self, filename, pivot=0.7):''' load rating data and split it to training set and test set '''print("generate_data filename=",filename)trainset_len = 0testset_len = 0
########################added by yuchi as follows###############################train_file = os.getcwd() + '/train.txt'#数据集分割后的得到的训练集output1 = open(train_file, 'w')test_file = os.getcwd() + '/test.txt'#数据集分割后得到的测试集output2 = open(test_file, 'w')
#########################added by yuchi above###############################for line in self.loadfile(filename):user, movie, rating, _ = line.split('::')# split the data by pivotif (random.random() < pivot):#待会儿需要改回来,用上面一句替换self.trainset.setdefault(user, {})self.trainset[user][movie] = int(rating)#print("trainset[user][movie]=",trainset[user][movie])trainset_len += 1#70% of all data
########################added by yuchi above#######################train_str = str(user) + ' ' + str(movie) + ' ' +  '%d' %self.trainset[user][movie] + '\n'#在前面加上 '%d' %是为了让数字转化为字符串output1.write(train_str)
########################added by yuchi above#######################else:self.testset.setdefault(user, {})self.testset[user][movie] = int(rating)testset_len += 1#30% of all datatest_str = str(user) + ' ' + str(movie) + ' ' +  '%d' %self.testset[user][movie] + '\n'
########################added by yuchi above#######################output2.write(test_str)
########################added by yuchi above#######################output1.close()output2.close()print >> sys.stderr, 'split training set and test set succ'print >> sys.stderr, 'train set = %s' % trainset_lenprint >> sys.stderr, 'test set = %s' % testset_len########################下面是解释###################def generate_dataset函数解释#    user, movie, rating, _ = line.split('::')#这里的双冒号是分隔符,用来获取属性,这里的单独的一个下划线“_”是一个变量名,代表ratings.txt中的第四个属性,Timestamp(时间戳)。#所以可以直接用print语句输出这个下划线变量。#这个函数中的filename也是指的是ratings.dat#这个函数既需要产生数据集,又需要产生测试集#因为在构造函数__init__中初始化了两个字典(字典其实就是C + +中的map类型)变量:#trainset和testset,他们分别表示训练集和测试集#所以在这里使用_len分别对这两个字典变量的容量进行初始化。#random.random()#生成0和1之间的随机浮点数float#由于random.random会随机生成浮点数,pivot设置为0.7, 也就是说,这个filename中#会有70%变成训练用数据集,30 % 变成测试用数据集。#那么哪些数据会成为那70 % 中的一部分, 哪些数据会成为30 % 的一部分呢?随机决定。#因此,在分割filename的时候:#也就是说:#一堆糖果,分给两个小朋友A和B,设定临界点为2,抛骰子,如果抛到1和2,一颗糖果归A;#如果抛到3~6,一颗糖果归B, 最后分成两堆。##########70 % 的概率会执行if语句,变成训练用数据集#添加完后,用以下语句表示容量+1#trainset_len += 1###########30 % 的概率会执行else语句,成为测试集#添加完后,用以下语句表示容量+1#testset_len += 1###########其中#int(rating)用来数据格式转化#user, movie, rating, _ = line.split('::')与下面的#self.trainset.setdefault(user, {})#对应#单独的一个_表示这个属性本代码不关心,随便起个名字,占坑#for循环在执行每次循环时,都会得到新的一条数据,用user,movie和rating和_去获得这个数据中的四个属性#然后在trainset这个字典变量中建立映射关系。#self.trainset.setdefault(user, {})#表示对字典的新一项初始化。#self.trainset[user][movie] = int(rating)#表示索引变量是user、movie#索引值是rating。#总得来讲,也就是说,从ratings.txt的每行的四个属性中,获取三个属性,丢掉一个属性,来重新建立数据集中的一个项。#函数的功能,从ratings中筛选得到有用的属性,重新建立映射关系,一部分变成训练用数据集,一部分变成测试用数据集。
#-----------------------------------------------------------------------------def calc_movie_sim(self):#这个函数总共3个双重for循环''' calculate movie similarity matrix '''print >> sys.stderr, 'counting movies number and popularity...'for user, movies in self.trainset.iteritems():#训练集的前两两个属性就是用户和电影编号,利用for循环遍历整个测试集。#这里的movies表示某特定用户看过的所有电影,所以movies不是指一部电影,是一个集合for movie in movies:#if movie not in self.movie_popular:# have been defined as map(dictionary)self.movie_popular[movie] = 0#流行度指的是用户对电影的评价数量。self.movie_popular[movie] += 1#这里没法直接写入txt,因为相同的电影,流行度刷新后,新的一行写入txt,旧的一行不会被删除
#这里的mouvie_popular在离开for循环以后得到的是两列属性,movieID和评价次数。print >> sys.stderr, 'count movies number and popularity succ'print("流行度初步计算结束")# save the total number of moviesself.movie_count = len(self.movie_popular)#流行电影的容量print >> sys.stderr, 'total movie number = %d' % self.movie_count
#-------------------------------以上得到的是每部电影被评价的次数--------------------------------------# count co-rated users between items#movie_sim_mat是相似度矩阵的意思itemsim_mat = self.movie_sim_mat#movie_sim_mat已经在构造函数中进行初始化#同样地,itemsim_mat也是个字典,print >> sys.stderr, 'building co-rated users matrix...'for user, movies in self.trainset.iteritems():for m1 in movies:for m2 in movies:if m1 == m2: continue#数据没清洗过的情况下使用itemsim_mat.setdefault(m1,{})itemsim_mat[m1].setdefault(m2,0)itemsim_mat[m1][m2] += 1#被同一个用户评过分的两个不同电影,他们在相似度矩阵中+1#注意,对itemsim_mat操作的同时,改变了movie_sim_mat#也就是说,类似于C++中,itemsim_mat就是self.movie_sim_mat的别名#注意,self.movie_sim_mat是对象中的成员,itemsim_mat不是#注意,代码中只有self.movie_sim_mat,不存在movie_sim_mat#注意,代码中只有itemsim_mat,不存在self.itemsim_mat####################以上是相似度矩阵的"初步计算",没有使用很复杂的计算方法,后面还要进行计算,才能得到最终的相似度矩阵# print >> sys.stderr, 'build co-rated users matrix succ'#物品的流行度即指有多少用户为某物品评分# calculate similarity matrixprint("☆☆☆☆☆☆×××××××××××××☆☆☆☆☆☆☆", self.movie_sim_mat[movie].items())print >> sys.stderr, 'calculating movie similarity matrix...'simfactor_count = 0#控制程序运行进度输出的,没啥用PRINT_STEP = 2000000#控制程序运行进度输出的,没啥用for m1, related_movies in itemsim_mat.iteritems():#注意,这里使用的是余弦相似度for m2, count in related_movies.iteritems():itemsim_mat[m1][m2] = count / math.sqrt(self.movie_popular[m1] * self.movie_popular[m2])simfactor_count += 1if simfactor_count % PRINT_STEP == 0:print >> sys.stderr, 'calculating movie similarity factor(%d)' % simfactor_countprint("☆☆☆☆☆☆×××××××××××××☆☆☆☆☆☆☆", self.movie_sim_mat[movie].items())print >> sys.stderr, 'calculate movie similarity matrix(similarity factor) succ'print >> sys.stderr, 'Total similarity factor number = %d' %simfactor_count# -----------------------------------------------------------------------------def recommend(self, user):''' Find K similar movies and recommend N movies. '''K = self.n_sim_movie#在构造函数中已经定义和初始化N = self.n_rec_movie#在构造函数中已经定义和初始化,某特定用户将会被推荐的电影数量rank = {}watched_movies = self.trainset[user]#这里之所以有sort函数是为了推荐符合度最高的几个电影给用户for movie, rating in watched_movies.iteritems():#从数据集中提取某个用户看过的电影中的两个数据for related_movie, w in sorted(self.movie_sim_mat[movie].items(),key=itemgetter(1), reverse=True)[:K]:#从大到小排序#上面的movie_sim_mat是个具备有3个属性的字典:两个相似的电影,以及他们的相似度,所以w是相似度的意思,related_movie是根据代码后面的[movie]得到的相关电影#因为一行有许多属性,所以上面这句代码中items的意思是取得该属性所在行的其他所有属性#由于movie_sim_mat中本来每行数据只有三个属性,由于这里使用了[movie]索引,所以得到剩下两个属性#而上面这句代码后面使用了itemgetter(1),表示对所得到的两个属性,按照第2的属性(也就是相似度系数)进行排序#reverse=true代表从大小排序,在代码中的意思是,在相似度矩阵中获取与movie这个变量相关的所有电影,并且按照相似度系数的大小从大到小排序#最后[:K]:表示取得K个项if related_movie in watched_movies:continue#如果相关电影在已经看过的电影中,则跳过,进行下一轮循环(我想这应该是数据没有清洗导致的)rank.setdefault(related_movie, 0)#这句话不属于上面的if的管辖范畴rank[related_movie] += w * rating# return the N best movies# 以上双循环的意思是,对某用户看过的所有电影进行遍历,# 对于某个特定的已经看过的电影而言,便利相似度矩阵中所有和这个“已经看过的电影”相关的电影# 相关的电影的意思是,矩阵中都是aij中,i对应于movie,j对应于related_movie# self.movie_sim_mat[movie].items()会返回两个参数,第一个参数赋值给related_movie,# 第二个参数赋值给w,代表“movie”和“related_movie”这两个变量的相似度,相似度在前面已经计算得出# 他这里把权重系数去乘以评分次数,制造出一个参数w*rating,作为rank中排序的指标# 来计算与“已经看过的每个电影”相关的return sorted(rank.items(), key=itemgetter(1), reverse=True)[:N]# 这句return的意思是相当于excel中的排序,这里的itemgetter(1)表示按照rank中# 数据的第二项对rank中所有数据进行排序# 注意itemgetter(i)的括号中的序号i从0开始,代表第1项# 另外注意,这里rank虽然是字典,但是return返回的类型是listdef evaluate(self):#这个是用来评价推荐的电影是否准确的。''' return precision, recall, coverage and popularity '''print >> sys.stderr,'Evaluation start...'#############################N = self.n_rec_movie#  varables for precision and recall hit = 0rec_count = 0test_count = 0# varables for coverageall_rec_movies = set()# varables for popularitypopular_sum = 0f = open("recommend.txt", "w")for i, user in enumerate(self.trainset):#i对应enumerate,user对应测试集trainsetif i % 500 == 0:print >> sys.stderr, 'recommended for %d users' % itest_movies = self.testset.get(user, {})rec_movies = self.recommend(user)#这一句代表推荐结果,注意推荐结果的类型是list,不是dict(字典)recommend_str = str(user) + ' ' + str(rec_movies) + ' ' +'\n'f.write(str(recommend_str))#后面的这个for循环是用来评价推荐的电影是否准确的for movie, w in rec_movies:if movie in test_movies:hit += 1all_rec_movies.add(movie)popular_sum += math.log(1 + self.movie_popular[movie])###################下面的属于外循环,不属于内循环#########################rec_count += N#no usetest_count += len(test_movies)#no usef.close()precision = hit / (1.0 * rec_count)#no userecall = hit / (1.0 * test_count)#no usecoverage = len(all_rec_movies) / (1.0 * self.movie_count)#no usepopularity = popular_sum / (1.0 * rec_count)#no useprint >> sys.stderr, 'precision=%.4f\trecall=%.4f\tcoverage=%.4f\tpopularity=%.4f' \% (precision, recall, coverage, popularity)if __name__ == '__main__':ratingfile = 'ml-1m/ratings.dat'itemcf = ItemBasedCF()itemcf.generate_dataset(ratingfile)itemcf.calc_movie_sim()
itemcf.evaluate()#这个函数中出推荐结果

http://www.ngui.cc/el/4466219.html

相关文章

基于物品的协同过滤

ItemCF&#xff1a;ItemCollaborationFilter&#xff0c;基于物品的协同过滤 算法核心思想&#xff1a;给用户推荐那些和他们之前喜欢的物品相似的物品。 比如&#xff0c;用户A之前买过《数据挖掘导论》&#xff0c;该算法会根据此行为给你推荐《机器学习》&#xff0c;但是It…

knn算法思想及代码实现

实验中用到的数据在我的上传中心有 1.什么是KNN K近邻算法&#xff08;K-Nearest Neighbour&#xff0c;K-NN&#xff09;是一种基本分类与回归方法&#xff0c;是一个理论上比较成熟的方法&#xff0c;也是最简单的机器学习算法之一。该方法的思路是&#xff1a;如果一个样本…

机器学习—— 评价指标

一、分类指标 1、准确率和召回率 准确率&#xff08;Precision&#xff09;是你给出的正确结果数占你给出的所有结果数的比例。 召回率&#xff08;Recall&#xff09;是你给出的正确结果数占所有正确结果数的比例。 比如&#xff1a;池塘里共有200生物&#xff0c;其中鱼140&…

机器学习测试二总结

1、卷积神经网络计算公示&#xff1a; 卷积层与池化层输出矩阵大小 C[(T-F2*P)/S]1 C为输出矩阵尺寸&#xff0c;T为待处理矩阵的尺寸&#xff0c;F为滤波器矩阵尺寸&#xff0c;P为填充矩阵&#xff08;pooling&#xff09;&#xff0c;S为步长 2、MLP是完全连通的有向图&…

0-1背包与完全背包的区别

0-1背包 重要部分&#xff1a;每次每个物品最多只能选择一次。 题面&#xff1a;有n个重量和价值分别为 Wi 与 Vi 的物品&#xff0c;现在从这些物品中挑选出总重量不超过 s 的物品&#xff0c;并且要求总价值最大。 输入&#xff1a; n4 S5 (w,v) {&#xff08;2,3&#xf…

最小生成树之最大生成树

poj 3723 Conscription 【最大生成树|最大权森林】 题目&#xff1a;poj 3723 Conscription 题意&#xff1a;要征兵n个男兵和m个女兵&#xff0c;每个花费10000元&#xff0c;但是如果已经征募的男士兵中有和将要征募的女士兵关系好的&#xff0c;那么可以减少花费&#xff0c…

Crazy Rows(2009 Round2 A)

Problem You are given an N x N matrix with 0 and 1 values. You can swap any two adjacent rows of the matrix. Your goal is to have all the 1 values in the matrix below or on the main diagonal. That is, for each X where 1 ≤ X ≤ N, there must be no 1 valu…

selenium启动chrome时,弹出设置页面:Windows Defender 防病毒要重置您的设置。和data页面

selenium启动chrome时&#xff0c;弹出设置页面:Windows Defender 防病毒要重置您的设置。和data页面 1.在使用selenium打开chrome时同时打开了两个标签页&#xff0c;且页面停留在chrome的设置页面&#xff0c;页面打开链接后data页面也没有消失 winr 运行 regedit &#xff…

Java从头来(一)

java命名规范 1、命名规则 包&#xff1a;英文全小写 类&#xff1a;英文&#xff0c;每个单词的首字母为大写&#xff0c;其它为小写&#xff0c;例如&#xff1a;JavaTest 变量&#xff1a;英文有意义&#xff0c;首字母第一个单词小写&#xff0c;其它单词首字母大写。例如…

深度学习——负采样

引用自&#xff1a;https://zhuanlan.zhihu.com/p/39684349 训练一个神经网络意味着要输入训练样本并且不断调整神经元的权重&#xff0c;从而不断提高对目标的准确预测。每当神经网络经过一个训练样本的训练&#xff0c;它的权重就会进行一次调整。 vocabulary的大小决定了我们…