如何利用自然語言處理構建基於內容的電影推薦系統
“empty brown theater chairs” by Tyler Callahan on Unsplash
你是否有過這樣的疑惑:為什麼Netflix,Amazon,Google總能推薦到你比較感興趣的產品?我們有時會對網際網路上的產品進行評分,以此體現我們對產品的偏好,同時,推薦系統會利用我們分享的資料,生成推薦結果。主流的推薦系統演算法大致分為兩類:基於使用者歷史資料的協同過濾演算法和基於內容資料的過濾演算法。兩者的區別其實從名稱上便可看出,但接下來我們將以電影推薦為例進一步闡述二者之間的不同。
協同過濾(Collaborative filters)
協同過濾依賴使用者的歷史評分資料,為使用者推薦自己未曾看過,而與自己相似的使用者已經觀看過的電影。為了確定兩個使用者之間是否相似,協同過濾會結合使用者所看過的電影以及他們對電影的評分。
Collaborative-based filter.
協同過濾演算法的準確性依賴於使用者對產品的歷史評分,但並非所有的使用者都會持續不斷的對產品進行評價,有一些使用者甚至未曾評價過任何產品。協同過濾演算法的另一個特點是能提供多樣化的建議,根據應用場景的不同,對推薦系統的評價也不盡相同。舉個例子,假設使用者A非常喜歡反烏托邦電影和黑色喜劇,使用者B也喜歡反烏托邦電影,但從來沒有看過黑色喜劇。協同過濾演算法將根據使用者A和使用者B對反烏托邦電影的喜愛,為使用者B推薦黑色喜劇。這個推薦結果將會產生兩種影響:使用者B也非常喜歡黑色喜劇,則推薦成功;如若使用者B喜歡輕喜劇,那麼推薦是不成功的。
基於內容的過濾(Content-based filters)
Content-based filter.
基於內容的推薦不再涉及其他使用者,只根據我們自身的喜愛,簡單的選擇內容相似的專案進行推薦。
相比協同過濾演算法,基於內容的推薦減少了推薦的多樣性,但使用者是否對專案進行了評分便不再影響推薦結果。還是前一個例子,也許使用者B潛意識裡也喜歡黑色喜劇,但除非他自己決定主動嘗試,否則他永遠也不會知道自己的這個喜好,因為基於內容的推薦只會繼續推薦反烏托邦或同種型別的電影。以電影為例,在計算相似度的時候,除了考慮片名,還可以考慮導演,主要演員等因素。
到目前為止,我已多次提及到相似度(similarity)這個詞,但是它究竟是什麼呢?相似度是我們在計算使用者之間或者專案之間的相似性時可以使用的度量標準之一。它雖然不可量化,但卻是可以通過計算得到。在構建基於內容的推薦系統之前,我將簡明地對相似度的概念做一個講解。
餘弦相似度(Cosine similarity)
向量可以是二維,三維甚至n維的。讓我們以二維向量為例回顧一下點積(dot product)。兩個向量之間的點積等於其中一個向量在另一個向量上的投影。因此,兩個相同向量(即相同分量)之間的點積等於該向量模的平方,而如果這兩個向量垂直,則點積為零。通常,對於n維向量,點積的計算公式如下所示。
Dot product.
點積在計算相似度時非常重要,因為它與相似度直接相關。兩個向量u和v之間相似度是由它們之間的點積和它們自身的模的比值定義的。
Similarity.
透過相似度的定義我們可以看出,如果兩個向量相同,相似度為1,如果兩個向量是正交的,相似度為0。換句話說,相似度是一個在0和1之間有界的數,它反應了這兩個向量的相似程度。
下面進入實戰階段。
1. 資料收集
實驗資料來自IMDB資料集,本次只選取了前250個高評分電影。資料集列表有250行(250部電影),38列。在構建模型的時候,我們只考慮了電影導演、主要演員、電影型別和電影情節這幾類特徵。
import pandas as pd from rake_nltk import Rake import numpy as np from sklearn.metrics.pairwise import cosine_similarity from sklearn.feature_extraction.text import CountVectorizer df = pd.read_csv('https://query.data.world/s/uikepcpffyo2nhig52xxeevdialfl7') df = df[['Title','Genre','Director','Actors','Plot']] df.head()
部分資料如下圖所示:
我們將每一部包含上述特徵的電影作為一列,以便能更好的進行向量化。我們還會使用到自然語言處理,將文字轉換為向量能幫助我們更好的計算餘弦相似度。
接下來,我們將對資料進行清洗。
2. 資料清洗(Data cleaning)
nltk(natural language toolkit)是一套基於python的自然語言處理工具集,它能夠幫助我們從文字中提取關鍵字,甚至可以為每個字打分。我們將使用Rake功能從Plot列中提取出關鍵字,相較於使用完整的句子對電影情節進行描述,我更青睞於使用一些與電影情節最相關的詞語。為此,我在Plot列中,對每行都使用了Rake功能,將獲取到的關鍵詞獨立作為新的一列,命名Key_words。
# initializing the new column df['Key_words'] = "" for index, row in df.iterrows(): plot = row['Plot'] # instantiating Rake, by default it uses english stopwords from NLTK # and discards all puntuation characters as well r = Rake() # extracting the words by passing the text r.extract_keywords_from_text(plot) # getting the dictionary whith key words as keys and their scores as values key_words_dict_scores = r.get_word_degrees() # assigning the key words to the new column for the corresponding movie row['Key_words'] = list(key_words_dict_scores.keys()) # dropping the Plot column df.drop(columns = ['Plot'], inplace = True)
除了Plot列,還需要對其餘列的資料進行清洗。同時,為了避免重複,需要對所有的內容進行小寫轉換,並且將所有的名和姓合併到一個單詞中。試想:如果電影A的導演是Danny Boyle,而電影B的主要演員是Danny DeVito,那麼電影A和B會因為Danny而擁有較高的相似度,但這並不是我們想要的。
在進行了所有的清理和合並之後,我將索引重新分配到movie title列,下圖是為向量化準備的Dataframe。
3. 建模(Modeling)
為了充分利用NLP挖掘電影之間的相似性,我們需要將文字轉為詞向量。我更傾向於使用CountVecorizer而非TfIdfVecorizer,因為我只需要一個簡單的頻率計數器來統計bag_of_words列中的每個單詞。Tf-Idf認為字詞的重要性隨著它在檔案中出現的次數成正比增加,但同時會隨著它在語料庫中出現的頻率成反比下降。這不適用於我們今天所講的應用場景,畢竟每個單詞對於相似度的衡量都非常重要。一旦我們得到了包含每個單詞計數的矩陣,便可應用cosine_similarity函式對相似度進行計算。
# instantiating and generating the count matrix count = CountVectorizer() count_matrix = count.fit_transform(df['bag_of_words']) # generating the cosine similarity matrix cosine_sim = cosine_similarity(count_matrix, count_matrix)
相似度矩陣如下圖所示:
Similarity matrix.
對角線上的值都為1,因為每部電影在和自己比較時是完全相似的。同時,這是一個對稱矩陣,因為電影A和B與電影B與A之間的相似度是相同的。
接下來,我們將電影標題作為輸入,返回前10個類似的電影作為推薦結果。此外,我們還給電影標題加上了數字索引,以匹配相似矩陣到實際電影標題的索引。實際上,函式一旦接收到輸入,就會檢測出與所輸入的電影相對應的行中最大的10個數字,獲取相應的索引並將其與電影標題系列匹配,以返回推薦的電影列表。當函式選取10個最高的相似度值時,丟棄了單位值,這樣就不會返回與輸入相同的電影標題。
# creating a Series for the movie titles so they are associated to an ordered numerical # list I will use in the function to match the indexes indices = pd.Series(df.index) #defining the function that takes in movie title # as input and returns the top 10 recommended movies def recommendations(title, cosine_sim = cosine_sim): # initializing the empty list of recommended movies recommended_movies = [] # gettin the index of the movie that matches the title idx = indices[indices == title].index[0] # creating a Series with the similarity scores in descending order score_series = pd.Series(cosine_sim[idx]).sort_values(ascending = False) # getting the indexes of the 10 most similar movies top_10_indexes = list(score_series.iloc[1:11].index) # populating the list with the titles of the best 10 matching movies for i in top_10_indexes: recommended_movies.append(list(df.index)[i]) return recommended_movies
4. 推薦系統測試(Testing the recommender)
由於我們只是用了包含250部電影的資料集,所構建的推薦系統性能有限。在測試的時候,輸入了我喜愛的電影“Fargo”,下圖為推薦的前10部電影。
Movies recommended because I like Fargo.
我對推薦結果是滿意的,從導演和情節上可以看出它們與我喜愛的電影有一些相似之處。上面列表中有我已經看過的電影,我喜歡它們就像我喜歡“Fargo”一樣,接下來我會去看列表中我還沒有看過的那幾部電影。
以上為譯文
本文由阿里云云棲社群組織翻譯。
文章原標題《How to build a content-based movie recommender system with Natural Language Processing》,作者: ofollow,noindex" target="_blank">Emma Grimaldi ,譯者:Elaine,審校:袁虎。
文章為簡譯,更為詳細的內容,請檢視 原文