python資料分析:基於協同過濾的電影推薦演算法
協同過濾
協同過濾(英語:Collaborative Filtering),簡單來說是利用某興趣相投、擁有共同經驗之群體的喜好來推薦使用者感興趣的資訊,個人透過合作的機制給予資訊相當程度的迴應(如評分)並記錄下來以達到過濾的目的進而幫助別人篩選資訊,迴應不一定侷限於特別感興趣的,特別不感興趣資訊的紀錄也相當重要。協同過濾又可分為評比(rating)或者群體過濾(social filtering)。其後成為電子商務當中很重要的一環,即根據某顧客以往的購買行為以及從具有相似購買行為的顧客群的購買行為去推薦這個顧客其“可能喜歡的品項”,也就是藉由社群的喜好提供個人化的資訊、商品等的推薦服務。除了推薦之外,近年來也發展出數學運算讓系統自動計算喜好的強弱進而去蕪存菁使得過濾的內容更有依據,也許不是百分之百完全準確,但由於加入了強弱的評比讓這個概念的應用更為廣泛,除了電子商務之外尚有資訊檢索領域、網路個人影音櫃、個人書架等的應用等。
優缺點
優點
以使用者的角度來推薦的協同過濾系統有下列優點:
- 能夠過濾機器難以自動內容分析的資訊,如藝術品,音樂等。
- 共享其他人的經驗,避免了內容分析的不完全或不精確,並且能夠基於一些複雜的,難以表述的概念(如資訊質量、個人品味)進行過濾。
- 有推薦新資訊的能力。可以發現內容上完全不相似的資訊,使用者對推薦資訊的內容事先是預料不到的。可以發現使用者潛在的但自己尚未發現的興趣偏好。
- 推薦個性化、自動化程度高。能夠有效的利用其他相似使用者的反饋資訊。加快個性化學習的速度。
缺點
雖然協同過濾作為一推薦機制有其相當的應用,但協同過濾仍有許多的問題需要解決。整體而言,最典型的問題有
- 新使用者問題(New User Problem) 系統開始時推薦質量較差
- 新專案問題(New Item Problem) 質量取決於歷史資料集
- 稀疏性問題(Sparsity)
- 系統延伸性問題(Scalability)。
資料集
本篇文章用的是電影資料集中的小資料集:600個使用者將100,000個評級和3,600個標籤應用程式應用於9,000部電影。上次更新時間:9/2018。
python實現
資料匯入
# 匯入各種庫
import pandas as pd
import numpy as np
import matplotlib. pyplot as plt
from scipy.sparse import csr_matrix
from sklearn.neighbors import NearestNeighbors
from sklearn.decomposition import TruncatedSVD
# 匯入電影連結檔案
links = pd.read_csv('links.csv')
links.head()
# 匯入電影資訊檔案
movies = pd.read_csv('movies.csv')
movies.head()
# 匯入評分檔案
ratings = pd.read_csv('ratings.csv')
ratings.head()
連結資訊,電影ID和一些評分網站ID:
電影資訊,電影ID,電影標題,電影的分類:
評分資訊,使用者ID,電影ID,評分,時間(無視就好):
構建電影詳細資訊
# 講電影資訊和電影連結根據電影id合併
movies_add_links = pd.merge(movies, links ,on='movieId')
def get_year(x):
try:
y = int(x.strip()[-5:-1])
except:
y = 0
return y
# 獲取電影年份
movies_add_links['movie_year'] = movies_add_links['title'].apply(get_year)
# 計算每個電影的被評論次數
rating_counts = pd.DataFrame(ratings.groupby('movieId').count()['rating'])
rating_counts.rename(columns={'rating':'ratingCount'}, inplace=True)
# 合併到一起
movie_add_rating = pd.merge(movies_add_links, rating_counts, on='movieId')
# 獲取每個電影的平均評分併合並
rating_means = pd.DataFrame(ratings.groupby('movieId').mean()['rating'])
rating_means.columns=['rating_mean']
movie_total = pd.merge(movie_add_rating, rating_means, on='movieId')
movie_total.head()
pandas更改列名可以使用rename進行精準更改,也可以世界對df.columns
進行操作
簡單分析資料
# 評分分佈情況
ratings.rating.value_counts(sort=True).plot('bar')
plt.title('Rating Distribution\n')
plt.xlabel('Rating')
plt.ylabel('Count')
plt.savefig('rating.png', bbox_inches='tight')
plt.show()
根據評分分佈可知 評分集中在3-4之間。。
### 視覺化每年電影數量和每年電影被評論數量 ###
# 設定畫幅
plt.figure(figsize=(16,16))
# 分層
plt.subplot(2,1,1)
# 每年電影數
movie_total.groupby('movie_year')['ratingCount'].count().plot('bar')
plt.title('Movies counts by years\n')
plt.xlabel('years')
plt.ylabel('counts')
# 每年電影評論數
plt.subplot(2,1,2)
movie_total.groupby('movie_year')['ratingCount'].sum().plot('bar')
plt.title('Movies ratings by years\n')
plt.xlabel('years')
plt.ylabel('ratings')
plt.savefig('mix.png', bbox_inches='tight')
plt.show()
- 可見1980年後電影數量飛速增長,到1995年平穩
- 雖然1995到2016年電影數量基本相差不是很大,但是1994,1995年電影的評論數(關注程度)遠高於以後
- 猜測1995年為電影的鼎盛時期
構建用於篩選電影資料
為了效果比較好,篩選出評價數佔比70%以上的書作參考
# 構建要使用與篩選的電影資訊
combine_movie = pd.merge(ratings, rating_counts, on='movieId')
combine_movie = combine_movie.dropna()
combine_movie.head()
# 計算70%評論數為多少用以篩選
combine_movie.ratingCount.quantile(np.arange(.5, 1, .05))
可見69為70%位置。。
popularity_threshold = 69
# 根據位置進行篩選
rating_popular_movie = combine_movie.query('ratingCount >= @popularity_threshold')
rating_popular_movie.head()
# 講表格轉化為2D矩陣
movie_pivot = rating_popular_movie.pivot(index='movieId', columns='userId', values='rating').fillna(0)
# 看看使用者多少,電影多少
movie_pivot.shape
(264, 602)
264個電影, 602個使用者
# 選出評分數最多的電影id
movie_1 = ratings.groupby('movieId').count().sort_values('rating', ascending=False).index[0]
movie_total[movie_total.movieId == movie_1]
評價數最高的電影為356號電影,平均評分4.16,評分也挺高的,該電影為《阿甘正傳》,也是家喻戶曉的勵志喜劇。
下面我們將使用3種方法從該電影獲取5個其他推薦電影:
推薦演算法
根據皮爾森關聯度
# 獲取電影詳細資訊函式
def get_movie(df_movie, moive_list):
# 根據電影ID獲取電影詳細資訊
df_movieId = pd.DataFrame(movie_list, index=np.arange(len(movie_list)), columns=['movieId'])
corr_movies = pd.merge(df_movieId, movie_total, on='movieId')
return corr_movies
def person_method(df_movie, pivot, movie, num):
# 獲取目標電影屬性
bones_ratings = pivot[movie]
# 計算出電影跟該電影的皮爾森相關性
similar_to_bones = pivot.corrwith(bones_ratings)
corr_bones = pd.DataFrame(similar_to_bones, columns=['pearson'])
# 棄去缺失值
corr_bones.dropna(inplace=True)
# 相關性與評論數合併
corr_summary = corr_bones.join(df_movie[['movieId','ratingCount']].set_index('movieId'))
# 刷選出對應數量的高關聯性電影
movie_list = corr_summary[corr_summary['ratingCount'] >= 100].sort_values('pearson', ascending=False).index[:num].tolist()
return movie_list
# 獲取電影ID列表
movie_list = person_method(movie_total, movie_pivot, movie_1, 6)
# 獲取電影詳細資料
corr_movies = get_movie(movie_total, movie_list)
corr_movies
結果如下:
得到的5個電影分別為《侏羅紀公園》、《勇敢的心》、《絕嶺雄風》、《冰血暴》和《永遠的蝙蝠俠》,簡單看一下這些電影都是比較偏動作一些,而且基本都是95年左右。
基於knn
我們使用sklearn.neighbors的無監督演算法。我們用來計算最近鄰居的演算法是“brute”,我們指定“metric =cosine”,以便演算法計算評級向量之間的餘弦相似度。
def knn_method(movie_pivot, movie, num):
# 壓縮稀疏矩陣
movie_pivot_matrix = csr_matrix(movie_pivot.values)
# 我們用來計算最近鄰居的演算法是“brute”,我們指定“metric =cosine”,以便演算法計算評級向量之間的餘弦相似度。
model_knn = NearestNeighbors(metric = 'cosine', algorithm = 'brute')
# 訓練模型
model_knn.fit(movie_pivot_matrix)
# 隨機選取一個電影
query_index = np.random.choice(movie_pivot.shape[0])
# 根據模型找到與所選電影最近6個(包含其本身)電影
distances, indices = model_knn.kneighbors(movie_pivot.loc[[movie], :].values.reshape(1, -1), n_neighbors = num)
# 獲取電影ID列表
movie_list = movie_pivot.iloc[indices[0],:].index
return movie_list
movie_list = knn_method(movie_pivot, movie_1, 6)
corr_movies = get_movie(movie_total, movie_list)
corr_movies
結果如下:
得到的5個電影分別為《肖申克的救贖》、《侏羅紀公園》、《低俗小說》、《勇敢的心》和《沉默的羔羊》
基於矩陣分解
矩陣分解只是一種用於玩矩陣的數學工具。矩陣分解技術通常更有效,因為它們允許使用者發現使用者和專案(書籍)之間互動的潛在(隱藏)功能。
我們使用奇異值分解(SVD)一種用於識別潛在因子的矩陣分解模型。
def SVD_method(movie_pivot, movie, num):
# 使用SVD對資訊進行矩陣分解
SVD = TruncatedSVD(n_components=12, random_state=17)
matrix = SVD.fit_transform(movie_pivot.values)
# 構建後資訊df
movie_SVD = pd.DataFrame(matrix, index=movie_pivot.index).T
# 求電影之間皮爾森相關度
corr = movie_SVD.corr()
search_movie = movie_pivot.loc[[movie_1], :].index[0]
# search_movie = """Schindler's List (1993)"""
movie_list = corr.sort_values(search_movie, ascending=False).index[0:num].tolist()
return movie_list
movie_list = SVD_method(movie_pivot, movie_1, 6)
corr_movies = get_movie(movie_total, movie_list)
corr_movies
結果如下:
得到的5個電影分別為《肖申克的救贖》、《辛德勒的名單》、《沉默的羔羊》、《阿波羅13號》、《勇敢的心》
總結
不知道你認為那種方式推薦的電影更適合你呢,當然我們也可以綜合三種方法,使用投票加權等方法,獲得3票的《勇敢的心》可能是最符合,但不一定符合你,推薦不可能做到完全精準,但是隻要做到推薦5個,其中有一個有想讓你點開的衝動,就夠了。。。