欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

用亲和性分析方法推荐电影

发布时间:2025/4/16 编程问答 42 豆豆
生活随笔 收集整理的这篇文章主要介绍了 用亲和性分析方法推荐电影 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

亲和性分析面临的问题

亲和性分析比分类更具探索性,因为通常我们无法拿到像在很多分类任务中所用的那样完整 的数据集。例如,在电影推荐任务中,我们拿到的是不同用户对不同电影的评价。但是,每个用 户不可能评价过所有电影,这就给亲和性分析带来一个不容忽视的大难题。如果用户没有评价过一部电影,是因为他们不喜欢这部电影(据此就不推荐给他们),还是因为他们出于别的原因还没有评价?

思考数据集中类似潜在问题该怎么解决,这将帮助我们提升推荐算法的准确性。

另外一个问题在于https://mp.csdn.net/mdeditor/81013001,像这个链接中说的亲和性测试方法过于简单粗暴(用排列组合枚举的办法)。从排列组合的知识出发,我们知道,对于每一个商品,只存在买或者不买两种情况。于是,所有可能的规则数量是2n12n−1。数据集有5个特征,可能的规则就有31条;有10个特征,可能的规则就有1023条;仅仅有100个特征,规则数就能达到30位数字。即使计算能力大幅提升也未 必能赶上在线商品的增长速度。因此,与其跟计算机过不去,不如寻找更加聪明的算法。


Apriori算法介绍

Apriori算法是经典的亲和性分析算法,它只从数据集中频繁出现的商品中选出共同出现的商品组成频繁项集(frequent itemset)避免了上述复杂度呈指数级增长的问题。一旦找到频繁项集,生成关联规则就很容易了。

Apriori算法的一个重要参数就是最小支持度。比如,要生成包含商品A、B的频繁项集(A, B), 要求支持度至少为30,那么A和B都必须至少在数据集中出现30次。更大的频繁项集也要遵守该 项约定,比如要生成频繁项集(A, B, C, D),那么子集(A, B, C)必须是频繁项集(当然D自己也 要满足最小支持度标准)。

生成频繁项集后,将不再考虑其他可能的却不够频繁的项集(这样的集合有很多),从而大 大减少测试新规则所需的时间。

其他亲和性分析算法有Eclat频繁项集挖掘算法(FP-growth)。从数据挖掘角度看,这些 算法比起基础的Apriori算法有很多改进,性能也有进一步提升。

在这个电影推荐过程中,我们主要分两步走。第一步,找出频繁项集。第二步,根据置信度选取关联规则。可以设定最小置信度,返回一部分规则,或者返回所有规则,让用户自己选。


数据集介绍

首先,我们下载电影评分数据集:http://grouplens.org/datasets/movielens/
这个数据集是美国苏明达州一个研究团队公开的。下载ml-100k.zip这个数据集。

接着,我们导入数据集

import os data_folder = os.path.join(os.path.expanduser("~"), "Data", "ml-100k") print(data_folder) ratings_filename = os.path.join(data_folder, "u.data") print(ratings_filename)

输出结果是:
C:\Users\mi\Data\ml-100k
C:\Users\mi\Data\ml-100k\u.data

MovieLens数据集非常规整,但是有几点跟pandas.read_csv方法的默认设置有出入,所以 要调整参数设置。第一个问题是数据集每行的几个数据之间用制表符而不是逗号分隔。其次,没 有表头,这表示数据集的第一行就是数据部分,我们需要手动为各列添加名称。

加载数据集时,把分隔符设置为制表符,告诉pandas不要把第一行作为表头(header=None), 设置好各列的名称。

import pandas as pd''' pd.csv文件中参数: names:指定列名,如果文件中不包含header的行,应该显性表示header=None delimiter: 指定分割符,默认是’,’C引擎不能自动检测分隔符,但Python解析引擎可以 header: 指定第几行作为列名(忽略注解行),如果没有指定列名,默认header=0; 如果指定了列名header=None ''' all_ratings = pd.read_csv(ratings_filename, delimiter="\t", header=None, names = ["UserID", "MovieID", "Rating", "Datetime"]) all_ratings["Datetime"] = pd.to_datetime(all_ratings['Datetime'],unit='s') all_ratings[0:5]

