[譯] 使用 Pandas 在 Python 中建立一個簡單的推薦系統

簡介

你有沒有想過 Netflix 如何根據你已經看過的電影向你推薦電影?或者電商網站如何顯示諸如「常常一塊兒購買」等選項?它們可能看起來只是簡單的選項,可是背後執行了一套複雜的統計算法以預測這些推薦。這樣的系統被稱爲導購系統,推薦系統或者推薦引擎。導購系統是數據科學和機器學習領域最著名的應用之一。前端

推薦系統採用這樣一種統計算法,該算法基於實體之間的類似性或先前評估這些實體的用戶之間的類似性來預測用戶對特定實體的評級。直觀上來說就是類似類型的用戶可能對同一組實體具備類似的評級。python

目前,許多大型科技公司都以這樣或那樣的方式使用推薦系統。從亞馬遜(產品推薦)到 YouTube(視頻推薦)再到 Facebook(朋友推薦)你會發現推薦系統無處不在。給用戶推薦相關產品和服務的能力對公司來講多是一個巨大的推進力,這就是爲何此技術在衆多網站中被廣泛運用的緣由。android

在這篇文章中,咱們將看到如何在 Python 中構建一個簡單的推薦系統。ios

推薦系統的類型

主要有兩種方式構建推薦系統:基於內容的過濾和協同過濾:git

基於內容過濾

在基於內容的過濾中,不一樣產品的類似性是根據產品的屬性計算出來的。例如,在一個基於內容的電影推薦系統中,電影之間的類似性是根據類型,電影中的演員,電影導演等計算的。github

協同過濾

協同過濾利用人羣的力量。協同過濾背後的直覺是若是 A 用戶喜歡產品 X 和 Y,那麼若是 B 用戶喜歡產品 X,他就有至關大的可能一樣喜歡產品 Y。算法

舉一個電影推薦系統的例子。假設大量的用戶對電影 X 和 Y 給出同樣的評分。一個新用戶來了,他對電影 X 給出了相同的評分可是還沒看過電影 Y。協同過濾系統就會把電影 Y 推薦給他。後端

Python 中的電影推薦系統實現

在這一節,咱們將使用 Python 開發一個很是簡單的電影推薦系統,它使用不一樣電影間的評分相關性,以便找到電影之間的類似性。bash

咱們將使用 MovieLens 數據集來處理該問題。要下載此數據集,能夠去數據集的主頁下載 "ml-latest-small.zip" 文件,它包含真實電影數據集的子集而且有 700 個用戶對 9000 部電影作出的 100000 條評分。機器學習

當你解壓文件後,就能看到 "links.csv"、"movies.csv"、"ratings.csv" 和 "tags.csv" 文件,以及 "README" 文檔。在本文中,咱們會使用到 "movies.csv" 和 "ratings.csv" 文件。

對於本文中的腳本,解壓的 "ml-latest-small" 文件夾已經被放在了 "E" 盤的 "Datasets" 文件夾中。

數據可視化和預處理

每一個數據科學問題的第一步都是數據可視化和預處理。咱們也是如此,接下來咱們先導入 "ratings.csv" 文件看看它有哪些內容。執行以下腳本:

import numpy as np
import pandas as pd

ratings_data = pd.read_csv("E:\Datasets\ml-latest-small\\ratings.csv")
ratings_data.head()
複製代碼

在上面的腳本中,咱們使用 Pandas 庫read_csv() 方法讀取 "ratings.csv" 文件。接下來,咱們調用 read_csv() 函數返回的 dataframe 對象的 head() 方法。它將展現數據集的前五行數據。

輸出結果以下:

userId movieId rating timestamp
0 1 31 2.5
1 1 1029 3.0
2 1 1061 3.0
3 1 1129 2.0
4 1 1172 4.0

從輸出結果中能夠看出 "ratings.csv" 文件包含 userId、movieId、ratings 和 timestamp 屬性。數據集的每一行對應一條評分。userId 列包含評分用戶的 ID。movieId 列包含電影的 Id,rating 列包含用戶的評分。評分的取值是 1 到 5。最後的 timestamp 表明用戶作出評分的時間。

這個數據集有一個問題。那就是它有電影的 ID 卻沒有電影名稱。咱們須要咱們要推薦的電影的名稱。而電影名稱存在 "movies.csv" 文件中。讓咱們導入它看看裏面有什麼內容吧。執行以下腳本:

