作者|GUEST
編譯|VK
來源|Analytics Vidhya
介紹
在當今繁忙的世界中,推薦系統變得越來越重要,人們總是在尋找最適合他們的產品/服務,因此,推薦系統非常重要,因為它們可以幫助用戶在不消耗認知資源的情況下做出正確的選擇,
在本博客中,我們將了解推薦系統的基礎知識,并學習如何通過實作K-最近鄰演算法來構建一個使用協同過濾的電影推薦系統,我們還將根據鄰居預測給定電影的評級,并將其與實際評級進行比較,
推薦系統的型別
推薦系統大致可分為三類
-
協同過濾
-
基于內容的篩選
-
混合推薦系統
協同過濾
這種過濾方法通常基于收集和分析用戶的行為、活動或偏好資訊,并根據與其他用戶的相似性來預測他們會喜歡什么,協作過濾方法的一個主要優點是它不依賴于機器可分析的內容,因此它能夠準確地推薦諸如電影之類的復雜專案,而不需要“理解”專案本身,
此外,還有幾種型別的協同過濾演算法
- 用戶-用戶協同過濾:嘗試搜索相似的客戶,并根據他/她的相似選擇提供產品,
- 項-項協同過濾:它與前面的演算法非常相似,但是我們沒有找到一個看起來很相似的客戶,而是嘗試查找相似的項,一旦我們有了商品外觀相似矩陣,我們就可以很容易地向從商店購買商品的顧客推薦相似的商品,
- 其他算法:還有其他方法,如市場籃子分析,它通過尋找交易中經常出現的專案組合來作業,

基于內容的過濾
這些過濾方法基于對專案的描述和用戶的組態檔,在基于內容的推薦系統中,使用關鍵字來描述專案,并建立用戶組態檔來說明用戶喜歡的專案型別,換句話說,這些演算法試圖推薦與用戶過去喜歡的產品相似的產品,
混合推薦系統

最近的研究表明,結合協作過濾和基于內容的過濾的混合方法在某些情況下可能更有效,混合方法可以通過多種方式實作,分別進行基于內容的預測和基于協作的預測,然后將它們組合在一起,將基于內容的特征添加到基于協作的方法中(或者反之亦然),或者將這些方法統一到一個模型中,
Netflix是使用混合推薦系統的一個很好的例子,該網站通過比較相似用戶的觀看和搜索習慣(即協作過濾)以及提供與用戶評價較高的電影具有相同特征的電影(基于內容的過濾)來提出建議,
現在我們已經對推薦系統有了基本的直覺,讓我們從用Python構建一個簡單的電影推薦系統開始,
在這里找到包含完整代碼、資料集和所有插圖的Python Notebook https://www.kaggle.com/heeraldedhia/movie-ratings-and-recommendation-using-knn
TMDb-電影資料庫
電影資料庫(TMDb)是一個社區建立的電影和電視資料庫,它擁有大量關于電影和電視節目的資料,以下是統計資料:https://www.themoviedb.org/

為了簡單和易于計算,我使用了這個巨大資料集的一個子集,即TMDb 5000資料集,它有5000部電影的資訊,分成2個CSV檔案,
- tmdb_5000_movies.包含分數、標題、發布日期、流派等資訊,
- tmdb_5000_credits.csv:包含每部電影的演員和劇組資訊,
資料集的鏈接在這里:https://www.kaggle.com/tmdb/tmdb-movie-metadata
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更改為string
movies['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, STOPWORDS
import nltk
from 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.com/2013/09/machine-learning-cosine-similarity-for-vector-space-models-part-iii/
下面我定義了一個函式相似度,它將檢查電影之間的相似性,
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/heeraldedhia/movie-ratings-and-recommendation-using-knn
進一步閱讀
-
推薦系統:https://en.wikipedia.org/wiki/Recommender_system
-
基于K近鄰演算法的機器學習基礎:https://towardsdatascience.com/machine-learning-basics-with-the-k-nearest-neighbors-algorithm-6a6e71d01761
-
使用Python的推薦系統.第2部分:協同過濾(K-最近鄰演算法):https://heartbeat.fritz.ai/recommender-systems-with-python-part-ii-collaborative-filtering-k-nearest-neighbors-algorithm-c8dcd5fd89b2#:~:text=When a KNN makes a,the most similar movie recommendations.
-
什么是余弦相似性?:https://deepai.org/machine-learning-glossary-and-terms/cosine-similarity
-
如何在KNN中找到K的最優值?:https://towardsdatascience.com/how-to-find-the-optimal-value-of-k-in-knn-35d936e554eb
原文鏈接:https://www.analyticsvidhya.com/blog/2020/08/recommendation-system-k-nearest-neighbors/
歡迎關注磐創AI博客站:
http://panchuang.net/
sklearn機器學習中文官方檔案:
http://sklearn123.com/
歡迎關注磐創博客資源匯總站:
http://docs.panchuang.net/
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/165394.html
標籤:其他
