基于关联规则(Apriori)+协同过滤(collaborative filtering)实现电影推荐系统

一、前言

1.数据集介绍

使用电影推荐系统常用的英文数据集The Movies Dataset, 该数据集包含了2017年7月之前公映的45,000条电影记录,每条记录包含了该影片的电影名、语种、演员、导演组、剧情关键词、预算、票房、平均评分、评分人数等重要信息。其中评分记录为270,000名用户对上述所有电影给出的2600万个评分,评分区间为1-5。
需要数据集的留下邮箱,看到后会第一时间发送的

2.方法概述

在介绍如何利用数据挖掘算法中的Apriori(关联规则)/collaborative filtering(协同过滤)算法来实现一个电影推荐系统。我们将通过加载数据、数据预处理、生成频繁项集和关联规则这几个步骤,最终通过关联规则生成电影推荐的列表。
任务目标:根据用户的观影记录或者喜好的电影列表,推荐相应的电影列表或者单部电影。例如,用户喜欢《Batman Returns》这部电影,要求推荐相应的电影列表

3.运行环境

python=3.6.5
numpy=1.18.1
matplotlib=3.3.
mlxtend=0.17.2

二、数据准备与预处理

1.数据熟悉

movies_metadata.csv: 电影元数据文件,包含了45,000部电影的每部电影的各个属性信息,一共包含24个字段,特征字段说明如下:

1)文件movies_metadata.csv包含24个字段,具体信息如下:
序号 字段名 数据类型 字段描述
1 adult String 成人
2 belongs_to_collection String 归属
3 budget Integer 预算
4 genres String 流派
5 homepage String 主页
6 id Integer 编码
7 imdb_id String imdb编码
8 original_language String 初始语言
9 original_title String 初始标题
10 overview String 概述
11 popularity String 声望
12 poster_path String 宣传路径
13 production_companies String 发行公司
14 production_countries String 发行国家
15 release_date String 发布时间
16 revenue Integer 收入
17 runtime Integer 运行
18 spoken_languages String 语言
19 status String 状态
20 tagline String 标题
21 title String 题目
22 video String 视频
23 vote_average Integer 平均投票
24 vote_count Integer 投票数

ratings_small.csv: 评分数据文件,包含700多个用户对9000多部电影的评分数据,一共包含以下4个字段:

2)文件ratings_small.csv包含4个字段,具体信息如下:
序号 字段名 数据类型 字段描述
1 userId Integer 用户编码
2 movieId Integer imdb编码
3 rating Integer 评级
4 timestamp Integer 时间戳

2.数据读取

导入需要的包

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

读取电影及评分数据

movies_df = pd.read_csv('movies_metadata.csv')
ratings_df = pd.read_csv('ratings_small.csv')

获得movies_df的尺寸

movies_df.shape 

(45466, 24)

获取movies_df的前五行数据

movies_df.head()

在这里插入图片描述
获得ratings_df的尺寸

ratings_df.shape

(100004, 4)

获取ratings_df的前五行数据

ratings_df.head(5)

在这里插入图片描述

3.数据预处理

清洗movies_metadata.csv和ratings_small.csv的数据:缺失值处理,数据去重。movies_metadata.csv缺少评分数据,ratings_small.csv缺少电影名数据,因此我们需要合并两张表的数据生成所需的DataFrame

movies_df = movies_df[['title', 'id']] #截取title和id这两列的数据
movies_df.dtypes #查看每列的数据类型

3.1 无用属性删除

删掉timestamp列的数据

ratings_df.drop(['timestamp'], axis=1, inplace=True) 

3.2 缺失属性处理


np.where(pd.to_numeric(movies_df['id'], errors='coerce').isna()) 

结果:(array([19730, 29503, 35587], dtype=int64),)

pd.to_numeric将id列的数据由字符串转为数值类型,不能转换的数据设置为NaN。
np.where返回满足()内条件的数据所在的位置。返回缺失值的位置,其中isna() 对于NaN返回True,否则返回False

movies_df.iloc[[19730, 29503, 35587]]

在这里插入图片描述
结果赋值给id列数据,删除id非法的行

movies_df['id'] = pd.to_numeric(movies_df['id'], errors='coerce') 
movies_df.drop(np.where(movies_df['id'].isna())[0], inplace=True) 
movies_df.shape

(45463, 2)

3.3 数据去重

movies_df.duplicated(['id', 'title']).sum() #返回重复项总数

30

movies_df.drop_duplicates(['id'], inplace=True) #数据去重
ratings_df.duplicated(['userId', 'movieId']).sum()

0

3.4 类型转换

对于movies_df的id列进行类型转换

movies_df['id'] = movies_df['id'].astype(np.int64)
movies_df.dtypes

