All Articles

初心者が協調フィルタリングをやってみた

協調フィルタリングとは

Amazonが商品をおすすめするときに使っている手法で、 相関係数を用いることでユーザーが買う確率の高い商品を導き出せる。 「似たやつが勝ってれば買う確率高いだろ」的な感じ

ひとまずやってみた

ネットで調べると相関係数の式が並んでいて読むのが大変そうだったので、 ひとまず動くコードを解き明かしながら理解してみた。 今回は以下のページを参考に寿司の購買データで協調フィルタリングをしてみました。 Pythonでレコメンドシステムを作る(ユーザベース協調フィルタリング)

理解した流れ

「相関係数を用いることでユーザーが買う確率の高い商品を導き出す」 を細かい流れに分解すると主に以下の3ステップになっていることがわかった。

  1. 対象ユーザーと相関が高いユーザーをランキングで出す
  2. ランキングが高いユーザーの情報を元に対象ユーザーの評価してないアイテムの評価を出す
  3. 評価してない+予測評価が高いものをオススメ商品とする

参考ページの方がすごくわかりやすくまとめてくださっていたおかげで、 ほとんど迷わず進めることができました。

実際のコード(コメント追記)

import numpy as np
  
# 人ベース
  
scores = np.loadtxt('sushi3-2016/sushi3b.5000.10.score', delimiter=' ')
  
  
def get_correlation_coefficents(scores, target_user_index):
    similarities = []
    target = scores[target_user_index]
  
    for i, score in enumerate(scores):
        # 共通の評価が少ない場合は除外
        # +1することで値なし(-1)を0にしている
        # (target + 1) * (score + 1) != 0 でどちらかが0のカラムは0にして、!= 0でbooleanにする
        # whereによってTrueになっているカラムの列番号を返す
        indices = np.where(((target + 1) * (score + 1)) != 0)[0]
        if len(indices) < 3 or i == target_user_index:
            continue
  
        # [0, 1]の値に相関係数が入る
        similarity = np.corrcoef(target[indices], score[indices])[0, 1]
        if np.isnan(similarity):
            continue
  
        similarities.append((i, similarity))
  
    return sorted(similarities, key=lambda s: s[1], reverse=True)
  
  
def predict(scores, similarities, target_user_index, target_item_index):
    target = scores[target_user_index]
  
    # >=0 で評価がついているもののみに絞る
    avg_target = np.mean(target[np.where(target >= 0)])
  
    numerator = 0.0
    denominator = 0.0
    k = 0
  
    for similarity in similarities:
        # 類似度の上位5人の評価値を使う
        # 相関係数が負の人もdenominator,numerator共に-1をかければ使用できそう
        if k > 5 or similarity[1] <= 0.0:
            break
  
        score = scores[similarity[0]]
        # 類似度が高い人で対象のitemを評価しているユーザーを選ぶ
        if score[target_item_index] >= 0:
            denominator += similarity[1]
            numerator += similarity[1] * (score[target_item_index] - np.mean(score[np.where(score >= 0)]))
            k += 1
  
    return avg_target + (numerator / denominator) if denominator > 0 else -1
  
  
def rank_items(scores, similarities, target_user_index):
    rankings = []
    target = scores[target_user_index]
    # 寿司ネタ100種類の全てで評価値を予測
    for i in range(100):
        # 既に評価済みの場合はスキップ
        if target[i] >= 0:
            continue
  
        rankings.append((i, predict(scores, similarities, target_user_index, i)))
    return sorted(rankings, key=lambda r: r[1], reverse=True)
  
  
if __name__ == "__main__":
    target_user_index = 0  # 0番目のユーザ
    similarities = get_correlation_coefficents(scores, target_user_index)
    print('Similarities: {}'.format(similarities))
    # Similarities: [(186, 1.0), (269, 1.0), (381, 1.0), ...
    print('scores[186]:\n{}'.format(scores[186]))
    target_item_index = 0  # 3番目のアイテム(エビ)
    print('Predict score: {:.3f}'.format(predict(scores, similarities, target_user_index, target_item_index)))
    rank = rank_items(scores, similarities, target_user_index)
    print('Ranking: {}'.format(rank))