courseraのgcp講座で紹介されていたContent-basedなレコメンドを、movie-lensで試そうと思います。
https://www.coursera.org/learn/recommendation-models-gcp
上記では、tensorflow1.4での実装でしたが、numpyで行います。
流れとしては以下の通りです。
①user×item行列と、item×feature行列を作成する
②ドット積をとり、user×feature行列を作成し、正規化する
③ドット積をとり、user×item行列を復元する
④ユーザーをピックアップしてソートし、Top-Nを出力する
今回は、映画のメタ情報として映画のジャンルを利用します。
それでは順番に行きましょう。
①user×item行列と、item×feature行列を作成する
1 2 3 4 5 6 7 8 9 |
import pandas as pd movies = pd.read_csv("movies.csv") ratings = pd.read_csv("ratings.csv") #moviesとratingをマージ df = pd.merge(ratings,movies,left_on="movieId",right_on="movieId") print(df.head()) |

genresは変なデータの入り方をしているので、少し前処理が必要です。
1 2 3 |
#user×item行列 users_movies = df.pivot_table(index="userId",columns="movieId",values="rating").fillna(0) print(users_movies) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
#genresの一覧のリストを作成する g = df["genres"].str.split("|",expand=True) genres = [] for i in range(10): tmp = g[i].drop_duplicates().tolist() for j in range(len(tmp)): genres.append(tmp[j]) tmp = [] genres = set(genres) genres = list(genres) print(genres) """ ['Comedy', 'War', 'Children', 'Fantasy', 'Sci-Fi', 'Western', 'Crime', 'Animation', 'Romance', 'Mystery', 'Action', None, '(no genres listed)', 'Musical', 'Adventure', 'Film-Noir', 'Horror', 'Thriller', 'Documentary', 'Drama', 'IMAX'] """ #item×feature行列を作成 movies_genres = pd.DataFrame() movies_genres["movieId"] = df["movieId"].drop_duplicates() for i in range(len(genres)): genre = genres[i] movies_genres[str(genre)] = df["genres"].str.contains(str(genre)) #True/Falseを1/0に直す movies_genres = movies_genres * 1 #movieId順にソートをかける movies_genres = movies_genres.sort_values("movieId").reset_index(drop=True).iloc[:,1:] print(movies_genres) |
見方としては、movieId=0はcomedy,Children,Fantasy…のジャンルに属していますよ、ということです。
②ドット積をとり、user×feature行列を作成し、正規化する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import numpy as np users_genres = np.dot(users_movies,movies_genres) #normalization for i in range(users_genres.shape[0]): users_genres[i] = users_genres[i]/users_genres[i].sum() print(users_genres) """ array([[0.03267327, 0.00990099, 0.06666667, ..., 0. , 0. , 0. ], [0.01541096, 0.0119863 , 0. , ..., 0.05136986, 0.04452055, 0. ], [0.00961538, 0. , 0.05192308, ..., 0. , 0. , 0. ], ..., [0.00931954, 0.00397451, 0.04563832, ..., 0.0065785 , 0.00246694, 0. ], [0.04713805, 0.01346801, 0.01010101, ..., 0.01010101, 0.02020202, 0. ], [0.0129562 , 0.0090146 , 0.03959854, ..., 0.02171533, 0.00153285, 0. ]]) """ |
③ドット積をとり、user×item行列を復元する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#user×movie行列を復元 users_movies_new = np.dot(users_genres,movies_genres.T) print(users_movies_new) """ array([[0.41485149, 0.25280528, 0.15412541, ..., 0.10165017, 0.17326733, 0.11716172], [0.13869863, 0.04280822, 0.11130137, ..., 0.2260274 , 0.1489726 , 0.09589041], [0.21923077, 0.17692308, 0.04423077, ..., 0.04615385, 0.2 , 0.03461538], ..., [0.31186185, 0.1552114 , 0.17508394, ..., 0.13191256, 0.1499349 , 0.13314603], [0.22558923, 0.13804714, 0.13131313, ..., 0.21548822, 0.12457912, 0.07744108], [0.25748175, 0.12675182, 0.14434307, ..., 0.13547445, 0.15467153, 0.11193431]]) """ |
(Aさんはこれまでの映画の視聴履歴から、このジャンルはこれくらい好きだとわかりました(=users_genres行列)。ということは、まだ見ていない映画についても、その映画のジャンルからおおよそこれくらいの点数をつけるだろうと予測できる(=users_movies_new))
0を予測値で埋めることができたのは良いのですが、レコメンドで使用するには、すでにユーザーが視聴した映画が推薦されないようにしたいです。
ということで、np.whereで未評価の映画のみの予測値の行列を作ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#元のuser×item行列:ユーザーが視聴した組み合わせ users_movies = np.array(users_movies) #ユーザーが既に視聴した映画の評点を0にする recmat = np.where(users_movies==0,users_movies_new,0) print(recmat) """ array([[0. , 0.25280528, 0. , ..., 0.10165017, 0.17326733, 0.11716172], [0.13869863, 0.04280822, 0.11130137, ..., 0.2260274 , 0.1489726 , 0.09589041], [0.21923077, 0.17692308, 0.04423077, ..., 0.04615385, 0.2 , 0.03461538], ..., [0. , 0. , 0. , ..., 0.13191256, 0.1499349 , 0.13314603], [0. , 0.13804714, 0.13131313, ..., 0.21548822, 0.12457912, 0.07744108], [0. , 0.12675182, 0.14434307, ..., 0.13547445, 0.15467153, 0.11193431]]) """ |
④ユーザーをピックアップしてソートし、Top-Nを出力する
見やすくするためにデータフレーム型にします。
1 2 3 4 5 |
rec_df = pd.DataFrame(recmat) rec_df.columns = users_movies.columns rec_df.index = users_movies.index print(rec_df.head()) |
userId = 1の人に対して5つレコメンドします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def recommend_topN(userId,N,reclist): user = rec_df.iloc[userId-1,:] sort = user.sort_values(ascending=False) rec = sort.index[:N] for i in range(N): print("あなたにおすすめの映画No.{}:{}".format(i+1,rec[i])) recommend_topN(1,5,reclist) """ あなたにおすすめの映画No.1:81132 あなたにおすすめの映画No.2:117646 あなたにおすすめの映画No.3:71999 あなたにおすすめの映画No.4:4956 あなたにおすすめの映画No.5:4719 """ |
movieIdでなくタイトルをみえるようにするには…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
movieNames = ratings_new.loc[:,["movieId","title"]] movies = movieNames.sort_values("movieId").drop_duplicates() def recommend_topN(userId,N,reclist): user = reclist.iloc[userId-1,:] sort = user.sort_values(ascending=False) rec = sort.index[:N] for i in range(N): movieId = int(rec[i]) title = movies[movies["movieId"] == movieId]["title"].values print("あなたにおすすめの映画No.{}:{}".format(i+1,title)) recommend_topN(1,10,reclist) """ あなたにおすすめの映画No.1:['Rubber (2010)'] あなたにおすすめの映画No.2:['Dragonheart 2: A New Beginning (2000)'] あなたにおすすめの映画No.3:['Aelita: The Queen of Mars (Aelita) (1924)'] あなたにおすすめの映画No.4:['Stunt Man, The (1980)'] あなたにおすすめの映画No.5:['Osmosis Jones (2001)'] あなたにおすすめの映画No.6:['Maximum Ride (2016)'] あなたにおすすめの映画No.7:['Interstate 60 (2002)'] あなたにおすすめの映画No.8:['Aqua Teen Hunger Force Colon Movie Film for Theaters (2007)'] あなたにおすすめの映画No.9:['Super Mario Bros. (1993)'] あなたにおすすめの映画No.10:['Chase, The (1994)'] """ |
最後まで読んでいただきありがとうございました。