3.5 数据合并

将左边的dataframe的movieId和右边的Dataframe的id进行对齐合并成新的Dataframe

ratings_df = pd.merge(ratings_df, movies_df, left_on='movieId', right_on='id') 

去掉多余的id列

ratings_df.drop(['id'], axis=1, inplace=True) 
ratings_df.head(10)

在这里插入图片描述

三、特征工程

有评分记录的电影的个数

len(ratings_df['title'].unique()) 

2794

统计每部电影的评分记录的总个数

ratings_count = ratings_df.groupby(['title'])['rating'].count().reset_index() 

列的字段重命名

ratings_count = ratings_count.rename(columns={'rating':'totalRatings'}) 
ratings_count.head(5)

在这里插入图片描述
添加totalRatings字段

ratings_total = pd.merge(ratings_df,ratings_count, on='title', how='left') 
ratings_total.head(5)

在这里插入图片描述
数据分析以截取合适的数据,获得关于totalRatings字段的统计信息

ratings_count['totalRatings'].describe()  

在这里插入图片描述

ratings_count.hist()

在这里插入图片描述
查看分位点

ratings_count['totalRatings'].quantile(np.arange(.6,1,0.01)) 

在这里插入图片描述

由上述数据分析可知,21%的电影的评分记录个数超过20个,选取总评个数超过阈值的电影评分数据

votes_count_threshold = 20
ratings_top = ratings_total.query('totalRatings > @votes_count_threshold') 
ratings_top.head(10)

在这里插入图片描述
检查有无缺失值

ratings_top.isna().sum() 

在这里插入图片描述
检查是否有重复数据

ratings_top.duplicated(['userId','title']).sum()  

140

只保留每个用户对每个电影的一条评分记录

ratings_top = ratings_top.drop_duplicates(['userId','title']) 
ratings_top.duplicated(['userId','title']).sum() 

调整表样式

df_for_apriori = ratings_top.pivot(index='userId',columns='title',values='rating') 
df_for_apriori.head(5)

在这里插入图片描述

缺失值填充0,有效评分规则,1表示有效,0表示无效,并且对每个数据应用上述规则

df_for_apriori = df_for_apriori.fillna(0) 
def encode_units(x):  
    if x <= 0:
        return 0
    if x > 0:
        return 1
df_for_apriori = df_for_apriori.applymap(encode_units) 
df_for_apriori.head(5)

在这里插入图片描述

四、算法建模

1.关联规则算法

1.1 算法简介

例子:啤酒与尿布:
沃尔玛超市在分析销售记录时,发现了啤酒与尿布经常一起被购买,于是他们调整了货架将两者放在了一起,结果真的提升了啤酒的销量。 原因解释: 爸爸在给宝宝买尿布的时候,会顺便给自己买点啤酒?

概述:
Apriori算法是一种最有影响力的挖掘布尔关联规则的频繁项集的算法,其命名Apriori源于算法使用了频繁项集性质的先验(Prior)知识。接下来我们将以超市订单的例子理解关联分析相关的重要概念: Support(支持度)、Confidence(置信度)、Lift(提升度)。

Support(支持度):指某事件出现的概率,在本例中即指某个商品组合出现的次数占总次数的比例。例:Support(‘Bread’) = 4/5 = 0.8 Support(‘Milk’) = 4/5 = 0.8
Support(‘Bread+Milk’) = 3/5 = 0.6
Confidence(置信度):本质上是个条件概率,即当购买了商品A的前提下,购买商品B的概率。例:Confidence(‘Bread’—> ‘Milk’) = Support(‘Bread+Milk’)/ Support(‘Bread’) = 0.6/0.8 = 0.75
Lift(提升度): 指商品A的出现,对商品B的出现的概率的提升程度。
Lift(A->B) = Confidence(A, B) / Support(B)例:Lift(‘Bread’—> ‘Milk’) = 0.75/0.8 = 0.9375

对于Lift(提升度)有三种情况:
Lift(A->B)>1: 代表A对B的出现概率有提升。
Lift(A->B)=1: 代表A对B的出现概率没有提升,也没有下降。
Lift(A->B)<1: 代表A对B的出现概率有下降效果。

原理:
该算法挖掘关联规则的过程,即是**查找频繁项集(frequent itemset)**的过程:
频繁项集:支持度大于等于最小支持度(Min Support)阈值的项集。
非频繁集:支持度小于最小支持度的项集。

流程:
K = 1, 计算K项集的支持度;
筛选掉小于最小支持度的项集;
如果项集为空,则对应K-1项集的结果为最终结果。否则K = K+1重复2-3步

1.2 Apriori算法建模

