カーネル密度推定を使った教師あり学習
この記事は機械学習の初心者が書いています。
予めご了承ください。
実際に使った例はこちら。
着想の具体的な背景・修正後の内容はこちら。
What's カーネル密度推定
ぶっちゃけWIkipediaを見たほうが早い。
簡単なヒストグラムを想像してください。
ヒストグラムが高い部分は全体の中でも物事が相対的に生じやすく、低い部分では相対的に生じにくいと言えます。
似たような話をどこかで聞いたことがないでしょうか?
これは確率密度関数の考えと同じです。
ヒストグラムとはある意味真の確率密度関数を実測値で推定したものなのです。
カーネル関数を使ってもっと連続的に、もっとなめらかに推定する方法がカーネル密度推定です。
What's 教師あり学習
Wikipedia先生を見るか、他の方のQiitaを読もう。
カーネル密度推定と教師あり学習
教師あり学習の「教師」は、「データ」と「正解ラベル」のセットです。
正解ラベルが「0, 1, 2」のデータセットを考えましょう。
これをラベル0のデータ、ラベル1のデータ、ラベル2のデータに分けます。
ここで正解ラベルが0の教師データを使ってカーネル密度推定をすると、「ラベルが0になる」という事象に対する確率密度関数が求まりますね。
すべてのラベルに対して教師データをもとに確率密度関数を求め、テストデータの確率密度を計算します。そして、値の大小で分類してみよう。
というのが今回の試みです。
厳密な話をすると、本当は母集団における各ラベルの割合も計算しないといけませんが……
難しい話はまた今度まとめたいと思います。
とりあえず実装してみよう
この世界は素晴らしい。
なぜなら、ガウシアンカーネルを用いたカーネル密度推定がSciPyで既に実装されているのだから。
Gaussian KDEの使い方
SciPyのgaussian_kdeの使い方を簡単にまとめます。
カーネル密度推定を行う
kernel = gaussian_kde(X, bw_method="scotts_factor", weights="None")
- X : カーネル密度推定を行うデータセット。
- bw_method : カーネルのバンド幅。指定しない場合はscotts_factor。
- weights : カーネル密度推定を行う際のウェイト。指定しない場合はすべて均等なウェイト。
確率を計算する
推定された確率密度関数に新たなデータを入力して、確率を計算します。
pd = kernel.evaluate(Z)
- Z : 確率を計算したいデータ点(複数可)。
Zの確率が格納されたリスト配列として返されます。
教師あり学習を行ってみる
Scikit-learnのirisデータセットで試してみましょう!
流れはこんな感じ
irisデータセット読み込み
→train_test_splitで訓練データとテストデータを分割
→訓練データとテストデータの標準化
→訓練データを使ってラベルごとにカーネル密度推定を実行
→テストデータのラベル別確率密度を計算
→値が一番大きいラベルを出力
↓スクリプト↓
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つのデータなので、データを転置します。
テストデータの確率密度を計算する際も同様です。
予測ラベルの出力
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で予測ラベルの正解率を確かめよう。
ドキドキ。
from sklearn.metrics import accuracy_score
print(accuracy_score(y_test, y_pred))
# 1.0
わーい。
最後に
カーネル密度推定の結果を教師あり学習の分類器にしてみました。
現実には、このような手法はあまり使われていません。
計算量が多くなったり、データによって精度が著しく落ちたりする欠点があるのだと思います。
しかし今回の試行からわかるように、データによっては割と速く、いい感じに分類できるようです。
その2につづく