这里我们再介绍一下to_datatime方法
利用 pandas 的to_datetime 方法,把 “date” 列的字符类型数据解析成 datetime 对象。unit=’s’是指时间精度精确到秒(unit of the arg (D,s,ms,us,ns))

结果如下:

稀疏矩阵

这其实是一个稀疏矩阵的形式。对于这个数据而言,每一行代表每一个用户给每一个电影打一个分数。数据集中有1000名用户和1700部电影,这就意味着整个矩阵很大。不可能所有用户对所有电影都打分,大部分矩阵是空的。我们用上述方式也可以表示整个数据,但相比较而言,更为紧凑。

任何没有出现在数据集中的用户和电影组合表示它们实际上是不存在的。这比起在内存中保存大量的0,节省了很多空间。这种格式叫作稀疏矩阵(sparse matrix)。根据经验来说,如果数 据集中60%或以上的数据为0,就应该考虑使用稀疏矩阵,从而节省不少空间。


数据处理

首先要确定用户是不是喜欢某一部电影。为此创建新特征Favorable,若用户喜欢该电影,值为True。

# Not all reviews are favourable! Our goal is "other recommended books", so we only want favourable reviews all_ratings["Favorable"] = all_ratings["Rating"] > 3 all_ratings[10:15]

可以看到数据发生了新的变化:

all_ratings[all_ratings["UserID"] == 1][:5]

我们可以看到userID为1的用户给5部电影打分的情况:

从数据集中选取一部分数据用作训练集,这能有效减少搜索空间,提升Apriori算法的速度。我们取UserID为前200的用户的打分数据,然后只取Favorite的部分

ratings = all_ratings[all_ratings['UserID'].isin(range(200))] # We start by creating a dataset of each user's favourable reviews favorable_ratings = ratings[ratings["Favorable"]] favorable_ratings[:5]

在生成项集时,需要搜索用户喜欢的电影。因此,接下来,我们需要知道每个用户各喜欢哪些电影,按照User ID进行分组,并遍历每个用户看过的每一部电影。

# We are only interested in the reviewers who have more than one review favorable_reviews_by_users = dict((k, frozenset(v.values)) for k, v in favorable_ratings.groupby("UserID")["MovieID"]) len(favorable_reviews_by_users)

输出结果是:199
这里介绍一下pandas的groupby技术:
groupby的作用就是拆分合并,或者说的更具体一点是(分组、应用、聚合)。这里我们把favorable_ratings中的数据中的“UserID”和“MovieID”拿出来然后分组聚合后添加到新的字典favorable_reviews_by_user中

frozenset是不可变集合的意思,一旦创建就不能修改。另外,集合比列表速度快。

我们可以用下面代码查看最受用户欢迎的5部电影:

# Find out how many movies have favourable ratings num_favorable_by_movie = ratings[["MovieID", "Favorable"]].groupby("MovieID").sum() num_favorable_by_movie.sort_values("Favorable", ascending=False)[:5]


这段代码的第一行表示的将ratings中的“MovieID”和“Favorable”拿出来重新分组,然后按照“MovieID”进行聚合,也就是说有相同的“MovieID”放在一起。之后用sum把Favorable为True的进行累加。

第二行用sort_values方法对列进行排序,ascending(上升)=False是按照降序排序,(即输出排名最高的前5个)。ascending默认是True


Apriori算法实现

我们把发现的频繁项集保存到以项集长度为键的字典中,便于根据长度查找,这样就可以找 到最新发现的频繁项集。

我们还需要确定项集要成为频繁项集所需的最小支持度。这个值需要根据数据集的具体情况 来设定,可自行尝试其他值,建议每次只改动10个百分点,即使这样你可能也会发现算法运行时 间变动很大!