导入关联规则算法相关包

from mlxtend.frequent_patterns import apriori
from mlxtend.frequent_patterns import association_rules

生成符合条件的频繁项集,并降序排列的频繁项集

frequent_itemsets = apriori(df_for_apriori, min_support=0.10, use_colnames=True) 
frequent_itemsets.sort_values('support', ascending=False)  

生成关联规则,只保留lift>1的部分

rules = association_rules(frequent_itemsets, metric="lift", min_threshold=1)  
rules.sort_values('lift', ascending=False)

在这里插入图片描述
输出结果进行观察

all_antecedents = [list(x) for x in rules['antecedents'].values]
desired_indices = [i for i in range(len(all_antecedents)) if len(all_antecedents[i])==1 and all_antecedents[i][0]=='Batman Returns']
apriori_recommendations=rules.iloc[desired_indices,].sort_values(by=['lift'],ascending=False)
apriori_recommendations.head() 

在这里插入图片描述

apriori_recommendations_list = [list(x) for x in apriori_recommendations['consequents'].values]
print("Apriori Recommendations for movie: Batman Returns\n")
for i in range(5):
    print("{0}: {1} with lift of {2}".format(i+1,apriori_recommendations_list[i],apriori_recommendations.iloc[i,6]))

在这里插入图片描述

结果说明:给出用户观看过的电影记录:《Batman Returns》,我们选出了lift(支持度)降序排列的前五条关联规则,给出了对应的推荐列表。

apriori_single_recommendations = apriori_recommendations.iloc[[x for x in range(len(apriori_recommendations_list)) if len(apriori_recommendations_list[x])==1],]
apriori_single_recommendations_list = [list(x) for x in apriori_single_recommendations['consequents'].values]
print("Apriori single-movie Recommendations for movie: Batman Returns\n")
for i in range(5):
    print("{0}: {1}, with lift of {2}".format(i+1,apriori_single_recommendations_list[i][0],apriori_single_recommendations.iloc[i,6]))

在这里插入图片描述

结果说明:我们约束consequents(后件)的长度为1,选出lift降序排列的前五个关联规则(关联规则格式为前件——>后件)。对于用户观看的电影记录《Batman Returns》,即antecedents(前件),我们根据规则按照推荐程度降序给出了单部电影推荐结果。

2.协同过滤算法

2.1算法简介

协同过滤一般是在海量的用户中发现一小部分和你品味比较相近的,在协同过滤中,这些用户称为邻居,然后根据他们喜欢的东西组织成一个排序的目录来推荐给你。问题的重点就是怎样去寻找和你比较相似的用户,怎么将那些邻居的喜好组织成一个排序的目录给用户。
文章主要介绍的是基于user的协同过滤,理论上是要找到找到和需要被推荐的用户最相似的用户,这里我们用邻居表示与你最为相似的用户。相似用户的计算可能有很多,在实际中我们会给出一个数字K表示和你最为相似的用户。在计算相似度的时候,理论上要计算被推荐的用户与所有用户的相似度,但是当数据量比较大的时候,这样做是很费时间的 ,数据集中可能有很多用户和需要被推荐的用户是没有关系的,在计算是完全是没有必要的,所以需要物品到用户的反查表,也就是没一件物品对应的用户信息,有了这个表,就可以过滤掉很多和你没有关系的用户,减少计算量。

2.2 Collaborative filtering算法建模

读取读取ratings_small.csv数据用于建模

ratings_path = "ratings_small.csv"
ratings_df = pd.read_csv(ratings_path)
ratings_df.head(5)

在这里插入图片描述
始的movieId并非是从0或1开始的连续值,为了便于构建其user-item矩阵,我们将重新排列movie-id

movie_id = ratings_df['movieId'].drop_duplicates()
movie_id.head()
movie_id = pd.DataFrame(movie_id)
movie_id['movieid'] = range(len(movie_id))
print(len(movie_id))
movie_id.head()

更新movieId–>movieid

ratings_df = pd.merge(ratings_df,movie_id,on=['movieId'],how='left')
ratings_df = ratings_df[['userId','movieid','rating','timestamp']] #更新movieId-->movieid

用户物品统计

n_users = ratings_df.userId.nunique()
n_items = ratings_df.movieid.nunique()
print(n_users)
print(n_items)

671
9066

# 拆分数据集
from sklearn.model_selection import train_test_split
# 按照训练集70%,测试集30%的比例对数据进行拆分
train_data,test_data =train_test_split(ratings_df,test_size=0.3)
# 训练集 用户-物品 矩阵
user_item_matrix = np.zeros((n_users,n_items))
for line in train_data.itertuples():
    #print(line)
    user_item_matrix[line[1]-1,line[2]] = line[3]

