个性化阅读
专注于IT技术分析

Python中的推荐系统:入门教程

本文概述

推荐系统是当今数据科学最流行的应用之一。它们用于预测用户对某项商品的”评价”或”偏好”。几乎每家大型科技公司都以某种形式应用了它们:亚马逊使用它向客户推荐产品, YouTube使用它来决定自动播放下一个要播放的视频, Facebook使用它来推荐喜欢的页面和关注的人。而且, 对于某些公司(例如Netflix和Spotify)而言, 其商业模式及其成功取决于其推荐的效力。实际上, Netflix在2009年甚至向愿意将其系统提高10%的任何人提供了100万美元。

在本教程中, 你将看到如何构建简单的以及基于内容的推荐系统的基本模型。尽管这些模型在复杂性, 质量或准确性方面都无法与行业标准相提并论, 但它将帮助你开始构建更复杂的模型, 从而产生更好的结果。

但是这些推荐系统是什么?

推荐系统大致可分为3种类型:

  • 简单推荐者:根据电影的受欢迎程度和/或体裁向每个用户提供通用推荐。该系统背后的基本思想是, 更受大众欢迎和好评的电影更有可能被普通观众所喜爱。 IMDB Top 250是此系统的示例。
  • 基于内容的推荐者:根据特定项目建议相似的项目。该系统使用项目元数据(例如电影的流派, 导演, 描述, 演员等)来提出这些建议。这些推荐系统背后的总体思想是, 如果某人喜欢某个特定项目, 那么他(她)也将喜欢与之相似的项目。
  • 协作过滤引擎:这些系统尝试根据其他用户的过去评分和偏好来预测用户将给某项的评分或偏好。协作过滤器不需要像基于内容的内容那样的项目元数据。

简单推荐

如上一节所述, 简单的推荐器是一种基本系统, 可以根据特定的指标或分数来推荐排名靠前的项目。在本节中, 你将使用从IMDB收集的元数据构建IMDB最佳250电影的简化克隆。

以下是涉及的步骤:

  • 确定指标或评分以对电影评分。
  • 计算每部电影的得分。
  • 根据得分对电影进行排序, 并输出最佳结果。

在执行上述任何步骤之前, 请将电影元数据数据集加载到pandas DataFrame中:

# Import Pandas
import pandas as pd

# Load Movies Metadata
metadata = pd.read_csv('../movies_dsc/data/movies_metadata.csv', low_memory=False)

