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

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

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

前回の記事は[こちら](https://qiita.com/sorax/items/8663906fae41798a00b8)。
次の記事は[こちら](https://qiita.com/sorax/items/7990f7734aee3861d6c1)。

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

私が勝手に関連付けました。
詳しいことは（あまり詳しくありませんが）[前回の記事](https://qiita.com/sorax/items/8663906fae41798a00b8)をご参照ください。
簡単にまとめると「カーネル密度推定を教師あり学習の分類器に使ってみた！」です。

## オブジェクト指向

[前回の記事](https://qiita.com/sorax/items/8663906fae41798a00b8)でまとめたスクリプトを改造して、オブジェクト指向にしてみました。
名前は「Gaussian kernel-density estimate classifier(ガウスカーネル密度推定分類器)」、略して「GKDEClassifier」です。いま勝手に名付けました。

↓スクリプト↓

~~~python
import numpy as np

class GKDEClassifier(object):
    
    def __init__(self, bw_method="scotts_factor", weights="None"):
        # カーネルのバンド幅
        self.bw_method = bw_method
        # カーネルのウェイト
        self.weights = weights
        
    def fit(self, X, y):
        # yのラベル数
        self.y_num = len(np.unique(y))
        # 推定した確率密度関数を格納するリスト
        self.kernel_ = []
        # 確率密度関数を格納
        for i in range(self.y_num):
            kernel = gaussian_kde(X[y==i].T)
            self.kernel_.append(kernel)
        return self

    def predict(self, X):
        # 予測ラベルを格納するリスト
        pred = []
        # テストデータのラベル別確率を格納するリスト
        self.p_ = []
        # ラベル別確率を格納
        for i in range(self.y_num):
            self.p_.append(self.kernel_[i].evaluate(X.T).tolist())
        # ndarray化
        self.p_ = np.array(self.p_)
        # 予測ラベルの割り振り
        for j in range(self.p_.shape[1]):
            pred.append(np.argmax(self.p_.T[j]))
        return pred
~~~

ラベルは0, 1, 2...(非負整数の小さい順)に割り振ってください。
もしかして：LabelEncoder

(2020/8/5 追記：[その3](https://qiita.com/sorax/items/7990f7734aee3861d6c1)で修正後のコードを公開しています)

### \_\_init\_\_メソッド

オブジェクトの初期化を行います。
ここではカーネル密度推定で必要なパラメータ、つまりSciPyのgaussian_kdeの初期化に必要な引数を指定します。
今回はgaussian_kdeのデフォルト値と同じ値を設定しました。

### fitメソッド

教師データを用いて学習を行います。
gaussian_kdeでカーネル密度推定を行ったあと、ラベル0の推定密度関数、ラベル1の推定密度関数……を順に格納していきます。

### predictメソッド

テストデータの予測を行います。

~~~python
for i in range(self.y_num):
            self.p_.append(self.kernel_[i].evaluate(X.T).tolist())
~~~

ここでは、kernel_から推定密度関数を一つずつ取り出し、テストデータの確率密度を計算しています。

その後のスクリプトが何やらごちゃごちゃしています。もっと簡潔に書きたかったのですが、なかなか思い通りの挙動をしなくて……
コーディング初心者あるある。動けばよい。動くだけマシ。

というわけでオブジェクト指向のガウスカーネル密度推定分類器、完成です。

## wineデータセット編

### PCAとの組み合わせ
wineデータセットには特徴量が13個ありますが、標準化したあと4次元まで次元削減します。
次元削減後のデータで学習と分類を行ってみましょう。

~~~python
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA

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

# データの分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,
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)

# 次元削減
pca = PCA(n_components=4)
X_train_pca = pca.fit_transform(X_train_std)
X_test_pca = pca.transform(X_test_std)

# 学習と予測
f = GKDEClassifier()
f.fit(X_train_pca, y_train)
y_pred = f.predict(X_test_pca)
~~~

結果は……？

~~~python
from sklearn.metrics import accuracy_score

print(accuracy_score(y_test, y_pred))
# 0.9722222222222222
~~~

わーい。
テストデータは36個ですから、正解率は35/36。なかなかです。

### 次元削減なし

どうなるでしょうか。

~~~python
# 学習と予測
f = GKDEClassifier()
f.fit(X_train_std, y_train)
y_pred = f.predict(X_test_std)

print(accuracy_score(y_test, y_pred))
# 0.9722222222222222
~~~

結果：同じ。

## 円型データセット編

円型のデータセットを作ってみました。

~~~python
from sklearn.datasets import make_circles
from matplotlib import pyplot as plt

X, y = make_circles(n_samples=1000, random_state=1, noise=0.1, factor=0.2)
plt.scatter(X[y==0, 0], X[y==0, 1], c="red", marker="^", alpha=0.5)
plt.scatter(X[y==1, 0], X[y==1, 1], c="blue", marker="o", alpha=0.5)
plt.show()
~~~
![gkde_c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640000/0793a130-bba5-c99c-b977-8ef9c5543a68.png)

中心部と外縁部でラベルが異なります。
正しく分類できるかな？

~~~python
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3,
random_state=1, stratify=y)

f = GKDEClassifier()
f.fit(X_train, y_train)
y_pred = f.predict(X_test)

print(accuracy_score(y_test, y_pred))
# 0.9933333333333333
~~~

結論：大勝利。

## 最後に

ここまでいい感じに分類できていますが、実は大事なことを忘れています。
それは、この分類方法の学術的な正しさです。次回はその議論をしようと思います。

[その3](https://qiita.com/sorax/items/7990f7734aee3861d6c1)につづく