作者|GUEST
编译|VK
起源|Analytics Vidhya
介绍
在当今忙碌的世界中,举荐零碎变得越来越重要。人们总是在寻找最适宜他们的产品/服务。因而,举荐零碎十分重要,因为它们能够帮忙用户在不耗费认知资源的状况下做出正确的抉择。
在本博客中,咱们将理解举荐零碎的基础知识,并学习如何通过实现K-最近邻算法来构建一个应用协同过滤的电影举荐零碎。咱们还将依据街坊预测给定电影的评级,并将其与理论评级进行比拟。
举荐零碎的类型
举荐零碎大抵可分为三类
- 协同过滤
- 基于内容的筛选
- 混合举荐零碎
协同过滤
这种过滤办法通常基于收集和剖析用户的行为、流动或偏好信息,并依据与其余用户的相似性来预测他们会喜爱什么。合作过滤办法的一个次要长处是它不依赖于机器可剖析的内容,因而它可能精确地举荐诸如电影之类的简单我的项目,而不须要“了解”我的项目自身。
此外,还有几种类型的协同过滤算法
- 用户-用户协同过滤:尝试搜寻类似的客户,并依据他/她的类似抉择提供产品。
- 项-项协同过滤:它与后面的算法十分类似,然而咱们没有找到一个看起来很类似的客户,而是尝试查找类似的项。一旦咱们有了商品外观相似矩阵,咱们就能够很容易地向从商店购买商品的顾客举荐类似的商品。
- 其余算法:还有其余办法,如市场篮子剖析,它通过寻找交易中经常出现的我的项目组合来工作。
基于内容的过滤
这些过滤办法基于对我的项目的形容和用户的配置文件。在基于内容的举荐零碎中,应用关键字来形容我的项目,并建设用户配置文件来阐明用户喜爱的我的项目类型。换句话说,这些算法试图举荐与用户过来喜爱的产品类似的产品。
混合举荐零碎
最近的钻研表明,联合合作过滤和基于内容的过滤的混合办法在某些状况下可能更无效。混合办法能够通过多种形式实现,别离进行基于内容的预测和基于合作的预测,而后将它们组合在一起,将基于内容的特色增加到基于合作的办法中(或者反之亦然),或者将这些办法对立到一个模型中。
Netflix是应用混合举荐零碎的一个很好的例子。该网站通过比拟类似用户的观看和搜寻习惯(即合作过滤)以及提供与用户评估较高的电影具备雷同特色的电影(基于内容的过滤)来提出倡议。
当初咱们曾经对举荐零碎有了根本的直觉,让咱们从用Python构建一个简略的电影举荐零碎开始。
在这里找到蕴含残缺代码、数据集和所有插图的Python Notebook https://www.kaggle.com/heeral...
TMDb-电影数据库
电影数据库(TMDb)是一个社区建设的电影和电视数据库,它领有大量对于电影和电视节目的数据。以下是统计数据:https://www.themoviedb.org/
为了简略和易于计算,我应用了这个微小数据集的一个子集,即TMDb 5000数据集。它有5000部电影的信息,分成2个CSV文件。
- tmdb_5000_movies.蕴含分数、题目、公布日期、流派等信息。
- tmdb_5000_credits.csv:蕴含每部电影的演员和剧组信息。
数据集的链接在这里:https://www.kaggle.com/tmdb/t...
Python实现
步骤1-导入数据集
导入所需的Python库,如Pandas、Numpy、Seaborn和Matplotlib。而后应用Pandas中预约义的read_CSV()函数导入CSV文件。
movies = pd.read_csv('../input/tmdb-movie-metadata/tmdb_5000_movies.csv')credits = pd.read_csv('../input/tmdb-movie-metadata/tmdb_5000_credits.csv')
步骤2-数据摸索和清理
咱们将首先应用head(),descripe()函数来查看数据集的值和构造,而后持续清理数据。
movies.head()
movies.describe()
相似地,咱们能够失去credits数据帧,并失去如下输入
检查数据集,咱们能够看到genres, keywords、production_companies、production_countries、spoken_languages都是JSON格局。相似地,在其余CSV文件中,cast和crew都是JSON格局。当初让咱们将这些列转换为易于浏览和解释的格局。咱们将把它们转换成字符串,稍后再转换成列表,以便于解释。
JSON格局相似于嵌入到字符串中的dictionary(key:value)对。一般来说,解析数据在计算上是低廉和耗时的。侥幸的是,这个数据集没有那么简单的构造。列之间的一个根本相似之处是它们有一个name键,它蕴含咱们须要收集的值。最简略的办法是解析JSON并查看每行的name键。找到name键后,将其值存储到一个列表中,并用list替换JSON。
然而咱们不能间接解析这个JSON,因为它必须首先被解码。为此,咱们应用json.loads把它解码。而后,咱们能够通过这个列表来剖析以找到所需的值。让咱们看看上面正确的语法。
# 将genres列从json更改为stringmovies['genres'] = movies['genres'].apply(json.loads)for index,i in zip(movies.index,movies['genres']): list1 = [] for j in range(len(i)): list1.append((i[j]['name'])) # “name”蕴含流派的名称 movies.loc[index,'genres'] = str(list1)
以相似的形式,咱们将把JSON转换为列的字符串列表:keywords、production_companys、cast和crew。咱们将应用movies.iloc[index]
步骤3-合并2个CSV文件
咱们将合并movies和credits数据帧并抉择所需的列,并有一个对立的movies dataframe来解决。
movies = movies.merge(credits, left_on='id', right_on='movie_id', how='left')movies = movies[['id', 'original_title', 'genres', 'cast', 'vote_average', 'director', 'keywords']]
咱们能够查看像这样的电影的大小和属性-
第4步-应用“Genres”列
咱们将革除“Genres ”列以找到“genre”列表
movies['genres'] = movies['genres'].str.strip('[]').str.replace(' ','').str.replace("'",'')movies['genres'] = movies['genres'].str.split(',')
让咱们依据电影类型的产生状况来描述电影类型,以便从风行水平上深刻理解电影类型。
plt.subplots(figsize=(12,10))list1 = []for i in movies['genres']: list1.extend(i)ax = pd.Series(list1).value_counts()[:10].sort_values(ascending=True).plot.barh(width=0.9,color=sns.color_palette('hls',10))for i, v in enumerate(pd.Series(list1).value_counts()[:10].sort_values(ascending=True).values): ax.text(.8, i, v,fontsize=12,color='white',weight='bold')plt.title('Top Genres')plt.show()
戏剧仿佛是继悲剧之后最受欢迎的类型
当初让咱们生成一个列表“genreList”,其中蕴含数据集中提到的所有可能的惟一类型。
genreList = []for index, row in movies.iterrows(): genres = row["genres"] for genre in genres: if genre not in genreList: genreList.append(genre)genreList[:10] # 当初咱们有了一个流派的列表
one-hot编码
“genreList”将保留所有流派。然而咱们如何能力晓得每部电影所属的类型呢。当初有些电影是“动作片”,有些是“动作片,冒险片”,等等。咱们须要依据电影类型对电影进行分类。
让咱们在dataframe中创立一个新列,该列将保留二值,示意是否存在该流派。首先,让咱们创立一个办法,它将返回每个电影流派的二值列表。“genreList”当初可用于与值进行比拟。
举个例子,咱们在列表中有20个流派。因而,上面的函数将返回一个蕴含20个元素的列表,这些元素能够是0或1。例如,咱们有一部电影,其中genre='Action',那么新列将蕴含[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]。
相似于“动作,冒险”,咱们会有[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]。将流派转换成这样的二值列表将有助于轻松地按流派对电影进行分类。
def binary(genre_list): binaryList = [] for genre in genreList: if genre in genre_list: binaryList.append(1) else: binaryList.append(0) return binaryList
将binary()函数利用于“genres”列以获取“genre_list”
对于其余特色,如演员、导演和关键字,咱们将遵循雷同的符号。
movies['genres_bin'] = movies['genres'].apply(lambda x: binary(x))movies['genres_bin'].head()
步骤5-应用Cast列
让咱们画一张演员出场率最高的图表
plt.subplots(figsize=(12,10))list1=[]for i in movies['cast']: list1.extend(i)ax=pd.Series(list1).value_counts()[:15].sort_values(ascending=True).plot.barh(width=0.9,color=sns.color_palette('muted',40))for i, v in enumerate(pd.Series(list1).value_counts()[:15].sort_values(ascending=True).values): ax.text(.8, i, v,fontsize=10,color='white',weight='bold')plt.title('Actors with highest appearance')plt.show()
塞缪尔·杰克逊,也就是《复仇者》中的尼克·弗瑞呈现在最多的电影中。我最后认为摩根·弗里曼可能是电影数量最多的演员,但数据胜于假如!
当我最后创立所有演员的列表时,它有大概5万的惟一值,因为很多电影都有大概15-20个演员的条目。
但咱们须要所有这些吗?答案是否定的。咱们只须要对电影有最大奉献的演员。《光明骑士》系列电影有很多演员参演。然而咱们只会抉择主要演员,比方克里斯蒂安·贝尔,迈克尔·凯恩,希斯·莱杰。我曾经从每部电影中选出了4位主要演员。
你可能会想到一个问题,那就是你如何确定演员在电影中的重要性。侥幸的是,JSON格局中的演员程序是依据演员对电影的奉献而定的。
让咱们看看咱们是如何做到这一点的,并创立一个列“cast_bin”
for i,j in zip(movies['cast'],movies.index): list2 = [] list2 = i[:4] movies.loc[j,'cast'] = str(list2)movies['cast'] = movies['cast'].str.strip('[]').str.replace(' ','').str.replace("'",'')movies['cast'] = movies['cast'].str.split(',')for i,j in zip(movies['cast'],movies.index): list2 = [] list2 = i list2.sort() movies.loc[j,'cast'] = str(list2)movies['cast']=movies['cast'].str.strip('[]').str.replace(' ','').str.replace("'",'')castList = []for index, row in movies.iterrows(): cast = row["cast"] for i in cast: if i not in castList: castList.append(i)movies[‘cast_bin’] = movies[‘cast’].apply(lambda x: binary(x))movies[‘cast_bin’].head()
第6步-应用“Directors ”列
让咱们画一张导演出场率最高的图表
def xstr(s): if s is None: return '' return str(s)movies['director'] = movies['director'].apply(xstr)plt.subplots(figsize=(12,10))ax = movies[movies['director']!=''].director.value_counts()[:10].sort_values(ascending=True).plot.barh(width=0.9,color=sns.color_palette('muted',40))for i, v in enumerate(movies[movies['director']!=''].director.value_counts()[:10].sort_values(ascending=True).values): ax.text(.5, i, v,fontsize=12,color='white',weight='bold')plt.title('Directors with highest movies')plt.show()
咱们创立了一个新的列“director_bin”,正如咱们之前所做的那样
directorList=[]for i in movies['director']: if i not in directorList: directorList.append(i)movies['director_bin'] = movies['director'].apply(lambda x: binary(x))movies.head()
最初,在实现了所有这些工作之后,咱们失去了如下所示的movies数据集
步骤7-应用Keywords列
关键字或标记蕴含对于电影的大量信息,这是查找相似电影的要害性能。例如:像《复仇者》和《蚂蚁侠》这样的电影可能有一些独特的关键词,比方超级英雄或者奇观。
为了剖析关键词,咱们将尝试不同的办法,并绘制一个词云,以取得更好的直觉:
from wordcloud import WordCloud, STOPWORDSimport nltkfrom nltk.corpus import stopwordsplt.subplots(figsize=(12,12))stop_words = set(stopwords.words('english'))stop_words.update(',',';','!','?','.','(',')','$','#','+',':','...',' ','')words=movies['keywords'].dropna().apply(nltk.word_tokenize)word=[]for i in words: word.extend(i)word=pd.Series(word)word=([i for i in word.str.lower() if i not in stop_words])wc = WordCloud(background_color="black", max_words=2000, stopwords=STOPWORDS, max_font_size= 60,width=1000,height=1000)wc.generate(" ".join(word))plt.imshow(wc)plt.axis('off')fig=plt.gcf()fig.set_size_inches(10,10)plt.show()
下面是一个单词云,显示了形容电影的次要关键字或标签
咱们从以下关键字中找到“words_bin”
movies['keywords'] = movies['keywords'].str.strip('[]').str.replace(' ','').str.replace("'",'').str.replace('"','')movies['keywords'] = movies['keywords'].str.split(',')for i,j in zip(movies['keywords'],movies.index): list2 = [] list2 = i movies.loc[j,'keywords'] = str(list2)movies['keywords'] = movies['keywords'].str.strip('[]').str.replace(' ','').str.replace("'",'')movies['keywords'] = movies['keywords'].str.split(',')for i,j in zip(movies['keywords'],movies.index): list2 = [] list2 = i list2.sort() movies.loc[j,'keywords'] = str(list2)movies['keywords'] = movies['keywords'].str.strip('[]').str.replace(' ','').str.replace("'",'')movies['keywords'] = movies['keywords'].str.split(',')words_list = []for index, row in movies.iterrows(): genres = row["keywords"] for genre in genres: if genre not in words_list: words_list.append(genre)movies['words_bin'] = movies['keywords'].apply(lambda x: binary(x))movies = movies[(movies['vote_average']!=0)] #删除得分为0且没有drector名称的电影movies = movies[movies['director']!='']
步骤8-电影之间的相似性
咱们将应用余弦相似性来寻找两部电影之间的相似性。余弦相似性是如何工作的?
假如咱们有两个向量。如果向量靠近平行,即向量之间的夹角为0,那么咱们能够说它们都是“类似的”,因为cos(0)=1。然而,如果向量是正交的,那么咱们能够说它们是独立的或不“类似的”,因为cos(90)=0。
上面是具体的钻研链接:http://blog.christianperone.c...
上面我定义了一个函数类似度,它将查看电影之间的相似性。
from scipy import spatialdef Similarity(movieId1, movieId2): a = movies.iloc[movieId1] b = movies.iloc[movieId2] genresA = a['genres_bin'] genresB = b['genres_bin'] genreDistance = spatial.distance.cosine(genresA, genresB) scoreA = a['cast_bin'] scoreB = b['cast_bin'] scoreDistance = spatial.distance.cosine(scoreA, scoreB) directA = a['director_bin'] directB = b['director_bin'] directDistance = spatial.distance.cosine(directA, directB) wordsA = a['words_bin'] wordsB = b['words_bin'] wordsDistance = spatial.distance.cosine(directA, directB) return genreDistance + directDistance + scoreDistance + wordsDistance
让咱们查看两部随机电影之间的相似性
咱们看到间隔大概是2.068,这是很高的。间隔越远,电影就越不类似。让咱们看看这些随机电影到底是什么。
很显著,光明骑士崛起和驯龙高手2是十分不同的电影。因而,间隔是微小的。
步骤9-分数预测器
所以当初当咱们把所有的事件都筹备好了,咱们当初就要建设一个分数预测器。次要函数是Similarity()函数,它将计算电影之间的相似性,并将找到10部最类似的电影。这10部电影将有助于预测咱们想要的电影的分数。咱们将取类似电影得分的平均值,而后找到想要的电影的分数。
当初,电影之间的相似性将取决于咱们新创建的蕴含二值列表的列。咱们晓得,像导演或演员这样的特点将在电影的胜利中表演十分重要的角色。咱们总是认为大卫·芬奇(David Fincher)和克里斯·诺兰(Chris Nolan)的电影会获得好问题。此外,如果他们与本人最喜爱的演员单干,这些演员总是能给他们带来胜利,并且也在他们最喜爱的题材上工作,那么胜利的几率就会更高。利用这些景象,让咱们尝试建设咱们的分数预测器。
import operatordef predict_score(): name = input('Enter a movie title: ') new_movie = movies[movies['original_title'].str.contains(name)].iloc[0].to_frame().T print('Selected Movie: ',new_movie.original_title.values[0]) def getNeighbors(baseMovie, K): distances = [] for index, movie in movies.iterrows(): if movie['new_id'] != baseMovie['new_id'].values[0]: dist = Similarity(baseMovie['new_id'].values[0], movie['new_id']) distances.append((movie['new_id'], dist)) distances.sort(key=operator.itemgetter(1)) neighbors = [] for x in range(K): neighbors.append(distances[x]) return neighbors K = 10 avgRating = 0 neighbors = getNeighbors(new_movie, K)print('\nRecommended Movies: \n') for neighbor in neighbors: avgRating = avgRating+movies.iloc[neighbor[0]][2] print( movies.iloc[neighbor[0]][0]+" | Genres: "+str(movies.iloc[neighbor[0]][1]).strip('[]').replace(' ','')+" | Rating: "+str(movies.iloc[neighbor[0]][2])) print('\n') avgRating = avgRating/K print('The predicted rating for %s is: %f' %(new_movie['original_title'].values[0],avgRating)) print('The actual rating for %s is %f' %(new_movie['original_title'].values[0],new_movie['vote_average']))
当初只需运行函数如下,并输出你喜爱的电影10个类似的电影和它的预测收视率
predict_score()
从而实现了基于K近邻算法的电影举荐零碎的实现。
K值
在这个我的项目中,我任意抉择了K=10的值。
但在KNN的其它利用中,求K值并不容易。较小的K值意味着噪声对后果的影响更大,而较大的K值会导致计算开销。数据科学家通常抉择奇数,如果类的数目是2,另一个抉择k的简略办法是设置k=sqrt(n)。
在这里找到蕴含残缺代码、数据集和所有插图的Python Notebook:https://www.kaggle.com/heeral...
进一步浏览
- 举荐零碎:https://en.wikipedia.org/wiki...
- 基于K近邻算法的机器学习根底:https://towardsdatascience.co...
- 应用Python的举荐零碎.第2局部:协同过滤(K-最近邻算法):https://heartbeat.fritz.ai/re...:~:text=When%20a%20KNN%20makes%20a,the%20most%20similar%20movie%20recommendations.
- 什么是余弦相似性?:https://deepai.org/machine-le...
- 如何在KNN中找到K的最优值?:https://towardsdatascience.co...
原文链接:https://www.analyticsvidhya.c...
欢送关注磐创AI博客站:
http://panchuang.net/
sklearn机器学习中文官网文档:
http://sklearn123.com/
欢送关注磐创博客资源汇总站:
http://docs.panchuang.net/