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


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

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

前回の記事はこちら
次の記事はこちら

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

私が勝手に関連付けました。
詳しいことは(あまり詳しくありませんが)前回の記事をご参照ください。
簡単にまとめると「カーネル密度推定を教師あり学習の分類器に使ってみた!」です。

オブジェクト指向

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

↓スクリプト↓

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で修正後のコードを公開しています)

__init__メソッド

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

fitメソッド

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

predictメソッド

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

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

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

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

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

wineデータセット編

PCAとの組み合わせ

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

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)

結果は……?

from sklearn.metrics import accuracy_score

print(accuracy_score(y_test, y_pred))
# 0.9722222222222222

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

次元削減なし

どうなるでしょうか。

# 学習と予測
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

結果:同じ。

円型データセット編

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

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()

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

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につづく