如何用深度學(xué)習(xí)推薦電影?教你做自己的推薦系統(tǒng)!
簡介
幾乎所有人都喜歡與家人、朋友一起觀看電影度過閑暇時(shí)光。大家可能都有過這樣的體驗(yàn):本想在接下來的兩個(gè)小時(shí)里看一個(gè)電影,卻坐在沙發(fā)上坐了20分鐘不知道看什么,選擇困難癥又犯了,結(jié)果好心情也變得沮喪。所以,我們很需要一個(gè)電腦代理,在做挑選電影的時(shí)候提供推薦。
現(xiàn)在,電影智能推薦系統(tǒng)已經(jīng)成為日常生活中的一部分。
Data Science Central 曾表示:
“雖然硬數(shù)據(jù)很難獲得,但知情人士估計(jì),對亞馬遜和Netflix這樣的大型電商平臺,推薦系統(tǒng)為它們帶來高達(dá)10%至25%的收入增長”。
在這個(gè)項(xiàng)目中, 我研究了一些針對電影推薦的基本算法,并嘗試將深度學(xué)習(xí)融入到電影推薦系統(tǒng)中。
把娛樂與視覺藝術(shù)相結(jié)合,電影是一個(gè)很好的例子。電影海報(bào)可以直接、快速地把電影信息傳達(dá)給觀眾。Design Mantic表示:“不論上映前后,電影海報(bào)都是創(chuàng)造噱頭的主要因素。多半的人(目標(biāo)觀眾)都根據(jù)海報(bào)來決定買不買票,看不看電影。”我們甚至可以僅僅根據(jù)海報(bào)字體,來推測這個(gè)電影的情緒。
這聽起來有點(diǎn)像魔術(shù)——但看一眼海報(bào)就預(yù)測出電影的類型,的確是可能的。就拿我來說,瞟一眼海報(bào)就知道我想不想看這個(gè)電影了。舉個(gè)例子,我不是卡通迷,一看到有卡通主題海報(bào),就知道不是我的菜。這個(gè)決策的過程很直接,并不需要閱讀電影評論(不確定誰真的有時(shí)間讀那些評論)。因此,除了標(biāo)準(zhǔn)的電影推薦算法,我還用了深度學(xué)習(xí)來處理海報(bào),并將相似的電影推薦給用戶。最終目標(biāo)是模仿人類視覺,并僅僅通過觀察海報(bào),就能用深度學(xué)習(xí)創(chuàng)建一個(gè)直觀的電影推薦系統(tǒng)。該項(xiàng)目是受到Ethan Rosenthal博客啟發(fā)。我對他博客里的代碼進(jìn)行了修改,以適應(yīng)這個(gè)項(xiàng)目的算法。
我們用的是從 MovieLens 下載的電影數(shù)據(jù)集。他包含9066個(gè)電影和671名用戶,分成了100000個(gè)打分和1300個(gè)標(biāo)簽。這個(gè)數(shù)據(jù)集最后更新于10/2016.
協(xié)同過濾
粗略地說,有三種類型的推薦系統(tǒng)(不包括簡單的評級方法)
-
基于內(nèi)容的推薦
-
協(xié)同過濾
-
混合模型
“基于內(nèi)容的推薦”是一個(gè)回歸問題,我們把電影內(nèi)容作為特征,對用戶對電影的評分做預(yù)測。
而在“協(xié)同過濾”推薦系統(tǒng)中,一般無法提前獲得內(nèi)容特征。是通過用戶之間的相似度(用戶們給了用一個(gè)電影相同的評級)和電影之間的相似度(有相似用戶評級的電影),來學(xué)習(xí)潛在特征,同時(shí)預(yù)測用戶對電影的評分。此外,學(xué)習(xí)了電影的特征之后,我們便可以衡量電影之間的相似度,并根據(jù)用戶歷史觀影信息,向他/她推薦最相似的電影。
“基于內(nèi)容的推薦”和“協(xié)同過濾”是10多年前最先進(jìn)的技術(shù)。很顯然,現(xiàn)在有很多模型和算法可以提高預(yù)測效果。比如,針對事先缺乏用戶電影評分信息的情況,可以使用隱式矩陣分解,用偏好和置信度取代用戶電影打分——比如用戶對電影推薦有多少次點(diǎn)擊,以此進(jìn)行協(xié)同過濾。另外,我們還可以將“內(nèi)容推薦”與“協(xié)同過濾”的方法結(jié)合起來,將內(nèi)容作為側(cè)面信息來提高預(yù)測精度。這種混合方法,可以用“學(xué)習(xí)進(jìn)行排序”("Learning to Rank" )算法來實(shí)現(xiàn)。
該項(xiàng)目中,我會聚焦于“協(xié)同過濾”方法。首先,我將討論如何 不使用回歸, 而是 電影(用戶)相似度來預(yù)測評分 ,并基于相似度做電影推薦。然后,我將討論如何 使用回歸同時(shí)學(xué)習(xí)潛在特征、做電影推薦 。最后會談?wù)?nbsp;如何在推薦系統(tǒng)中使用深度學(xué)習(xí) 。
電影相似性
對于基于協(xié)作過濾的推薦系統(tǒng),首先要建立評分矩陣。其中,每一行表示一個(gè)用戶,每一列對應(yīng)其對某一電影的打分。建立的評分矩陣如下:
- df = pd.read_csv('ratings.csv', sep=',')
- df_id = pd.read_csv('links.csv', sep=',')
- df = pd.merge(df, df_id, on=['movieId'])
- rating_matrix = np.zeros((df.userId.unique().shape[0], max(df.movieId)))
- for row in df.itertuples():
- rating_matrix[row[1]-1, row[2]-1] = row[3]
- rating_matrix = rating_matrix[:,:9000]
這里“ratings.csv”包含用戶id,電影id, 評級,和時(shí)間信息;"link.csv"包括電影id, IMDB id,和TMDB id。每一個(gè)電影利用 API 從 Movie Databasewebsite 獲得海報(bào),都需要 IMDB id——因此,我們將兩個(gè)表格結(jié)合到一起。我們檢驗(yàn)了評分矩陣的稀疏性,如下:
- sparsity = float(len(ratings.nonzero()[0]))
- sparsity /= (ratings.shape[0] * ratings.shape[1])
- sparsity *= 100
當(dāng)非零項(xiàng)(entry)只有1.40%的時(shí)候評級矩陣是稀疏的?,F(xiàn)在,為了訓(xùn)練和測試,我們將評分矩陣分解成兩個(gè)較小的矩陣。我們從評分矩陣中刪除了10個(gè)評分,把它們放入測試集。
- train_matrix = rating_matrix.copy()
- test_matrix = np.zeros(ratings_matrix.shape)
- for i in xrange(rating_matrix.shape[0]):
- rating_idx = np.random.choice(
- rating_matrix[i, :].nonzero()[0],
- size=10,
- replace=True)
- train_matrix[i, rating_idx] = 0.0
- test_matrix[i, rating_idx] = rating_matrix[i, rating_idx]
根據(jù)以下公式計(jì)算用戶/電影中的(余弦Cosine) 相似性
這里s(u,v)是用戶u和v之間的余弦相似度。
- similarity_user = train_matrix.dot(train_matrix.T) + 1e-9
- norms = np.array([np.sqrt(np.diagonal(similarity_user))])
- similarity_user = ( similarity_user / (norms * norms.T) )
- similarity_movie = train_matrix.T.dot(train_matrix) + 1e-9
- norms = np.array([np.sqrt(np.diagonal(similarity_movie))])
- similarity_movie = ( similarity_movie / (norms * norms.T) )
利用用戶之間的相似性,我們能預(yù)測每個(gè)用戶對電影的評級,并計(jì)算出相應(yīng)的MSE。該預(yù)測基于相似用戶的評分。特別地,可以根據(jù)以下公式進(jìn)行打分預(yù)測:
用戶u對電影i的預(yù)測,是用戶v對電影的評分的(標(biāo)準(zhǔn)化的)加權(quán)和。權(quán)重為用戶u和v的相似度。
- from sklearn.metrics import mean_squared_error
- prediction = similarity_user.dot(train_matrix) / np.array([np.abs(similarity_user).sum(axis=1)]).T
- prediction = prediction[test_matrix.nonzero()].flatten()
- test_vector = test_matrix[test_matrix.nonzero()].flatten()
- mse = mean_squared_error(prediction, test_vector)
- print 'MSE = ' + str(mse)
預(yù)測的MSE為9.8252。這個(gè)數(shù)字意味著什么?這個(gè)推薦系統(tǒng)是好是壞?僅僅看著MSE結(jié)果來評估預(yù)測效果不是很符合直覺。因此,我們直接檢查電影推薦來評估。我們將搜索一個(gè)感興趣的電影,并讓電腦代理來推薦幾部電影。首先要得到相應(yīng)的電影海報(bào),這樣就能看到都有什么電影被推薦。我們使用IMDB id,使用它的API從Movie Database 網(wǎng)站獲取海報(bào)。
- import requests
- import json
- from IPython.display import Image
- from IPython.display import display
- from IPython.display import HTML
- idx_to_movie = {}
- for row in df_id.itertuples():
- idx_to_movie[row[1]-1] = row[2]
- idx_to_movie
- k = 6
- idx = 0
- movies = [ idx_to_movie[x] for x in np.argsort(similarity_movie[idx,:])[:-k-1:-1] ]
- movies = filter(lambda imdb: len(str(imdb)) == 6, movies)
- n_display = 5
- URL = [0]*n_display
- IMDB = [0]*n_display
- i = 0
- for movie in movies:
- (URL[i], IMDB[i]) = get_poster(movie, base_url)
- i += 1
- images = ''
- for i in range(n_display):
- images += "<img style='width: 100px; margin: 0px; \
- float: left; border: 1px solid black;' src='%s' />" \
- % URL[i]
- display(HTML(images))
好玩的來了!讓我們來搜索一個(gè)電影并看看四個(gè)最相似的推薦。讓我們試著搜索《盜火線》,在左手邊第一個(gè),后面是四部推薦的電影。
《盜火線》是1995年上映的一部美國犯罪電影,由羅伯特·德·尼羅、阿爾·帕西諾主演。搜索結(jié)果看起來不錯(cuò)。但《離開拉斯維加斯》可能不是一個(gè)好的建議,我猜原因是因?yàn)殡娪啊队玛J奪命島》里有尼古拉斯·凱奇,《The Rock》,以及對喜歡 《盜火線》的觀眾而言,它是一個(gè)不錯(cuò)的推薦。這可能是相似性矩陣和協(xié)同過濾的缺點(diǎn)之一。讓我們試試更多的例子。
這個(gè)看起還好?!锻婢呖倓訂T2》絕對是應(yīng)該推薦給喜歡《玩具總動員》的觀眾。但是《阿甘正傳》在我看來不合適。顯然,因?yàn)闇?middot;漢克斯的聲音出現(xiàn)在《玩具總動員》里,所以《阿甘正傳》也被推薦了。值得注意的是,我們可以只看一眼海報(bào)就分辨出《玩具總動員》與 《阿甘正傳》的區(qū)別,比如電影類型、情緒等。假設(shè)每一個(gè)小孩都喜歡《玩具總動員》,他們可能會忽略《阿甘正傳》。
交替隨機(jī)梯度下降
在前面的討論中,我們簡單地計(jì)算了用戶和電影的余弦相似度,并以此來預(yù)測用戶對電影的評分,還根據(jù)某電影推薦其它電影。現(xiàn)在,我們可以把問題做為一個(gè)回歸問題;對所有的電影加入潛在特征y,對所有用戶加入權(quán)重向量x。目標(biāo)是將評分預(yù)測的(在 2-norm 的正則化條件下)MSE最小化。
雷鋒網(wǎng) (公眾號:雷鋒網(wǎng)) 提醒:權(quán)重向量和特征向量都是決策變量。顯然,這不是一個(gè)凸函數(shù)問題,現(xiàn)在也不需要過分擔(dān)心這個(gè)非凸函數(shù)的收斂性。有很多方法能解決非凸函數(shù)的優(yōu)化問題。方法之一就是以交替方式()解決權(quán)重向量(對用戶)和特征向量(對電影)。處理權(quán)重向量時(shí),假設(shè)特征向量是常向量;處理特征向量時(shí),假設(shè)權(quán)重向量是常向量。解決這個(gè)回歸問題的另一種方法,是將權(quán)重向量與特征向量的更新結(jié)合起來,在同一個(gè)迭代中更新它們。另外,還可以借助隨機(jī)梯度下降來加速計(jì)算。這里,我用隨機(jī)梯度下降來解決這個(gè)回歸問題,我們的MSE預(yù)測如下:
這個(gè)MSE比用相似性矩陣得到的,要小得多。當(dāng)然,我們也可以使用網(wǎng)格搜索和交叉驗(yàn)證對模型、算法調(diào)參。再看看電影搜索的推薦:
看起來并不是很好。我覺得這四部電影不應(yīng)該通過搜索《盜火線》推薦給我,他們看起來與《盜火線》完全不相關(guān),這四個(gè)電影是浪漫、戲劇類。如果我找的是一部有大明星的美國犯罪電影,我憑什么會想要看戲劇電影? 這讓我很困惑——一個(gè)好的MSE的結(jié)果可能會給我們一個(gè)風(fēng)馬牛不相及的推薦。
因此,我們討論一下基于協(xié)同過濾的推薦系統(tǒng)的弱點(diǎn)。
-
協(xié)同過濾方法通過使用數(shù)據(jù),來發(fā)現(xiàn)類似的用戶和電影,這將導(dǎo)致熱門電影比小眾電影更容易被推薦。
-
由于新上映的電影沒有太多的使用數(shù)據(jù),指望協(xié)同過濾向用戶推薦任何新電影很不現(xiàn)實(shí)。
接下來,我們將考慮采用另一種方法來處理協(xié)同過濾問題——用深度學(xué)習(xí)推薦電影。
深度學(xué)習(xí)
我們將在Keras中用VGG16來訓(xùn)練神經(jīng)網(wǎng)絡(luò)。我們的數(shù)據(jù)集中沒有目標(biāo),只是將倒數(shù)第四層作為一個(gè)特征向量。我們用這個(gè)特征向量,來描述數(shù)據(jù)集中的每一個(gè)電影。雷鋒網(wǎng)提醒,在訓(xùn)練神經(jīng)網(wǎng)絡(luò)之前,還需要做一些預(yù)處理,訓(xùn)練過程如下。
- df_id = pd.read_csv('links.csv', sep=',')
- idx_to_movie = {}
- for row in df_id.itertuples():
- idx_to_movie[row[1]-1] = row[2]
- total_movies = 9000
- movies = [0]*total_movies
- for i in range(len(movies)):
- if i in idx_to_movie.keys() and len(str(idx_to_movie[i])) == 6:
- movies[i] = (idx_to_movie[i])
- movies = filter(lambda imdb: imdb != 0, movies)
- total_movies = len(movies)
- URL = [0]*total_movies
- IMDB = [0]*total_movies
- URL_IMDB = {"url":[],"imdb":[]}
- i = 0
- for movie in movies:
- (URL[i], IMDB[i]) = get_poster(movie, base_url)
- if URL[i] != base_url+"":
- URL_IMDB["url"].append(URL[i])
- URL_IMDB["imdb"].append(IMDB[i])
- i += 1
- # URL = filter(lambda url: url != base_url+"", URL)
- df = pd.DataFrame(data=URL_IMDB)
- total_movies = len(df)
- import urllib
- poster_path = "/Users/wannjiun/Desktop/nycdsa/project_5_recommender/posters/"
- for i in range(total_movies):
- urllib.urlretrieve(df.url[i], poster_path + str(i) + ".jpg")
- from keras.applications import VGG16
- from keras.applications.vgg16 import preprocess_input
- from keras.preprocessing import image as kimage
- image = [0]*total_movies
- x = [0]*total_movies
- for i in range(total_movies):
- image[i] = kimage.load_img(poster_path + str(i) + ".jpg", target_size=(224, 224))
- x[i] = kimage.img_to_array(image[i])
- x[i] = np.expand_dims(x[i], axis=0)
- x[i] = preprocess_input(x[i])
- model = VGG16(include_top=False, weights='imagenet')
- prediction = [0]*total_movies
- matrix_res = np.zeros([total_movies,25088])
- for i in range(total_movies):
- prediction[i] = model.predict(x[i]).ravel()
- matrix_res[i,:] = prediction[i]
- similarity_deep = matrix_res.dot(matrix_res.T)
- norms = np.array([np.sqrt(np.diagonal(similarity_deep))])
- similarity_deep = similarity_deep / norms / norms.T
在代碼中,我們首先使用API和IMDB id,從TMDB網(wǎng)站獲取電影海報(bào)。然后向VGG16提供海報(bào)來訓(xùn)練神經(jīng)網(wǎng)絡(luò)。最后,用VGG16學(xué)習(xí)的特征來計(jì)算余弦相似性。獲得電影相似性之后,我們可以推薦相似度最高的電影。VGG16總共有25088個(gè)學(xué)來的特征,我們使用這些特征來描述數(shù)據(jù)集中的每個(gè)電影。
來看看使用深度學(xué)習(xí)的電影推薦系統(tǒng)。
《導(dǎo)火線》不再和愛情戲劇一起出現(xiàn)了!這些電影海報(bào)有一些相同的特點(diǎn):深藍(lán)色的、上面還有人物等等。讓我們再來試試《玩具總動員》。
《阿甘正傳》不會再被推薦了!結(jié)果看起來不錯(cuò),朕心甚慰,再來試試別的!
注意,這些海報(bào)里都有一或兩個(gè)人,并有冷色系的主題風(fēng)格。
這些海報(bào)想讓觀眾知道相應(yīng)電影的氛圍歡樂、緊張,并有很多動作鏡頭,所以海報(bào)的顏色也很強(qiáng)烈。
不同于上一組,這些海報(bào)想告訴觀眾:這些電影講述的是一個(gè)單身漢。
我們找到的與《功夫熊貓》類似的電影。
這一組很有趣。一群相似的怪獸以及湯姆·克魯斯!
所有這些海報(bào)里都有姿勢類似的女士。等等,那個(gè)是奧尼爾?。?/p>
成功找到了蜘蛛俠!
這些海報(bào)的排版設(shè)計(jì)很接近。
結(jié)論
在推薦系統(tǒng)中有幾種使用深度學(xué)習(xí)的方法:
-
無監(jiān)督學(xué)習(xí)
-
從協(xié)同過濾中預(yù)測潛在特征
-
將深度學(xué)習(xí)生成的特征作為輔助信息
電影海報(bào)具有創(chuàng)造噱頭和興趣的視覺元素。這個(gè)項(xiàng)目中,我們使用了無監(jiān)督深度學(xué)習(xí),通過海報(bào)來學(xué)習(xí)電影的相似性。顯然,這只是在推薦系統(tǒng)中使用深度學(xué)習(xí)的第一步,我們還可以嘗試很多東西。例如,我們可以用深度學(xué)習(xí)來預(yù)測協(xié)同過濾生成的潛在特征。Spotify的音樂推薦也使用了類似的方法,區(qū)別于圖像處理,他們通過處理歌曲的聲音,來用深度學(xué)習(xí)來預(yù)測協(xié)同過濾中的潛在特征。還有一個(gè)可能的方向。是把深度學(xué)習(xí)學(xué)到的特征作為輔助信息,來提高預(yù)測的準(zhǔn)確性。