movie_names = pd.read_csv("E:\Datasets\ml-latest-small\\movies.csv")  
movie_names.head()  
複製代碼

輸出結果以下:

movieId title genres
0 1 Toy Story (1995) Adventure|Animation|Children|Comedy|Fantasy
1 2 Jumanji (1995) Adventure|Children|Fantasy
2 3 Grumpier Old Men (1995) Comedy|Romance
3 4 Waiting to Exhale (1995) Comedy|Drama|Romance
4 5 Father of the Bride Part II (1995) Comedy

如你所見,數據集包含 movieId,電影名稱和它的類型。咱們須要一個包含 userId,電影名稱和評分的數據集。而咱們須要的信息在兩個不一樣的 dataframe 對象中:"ratings_data" 和 "movie_names"。爲了把咱們想要的信息放在一個 dataframe 中,咱們能夠根據 movieId 列合併這兩個 dataframe 對象,由於它在這兩個 dataframe 對象中是通用的。

咱們可使用 Pandas 庫的 merge() 函數,以下所示:

movie_data = pd.merge(ratings_data, movie_names, on='movieId')
複製代碼

如今咱們來看看新的 dataframe:

movie_data.head()
複製代碼

輸出結果以下:

userId movieId rating timestamp title genres
0 1 31 2.5 1260759144 Dangerous Minds (1995) Drama
1 7 31 3.0 851868750 Dangerous Minds (1995) Drama
2 31 31 4.0 12703541953 Dangerous Minds (1995) Drama
3 32 31 4.0 834828440 Dangerous Minds (1995) Drama
4 36 31 3.0 847057202 Dangerous Minds (1995) Drama

咱們能夠看到新建立的 dataframe 正如要求的那樣包含 userId,電影名稱和電影評分。

如今讓咱們看看每部電影的平均評分。爲此,咱們能夠按照電影的標題對數據集進行分組,而後計算每部電影評分的平均值。接下來咱們將使用 head() 方法顯示前五部電影及其平均評分。請看以下腳本:

movie_data.groupby('title')['rating'].mean().head()
複製代碼

輸出結果以下:

title
"Great Performances" Cats (1998)           1.750000
$9.99 (2008)                               3.833333
'Hellboy': The Seeds of Creation (2004)    2.000000
'Neath the Arizona Skies (1934) 0.500000 'Round Midnight (1986)                     2.250000
Name: rating, dtype: float64
複製代碼

你能夠看到平均評分是沒有排序的。讓咱們按照平均評分的降序對評分進行排序:

movie_data.groupby('title')['rating'].mean().sort_values(ascending=False).head()
複製代碼

若是你執行了上面的腳本,輸出結果應該以下所示:

title
Burn Up! (1991)                                     5.0
Absolute Giganten (1999)                            5.0
Gentlemen of Fortune (Dzhentlmeny udachi) (1972)    5.0
Erik the Viking (1989)                              5.0
Reality (2014)                                      5.0
Name: rating, dtype: float64
複製代碼

這些電影現已根據評分的降序排序。然而有一個問題是,若是隻有一個用戶對電影作了評價且分數爲五星,這部電影就會排到列表的頂部。所以,上述統計數據可能具備誤導性。一般來說,一部真正的好電影會有大批用戶給更高的評分。

如今讓咱們繪製一部電影的評分總數:

movie_data.groupby('title')['rating'].count().sort_values(ascending=False).head()
複製代碼

執行上面的腳本返回以下結果:

title
Forrest Gump (1994)                          341
Pulp Fiction (1994)                          324
Shawshank Redemption, The (1994)             311
Silence of the Lambs, The (1991)             304
Star Wars: Episode IV - A New Hope (1977)    291
Name: rating, dtype: int64
複製代碼

如今你會看到真正的好電影就排在頂部了。以上列表證明了咱們的觀點,好電影一般會收到更高的評分。如今咱們知道每部電影的平均評分和評分數量都是重要的屬性了。讓咱們建立一個新的包含這些屬性的 dataframe。

執行以下腳本建立 ratings_mean_count dataframe,首先將每部電影的平均評分添加到這個 dataframe:

ratings_mean_count = pd.DataFrame(movie_data.groupby('title')['rating'].mean())
複製代碼

