カーネル密度推定を使った教師あり学習
この記事は機械学習の初心者が書いています。
予めご了承ください。
カーネル密度推定と教師あり学習の関連性
私が勝手に関連付けました。
詳しいことは(あまり詳しくありませんが)前回の記事をご参照ください。
簡単にまとめると「カーネル密度推定を教師あり学習の分類器に使ってみた!」です。
オブジェクト指向
前回の記事でまとめたスクリプトを改造して、オブジェクト指向にしてみました。
名前は「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につづく