pythonで推薦システムを作る

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

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

#参考
[Pythonでレコメンドシステムを作る(ユーザベース協調フィルタリング)](https://ohke.hateblo.jp/entry/2017/09/22/230000)
[Pythonで簡単な協調フィルタリングを実装するためのノート](https://qiita.com/hik0107/items/96c483afd6fb2f077985)
[協調フィルタリングを利用した推薦システム構築](https://www.slideshare.net/masayuki1986/recommendation-ml)

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

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

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

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

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

```math
score= \frac{1}{(1+ユークリッド距離)}
```
・scoreは0から1の値を返し、大きいほうが類似性が高いことを示す

詳しい解説は[協調フィルタリングを利用した推薦システム構築](https://www.slideshare.net/masayuki1986/recommendation-ml)のユーザーベース協調フィルタリングを見てください

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


#データの用意
ここで使うデータは酒の評価と、おつまみを1~5で評価したものになります。
テスト段階なのでデータは4つしか用意していません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/515470/bfcbb966-ad57-92d4-38b8-986928c8c432.png)

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

```python
target_data = [-1, -1, 5.0, -1, 4.0, -1, -1, 1.0, -1, -1, -1] #-1はNONE
```
sake3を選択したときのおつまみ評価の予想値をおつまみ2,5の評価値を用いて予想します。

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

```python
def findSameSakeList(sheet, userSakeReputation):
    SameSakeList = [] #画像の酒の評価が５のデータを二次元リスト化する
    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おつまみデータの類似度を求める

```python
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おつまみの評価値を予測する
評価値の予測は

```math
sampleユーザーの該当おつまみの重み付き評価値 = 類似性 × sample評価値　
```
その後　、評価値の合計をとって正規化する

```math
正規化したscore= \frac{上で求めた重み付き評価値の全ユーザー分の合計点数}{評価者の類似度の合計}
```

```python
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    ) #選んだ酒の評価が５の人のヴァリューのリスト
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]]


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