前回の記事では、映画を評価した2人のユーザのユークリッド距離を計算するところまでいきました。
ただ、ユークリッド距離だと、出力される値の範囲が分かりません。
例えば距離を計算して出てきた値によって相関が高いのか低いのかが判断できないのです。
そこで映画を見た二人の相関が高いかどうかを調べる指標としてピアソン相関を使います。
ピアソン相関であれば出力が-1から1の範囲なので、出力が1に近ければ相関が高いと判断できます。
まずこの関数を実装するところから始めようと思いやーす。
目次
ピアソン相関
やることはシンプルで、
- 二人の共通の映画を取り出す
- 取り出した映画に対する二人の評価を取得する
- それらのピアソン相関値を求める
pandasの中にピアソン相関を求める関数があるのでそれを使います。
コードを書くと以下の通り。
import math def pearson_score(user1,user2): movies1 = get_movies(user1) movies2 = get_movies(user2) # # リスト同士を比較して、共通の映画を取り出す both_movies = set(movies1) & set(movies2) # 共通の映画がなかった if len(both_movies) == 0: return 0 rating1=[] rating2=[] for m in both_movies: rating1.append(get_rating(user1,m)) rating2.append(get_rating(user2,m)) # リストをps.Seriesに変換 s1=pd.Series(rating1) s2=pd.Series(rating2) # pandasを使用してPearsonスコアを計算 # データによっては2つの標準偏差が0になるので、0除算となりNaNが返ってくる score=s1.corr(s2) return score
上記の関数を使って100人の相関値を計算してみましょう。自分と相関のある人Top5を求めてみます。
import numpy def most_similar_user(target,num=5): uids = list(set(df_rating.UserID)) print(type(uids)) print(len(uids)) scores=[] for uid in uids[:100]: if target==uid: continue # タプルで保存 r = (uid,pearson_score(target,uid)) print(r) if numpy.isnan(r[1]) == False: scores.append(r) sorted_by_score = sorted(scores, key=lambda tup: tup[1],reverse=True) return sorted_by_score[:num] print(most_similar_user(23))
たまに、ピアソン相関の返り値がnanで返ってくるのでそういうのは無視して次に進んでいます。
出力は以下の通り。
[(61, 0.9365858115816939), (41, 0.7076731463403722), (21, 0.6123724356957946), (25, 0.5970863767331769), (64, 0.5477225575051661)]
23さんと相関の高い人は、順番に61,41,21さんってことが分かりました。
ここまではサクサクと進みました。
オススメ映画を取得する
オススメ映画を取得するにはどうすれば良いでしょうか。
他の人の評価を足し合わせて高い順にレコメンドするだけで良いですが、もう少し工夫して以下のような作戦をとります。
「自分と相関が高い人を重視する」。
つまり、他の人がした評価に相関値を掛け合わせた値を評価指標として使います。
自分との相関が0.1の人の評価「5」と0.9の人の評価「5」では、後者の方がポイントが高いということです。
Aさんにレコメンドする映画を探しているとして、
- Bさんが観た映画リストの中でAさんが観ていない映画のリストを取得する
- その中の映画に対して、Bさんの映画の評価と、AさんとBさんの相関値をかけたものをその映画のポイントとする
- Cさん以降も同じ処理を繰り返して、ポイントを加算していく。
こんな感じのコードを書いていきたいと思います。
def get_recommendation_movies(target,num=10): uids = list(set(df_rating.UserID)) weignt_score = {} pscore_sum = {} for uid in uids[:100]: print(uid) if target==uid: continue pscore = pearson_score(target,uid) print(pscore) if(pscore < 0 or numpy.isnan(pscore)): continue movies1 = get_movies(target) movies2 = get_movies(uid) # 対象のユーザが観た映画を除く unwatched = set(movies2) - set(movies1) for m in unwatched: weignt_score.setdefault(m,0) weignt_score[m] += get_rating(uid,m) * pscore sorted_weight_score = sorted(weignt_score.items(), key=lambda kv: kv[1],reverse=True) print(sorted_weight_score[:num]) for k,v in sorted_weight_score[:num]: print(get_movie_title(k)) get_recommendation_movies(32)
出力は以下の通り。
['Gladiator'] ['Star Wars: Episode V - The Empire Strikes Back'] ['Men in Black'] ['Shakespeare in Love'] ['Mission: Impossible'] ['Raiders of the Lost Ark'] ['Star Wars: Episode IV - A New Hope'] ['Braveheart'] ['Shawshank Redemption, The'] ['Star Wars: Episode VI - Return of the Jedi']
こんな感じ!
おわり。
参考
https://towardsdatascience.com/building-a-movie-recommendation-engine-using-pandas-e0a105ed6762