# Print the first three rows
metadata.head(3)
成人 当属收藏 预算 体裁 主页 id imdb_id original_language original_title 概观 发布日期 收入 运行 口语 状态 标语 标题 视频 平均投票 投票数
0 false {‘id’:10194, ‘name’:’Toy Story Collection’, … 30000000 [{‘id’:16, ‘name’:’Animation’}, {‘id’:35, ‘… http://toystory.disney.com/toy-story 862 tt0114709 en 玩具总动员 在伍迪的带领下, 安迪的玩具快乐地生活在他的… 1995-10-30 373554033.0 81.0 [{‘iso_639_1’:’en’, ‘name’:’English’}] 已发行 NaN 玩具总动员 false 7.7 5415.0
1 false NaN 65000000 [{‘id’:12, ‘name’:’Adventure’}, {‘id’:14, ‘… NaN 8844 tt0113497 en 朱曼吉 当兄弟姐妹朱迪和彼得发现一个伙伴时… 1995-12-15 262797249.0 104.0 [{‘iso_639_1’:’en’, ‘name’:’English’}, {‘iso … 已发行 掷骰子, 释放兴奋! 朱曼吉 false 6.9 2413.0
2 false {‘id’:119050, ‘name’:’脾气暴躁的老人收集… 0 [{‘id’:10749, ‘name’:’Romance’}, {‘id’:35, … NaN 15602 tt0113228 en 脾气暴躁的老人 一场家庭婚礼重新点燃了古老的仇恨。 1995-12-22 0.0 101.0 [{‘iso_639_1’:’en’, ‘name’:’English’}] 已发行 还在大吼。仍在战斗。仍在准备… 脾气暴躁的老人 false 6.5 92.0

3行×24列

你可以想到的最基本的指标之一就是评分。但是, 使用此指标有一些警告。首先, 它没有考虑电影的受欢迎程度。因此, 与10个选民的评分为8.9的电影相比, 10个选民的评分为9的电影将被视为”更好”。

与此相关的是, 该指标还将倾向于偏爱和/或极高收视率的选民人数较少的电影。随着选民人数的增加, 电影的评级会趋于规范, 并朝着反映电影质量的价值迈进。用很少的选民来分辨电影的质量更加困难。

考虑到这些缺点, 有必要提出一个加权评分, 其中要考虑平均评分及其获得的票数。这样的系统将确保100, 000个投票者的评级为9的电影比具有相同等级但数百个投票者的YouTube网络系列获得(远)更高的评分。

由于你尝试构建IMDB的Top 250的克隆, 因此将使用其加权评级公式作为度量标准/分数。在数学上, 它表示如下:

加权评分(WR)= $(\ frac {v} {v + m}。R)+(\ frac {m} {v + m}。C)$

其中

  • v是电影的票数;
  • m是图表中要求列出的最低投票数;
  • R是电影的平均评分;和
  • C是整个报告的平均投票

对于数据集中的每个电影, 你已经具有v(vote_count)和R(vote_average)的值。也可以从该数据直接计算C。

你需要确定一个合适的m值, 即图表中需要列出的最低票数。 m没有正确的值。你可以将其视为初步的否定过滤器, 它会忽略票数少于一定数量的电影。过滤器的选择性由你决定。

在这种情况下, 你将使用第90个百分位数作为截止点。换句话说, 要使电影在图表中出现, 其票数必须超过列表中至少90%的电影。 (另一方面, 如果你选择了第75个百分位, 则按照获得的票数来考虑电影中排名前25%的电影。随着百分位数的减少, 考虑的电影数将增加。可以自由播放使用此值, 并观察最终图表中的变化)。

首先, 让我们计算C的值, 即所有电影的平均评分:

# Calculate C
C = metadata['vote_average'].mean()
print(C)
5.61820721513

IMDB上的电影平均收视率为5.6, 约为10。

接下来, 让我们计算第90个百分点的电影获得的票数m。使用pandas系列的.quantile()方法, pandas库使此任务变得非常简单:

# Calculate the minimum number of votes required to be in the chart, m
m = metadata['vote_count'].quantile(0.90)
print(m)
160.0

接下来, 你可以根据票数过滤符合排行榜的电影:

# Filter out all qualified movies into a new DataFrame
q_movies = metadata.copy().loc[metadata['vote_count'] >= m]
q_movies.shape
(4555, 24)

你使用.copy()方法来确保创建的新q_movies DataFrame独立于原始元数据DataFrame。换句话说, 对q_movies数据帧所做的任何更改都不会影响元数据。

你会看到有4555部电影符合此列表的资格。现在, 你需要计算每个合格电影的指标。为此, 你将定义一个函数weighted_rating()并定义一个新的功能得分, 通过将该功能应用于合格电影的DataFrame, 可以计算出该功能的得分:

# Function that computes the weighted rating of each movie
def weighted_rating(x, m=m, C=C):
    v = x['vote_count']
    R = x['vote_average']
    # Calculation based on the IMDB formula
    return (v/(v+m) * R) + (m/(m+v) * C)
# Define a new feature 'score' and calculate its value with `weighted_rating()`
q_movies['score'] = q_movies.apply(weighted_rating, axis=1)

最后, 让我们根据得分功能对DataFrame进行排序, 并输出标题, 投票数, 平均投票和加权评级或前15名电影的得分。

#Sort movies based on score calculated above
q_movies = q_movies.sort_values('score', ascending=False)

#Print the top 15 movies
q_movies[['title', 'vote_count', 'vote_average', 'score']].head(15)
标题 投票数 平均投票 得分
314 肖申克的救赎 8358.0 8.5 8.445869
834 教父 6024.0 8.5 8.425439
10309 Dilwale Dulhania Le Jayenge 661.0 9.1 8.421453
12481 黑暗骑士 12269.0 8.3 8.265477
2843 搏击俱乐部 9678.0 8.3 8.256385
292 低俗小说 8670.0 8.3 8.251406
522 辛德勒的名单 4436.0 8.3 8.206639
23673 鞭打 4376.0 8.3 8.205404
5481 千与千寻 3968.0 8.3 8.196055
2211 生活是美好的 3643.0 8.3 8.187171
1178 教父:第二部分 3418.0 8.3 8.180076
1152 一只飞过杜鹃巢的鸟 3001.0 8.3 8.164256
351 阿甘 8147.0 8.2 8.150272
1154 帝国反击战 5998.0 8.2 8.132919
1176 精神病 2405.0 8.3 8.132715

你会看到该图表与IMDB排行榜250强图表有很多共同点:例如, 你的前两部电影” Shawshank Redemption”和” The Godfather”与IMDB相同。

Python中基于内容的推荐器

基于地块描述的推荐人

在本节中, 你将尝试构建一个推荐与特定电影相似的电影的系统。更具体地说, 你将根据所有电影的情节描述计算成对相似度得分, 并根据该相似度得分推荐电影。

你可以将图描述用作元数据数据集中的概览功能。让我们检查一些电影的情节:

#Print plot overviews of the first 5 movies.
metadata['overview'].head()
0    Led by Woody, Andy's toys live happily in his ...
1    When siblings Judy and Peter discover an encha...
2    A family wedding reignites the ancient feud be...
3    Cheated on, mistreated and stepped on, the wom...
4    Just when George Banks has recovered from his ...
Name: overview, dtype: object

以目前的形式, 无法计算任何两个概览之间的相似度。为此, 你需要计算每个概述或文档的词向量, 从现在开始将对其进行调用。

你将为每个文档计算术语频率-反文档频率(TF-IDF)向量。这将为你提供一个矩阵, 其中每列代表概述词汇中的一个单词(所有出现在至少一个文档中的单词), 每列代表一部电影, 就像以前一样。

从本质上讲, TF-IDF得分是单词在文档中出现的频率, 并根据出现该单词的文档数量进行加权。这样做是为了降低在情节概述中经常出现的单词的重要性, 从而降低其在计算最终相似度得分中的重要性。

幸运的是, scikit-learn为你提供了一个内置的TfIdfVectorizer类, 该类以两行代码生成TF-IDF矩阵。

#Import TfIdfVectorizer from scikit-learn
from sklearn.feature_extraction.text import TfidfVectorizer

#Define a TF-IDF Vectorizer Object. Remove all english stop words such as 'the', 'a'
tfidf = TfidfVectorizer(stop_words='english')

#Replace NaN with an empty string
metadata['overview'] = metadata['overview'].fillna('')

#Construct the required TF-IDF matrix by fitting and transforming the data
tfidf_matrix = tfidf.fit_transform(metadata['overview'])

#Output the shape of tfidf_matrix
tfidf_matrix.shape
(45466, 75827)

你会看到超过75, 000个不同的词用于描述数据集中的45, 000部电影。

有了这个矩阵, 你现在可以计算相似度得分。有几个候选对象。例如欧几里得, 皮尔逊和余弦相似度得分。同样, 对于哪个分数最好, 也没有正确的答案。不同的分数在不同的情况下效果很好, 尝试使用不同的指标通常是一个好主意。

你将使用余弦相似度来计算表示两个电影之间相似度的数值。你可以使用余弦相似度评分, 因为它与幅度无关, 并且相对容易且快速地计算(尤其是与TF-IDF评分结合使用时, 这将在后面说明)。在数学上, 它的定义如下:

$余弦(x, y)= \ frac {x。 y ^ \ intercal} {|| x ||。|| y ||} $

由于你使用了TF-IDF矢量化器, 因此计算点积将直接为你提供余弦相似度得分。因此, 你将使用sklearn的linear_kernel()而不是cosine_similarities(), 因为它速度更快。

# Import linear_kernel
from sklearn.metrics.pairwise import linear_kernel

# Compute the cosine similarity matrix
cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)

你将定义一个函数, 该函数以电影标题作为输入并输出10个最相似电影的列表。首先, 为此, 你需要电影标题和DataFrame索引的反向映射。换句话说, 你需要一种机制来标识给定电影标题的元数据DataFrame中电影的索引。

#Construct a reverse map of indices and movie titles
indices = pd.Series(metadata.index, index=metadata['title']).drop_duplicates()

你现在可以很好地定义推荐功能。你将遵循以下步骤:

  • 根据标题获得电影的索引。
  • 获取该特定电影与所有电影的余弦相似度得分列表。将其转换为元组列表, 其中第一个元素是其位置, 第二个元素是相似性分数。
  • 根据相似性得分对上述元组列表进行排序;也就是说, 第二个元素。
  • 获取此列表的前10个元素。忽略第一个涉及自我的元素(与特定电影最相似的电影是电影本身)。
  • 返回与顶部元素的索引对应的标题。
# Function that takes in movie title as input and outputs most similar movies
def get_recommendations(title, cosine_sim=cosine_sim):
    # Get the index of the movie that matches the title
    idx = indices[title]

    # Get the pairwsie similarity scores of all movies with that movie
    sim_scores = list(enumerate(cosine_sim[idx]))

    # Sort the movies based on the similarity scores
    sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)

    # Get the scores of the 10 most similar movies
    sim_scores = sim_scores[1:11]

    # Get the movie indices
    movie_indices = [i[0] for i in sim_scores]

    # Return the top 10 most similar movies
    return metadata['title'].iloc[movie_indices]
get_recommendations('The Dark Knight Rises')
12481                                      The Dark Knight
150                                         Batman Forever
1328                                        Batman Returns
15511                           Batman: Under the Red Hood
585                                                 Batman
21194    Batman Unmasked: The Psychology of the Dark Kn...
9230                    Batman Beyond: Return of the Joker
18035                                     Batman: Year One
19792              Batman: The Dark Knight Returns, Part 1
3095                          Batman: Mask of the Phantasm
Name: title, dtype: object
get_recommendations('The Godfather')
1178               The Godfather: Part II
44030    The Godfather Trilogy: 1972-1990
1914              The Godfather: Part III
23126                          Blood Ties
11297                    Household Saints
34717                   Start Liquidation
10821                            Election
38030            A Mother Should Be Loved
17729                   Short Sharp Shock
26293                  Beck 28 - Familjen
Name: title, dtype: object

你会看到, 尽管你的系统在查找具有类似情节描述的电影方面做得不错, 但是推荐的质量却不是那么好。 《黑暗骑士崛起》返回了所有蝙蝠侠电影, 而喜欢该电影的人更有可能喜欢其他克里斯托弗·诺兰的电影。这是你当前的系统无法捕获的。

基于片名, 体裁和关键字的推荐人

不用说, 使用更好的元数据将提高推荐者的质量。这正是本节中要执行的操作。你将基于以下元数据构建推荐器:3个顶级演员, 导演, 相关流派和电影情节关键字。

关键字, 演员和船员数据在当前数据集中不可用, 因此第一步是将它们加载并合并到主DataFrame中。

# Load keywords and credits
credits = pd.read_csv('../movies_dsc/data/credits.csv')
keywords = pd.read_csv('../movies_dsc/data/keywords.csv')

# Remove rows with bad IDs.
metadata = metadata.drop([19730, 29503, 35587])

# Convert IDs to int. Required for merging
keywords['id'] = keywords['id'].astype('int')
credits['id'] = credits['id'].astype('int')
metadata['id'] = metadata['id'].astype('int')

# Merge keywords and credits into your main metadata dataframe
metadata = metadata.merge(credits, on='id')
metadata = metadata.merge(keywords, on='id')
# Print the first two movies of your newly merged metadata
metadata.head(2)
成人 当属收藏 预算 体裁 主页 id imdb_id original_language original_title 概观 口语 状态 标语 标题 视频 平均投票 投票数 船员 关键字
0 false {‘id’:10194, ‘name’:’Toy Story Collection’, … 30000000 [{‘id’:16, ‘name’:’Animation’}, {‘id’:35, ‘… http://toystory.disney.com/toy-story 862 tt0114709 en 玩具总动员 在伍迪的带领下, 安迪的玩具快乐地生活在他的… [{‘iso_639_1’:’en’, ‘name’:’English’}] 已发行 NaN 玩具总动员 false 7.7 5415.0 [{‘cast_id’:14, ‘character’:’Woody(voice)’, … [{‘credit_id’:’52fe4284c3a36847f8024f49’, ‘de … [{‘id’:931, ‘name’:’jealousy’}, {‘id’:4290, …
1 false NaN 65000000 [{‘id’:12, ‘name’:’Adventure’}, {‘id’:14, ‘… NaN 8844 tt0113497 en 朱曼吉 当兄弟姐妹朱迪和彼得发现一个伙伴时… [{‘iso_639_1’:’en’, ‘name’:’English’}, {‘iso … 已发行 掷骰子, 释放兴奋! 朱曼吉 false 6.9 2413.0 [{‘cast_id’:1, ‘character’:’Alan Parrish’, ‘… [{‘credit_id’:’52fe44bfc3a36847f80a7cd1’, ‘de … [{‘id’:10090, ‘name’:’board game’}, {‘id’:1 …

2行×27列

从你的新功能, 演员, 工作人员和关键字中, 你需要提取三个最重要的演员, 导演和与该电影关联的关键字。现在, 你的数据以”字符串化”列表的形式出现。你需要将它们转换为对你有用的形式。

# Parse the stringified features into their corresponding python objects
from ast import literal_eval

features = ['cast', 'crew', 'keywords', 'genres']
for feature in features:
    metadata[feature] = metadata[feature].apply(literal_eval)

接下来, 编写函数, 以帮助你从每个功能中提取所需的信息。首先, 你将导入NumPy包以访问其NaN常量。接下来, 你可以使用它编写get_director()函数:

# Import Numpy 
import numpy as np
# Get the director's name from the crew feature. If director is not listed, return NaN
def get_director(x):
    for i in x:
        if i['job'] == 'Director':
            return i['name']
    return np.nan
# Returns the list top 3 elements or entire list; whichever is more.
def get_list(x):
    if isinstance(x, list):
        names = [i['name'] for i in x]
        #Check if more than 3 elements exist. If yes, return only first three. If no, return entire list.
        if len(names) > 3:
            names = names[:3]
        return names

    #Return empty list in case of missing/malformed data
    return []
# Define new director, cast, genres and keywords features that are in a suitable form.
metadata['director'] = metadata['crew'].apply(get_director)

features = ['cast', 'keywords', 'genres']
for feature in features:
    metadata[feature] = metadata[feature].apply(get_list)
# Print the new features of the first 3 films
metadata[['title', 'cast', 'director', 'keywords', 'genres']].head(3)
标题 导向器 关键字 体裁
0 玩具总动员 [汤姆·汉克斯, 蒂姆·艾伦, 唐·里克尔斯] 约翰·拉瑟特 [嫉妒, 玩具, 男孩] [动画, 喜剧, 家庭]
1 朱曼吉 [罗宾·威廉姆斯, 乔纳森·海德, 克尔斯滕·邓斯特] 乔·约翰斯顿 [棋盘游戏, 失踪, 基于儿童的… [冒险, 幻想, 家庭]
2 脾气暴躁的老人 [Walter Matthau, Jack Lemmon, Ann-Margret] 霍华德·迪奇 [钓鱼, 最好的朋友, 在学徒期间] [浪漫喜剧]

下一步是将名称和关键字实例转换为小写并去除它们之间的所有空格。这样做是为了使你的向量化器不会将” Johnny Depp”和” Johnny Galecki”的Johnny视为相同。在此处理步骤之后, 上述角色将被表示为” johnnydepp”和” johnnygalecki”, 并且对你的矢量化器是不同的。

# Function to convert all strings to lower case and strip names of spaces
def clean_data(x):
    if isinstance(x, list):
        return [str.lower(i.replace(" ", "")) for i in x]
    else:
        #Check if director exists. If not, return empty string
        if isinstance(x, str):
            return str.lower(x.replace(" ", ""))
        else:
            return ''
# Apply clean_data function to your features.
features = ['cast', 'keywords', 'director', 'genres']

for feature in features:
    metadata[feature] = metadata[feature].apply(clean_data)

现在, 你可以创建”元数据汤”, 它是一个字符串, 其中包含你要馈送到矢量化器的所有元数据(即演员, 导演和关键字)。

def create_soup(x):
    return ' '.join(x['keywords']) + ' ' + ' '.join(x['cast']) + ' ' + x['director'] + ' ' + ' '.join(x['genres'])
# Create a new soup feature
metadata['soup'] = metadata.apply(create_soup, axis=1)

后续步骤与你对基于情节描述的推荐器所做的操作相同。一个重要的区别是你使用了CountVectorizer()而不是TF-IDF。这是因为你不希望减轻演员/导演在相对较多的电影中的表演或导演的影响。这没有什么直观的意义。

# Import CountVectorizer and create the count matrix
from sklearn.feature_extraction.text import CountVectorizer

count = CountVectorizer(stop_words='english')
count_matrix = count.fit_transform(metadata['soup'])
# Compute the Cosine Similarity matrix based on the count_matrix
from sklearn.metrics.pairwise import cosine_similarity

cosine_sim2 = cosine_similarity(count_matrix, count_matrix)
# Reset index of your main DataFrame and construct reverse mapping as before
metadata = metadata.reset_index()
indices = pd.Series(metadata.index, index=metadata['title'])

现在, 你可以通过传入新的cosine_sim2矩阵作为第二个参数来重用get_recommendations()函数。

get_recommendations('The Dark Knight Rises', cosine_sim2)
12589      The Dark Knight
10210        Batman Begins
9311                Shiner
9874       Amongst Friends
7772              Mitchell
516      Romeo Is Bleeding
11463         The Prestige
24090            Quicksand
25038             Deadfall
41063                 Sara
Name: title, dtype: object
get_recommendations('The Godfather', cosine_sim2)
1934            The Godfather: Part III
1199             The Godfather: Part II
15609                   The Rain People
18940                         Last Exit
34488                              Rege
35802            Manuscripts Don't Burn
35803            Manuscripts Don't Burn
8001     The Night of the Following Day
18261                 The Son of No One
28683            In the Name of the Law
Name: title, dtype: object

你会看到, 由于更多的元数据, 推荐人已成功捕获了更多信息, 并为你提供了(可能是)更好的推荐。当然, 有很多方法可以使用此系统来改善建议。

一些建议:

  • 引入受欢迎程度过滤器:此推荐器将列出30部最相似的电影, 计算加权收视率(使用上面的IMDB公式), 根据此收视率对电影进行排序, 然后返回前10部电影。
  • 其他剧组成员:其他剧组成员的姓名, 例如编剧和制片人, 也可以包括在内。
  • 增加导演的权重:要给导演更多的权重, 可以在汤中多次提及他或她, 以增加与同一导演的电影相似度。

你可以在本笔记本中找到实现的这些想法。

使用Python进行协同过滤

在本教程中, 你学习了如何构建自己的简单的基于内容的电影推荐系统。还有另一种非常受欢迎的推荐器类型, 称为协作过滤器。

协作过滤器可以进一步分为两种类型:

  • 基于用户的过滤:这些系统向相似用户喜欢的用户推荐产品。例如, 假设爱丽丝(Alice)和鲍勃(Bob)对书籍有相似的兴趣(也就是说, 他们在很大程度上喜欢和不喜欢同一本书)。现在, 让我们说一本新书已经投放市场, 爱丽丝已经阅读并喜欢它。因此, 鲍勃很有可能也会喜欢它, 因此, 系统会将本书推荐给鲍勃。
  • 基于项目的过滤:这些系统与你构建的内容推荐引擎极为相似。这些系统根据人们过去对它的评价来识别类似的项目。例如, 如果爱丽丝(Alice), 鲍勃(Bob)和夏娃(Eve)给《指环王》(The Lord of the Rings)和《霍比特人》(The Hobbit)授予了5星, 则系统会将这些项目标识为相似。因此, 如果有人购买了《指环王》, 系统还会向他或她推荐《霍比特人》。

你不会在本教程中构建这些系统, 但是你已经熟悉了这样做所需的大多数想法。从协作过滤器开始的一个好地方是检查MovieLens数据集, 该数据集可在此处找到。

总结

在本教程中, 你介绍了如何构建简单的以及基于内容的推荐程序。希望你现在处于良好的位置, 可以对你构建的基本系统进行改进, 并尝试使用其他类型的推荐程序(例如协作过滤器)。希望你和我阅读时一样开心。推荐愉快!

赞(0)
未经允许不得转载:srcmini » Python中的推荐系统:入门教程

评论 抢沙发

评论前必须登录!