協調フィルタリングとは

# はじめに
今回は協調フィルタリング（アイテムベース/ユーザーベース）に関してまとめました。レコメンドシステムの種類に関しては、<a href="https://qiita.com/k-oto/items/7790941656fba0068ed8">こちらの記事</a>を参考にしていただければと思います。

# 協調フィルタリングとは
協調フィルタリングは、複数のユーザーの評価を元に推薦するアイテムを決定する手法です。ここでいう評価には、明示的な評価（5starなどユーザーがつける評価）と、暗黙的な評価（ユーザーの行動履歴から導かれる評価）があります。<br>
協調フィルタリングにはアイテムベースとユーザーベースという2種類があり、それぞれ以下のような推薦の仕方をします。<br>
**アイテムベース：** ユーザーが過去に好んだアイテムと類似度が高いアイテムを推薦
**ユーザーベース：** 嗜好の似ているユーザーが好むアイテムを推薦

## 協調フィルタリングの特徴
- ユーザーの嗜好（行動履歴や評価）が反映されるので、コンテンツベースの推薦手法よりもパーソナライズされる
- 評価値行列さえあればドメイン知識がなくても推薦できる
- 新しい出会いの可能性がある（**セレンディピティ**）
- ユーザー数の多いシステムでないと有用な結果が得られない
- 評価値行列を元に推薦を行うので、新規ユーザーや新商品へのレコメンドが上手く行えない（**コールドスタート問題**）



# アイテムベースの協調フィルタリング
まずは以下の例を使ってアイテムベースの協調フィルタリングをみてみます。


|     |オランダ|スイス|アメリカ|韓国|
|-----|------|-----|-----|-----|
|対象者|  5   |  0  |  0  |  0  |
|user1|  4   |  4  |  0  |  0  | 
|user2|  1   |  0  |  0  |  0  |
|user3|  0   |  2  |  3  |  2  |
|user4|  4   |  3  |  1  |  0  |
|user5|  1   |  0  |  4  |  5  |

6人のユーザーが旅行した4都市を、1〜5で評価しているとします（0は未評価とします）。
**対象者**はオランダのみ評価しており、残りの3つのうち次に対象者に推薦する旅行先を決めるというシチュエーションとします。推薦する旅行先は、他の旅行者1〜5の評価データを用いて決定します。

具体的には、評価データから各都市とオランダの類似度を求め、オランダに近い国を対象者に推薦します（対象者はオランダに最高評価5をつけているため）。この時類似度を求める方法はいくつかあるのですが、今回はコサイン類似度を用います。

$$\cos({x}, {y}) = \frac{{x} \cdot {y}}{|{x}| |{y}|}$$
これがコサイン類似度の計算式なので、試しにオランダとスイスの類似度を計算してみます。

$x = (4, 1, 0, 4, 1)$と$y = (4, 0, 2, 3, 0)$より、
$x \cdot y = 4 \cdot 4 + 4 \cdot 3 = 28 $
$|x| = \sqrt{16 + 1 + 16 + 1} = \sqrt{34}$
$|y| = \sqrt{16 + 4 + 9} = \sqrt{29}$

$$\cos({x}, {y}) = \frac{28}{{\sqrt{34}} {\sqrt{29}}} = 0.8917$$

したがって、オランダとスイスの類似度は0.89です。
他の国に関しても計算してみます。

```python:コード
import numpy as np

def cos_sim(v1, v2):
    return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))

NL = [4,1,0,4,1]
CH = [4,0,2,3,0]
US = [0,0,3,1,4]
KR = [0,0,2,0,5]

print("オランダとオランダの類似度：" , cos_sim(NL, NL) )
print("オランダとスイスの類似度：", cos_sim(NL, CH))
print("オランダとアメリカの類似度：", cos_sim(NL, US))
print("オランダと韓国の類似度：", cos_sim(NL, KR))
```
```pyton:実行結果
オランダとオランダの類似度： 1.0
オランダとスイスの類似度： 0.8917016574178748
オランダとアメリカの類似度： 0.269069117598525
オランダと韓国の類似度： 0.1592324388246205
```
オランダ自身の類似度は1になります。
計算結果からオランダとスイスの類似度が高いことが分かったので、対象者に次の旅行先に推薦するならスイスが良さそうだということが分かります。


# ユーザーベースの協調フィルタリング
続いてユーザーベースの協調フィルタリングに関してみていきます。

|     |itemA|itemB|itemC|itemD|itemE|
|-----|------|-----|-----|-----|----|
|対象者|  5   |  3  |  4  |  4  | ?  |
|user1|  3   |  1  |  2  |  3  | 3  | 
|user2|  4   |  3  |  4  |  3  | 5  |
|user3|  3   |  3  |  1  |  5  | 4  |
|user4|  1   |  5  |  5  |  2  | 1  |

アイテムベースの協調フィルタリングではアイテムの類似度を計算しましたが、今度はユーザーの類似度を計算します。コサイン関数ではなく、ピアソン相関係数を使って類似度を求めます。<br>
$$
r = \frac{\sum{(x-\bar{x})(y-\bar{y})}}{\sqrt{\sum(x-\bar{x})^2}\sqrt{(y-\bar{y})^2}}
$$<br>
相関係数の計算方法はいくつかありますが、今回はscipyのpearsonrを使って計算しました。
この記事にピアソン相関係数の色んな求め方が書かれていたので参考までに。
https://www.st-hakky-blog.com/entry/2018/01/30/004659

```python:コード
from scipy.stats import pearsonr

target = [5,3,4,4]
user1 = [3,1,2,3]
user2 = [4,3,4,3]
user3 = [3,3,1,5]
user4 = [1,5,5,2]

print("targetとuser1の相関係数：", pearsonr(target, user1))
print("targetとuser2の相関係数：", pearsonr(target, user2))
print("targetとuser3の相関係数：", pearsonr(target, user3))
print("targetとuser4の相関係数：", pearsonr(target, user4))
```
```python:実行結果
targetとuser1の相関係数: (0.8528028654224415, 0.14719713457755845)
targetとuser2の相関係数: (0.7071067811865475, 0.29289321881345254)
targetとuser3の相関係数: (0.0, 1.0)
targetとuser4の相関係数: (-0.7921180343813393, 0.20788196561866068)
```
出力されたデータの右側の値は帰無仮説（無相関）とし設定した場合のp値です。今回は単純に左側のピアソン相関係数を見ていただければと思います。対象者はuser1とuser2と相関があると言えます。

ここで、相関のあるuser1とuser2のitemEに対する評価を用いて加重平均を出し、対象者のitemEへの評価を予測します。

```python:コード
print((0.85*3+0.70*5)/(0.85+0.70))
```
```python:実行結果
3.9032258064516134
```
したがって、対象者のitemE対する評価は3.90だと予測できます。
この評価値予測から推薦するのかしないのか、、、という流れになります。


# 最後に
今回は簡単な例を用いて協調フィルタリングに関してまとめました。レコメンドって勉強すればするほど奥が深いなと感じています。何を目的としてレコメンドを行っているか、KPIを何で置いてるか、扱う商材やユーザーによって手法や閾値も変わってくると思います。次回は実際に企業で使われているユースケース等も参考にまとめてみたいです。

# 参照
レコメンドアルゴリズムの基礎と「B-dash」におけるシステム構成の紹介
https://www.slideshare.net/takemikami/bdash<br>
【Python】ピアソンの相関係数をいろいろな方法で計算する方法まとめ(SciPy / Numpy / Pandas)
https://www.st-hakky-blog.com/entry/2018/01/30/004659