接下來,咱們須要把電影的評分數添加到 ratings_mean_count dataframe。執行以下腳原本實現:

ratings_mean_count['rating_counts'] = pd.DataFrame(movie_data.groupby('title')['rating'].count())
複製代碼

如今咱們再看下新建立的 dataframe。

ratings_mean_count.head()
複製代碼

輸出結果以下:

title rating rating_counts
"Great Performances" Cats (1998) 1.750000 2
$9.99 (2008) 3.833333 3
'Hellboy': The Seeds of Creation (2004) 2.000000 1
'Neath the Arizona Skies (1934) 0.500000 1
'Round Midnight (1986) 2.250000 2

你能夠看到電影標題,以及電影的平均評分和評分數。

讓咱們繪製上面 dataframe 中 "rating_counts" 列所表明的評分數的直方圖。執行以下腳本:

import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style('dark')
%matplotlib inline

plt.figure(figsize=(8,6))
plt.rcParams['patch.force_edgecolor'] = True
ratings_mean_count['rating_counts'].hist(bins=50)
複製代碼

如下是上述腳本的輸出:

Ratings histogram

從上圖中,咱們能夠看到大部分電影的評分不到 50 條。並且有 100 條以上評分的電影數量很是少。

如今咱們繪製平均評分的直方圖。代碼以下:

plt.figure(figsize=(8,6))
plt.rcParams['patch.force_edgecolor'] = True
ratings_mean_count['rating'].hist(bins=50)
複製代碼

輸出結果以下:

Average ratings histogram

您能夠看到整數值的 bar 比浮點值更高,由於大多數用戶會作出整數評分,即 一、二、三、4 或 5。此外,很明顯,數據的正態分佈較弱,平均值約爲 3.5。數據中有一些異常值。

前面,咱們說有更多評分數的電影一般也有高平均評分,由於一部好電影一般都是家喻戶曉的,而不少人都會看這樣的電影,所以一般會有更高的評分。咱們看看在咱們的數據集中的電影是否也是這種狀況。咱們將平均評分與評分數量進行對比:

plt.figure(figsize=(8,6))
plt.rcParams['patch.force_edgecolor'] = True
sns.jointplot(x='rating', y='rating_counts', data=ratings_mean_count, alpha=0.4)
複製代碼

輸出結果以下:

Average ratings vs number of ratings

該圖代表,相較於低平均分的電影來講,高平均分的電影每每有更多的評分數量。

找出電影之間的類似之處

咱們在數據的可視化和預處理上花了較多時間。如今是時候找出電影之間的類似之處了。

咱們將使用電影評分之間的相關性做爲類似性度量。爲了發現電影評分之間的相關性,咱們須要建立一個矩陣,其中每列是電影名稱,每行包含特定用戶爲該電影指定的評分。請記住,此矩陣將具備大量空值,由於不是每一個用戶都會對每部電影進行評分。

建立電影標題和相應的用戶評分矩陣,執行以下腳本:

user_movie_rating = movie_data.pivot_table(index='userId', columns='title', values='rating')
複製代碼
user_movie_rating.head()
複製代碼
title "Great Performances" Cats (1998) $9.99 (1998) 'Hellboy': The Seeds of Creation (2008) 'Neath the Arizona Skies (1934) 'Round Midnight (1986) 'Salem's Lot (2004) 'Til There Was You (1997) 'burbs, The (1989) 'night Mother (1986) (500) Days of Summer (2009) ... Zulu (1964) Zulu (2013)
userId
1 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN
2 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN
3 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN
4 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN
5 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN

咱們知道每列包含全部用戶對某部電影的評分。讓咱們找到電影 "Forrest Gump (1994)" 的全部用戶評分,而後找出跟它類似的電影。咱們選這部電影是由於它評分數最多,咱們但願找到具備更高評分數的電影之間的相關性。

要查找 "Forrest Gump (1994)" 的用戶評分,執行以下腳本:

forrest_gump_ratings = user_movie_rating['Forrest Gump (1994)']
複製代碼

如上腳本將返回一個 Pandas 序列。讓咱們看看它長什麼樣。

forrest_gump_ratings.head()
複製代碼
userId
1    NaN
2    3.0
3    5.0
4    5.0
5    4.0
Name: Forrest Gump (1994), dtype: float64
複製代碼