我们先来实现Apriori算法的第一步,为每一部电影生成只包含它自己的项集,检测它是否够频繁。电影编号使用frozenset,后面要用到集合操作。此外,它们也可以用作字典的键(普通集合不可以

import sys frequent_itemsets = {} # itemsets are sorted by length min_support = 50# k=1 candidates are the isbns with more than min_support favourable reviews frequent_itemsets[1] = dict((frozenset((movie_id,)), row["Favorable"])for movie_id, row in num_favorable_by_movie.iterrows()if row["Favorable"] > min_support)

接着,用一个函数来实现发现新的频繁项集,创建超集,检测频繁程度。

from collections import defaultdictdef find_frequent_itemsets(favorable_reviews_by_users, k_1_itemsets, min_support):#进行字典初始化counts = defaultdict(int)# 遍历所有用户和打分情况for user, reviews in favorable_reviews_by_users.items():# 遍历前面找出的项集,判断它们是否是当前评分项集的子集。# 如果是,表明用户已经 为子集中的电影打过分for itemset in k_1_itemsets:if itemset.issubset(reviews):# 遍历用户打过分却没有出现在项集里的电影,用它生成超集,更新该项集的计数for other_reviewed_movie in reviews - itemset:current_superset = itemset | frozenset((other_reviewed_movie,))counts[current_superset] += 1# 最后检测达到支持度要求的项集,看它的频繁程度够不够,并返回其中的频繁项集。 return dict([(itemset, frequency) for itemset, frequency in counts.items() if frequency >= min_support])

创建循环,运行Apriori算法,存储算法运行过程中发现的新项集。循环体中,k表示即将发 现的频繁项集的长度,用键k - 1可以从frequent_itemsets字典中获取刚发现的频繁项集。新 发现的频繁项集以长度为键,将其保存到字典中。

如果在上述循环中没能找到任何新的频繁项集,就跳出循环(输出信息,告知我们没能找到 长度为k的频繁项集)。

如果确实找到了频繁项集,我们也让程序输出信息,告知我们它会再次运行。因为算法运行 时间很长,所以每隔一段时间输出一下状态是很有必要的!

最后,循环结束,我们对只有一个元素的项集不再感兴趣——它们对生成关联规则没有用 处——生成关联规则至少需要两个项目。删除长度为1的项集。

sys.stdout.flush() for k in range(2, 20):# Generate candidates of length k, using the frequent itemsets of length k-1# Only store the frequent itemsetscur_frequent_itemsets = find_frequent_itemsets(favorable_reviews_by_users, frequent_itemsets[k-1],min_support)if len(cur_frequent_itemsets) == 0:print("Did not find any frequent itemsets of length {}".format(k))sys.stdout.flush()breakelse:print("I found {} frequent itemsets of length {}".format(len(cur_frequent_itemsets), k))#print(cur_frequent_itemsets)sys.stdout.flush()frequent_itemsets[k] = cur_frequent_itemsets # We aren't interested in the itemsets of length 1, so remove those del frequent_itemsets[1]

用sys.stdout.flush()方法,确保代码还在运行时,把缓冲区内容输出 到终端。有时,在位于笔记本文件特定格子的大型循环中,代码结束运行前不会 输出,用flush方法可以保证及时输出。但是,该方法不宜过多使用——flush 操作(输出也是)所带来的计算开销会拖慢程序的运行速度。


抽取关联规则

最后一步我们要抽取关联规则。频繁项集是一组达到最小支持度的项目,而关联规则包含了前提和结论。我们定义关联规则如下:如果一个用户喜欢前提中的所有电影,那么他也会结论中的电影。

下面代码通过遍历不同长度的频繁项集,来为每个项集生成关联规则。

# Now we create the association rules. First, they are candidates until the confidence has been tested# 通过遍历不同长度的频繁项集,为每个项集生成规则 candidate_rules = [] for itemset_length, itemset_counts in frequent_itemsets.items():for itemset in itemset_counts.keys():# 遍历项集中的每一部电影,把它作为结论。项集中的其他电影作为前提,用前提和结 论组成备选规则for conclusion in itemset:premise = itemset - set((conclusion,))candidate_rules.append((premise, conclusion)) print(candidate_rules[:5])

输出结果如下:
[(frozenset({79}), 258), (frozenset({258}), 79), (frozenset({50}), 64), (frozenset({64}), 50), (frozenset({127}), 181)]


计算置信度

接下来,计算每条规则的置信度。

首先创造两个字典,用来存储规则应验(正例)和规则不适用(反例)的次数。

# Now, we compute the confidence of each of these rules. This is very similar to what we did in chapter 1 correct_counts = defaultdict(int) incorrect_counts = defaultdict(int) for user, reviews in favorable_reviews_by_users.items():for candidate_rule in candidate_rules:premise, conclusion = candidate_rule# 看用户是否喜欢前提中的所有电影if premise.issubset(reviews):# 符合前提条件,看用户是否喜欢结论中的电影。如果是的话,适用规则,反之,规则不适用if conclusion in reviews:correct_counts[candidate_rule] += 1else:incorrect_counts[candidate_rule] += 1 rule_confidence = {candidate_rule: correct_counts[candidate_rule] / float(correct_counts[candidate_rule] + incorrect_counts[candidate_rule])for candidate_rule in candidate_rules}

对置信度进行排序后输出置信度最高的前五条规则:

from operator import itemgetter sorted_confidence = sorted(rule_confidence.items(), key=itemgetter(1), reverse=True)for index in range(5):print("Rule #{0}".format(index + 1))(premise, conclusion) = sorted_confidence[index][0]print("Rule: If a person recommends {0} they will also recommend {1}".format(premise, conclusion))print(" - Confidence: {0:.3f}".format(rule_confidence[(premise, conclusion)]))print("")

结果如下:
Rule #1
Rule: If a person recommends frozenset({64, 56, 98, 50, 7}) they will also recommend 174
- Confidence: 1.000

Rule #2
Rule: If a person recommends frozenset({98, 100, 172, 79, 50, 56}) they will also recommend 7
- Confidence: 1.000
。。。。。。

显然这样结果中只有电影编号,没有显示电影名称,很不好。下载的数据集中的u.items 文件里存储了电影名称和编号(还有体裁等信息)。

用pandas从u.items文件加载电影名称信息。关于该文件和类别的更多信息请见数据集中的 README文件。u.items文件为CSV格式,但是用竖线分隔数据。读取时需要指定分隔符,设置表头和编码格式。每一列的名称是从README文件中找到的。

# Even better, we can get the movie titles themselves from the dataset movie_name_filename = os.path.join(data_folder, "u.item") movie_name_data = pd.read_csv(movie_name_filename, delimiter="|", header=None, encoding = "mac-roman") movie_name_data.columns = ["MovieID", "Title", "Release Date", "Video Release", "IMDB", "<UNK>", "Action", "Adventure","Animation", "Children's", "Comedy", "Crime", "Documentary", "Drama", "Fantasy", "Film-Noir","Horror", "Musical", "Mystery", "Romance", "Sci-Fi", "Thriller", "War", "Western"]

创建一个电影名称获取函数:

def get_movie_name(movie_id):# 在数据框movie_name_data中查找电影编号,找到后,只返回电影名称列的数据title_object = movie_name_data[movie_name_data["MovieID"] == movie_id]["Title"] # 我们用title_object的values属性获取电影名称(不是存储在title_object中的 Series对象)。我们只对第一个值感兴趣title = title_object.values[0]return titleget_movie_name(4)

结果如下:’Get Shorty (1995)’

调整之后,就可以输出比较不错的结果了

for index in range(5):print("Rule #{0}".format(index + 1))(premise, conclusion) = sorted_confidence[index][0]premise_names = ", ".join(get_movie_name(idx) for idx in premise)conclusion_name = get_movie_name(conclusion)print("Rule: If a person recommends {0} they will also recommend {1}".format(premise_names, conclusion_name))print(" - Confidence: {0:.3f}".format(rule_confidence[(premise, conclusion)]))print("")

输出结果如下:
Rule #1
Rule: If a person recommends Shawshank Redemption, The (1994), Pulp Fiction (1994), Silence of the Lambs, The (1991), Star Wars (1977), Twelve Monkeys (1995) they will also recommend Raiders of the Lost Ark (1981)
- Confidence: 1.000

Rule #2
Rule: If a person recommends Silence of the Lambs, The (1991), Fargo (1996), Empire Strikes Back, The (1980), Fugitive, The (1993), Star Wars (1977), Pulp Fiction (1994) they will also recommend Twelve Monkeys (1995)
- Confidence: 1.000
……
看起来效果就好很多


参考资料
《Python数据挖掘入门与实践》

总结

以上是生活随笔为你收集整理的用亲和性分析方法推荐电影的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。