【機械学習】カーネル密度推定を使った教師あり学習

# カーネル密度推定を使った教師あり学習

この記事は機械学習の初心者が書いています。
予めご了承ください。

実際に使った例は[こちら](https://qiita.com/sorax/items/644dbba1016af1a7ede5)。
着想の具体的な背景・修正後の内容は[こちら](https://qiita.com/sorax/items/7990f7734aee3861d6c1)。

## What's カーネル密度推定

ぶっちゃけ[WIkipedia](https://ja.wikipedia.org/wiki/%E3%82%AB%E3%83%BC%E3%83%8D%E3%83%AB%E5%AF%86%E5%BA%A6%E6%8E%A8%E5%AE%9A)を見たほうが早い。

簡単なヒストグラムを想像してください。
ヒストグラムが高い部分は全体の中でも***物事が相対的に生じやすく***、低い部分では***相対的に生じにくい***と言えます。
似たような話をどこかで聞いたことがないでしょうか？

これは確率密度関数の考えと同じです。
ヒストグラムとはある意味***真の確率密度関数***を***実測値で推定したもの***なのです。
カーネル関数を使ってもっと連続的に、もっとなめらかに推定する方法が***カーネル密度推定***です。

## What's 教師あり学習
[Wikipedia](https://ja.wikipedia.org/wiki/%E6%95%99%E5%B8%AB%E3%81%82%E3%82%8A%E5%AD%A6%E7%BF%92)先生を見るか、他の方のQiitaを読もう。

## カーネル密度推定と教師あり学習

教師あり学習の「教師」は、「データ」と「正解ラベル」のセットです。

正解ラベルが「0, 1, 2」のデータセットを考えましょう。
これをラベル0のデータ、ラベル1のデータ、ラベル2のデータに分けます。
ここで正解ラベルが0の教師データを使ってカーネル密度推定をすると、「ラベルが0になる」という事象に対する確率密度関数が求まりますね。

すべてのラベルに対して教師データをもとに確率密度関数を求め、テストデータの確率密度を計算します。そして、値の大小で分類してみよう。
というのが今回の試みです。

厳密な話をすると、本当は母集団における各ラベルの割合も計算しないといけませんが……
難しい話はまた今度まとめたいと思います。

## とりあえず実装してみよう

この世界は素晴らしい。
なぜなら、ガウシアンカーネルを用いたカーネル密度推定が[SciPy](https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.gaussian_kde.html)で既に実装されているのだから。

### Gaussian KDEの使い方

SciPyのgaussian_kdeの使い方を簡単にまとめます。

#### カーネル密度推定を行う

~~~python
kernel = gaussian_kde(X, bw_method="scotts_factor", weights="None")
~~~

- X : カーネル密度推定を行うデータセット。
- bw_method : カーネルのバンド幅。指定しない場合はscotts_factor。
- weights : カーネル密度推定を行う際のウェイト。指定しない場合はすべて均等なウェイト。

#### 確率を計算する

推定された確率密度関数に新たなデータを入力して、確率を計算します。

~~~python
pd = kernel.evaluate(Z)
~~~

- Z : 確率を計算したいデータ点(複数可)。

Zの確率が格納されたリスト配列として返されます。

### 教師あり学習を行ってみる

Scikit-learnのirisデータセットで試してみましょう！

流れはこんな感じ
irisデータセット読み込み
→train_test_splitで訓練データとテストデータを分割
→訓練データとテストデータの標準化
→訓練データを使ってラベルごとにカーネル密度推定を実行
→テストデータのラベル別確率密度を計算
→値が一番大きいラベルを出力

↓スクリプト↓

~~~python
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from scipy.stats import gaussian_kde

# irisデータセットの読み込み
iris = datasets.load_iris()
X = iris.data
y = iris.target

# 訓練データ、テストデータの分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3,
random_state=1, stratify=y)

# 標準化
sc = StandardScaler()
sc = sc.fit(X_train)
X_train_std = sc.transform(X_train)
X_test_std = sc.transform(X_test)

# カーネル密度推定
kernel0 = gaussian_kde(X_train_std[y_train==0].T)
kernel1 = gaussian_kde(X_train_std[y_train==1].T)
kernel2 = gaussian_kde(X_train_std[y_train==2].T)

# テストデータの確率密度を計算
p0s = kernel0.evaluate(X_test_std.T)
p1s = kernel1.evaluate(X_test_std.T)
p2s = kernel2.evaluate(X_test_std.T)

# 予測ラベルの出力
y_pred = []
for p0, p1, p2 in zip(p0s, p1s, p2s):
    if max(p0, p1, p2) == p0:
        y_pred.append(0)
    elif max(p0, p1, p2) == p1:
        y_pred.append(1)
    else:
        y_pred.append(2)
~~~

#### 標準化の注意事項
訓練データの平均と標準偏差を用いてテストデータの標準化を行います。
別々に標準化を行うと、データに偏り・ズレが生じてしまうことがあるからです。

#### カーネル密度推定の注意事項
gaussian_kdeにそのままデータセットを読み込ませると、***列ベクトルが1つのデータ***として処理されるようです。
しかしirisデータセットは***行ベクトルが1つのデータ***なので、データを転置します。
テストデータの確率密度を計算する際も同様です。

#### 予測ラベルの出力

~~~python
y_pred = []
for p0, p1, p2 in zip(p0s, p1s, p2s):
    if max(p0, p1, p2) == p0:
        y_pred.append(0)
    elif max(p0, p1, p2) == p1:
        y_pred.append(1)
    else:
        y_pred.append(2)
~~~

テストデータの確率密度は、ラベルごとにp0s、p1s、p2sに格納されます。
それぞれ1つずつ取り出し、

- ラベル0の値が最大なら0
- ラベル1の値が最大なら1
- そうでなければ2

結果をテストデータの順にリストy_predに格納します。

### 結果発表

scikit-learnのaccuracy_scoreで予測ラベルの正解率を確かめよう。
ドキドキ。

~~~python
from sklearn.metrics import accuracy_score
print(accuracy_score(y_test, y_pred))
# 1.0
~~~

わーい。

## 最後に

カーネル密度推定の結果を教師あり学習の分類器にしてみました。
現実には、このような手法はあまり使われていません。
計算量が多くなったり、データによって精度が著しく落ちたりする欠点があるのだと思います。
しかし今回の試行からわかるように、データによっては割と速く、いい感じに分類できるようです。

[その2](https://qiita.com/sorax/items/644dbba1016af1a7ede5)につづく