如今讓咱們檢索全部和 "Forrest Gump (1994)" 相似的電影。咱們可使用以下所示的 corrwith() 函數找到 "Forest Gump (1994)" 和全部其餘電影的用戶評分之間的相關性

movies_like_forest_gump = user_movie_rating.corrwith(forrest_gump_ratings)

corr_forrest_gump = pd.DataFrame(movies_like_forest_gump, columns=['Correlation'])
corr_forrest_gump.dropna(inplace=True)
corr_forrest_gump.head()
複製代碼

在上面的腳本中,咱們首先使用 corrwith() 函數檢索與 "Forrest Gump (1994)" 相關的全部電影的列表及其相關值。接下來,咱們建立了包含電影名稱和相關列的 dataframe。而後咱們從 dataframe 中刪除了全部 NA 值,並使用 head 函數顯示其前 5 行。

輸出結果以下:

title Correlation
$9.99 (2008) 1.000000
'burbs, The (1989) 0.044946
(500) Days of Summer (2009) 0.624458
*batteries not included (1987) 0.603023
...And Justice for All (1979) 0.173422

讓咱們按照相關性的降序對電影進行排序,以便在頂部看到高度相關的電影。執行以下腳本:

corr_forrest_gump.sort_values('Correlation', ascending=False).head(10)
複製代碼

如下是上述腳本的輸出:

title Correlation
$9.99 (2008) 1.0
Say It Isn't So (2001) 1.0
Metropolis (2001) 1.0
See No Evil, Hear No Evil (1989) 1.0
Middle Men (2009) 1.0
Water for Elephants (2011) 1.0
Watch, The (2012) 1.0
Cheech & Chong's Next Movie (1980) 1.0
Forrest Gump (1994) 1.0
Warrior (2011) 1.0

從輸出結果中你能夠發現和 "Forrest Gump (1994)" 高度相關的電影並非頗有名。這代表單獨的相關性不是一個很好的類似度量,由於可能有一個用戶只觀看了 "Forest Gump (1994)" 和另一部電影,並將它們都評爲 5 分。

該問題的解決方案是僅檢索具備至少 50 個評分的相關電影。爲此,咱們將 rating_mean_count dataframe 中的 rating_counts 列添加到咱們的 corr_forrest_gump dataframe 中。執行以下腳本:

corr_forrest_gump = corr_forrest_gump.join(ratings_mean_count['rating_counts'])
corr_forrest_gump.head()
複製代碼

輸出結果以下:

title Correlation rating_counts
$9.99 (2008) 1.000000 3
'burbs, The (1989) 0.044946 19
(500) Days of Summer (2009) 0.624458 45
*batteries not included (1987) 0.603023 7
...And Justice for All (1979) 0.173422 13

你能夠看到有着最高相關性的電影 "$9.99" 只有 3 條評分。這代表只有 3 個用戶給了 "Forest Gump (1994)" 和 "$9.99" 一樣的評分。可是,咱們能夠推斷,不能僅根據 3 個評分就說一部電影與另外一部類似。這就是咱們添加 "rating_counts" 列的緣由。如今讓咱們過濾評分超過 50 條的與 "Forest Gump (1994)" 相關的電影。以下代碼執行此操做:

corr_forrest_gump[corr_forrest_gump ['rating_counts']>50].sort_values('Correlation', ascending=False).head()
複製代碼

腳本輸出結果以下:

title Correlation rating_counts
Forrest Gump (1994) 1.000000 341
My Big Fat Greek Wedding (2002) 0.626240 51
Beautiful Mind, A (2001) 0.575922 114
Few Good Men, A (1992) 0.555206 76
Million Dollar Baby (2004) 0.545638 65

如今你能夠從輸出中看到與 "Forrest Gump (1994)" 高度相關的電影。列表中的電影是好萊塢電影中最着名的電影之一,並且因爲 "Forest Gump (1994)" 也是一部很是着名的電影,這些電影頗有多是相關的。

結論

在本文中,咱們學習了什麼是推薦系統以及如何只使用 Pandas 庫在 Python 中建立它。值得一提的是,咱們建立的推薦系統很是簡單。現實生活中的推薦系統使用很是複雜的算法,咱們將在後面的文章中討論。

若是您想了解有關推薦系統的更多信息,我建議看看這個很是好的課程使用機器學習和 AI 構建推薦系統。它比咱們在本文中所作的更深刻,涵蓋了更復雜和準確的方法。

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索