スポンサーリンク

機械学習の分類結果を可視化!決定境界

広告

 前回、機械学習をして学習したアルゴリズムの正答率を出しました。
www.dskomei.com
 しかし、前回の結果からは、テストデータに対してそこそこの正答率を出すのはわかったし、アルゴリズムによって正答率に違いがあるのもわかりましたが、各々のアルゴリズムが学習した結果、どのように分類しているかがわかりません。ずばり言い当てると噂の占い師がこれまで99.99%当ててきたと言ったとしても信用しがたいですよね。実際のこれまで行った占いとその結果も併せて教えてもらえば信用が高まりますよね。

 今回は、学習したアルゴリズムがどのように分類しているかを可視化したいと思います。これにより機械学習による分類へのイメージがより鮮明になりますし、データの中でどの部分が学習できていないかもわかります。また、各クラスわける境目のことを決定境界と呼びます。

 まずはコードを見たいという方は今回のコードをクリックしてください。

これを読み終えると
  • 学習した機械学習のアルゴリズムの分類の様子の図示の方法がわかる
  • 決定境界が何かを理解できる
  • 決定境界を描けるようになる


決定境界とは何かを見てみる

  決定境界の説明の前に実装した結果の画像をさっそく見てみます。今回もおなじみの「iris」のデータを使っての分類を行っています。

f:id:dskomei:20180228214011p:plain
様々なアルゴリズムの決定境界
  この図を見てわかる通り、決定境界とは分類クラスの境目のことです。それぞれのクラスに正しく分類しているのが良い決定境界であり、機械学習アルゴリズムは良い決定境界を作るために学習しています。上の図を見てみると、決定境界と一言で言ってもアルゴリズムによって描き方が様々であり、大きな特徴としては、ロジスティック回帰・決定木の境界線は直線であるのに対して、K近傍探索・SVM・ランダムフォレストは曲線です。また、ランダムフォレストは前回の測定では過学習しているのではないかということでしたが、今回の結果からもその様子が伺えます。テスト勉強で丸暗記していって、テストでは全く異なる問題がでてきて対応できないタイプです。それぞれの特徴があって面白いですね。

決定境界を描くまでの流れ
  1. プロットを目視できるようにデータを被説明変数と説明変数2変数分のみを抽出
  2. 1.のデータを使ってモデルを学習
  3. 1.の説明変数に合わせた細かい入力データを作り、学習したモデルで分類をする
    これをプロットすることで、色別れの領域を描ける
  4. 1.のデータを3.に重ねる


決定境界を描く部分に焦点を当てて説明

 見出し決定境界を描くまでの流れの3.、4.の部分を先に説明します。第一に、今回は3クラスの分類のため3種類のマーカと色を用意しています。

import numpy as np
from matplotlib.colors import ListedColormap
import matplotlib.pyplot as plt
markers = ('s', 'x', 'o')
cmap = ListedColormap(('red', 'blue', 'green'))

 次に決定境界を描くためのデータを用意します。決定境界を描くためにはプロットする入力データが取り得る範囲をクラスごとに色付けをしていく必要があります。そのため、入力データを満たす領域を描けるような細かなデータを作成します。その方法がメッシュデータです。グラフを格子状に区切った際の交点のことです。この交点に高さを加えることで等高線を引くことができます。それではメッシュデータを作成してみます。与えられたデータの最小値と最大値までのメッシュデータを0.01刻みで作っています。\(x\)は2変数の入力データのデータフレームです。

x1_min, x1_max = x[:, 0].min()-1, x[:, 0].max()+1
x2_min, x2_max = x[:, 1].min()-1, x[:, 1].max()+1
x1_mesh, x2_mesh = np.meshgrid(np.arange(x1_min, x1_max, 0.01),
                                   np.arange(x2_min, x2_max, 0.01))

 メッシュデータの作成において簡単に試してみます。下の例では0~5の1刻みの連番においてメッシュデータを作成しています。出力した結果を見ると0~5の1刻みの2次元マップの交点ができていることがわかります。

import numpy as np
x = np.arange(0, 5)
x1_mesh, x2_mesh = np.meshgrid(x,x)
print(x1_mesh.ravel())
print(x2_mesh.ravel())
[0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4]
[0 0 0 0 0 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 4 4 4 4 4]

 メッシュデータのすべてに対して学習したモデルで分類をしています。これによりメッシュデータに分類結果の高さを加えることで、クラスごとの色分けを行うことができます。

z = model.predict(np.array([x1_mesh.ravel(), x2_mesh.ravel()]).T)
z = z.reshape(x1_mesh.shape)

 matplotlibのcontourfを使って決定境界を描いています。contourfはz軸の範囲ごとに色を割り当てて描いており、contour(fをとると)にすると、色を塗らずにプロットするだけになるので決定境界線を描けます。

