1. 程式人生 > >【譯】使用 Pandas 在 Python 中建立一個簡單的推薦系統

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

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

簡介

你有沒有想過 Netflix 如何根據你已經看過的電影向你推薦電影?或者電商網站如何顯示諸如“經常一起購買”等選項?它們可能看起來只是簡單的選項,但是背後執行了一套複雜的統計演算法以預測這些推薦。這樣的系統被稱為導購系統,推薦系統或者推薦引擎。

導購系統是資料科學和機器學習領域最著名的應用之一。

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

目前,許多大型科技公司都以這樣或那樣的方式使用推薦系統。從亞馬遜(產品推薦)到 YouTube(視訊推薦)再到 Facebook(朋友推薦)你會發現推薦系統無處不在。給使用者推薦相關產品和服務的能力對公司來說可能是一個巨大的推動力,這就是為什麼此技術在眾多網站中被普遍運用的原因。

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

推薦系統的型別

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

基於內容過濾

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

協同過濾

協同過濾利用人群的力量。協同過濾背後的直覺是如果 A 使用者喜歡產品 X 和 Y,那麼如果 B 使用者喜歡產品 X,他就有相當大的可能同樣喜歡產品 Y。

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

Python 中的電影推薦系統實現

在這一節,我們將使用 Python 開發一個非常簡單的電影推薦系統,它使用不同電影間的評分相關性,以便找到電影之間的相似性。

我們將使用 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 比浮點值更高,因為大多數使用者會做出整數評分,即 1、2、3、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前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