构建用户相似矩阵 - 采用余弦距离

from sklearn.metrics.pairwise import pairwise_distances
# 相似度计算定义为余弦距离
user_similarity_m = pairwise_distances(user_item_matrix,metric='cosine')# 每个用户数据为一行,此处不需要再进行转置

相似矩阵

user_similarity_m[0:5,0:5].round(2)

在这里插入图片描述
现在我们只分析上三角,得到等分位数

user_similarity_m_triu = np.triu(user_similarity_m,k=1) # 取得上三角数据
user_sim_nonzero = np.round(user_similarity_m_triu[user_similarity_m_triu.nonzero()],3)
np.percentile(user_sim_nonzero,np.arange(0,101,10))
mean_user_rating = user_item_matrix.mean(axis=1)
rating_diff = (user_item_matrix - mean_user_rating[:,np.newaxis]) 

np.newaxis作用:为mean_user_rating增加一个维度,实现加减操作

以np.array([np.abs(item_similarity_m).sum(axis=1)]是为了可以使评分在1~ 5 之间,使1~5的标准化

user_precdiction = mean_user_rating[:,np.newaxis] + user_similarity_m.dot(rating_diff) / np.array([np.abs(user_similarity_m).sum(axis=1)]).T

只取数据集中有评分的数据集进行评估

from sklearn.metrics import mean_squared_error
from math import sqrt
prediction_flatten = user_precdiction[user_item_matrix.nonzero()]
user_item_matrix_flatten = user_item_matrix[user_item_matrix.nonzero()]
error_train = sqrt(mean_squared_error(prediction_flatten,user_item_matrix_flatten)) # 均方根误差计算
print('训练集预测均方根误差:',error_train)

在这里插入图片描述

test_data_matrix = np.zeros((n_users,n_items))
for line in test_data.itertuples():
    test_data_matrix[line[1]-1,line[2]-1]=line[3]

预测矩阵


rating_diff = (test_data_matrix - mean_user_rating[:,np.newaxis]) # np.newaxis作用:为mean_user_rating增加一个维度,实现加减操作
user_precdiction = mean_user_rating[:,np.newaxis] + user_similarity_m.dot(rating_diff) / np.array([np.abs(user_similarity_m).sum(axis=1)]).T

只取数据集中有评分的数据集进行评估

prediction_flatten = user_precdiction[user_item_matrix.nonzero()]
user_item_matrix_flatten = user_item_matrix[user_item_matrix.nonzero()]
error_test = sqrt(mean_squared_error(prediction_flatten,user_item_matrix_flatten)) # 均方根误差计算
print('测试集预测均方根误差:',error_test)

在这里插入图片描述

五、参考致谢

协同过滤算法: collaborative filtering.
关联规则算法: Apriori.

  • 34
    点赞
  • 155
    收藏
    觉得还不错? 一键收藏
  • 95
    评论
关联规则算法是一种常用的数据挖掘算法,可以用来挖掘数据中的关联关系。在电影推荐领域,我们可以利用关联规则算法来挖掘用户的观影历史,从而推荐相似的电影给用户。 具体地,我们可以将每个用户观看过的电影列表看作一个交易记录,每个电影看作一个商品。然后,我们可以使用 Apriori 算法或 FP-growth 算法来挖掘出频繁项集和关联规则。其中,频繁项集是指在所有交易记录中出现的频率超过预设阈值的商品集合,而关联规则则是指在频繁项集中,两个商品之间的关联关系。 例如,假设我们有以下三个用户的观影历史: 用户1:电影A,电影B,电影C 用户2:电影A,电影B,电影D,电影E 用户3:电影B,电影D,电影E 我们可以将这三个用户的观影历史转化为以下交易记录: {A,B,C} {A,B,D,E} {B,D,E} 然后,我们可以使用 Apriori 算法或 FP-growth 算法来挖掘出频繁项集和关联规则。例如,假设我们设置阈值为 2,那么我们可以得到以下频繁项集: {A,B},{B,D},{B,E} 然后,我们可以使用关联规则算法来挖掘出关联规则。例如,假设我们设置置信度为 0.5,那么我们可以得到以下关联规则: {A} -> {B} {B} -> {A} {B} -> {D} {B} -> {E} 这些关联规则表示,如果用户观看了电影 A,那么他们也很可能会观看电影 B;如果用户观看了电影 B,那么他们也很可能会观看电影 A、D 或 E。我们可以利用这些关联规则推荐电影给用户。 具体地,当用户观看了某个电影时,我们可以根据关联规则推荐与该电影相关的其他电影。例如,如果用户观看了电影 A,那么我们可以推荐电影 B 给他们。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 95
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值