實(shí)際工作和生活中,不可避免地需要一些排序規(guī)則。這篇文章或多或少會(huì)有一些參考價(jià)值咐汞。
原文地址:Building an IMDB Top 250 Clone with Pandas
互聯(lián)網(wǎng)電影數(shù)據(jù)庫(kù)(IMDB)維護(hù)著一份名為IMDB Top 250的表格话瞧,該表格是一份根據(jù)某種評(píng)分原則生成的排名前250的電影墩划。表格中的電影都是非紀(jì)錄片,且為劇場(chǎng)版本晓避,影片時(shí)長(zhǎng)至少45分鐘簇捍,影評(píng)數(shù)超過250000條:
這個(gè)表格可以看成最簡(jiǎn)單的推薦器。它沒有考慮特定用戶的喜好俏拱,也沒有試圖推斷不同電影的相似度暑塑。它僅僅根據(jù)預(yù)定義的指標(biāo)計(jì)算每部電影的評(píng)分,并以此輸出一份排好序的電影列表锅必。
本文包括以下內(nèi)容:
- 重構(gòu)一張IMDB Top 250的表格(后面代指簡(jiǎn)單的推薦器)
- 進(jìn)一步完善表格的功能事格,構(gòu)建一個(gè)基于知識(shí)的推薦器惕艳。該模型考慮了用戶的關(guān)于影片類型,年代驹愚,時(shí)長(zhǎng)远搪,語(yǔ)言的喜好,推薦滿足所有條件的電影逢捺。
您需要在系統(tǒng)上安裝Python谁鳍。最后,為了使用Git倉(cāng)庫(kù)劫瞳,你也需要安裝Git倘潜。這篇文章的代碼在Github的地址:https://github.com/PacktPublishing/Hands-On-Recommendation-Systems-with-Python/tree/master/Chapter3。你還可以在http://bit.ly/2v7SZD4上查看代碼視頻志于。
簡(jiǎn)單的推薦器
構(gòu)建一個(gè)簡(jiǎn)單的推薦器的第一步是建立自己的工作目錄涮因。新建一個(gè)文件夾,命名為IMDB恨憎。建立一個(gè)名為Simple Recommender的Jupyter Notebook蕊退,然后在瀏覽器里打開。
可用的數(shù)據(jù)集的地址:https://www.kaggle.com/rounakbanik/the-movies-dataset/downloads/movies_metadata.csv/7
import pandas as pd
import numpy as np
#Load the dataset into a pandas dataframe
df = pd.read_csv('../data/movies_')
#Display the first five movies in the dataframe
df.head()
在運(yùn)行單元格時(shí)憔恳,你應(yīng)該能看到notebook中熟悉的類似表格的結(jié)構(gòu)出現(xiàn)瓤荔。
構(gòu)建簡(jiǎn)單的推薦器非常簡(jiǎn)單。步驟如下:
- 選擇一個(gè)指標(biāo)(或分?jǐn)?shù))來(lái)評(píng)價(jià)電影
- 確定要在表格上顯示的電影的先決條件
- 計(jì)算滿足條件的每部電影的分?jǐn)?shù)
- 按照分?jǐn)?shù)的降序輸出電影列表
衡量準(zhǔn)則
衡量準(zhǔn)則是指對(duì)電影排名的定量標(biāo)準(zhǔn)钥组。如果一部電影比另一部電影有更高的定量指標(biāo)分输硝,則認(rèn)為該電影要優(yōu)于另一部。因此程梦,對(duì)于建立高質(zhì)量的電影推薦器点把,一個(gè)魯棒的可信賴的衡量準(zhǔn)則非常重要。
衡量準(zhǔn)則的選擇是任意的屿附。一種最簡(jiǎn)單的指標(biāo)是電影評(píng)分郎逃。然后,這種方式有各種的缺點(diǎn)挺份。首先褒翰,影片評(píng)分沒有考慮電影的歡迎度。因此匀泊,一部被100,000位用戶評(píng)為9分電影的評(píng)分會(huì)低于另一部只有100位用戶評(píng)為9.5分的電影优训。這是不可取的,因?yàn)楹芸赡苓@類只有100人觀看和評(píng)分的電影迎合了一個(gè)非常特定的群體各聘,并不像前者一樣揣非,受大眾喜愛,吸引普通觀眾躲因。
這也是一個(gè)事實(shí)早敬,隨著投票人數(shù)的增長(zhǎng)忌傻,電影評(píng)分趨于正常化,并接近一個(gè)值,能反應(yīng)電影質(zhì)量和受歡迎度的價(jià)值滑频。換而言之著拭,只有少量評(píng)分的電影,其評(píng)分并不十分可信衡怀。一部只有5位用戶評(píng)為10分的電影棍矛,并不意味著它是一部好電影。
因此抛杨,需要定義一個(gè)指標(biāo)够委,某種程度上,將影片評(píng)分及其參與的投票數(shù)(代表人氣)都考慮進(jìn)來(lái)怖现。這將使得一部轟動(dòng)一時(shí)的電影更受青睞茁帽,這部電影的評(píng)分為8,用戶數(shù)為100,000屈嗤,而另一部電影的評(píng)分為9潘拨,用戶數(shù)只有100。
幸運(yùn)的是饶号,您不必為指標(biāo)集思廣益铁追。您可以使用IMDB的加權(quán)評(píng)級(jí)公式作為指標(biāo)。在數(shù)學(xué)上茫船,它可以表示如下:
加權(quán)評(píng)分(WR)=
(v/(v + m) * R)+ (m/(v+m) * C)
參數(shù)解釋如下:
- v表示電影獲得的票數(shù)
- m表示電表格中電影所需的最小票數(shù)(先決條件)
- R代指電影的平均評(píng)分
- C表示數(shù)據(jù)集中所有電影的評(píng)分分
v和R各自以電影的vote_count和vote_average的特征計(jì)算琅束。計(jì)算C則非常簡(jiǎn)單。
先決條件
IMDB加權(quán)公式還有一個(gè)變量m算谈,需要它計(jì)算得分涩禀。此變量用于確保僅考慮高于特定人氣閾值的電影進(jìn)行排名。因此然眼,m的值確定有資格在表格中的電影艾船,并且通過作為公式的一部分,確定得分的最終值罪治。
正如衡量準(zhǔn)則丽声,m值的選擇是任意的。換言之觉义,m沒有一個(gè)正確的值雁社。最好嘗試不同的m值,然后選擇你(以及你的觀眾)認(rèn)為最好的推薦值晒骇。唯一需要記住的是霉撵,m的值越高磺浙,對(duì)電影受歡迎程度的重視程度越高,因此選擇性越高徒坡。
推薦而言撕氧,請(qǐng)使用第80百分位影片獲得的投票數(shù)作為m的值。換句話說喇完,對(duì)于要在排名中考慮的電影伦泥,它必須獲得比數(shù)據(jù)集中至少80%的電影更多的選票。另外锦溪,在先前描述的加權(quán)公式中使用由第80百分位電影獲得的投票數(shù)來(lái)得出分?jǐn)?shù)的值不脯。
現(xiàn)在,計(jì)算m的值:
#Calculate the number of votes garnered by the 80th percentile movie
m = df['vote_count'].quantile(0.80)
m
OUTPUT:
50.0
可以看到刻诊,只有百分之20的電影獲得了超過50個(gè)的評(píng)分防楷。因此,m的值取50.
另一個(gè)考慮的先決條件是影片時(shí)長(zhǎng)则涯。僅僅考慮時(shí)長(zhǎng)在45分鐘到300分鐘的電影复局。定義一個(gè)Dataframe,q_movies粟判,包含符合條件的所有電影亿昏。
#Only consider movies longer than 45 minutes and shorter than 300 minutes
q_movies = df[(df['runtime'] >= 45) & (df['runtime'] <= 300)]
#Only consider movies that have garnered more than m votes
q_movies = q_movies[q_movies['vote_count'] >= m]
#Inspect the number of movies that made the cut
q_movies.shape
OUTPUT:
(8963, 24)
數(shù)據(jù)集中45000部電影,大約9000(20%)部符合條件档礁。
計(jì)算分值
在得到分值之前龙优,最后需要計(jì)算的值就是C,數(shù)據(jù)集中所有電影的平均分:
# Calculate C
C = df['vote_average'].mean()
C
OUTPUT:
5.6182072151341851
電影的平均得分為5.6/10事秀。IMDB似乎對(duì)電影的評(píng)分要求特別嚴(yán)格彤断。 現(xiàn)在已經(jīng)有C的值,可以對(duì)每部電影打分了易迹。
首先宰衙,定義一個(gè)計(jì)算電影評(píng)分的函數(shù),輸入?yún)?shù)為電影的特征睹欲,m和C的值:
# Function to compute the IMDB weighted rating for each movie
def weighted_rating(x, m=m, C=C):
v = x['vote_count']
R = x['vote_average']
# Compute the weighted score
return (v/(v+m) * R) + (m/(m+v) * C)
然后供炼,使用熟悉的apply函數(shù)作用在Dataframe q_movie上,構(gòu)建一個(gè)新的得分特征列窘疮。因?yàn)榇撸?jì)算是作用在每一行的,設(shè)置axis為1闸衫,表示基于行的操作涛贯。
# Compute the score using the weighted_rating function defined above
q_movies['score'] = q_movies.apply(weighted_rating, axis=1)
排序及輸出
只剩最后一步。現(xiàn)在需要基于計(jì)算出來(lái)的score蔚出,將Dataframe排序弟翘,輸出一個(gè)top的電影列表:
嗯虫腋,這樣,推薦器就建好了稀余。
你可以看到寶萊塢電影Dilwale Dulhania Le Jayenge位居榜首悦冀。 它的票數(shù)明顯少于其他前25部電影。 這有力地表明你應(yīng)該探索更高的m值睛琳。 嘗試不同的m值盒蟆,觀察圖表中的電影如何變化。
基于知識(shí)的推薦器
接下來(lái)师骗,你將構(gòu)建一個(gè)基于知識(shí)的推薦器茁影,方法類似于上面的IMDB Top 250。這將是一個(gè)簡(jiǎn)單函數(shù)丧凤,執(zhí)行下面幾個(gè)任務(wù):
- 詢問用戶他/她正在尋找的電影類型
- 詢問用戶傾向的影片時(shí)長(zhǎng)
- 詢問用戶傾向的影片的年代
- 使用收集的信息,向用戶推薦具有高加權(quán)等級(jí)(根據(jù)IMDB公式)并滿足上述條件的電影
您擁有的數(shù)據(jù)包含有關(guān)影片時(shí)長(zhǎng)步脓,流派和時(shí)間線的信息愿待,但它目前不是可直接使用的形式。 在將數(shù)據(jù)用于構(gòu)建此推薦程序之前靴患,您的數(shù)據(jù)需要進(jìn)行處理仍侥。
在您的IMDB文件夾中,創(chuàng)建一個(gè)名為Knowledge Recommender的新Jupyter Notebook鸳君。 此notebook將包含您在本節(jié)中編寫的所有代碼农渊。
將依賴包和數(shù)據(jù)加載到notebook中。 另外或颊,請(qǐng)查看已有的特征砸紊,并確定對(duì)此任務(wù)有用的特征:
import pandas as pd
import numpy as np
df = pd.read_csv('../data/movies_metadata.csv')
#Print all the features (or columns) of the DataFrame
df.columns
OUTPUT:
Index(['adult', 'belongs_to_collection', 'budget', 'genres', 'homepage', 'id',
'imdb_id', 'original_language', 'original_title', 'overview',
'popularity', 'poster_path', 'production_companies',
'production_countries', 'release_date', 'revenue', 'runtime',
'spoken_languages', 'status', 'tagline', 'title', 'video',
'vote_average', 'vote_count'],
dtype='object')
結(jié)果來(lái)看,很清晰地看到哪些特征需要囱挑,哪些不需要醉顽。接下來(lái),簡(jiǎn)化你的Dataframe平挑,只包含你模型需要的特征:
#Only keep those features that we require
df = df[['title','genres', 'release_date', 'runtime', 'vote_average', 'vote_count']]
df.head()
從release_date特征中提取發(fā)布年份:
#Convert release_date into pandas datetime format
df['release_date'] = pd.to_datetime(df['release_date'], errors='coerce')
#Extract year from the datetime
df['year'] = df['release_date'].apply(lambda x: str(x).split('-')[0] if x != np.nan else np.nan)
年份特征仍然是一個(gè)對(duì)象游添,并且充滿了NaT值,這是一種由Pandas使用的空值通熄。 將這些值轉(zhuǎn)換為整數(shù)0唆涝,并將year特征的數(shù)據(jù)類型轉(zhuǎn)換為int。
為此唇辨,定義輔助函數(shù)convert_int廊酣,并將其應(yīng)用于年份特征:
#Helper function to convert NaT to 0 and all other years to integers.
def convert_int(x):
try:
return int(x)
except:
return 0
#Apply convert_int to the year feature
df['year'] = df['year'].apply(convert_int)
You do not require the release_date feature anymore. So, go ahead and remove it:
#Drop the release_date column
df = df.drop('release_date', axis=1)
#Display the dataframe
df.head()
影片時(shí)長(zhǎng)特征已經(jīng)是可用的形式。 它不需要任何額外的處理赏枚。 現(xiàn)在啰扛,把你的注意力轉(zhuǎn)向影片的類別嚎京。
影片類別
你也許發(fā)現(xiàn)類別信息是以一種類似于Json對(duì)象(或Python字典)的格式呈現(xiàn)。瞄一眼電影的類別對(duì)象:
#Print genres of the first movie
df.iloc[0]['genres']
OUTPUT:
"[{'id': 16, 'name': 'Animation'}, {'id': 35, 'name': 'Comedy'}, {'id': 10751, 'name': 'Family'}]"
可以發(fā)現(xiàn)隐解,輸出的是一個(gè)字符串形式的字典鞍帝。為了讓該特征能用,有必要將其字符串轉(zhuǎn)為原生的Python字典煞茫。幸運(yùn)的是帕涌,Python中名為literal_eval的函數(shù)(在ast庫(kù)里)可以準(zhǔn)確地處理。literal_eval可以將任意的字符串轉(zhuǎn)為相應(yīng)的Python對(duì)象:
#Import the literal_eval function from ast
from ast import literal_eval
#Define a stringified list and output its type
a = "[1,2,3]"
print(type(a))
#Apply literal_eval and output type
b = literal_eval(a)
print(type(b))
OUTPUT:
<class 'str'>
<class 'list'>
現(xiàn)在已經(jīng)有所有必要的工具续徽,將類別特征轉(zhuǎn)為Python字典格式蚓曼。
同時(shí),每個(gè)字典代表一個(gè)類別钦扭,存在兩個(gè)鍵:id和name纫版。然而,對(duì)于這個(gè)任務(wù)客情,只需要name其弊。因此,將字典列表轉(zhuǎn)換為字符串列表膀斋,其中每個(gè)字符串都是一個(gè)類別名稱
#Convert all NaN into stringified empty lists
df['genres'] = df['genres'].fillna('[]')
#Apply literal_eval to convert to the list object
df['genres'] = df['genres'].apply(literal_eval)
#Convert list of dictionaries to a list of strings
df['genres'] = df['genres'].apply(lambda x: [i['name'] for i in x] if isinstance(x, list) else [])
df.head()
打印Dataframe的頭部梭伐,將展示一個(gè)新的genres特征,其包含一個(gè)genre名字的列表仰担。然而糊识,事情還沒完成。最后一步是摔蓝,explode這個(gè)genres列赂苗。換言之,如果一部電影有多個(gè)類別贮尉,生成這個(gè)電影的多個(gè)備份哑梳,每個(gè)備份對(duì)應(yīng)一個(gè)類別。
舉例而言绘盟,假設(shè)一部名為Just Go With It的電影鸠真,有romance和comedy兩個(gè)類別,將其explode成兩行龄毡。一行是Just Go With I被標(biāo)記為romance吠卷,另一行則是標(biāo)記為comedy:
#Create a new feature by exploding genres
s = df.apply(lambda x: pd.Series(x['genres']),axis=1).stack().reset_index(level=1, drop=True)
#Name the new feature as 'genre'
s.name = 'genre'
#Create a new dataframe gen_df which by dropping the old 'genres' feature and adding the new 'genre'.
gen_df = df.drop('genres', axis=1).join(s)
#Print the head of the new gen_df
gen_df.head()
你應(yīng)該能看到三行Toy Story,分別對(duì)應(yīng)著animation, family, 和 comedy沦零。這個(gè)gen_df的DataFrame對(duì)構(gòu)建知識(shí)base的推薦器很重要祭隔。
build_chart函數(shù)
現(xiàn)在終于可以寫一個(gè)函數(shù),作為推薦器了。現(xiàn)在不能像之前一樣計(jì)算m和C的值疾渴,因?yàn)椴皇敲坎侩娪岸挤弦笄Ч帷Q句話說,分為以下三步:
- 獲取用戶的偏好
- 過濾出符合用戶條件的電影
- 根據(jù)以上搞坝,計(jì)算m和C的值搔谴,然后按照上一節(jié)中的步驟構(gòu)建圖表
因此,build_chart函數(shù)只接受兩個(gè)輸入:gen_df DataFrame和用于計(jì)算m值的百分位數(shù)桩撮。 默認(rèn)情況下敦第,百分位數(shù)設(shè)置為80%或0.8:
def build_chart(gen_df, percentile=0.8):
#Ask for preferred genres
print("Input preferred genre")
genre = input()
#Ask for lower limit of duration
print("Input shortest duration")
low_time = int(input())
#Ask for upper limit of duration
print("Input longest duration")
high_time = int(input())
#Ask for lower limit of timeline
print("Input earliest year")
low_year = int(input())
#Ask for upper limit of timeline
print("Input latest year")
high_year = int(input())
#Define a new movies variable to store the preferred movies. Copy the contents of gen_df to movies
movies = gen_df.copy()
#Filter based on the condition
movies = movies[(movies['genre'] == genre) &
(movies['runtime'] >= low_time) &
(movies['runtime'] <= high_time) &
(movies['year'] >= low_year) &
(movies['year'] <= high_year)]
#Compute the values of C and m for the filtered movies
C = movies['vote_average'].mean()
m = movies['vote_count'].quantile(percentile)
#Only consider movies that have higher than m votes. Save this in a new dataframe q_movies
q_movies = movies.copy().loc[movies['vote_count'] >= m]
#Calculate score using the IMDB formula
q_movies['score'] = q_movies.apply(lambda x: (x['vote_count']/(x['vote_count']+m) * x['vote_average'])
+ (m/(m+x['vote_count']) * C)
,axis=1)
#Sort movies in descending order of their scores
q_movies = q_movies.sort_values('score', ascending=False)
return q_movies
是時(shí)候把你的模型付諸行動(dòng)了!
您可能需要推薦動(dòng)畫電影店量,并有以下要求:影片時(shí)長(zhǎng)在30分鐘到2小時(shí)之間的芜果,發(fā)布時(shí)間在1990年到2005年。查看結(jié)果:
您可以看到它輸出的電影滿足您作為輸入傳遞的所有條件融师。 由于您應(yīng)用了IMDB的指標(biāo)右钾,您還可以觀察到您的電影同時(shí)受到高度評(píng)價(jià)和歡迎。