pythonで推薦システムを作る


概要

ハッカソンで推薦システムを作ることになったので、備忘録的にメモしておきます。

お酒に合ったおつまみを推薦するシステムを作る。
機械学習的なモデルにしたかったが、知識と時間が足りないため協調フィルタリングを用ることとした。

参考

Pythonでレコメンドシステムを作る(ユーザベース協調フィルタリング)
Pythonで簡単な協調フィルタリングを実装するためのノート
協調フィルタリングを利用した推薦システム構築

協調フィルタリング

協調フィルタリング(レコメンドシステム)とは一言で言うと
「あなたへのおすすめ」「この商品を買った人はこんな商品も買っています」
のアレです。amazonやyoutubeでおなじみですね。

ユーザーベースと、アイテム(商品)ベースとの2つの方法に大別される。
ユーザーベースの強調フィルタリングは、他ユーザーの既知の評価からtargetユーザーの評価(未知)を推定するものです。

クロス集計、ユークリッド距離、ピアソン相関係数、

類似性の算出方法

類似性を「同じ商品への評価が高い(低い)と類似性が高い」と定義する。
各ユーザーの商品に対する評価値を用いて類似性を算出する

・類似しているものほどユークリッド距離の値が小さくなる
・逆数をとって、類似しているものほど高いスコアになるようにする
・類似性が最大の場合はユークリッド距離が0なので、1を足す

score= \frac{1}{(1+ユークリッド距離)}

・scoreは0から1の値を返し、大きいほうが類似性が高いことを示す

詳しい解説は協調フィルタリングを利用した推薦システム構築のユーザーベース協調フィルタリングを見てください

実装の流れ

①エクセルデータから、target酒の評価が5の人を抜きだす(sampleおつまみデータ)
②targetおつまみの評価(既知の分のみ)とsampleおつまみデータの類似度を求める
③類似度から未知の分のtargetおつまみの評価値を予測する

データの用意

ここで使うデータは酒の評価と、おつまみを1~5で評価したものになります。
テスト段階なのでデータは4つしか用意していません。

未知ユーザーのデータは以下のように定義しました。このユーザーの他の値の予想値を求めます。

target_data = [-1, -1, 5.0, -1, 4.0, -1, -1, 1.0, -1, -1, -1] #-1はNONE

sake3を選択したときのおつまみ評価の予想値をおつまみ2,5の評価値を用いて予想します。

①エクセルデータから、target酒の評価が5の人を抜きだす(sampleおつまみデータ)

def findSameSakeList(sheet, userSakeReputation):
    SameSakeList = [] #画像の酒の評価が5のデータを二次元リスト化する
    sampleLen = len(sheet.col_values(0))-1
    for i in range(sampleLen):
        row = sheet.row_values(i+1)
        if row[userSakeReputation] == 5:
            SameSakeList.append(row)
        else:
            pass
    return SameSakeList

sake_number = 2
wb = xlrd.open_workbook(r'C:\Users\daisuke\Desktop\voyage\testdata.xlsx')
sheet = wb.sheet_by_index(0)
samePersonList = findSameSakeList(sheet, sake_number) #該当する

②targetおつまみの評価(既知の分のみ)とsampleおつまみデータの類似度を求める

def get_similarities(samePersonList, target_data):
    similarities = []
    sampleLen = len(samePersonList)

    for j in range(sampleLen):#sheetのrow番号
        distance_list = []
        for i, value in enumerate(target_data):
            if value == -1:
                pass
            else:
                distance = value - samePersonList[j][i]
                distance_list.append(pow(distance, 2))

        similarities.append([j, 1/(1+np.sqrt(sum(distance_list)))])

    return sorted(similarities, key=lambda s: s[1], reverse=True)

③類似度から未知の分のtargetおつまみの評価値を予測する

評価値の予測は

sampleユーザーの該当おつまみの重み付き評価値 = 類似性 × sample評価値 

その後 、評価値の合計をとって正規化する

正規化したscore= \frac{上で求めた重み付き評価値の全ユーザー分の合計点数}{評価者の類似度の合計}
def predict(samePersonList, similarities):#全samepersonに対して類似度×評価値をして予測評価値を出す
    predict_list = []
    for index, value in similarities:
        samePersonList[index] = [round(i*value,5) for i in samePersonList[index]] #round で小数を丸める

    np_samePerson = np.array(samePersonList)
    np_samePerson = list(np.mean(np_samePerson, axis=0))

    for index, value in enumerate(np_samePerson):
        predict_list.append([index, value])
    return sorted(predict_list, key= lambda s: s[1], reverse=True)


samePersonList = findSameSakeList(sheet, sake_number    ) #選んだ酒の評価が5の人のヴァリューのリスト
similarities = get_similarities(samePersonList, target_data)
ranking = predict(samePersonList, similarities)
pprint.pprint(ranking)

 #正規化したsocreをランキング付けして出力
 #[[2, 1.225635],
 #[5, 1.100635],
 #[3, 0.850635],
 #[1, 0.745125],
 #[4, 0.725635],
 #[8, 0.620125],
 #[9, 0.6103799999999999],
 #[7, 0.605505],
 #[0, 0.48538],
 #[6, 0.125],
 #[10, 0.0]]


酒も一緒に予想評価を出してます