plt.contourf(x1_mesh, x2_mesh, z, alpha=0.4, cmap=cmap)
plt.xlim(x1_mesh.min(), x1_mesh.max())
plt.ylim(x2_mesh.min(), x2_mesh.max())

 上記までで決定境界面の色付けは終わり、残りは入力データをその決定境界面にプロットするのみとなりました。各クラスごとに上で指定したマーカ/色の種類でプロットします。\(y\)は入力データに対応した教師データです。

for idx, cl in enumerate(np.unique(y)):
    plt.scatter(x=x[y == cl, 0],
                y=x[y == cl, 1],
                alpha=0.6,
                c=cmap(idx),
                edgecolors='black',
                marker=markers[idx],
                label=cl)


今回のコード
import numpy as np
from sklearn import datasets
from matplotlib.colors import ListedColormap
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
import math


#
# 決定境界プロット関数
#
def plot_decision_regions(x, y, model, resolution=0.01):

    ## 今回は被説明変数が3クラスのため散布図のマーカータイプと3種類の色を用意
    ## クラスの種類数に応じて拡張していくのが良いでしょう
    markers = ('s', 'x', 'o')
    cmap = ListedColormap(('red', 'blue', 'green'))

    ## 2変数の入力データの最小値から最大値まで引数resolutionの幅でメッシュを描く
    x1_min, x1_max = x[:, 0].min()-1, x[:, 0].max()+1
    x2_min, x2_max = x[:, 1].min()-1, x[:, 1].max()+1
    x1_mesh, x2_mesh = np.meshgrid(np.arange(x1_min, x1_max, resolution),
                                   np.arange(x2_min, x2_max, resolution))

    ## メッシュデータ全部を学習モデルで分類
    z = model.predict(np.array([x1_mesh.ravel(), x2_mesh.ravel()]).T)
    z = z.reshape(x1_mesh.shape)

    ## メッシュデータと分離クラスを使って決定境界を描いている
    plt.contourf(x1_mesh, x2_mesh, z, alpha=0.4, cmap=cmap)
    plt.xlim(x1_mesh.min(), x1_mesh.max())
    plt.ylim(x2_mesh.min(), x2_mesh.max())

    for idx, cl in enumerate(np.unique(y)):
        plt.scatter(x=x[y == cl, 0],
                    y=x[y == cl, 1],
                    alpha=0.6,
                    c=cmap(idx),
                    edgecolors='black',
                    marker=markers[idx],
                    label=cl)

#
# データの取得
#
data = datasets.load_iris()
x_data = data.data
y_data = data.target

# 2変数だけを抽出
x_data = x_data[:, [0,1]]

# 入力データの各変数が平均0,標準偏差1になるように正規化
# 各アルゴリズムのプロット結果を比較しやすいように予め全入力データを正規化
sc = StandardScaler()
sc.fit(x_data)
x_data = sc.transform(x_data)


# データを学習用/テスト用に分割している
x_train, x_test, y_train, y_test = train_test_split(x_data,
                                                    y_data,
                                                    test_size=0.2)

#
# 機械学習アルゴリズムの定義
#
lr = LogisticRegression(C=10)
knn = KNeighborsClassifier(n_neighbors=5)
svm = SVC(kernel='rbf', C=1.0)
dc = DecisionTreeClassifier(criterion='entropy', max_depth=3)
rf = RandomForestClassifier(criterion='entropy',
                            n_estimators=10)

models = [lr, knn, svm, dc, rf]
model_names = ['logistic regression',
               'k nearest neighbor',
               'svm',
               'decision tree',
               'random forest']

#
# それぞれのモデルにおいて決定境界をプロット
#
plt.figure(figsize=(8,6))
plot_num = 1
for model_name, model in zip(model_names, models):

    plt.subplot(math.ceil(len(models)/2), 2, plot_num)
    # モデルの学習
    model.fit(x_train, y_train)
    # 決定境界をプロット
    plot_decision_regions(x_data, y_data, model)
    plt.title(model_name)
    plot_num += 1

plt.tight_layout()
plt.savefig('./images/decision_region.png')
plt.show()



 決定境界に関して様々なアルゴリズムで記載されています。更にデータの前処理からkerasを使ってのディープラーニングの作り方まで、この本を読めば機械学習を一通り抑えられます。

[第2版]Python 機械学習プログラミング 達人データサイエンティストによる理論と実践 (impress top gear)

終わりに

 正答率を追うよりもグラフのをの方が惹きつけられますよね。
グラフ結果まで載せられるように今後とも頑張ります!
 説明変数が2変数より多い場合には、説明変数を2変数に絞るにあたってどれを選べばいいのかは悩ましい問題です。飲み会で誰の隣になると楽しいかを見極めるのは難しいですよね。そんな感じです。それはさておき、変数選択の方法は色々あるので、勉強しながら今後まとめていきたいと思います。