Keras : Ex-Tutorials : Keras でアヤメ分類 (scikit-learn との比較)

Keras : Ex-Tutorials : Keras でアヤメ分類 (scikit-learn との比較) (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 06/17/2018 (2.2.0)

* 本ページは、Keras 開発チーム推奨の外部チュートリアル・リソースの一つ : `”Hello world” in keras` を
翻訳した上でまとめ直して補足説明したものです:

* サンプルコードの動作確認はしておりますが、適宜、追加改変している場合もあります。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

本文

Keras は高位ニューラルネット・ライブラリで、TensorFlow バックエンド回りの (scikit-learn の API に類似した) API をラップします。scikit-learn との類似性を考えて、そして今ではニューラルネット設計とテストに総ての人がアクセス可能であることを強調するために、scikit-learn と明示的に比較することにより Keras をどのように使用するかの簡単なチュートリアルとします。

Scikit-learn は Python 開発者により使用される最もポピュラーな古典的機械学習ライブラリです。Scikit-learn についての多くの素晴らしいものの中でも特筆すべきはその単純で首尾一貫した API で、これは Estimator オブジェクト回りに構築されています。この API は機械学習ワークフローの良い記述で、それはパッケージを通して首尾一貫して適用されます。

必要なライブラリをインポートすることから始めます : scikit-learn、Keras そして幾つかのプロット用ライブラリです :

import seaborn as sns
import numpy as np

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegressionCV

from keras.models import Sequential
from keras.layers.core import Dense, Activation
from keras.utils import np_utils
Using TensorFlow backend.

 

アイリス・データ

有名な アイリス・データセット (published by Ronald Fisher in 1936) は機械学習フレームワークの API をデモする良い方法です。ある意味、それは機械学習の “Hello world” です。

データは単純で、極めて単純な分類器で高い精度を得ることが可能です。従ってこの問題を解くためにニューラルネットを使用することは大仰です。しかしこれで良いのです!目標は、モデル設計や選択の詳細ではなくデータから動作する分類器を得るために必要なコードを探検することです。

アイリス・データセットは多くの機械学習ライブラリに組み込まれています。seaborn のコピーは容易に可視化可能なラベル付けられた dataframe として扱われますので、seaborn を使用します、さてそれをそこからロードして最初の 5 サンプルを見てみましょう。

iris = sns.load_dataset("iris")
iris.head()

 
各サンプル (i.e., 花) について、データの 5 つのピースがあります。4 つは花のサイズ (センチ) の標準的寸法で、5 番目はアイリスの種です。3 つの種: セトサ、バージカラー、バージニカがあります。私達のジョブは 2 つの花弁 (= petal) と 2 つのがく片 (= sepal) の寸法が与えられたとき、アイリスの種を予測できる分類器を構築することです。モデル構築を始める前に素早い可視化を行なってみましょう (常に良い考えです!) :

sns.pairplot(iris, hue='species');

 

訓練とテストのためにデータをマンジングして分割する

最初にアイリス dataframe から生データを引っ張り出す必要があります。花弁とがく片データを配列 X に、種のラベルを対応する配列 y に保持します。

X = iris.values[:, :4]
y = iris.values[:, 4]

教師あり機械学習では標準的ですが、データの幾つかで訓練して残りでモデルのパフォーマンスを測定します。これは手動で行なうのも単純ですが、train_test_split() 関数のように scikit-learn にも組み込まれています。

train_X, test_X, train_y, test_y = train_test_split(X, y, train_size=0.5, test_size=0.5, random_state=0)

 

scikit-learn 分類器を訓練する

ロジスティック回帰分類器を訓練します。組み込みハイパーパラメータ交差検証でこれを行なうことは scikit-learn で 1 行です。総ての scikit-learn Estimator オブジェクトのように、LogisticRegressionCV 分類器は .fit() メソッドを持ちます、これは訓練データのベストに適合するモデルパラメータを学習する不愉快な (= gory) 数値的詳細をケアします。そこでそのメソッドが行なうことの総てです :

lr = LogisticRegressionCV()
lr.fit(train_X, train_y)
LogisticRegressionCV(Cs=10, class_weight=None, cv=None, dual=False,
           fit_intercept=True, intercept_scaling=1.0, max_iter=100,
           multi_class='ovr', n_jobs=1, penalty='l2', random_state=None,
           refit=True, scoring=None, solver='lbfgs', tol=0.0001, verbose=0)

 

精度を用いて分類器にアクセス

今テストセットの断片を訓練された分類器が正しく分類するか (i.e., 精度) を測定できます。

print("Accuracy = {:.2f}".format(lr.score(test_X, test_y)))
Accuracy = 0.83

 

今 Keras で非常に類似したことを行ないます

丁度見たように、scikit-learn は分類器の構築を非常に単純にします :

  • 分類器をインスタンス化するために 1 行。
  • それを訓練するために 1 行。
  • そしてそのパフォーマンスを測定するために 1 行。

分類器の構築は Keras ではほんの少しだけ複雑です。データマンジングは少しだけ変わり、ネットワークをそれを分類器としてインスタンス化する前に何某かのワークを行なう必要がありますが、それ以外は scikit-learn で作業することに非常に似ています。

最初にデータマンジングを少々: scikit-learn の分類器は文字列ラベル e.g., “setosa” を受け取りました。しかし Keras は one-hot エンコードされていることを必要とします。これは次のように見えるデータを :

setosa
versicolor
setosa
virginica
...

次のように見えるテーブルに変換する必要があることを意味します :

setosa versicolor virginica
     1          0         0
     0          1         0
     1          0         0
     0          0         1

これを行なう多くの方法があります。もし貴方が pandas に慣れていれば pandas.get_dummies() があり、one-hot-encoding は scikit-learn にあります。ここでは単に Keras ユティリティと numpy を使用します。

def one_hot_encode_object_array(arr):
    '''One hot encode a numpy array of objects (e.g. strings)'''
    uniques, ids = np.unique(arr, return_inverse=True)
    return np_utils.to_categorical(ids, len(uniques))

train_y_ohe = one_hot_encode_object_array(train_y)
test_y_ohe = one_hot_encode_object_array(test_y)

 

ニューラルネットワーク・モデルを構築する

この特定のケースで必要とされるデータ・マンジングを別にすれば、Keras で作業する最も本質的で重要な違いはモデルの構造をそれをインスタンス化して使用する前に指定しなければならないことです。

scikit-learn ではモデルは既成品です。しかし Keras はニューラルネットワーク・ネットワーク・ライブラリです。そのようなものとして、データの特徴/クラスの数が制約を与える一方で、モデル構造の総ての他の局面を決定することができます : 層の数、層のサイズ、層の間の接続の性質、etc.

私達のケースでは、極めて単純なネットワークを構築します。データにより 2 つの選択が成されます。4 つの特徴と 3 つのクラスを持ちますので、入力層は 4 ユニットを、出力層は 3 ユニットを持たなければなりません。隠れ層だけを定義しなければなりません。このプロジェクトのためには1 つの隠れ層だけを持ち、それに 16 ユニットを与えます。

モデルを最も一般的な方法で定義します : 層のシーケンシャルなスタックとして :

model = Sequential()

次の 2 行は入力層サイズ (input_shape=(4,) と隠れ層のサイズと活性化関数を定義します。

model.add(Dense(16, input_shape=(4,)))
model.add(Activation('sigmoid'))

… そして次の行は出力層のサイズと活性化関数を定義します。

model.add(Dense(3))
model.add(Activation('softmax'))

最後に最適化するために最適化ストラテジーと損失関数を指定します。またモデルにそれが動作しているときに精度を計算することも指示します。

model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=["accuracy"])

 

ニューラルネットワーク分類器を使用する

構造 model を定義してそれをコンパイルしましたので、API が scikit-learn の分類器に殆ど同一のオブジェクトを持ちました。特に、それは .fit() と .predict() メソッドを持ちます。fit しましょう。

ニューラルネットワークの訓練はしばしば “ミニバッチ処理” の概念を伴います、これはデータのサブセットをネットワークに見せて、重みを調整し、そしてそれからデータの他のサブセットに見せることを意味します。ネットワークが総てのデータを一度見たとき、それは “エポック” と呼ばれます。

ミニバッチ/エポック・ストラテジーの調整は幾分問題に依りますが、この場合 1 のミニバッチを使用します。これはそれを効率的に良くて古い確率的勾配降下にします、i.e. データはネットワークに一度に 1 つの花が見せられ、重みは直ちに調整されます。

model.fit(train_X, train_y_ohe, epochs=100, batch_size=1, verbose=1);
Epoch 1/100
75/75 [==============================] - 0s 3ms/step - loss: 0.1238 - acc: 0.9867
Epoch 2/100
75/75 [==============================] - 0s 832us/step - loss: 0.1254 - acc: 0.9467
Epoch 3/100
75/75 [==============================] - 0s 865us/step - loss: 0.1272 - acc: 0.9600
Epoch 4/100
75/75 [==============================] - 0s 647us/step - loss: 0.1188 - acc: 0.9867
Epoch 5/100
75/75 [==============================] - 0s 684us/step - loss: 0.1216 - acc: 0.9600
Epoch 6/100
75/75 [==============================] - 0s 675us/step - loss: 0.1192 - acc: 0.9600
Epoch 7/100
75/75 [==============================] - 0s 669us/step - loss: 0.1152 - acc: 0.9733
Epoch 8/100
75/75 [==============================] - 0s 696us/step - loss: 0.1220 - acc: 0.9867
Epoch 9/100
75/75 [==============================] - 0s 715us/step - loss: 0.1164 - acc: 0.9733
Epoch 10/100
75/75 [==============================] - 0s 791us/step - loss: 0.1148 - acc: 0.9600
Epoch 11/100
75/75 [==============================] - 0s 659us/step - loss: 0.1145 - acc: 0.9733
Epoch 12/100
75/75 [==============================] - 0s 763us/step - loss: 0.1130 - acc: 0.9733
Epoch 13/100
75/75 [==============================] - 0s 660us/step - loss: 0.1086 - acc: 0.9733
Epoch 14/100
75/75 [==============================] - 0s 687us/step - loss: 0.1074 - acc: 0.9867
Epoch 15/100
75/75 [==============================] - 0s 743us/step - loss: 0.1117 - acc: 0.9867
Epoch 16/100
75/75 [==============================] - 0s 714us/step - loss: 0.1058 - acc: 0.9600
Epoch 17/100
75/75 [==============================] - 0s 704us/step - loss: 0.1048 - acc: 0.9867
Epoch 18/100
75/75 [==============================] - 0s 724us/step - loss: 0.1035 - acc: 0.9867
Epoch 19/100
75/75 [==============================] - 0s 899us/step - loss: 0.1053 - acc: 0.9600
Epoch 20/100
75/75 [==============================] - 0s 857us/step - loss: 0.1036 - acc: 0.9733
Epoch 21/100
75/75 [==============================] - 0s 766us/step - loss: 0.1013 - acc: 0.9733
Epoch 22/100
75/75 [==============================] - 0s 732us/step - loss: 0.0995 - acc: 0.9733
Epoch 23/100
75/75 [==============================] - 0s 769us/step - loss: 0.0989 - acc: 0.9733
Epoch 24/100
75/75 [==============================] - 0s 663us/step - loss: 0.0997 - acc: 0.9733
Epoch 25/100
75/75 [==============================] - 0s 656us/step - loss: 0.0980 - acc: 0.9733
Epoch 26/100
75/75 [==============================] - 0s 694us/step - loss: 0.0992 - acc: 0.9867
Epoch 27/100
75/75 [==============================] - 0s 738us/step - loss: 0.0991 - acc: 0.9733
Epoch 28/100
75/75 [==============================] - 0s 753us/step - loss: 0.0967 - acc: 0.9600
Epoch 29/100
75/75 [==============================] - 0s 832us/step - loss: 0.0942 - acc: 0.9867
Epoch 30/100
75/75 [==============================] - 0s 869us/step - loss: 0.1007 - acc: 0.9733
Epoch 31/100
75/75 [==============================] - 0s 749us/step - loss: 0.0955 - acc: 0.9867
Epoch 32/100
75/75 [==============================] - 0s 786us/step - loss: 0.0917 - acc: 0.9867
Epoch 33/100
75/75 [==============================] - 0s 717us/step - loss: 0.0911 - acc: 0.9733
Epoch 34/100
75/75 [==============================] - 0s 743us/step - loss: 0.0931 - acc: 0.9733
Epoch 35/100
75/75 [==============================] - 0s 684us/step - loss: 0.0893 - acc: 0.9600
Epoch 36/100
75/75 [==============================] - 0s 764us/step - loss: 0.0913 - acc: 0.9867
Epoch 37/100
75/75 [==============================] - 0s 683us/step - loss: 0.0913 - acc: 0.9733
Epoch 38/100
75/75 [==============================] - 0s 737us/step - loss: 0.0908 - acc: 0.9733
Epoch 39/100
75/75 [==============================] - 0s 713us/step - loss: 0.0890 - acc: 0.9733
Epoch 40/100
75/75 [==============================] - 0s 771us/step - loss: 0.0903 - acc: 0.9733
Epoch 41/100
75/75 [==============================] - 0s 687us/step - loss: 0.0872 - acc: 0.9867
Epoch 42/100
75/75 [==============================] - 0s 741us/step - loss: 0.0865 - acc: 0.9600
Epoch 43/100
75/75 [==============================] - 0s 654us/step - loss: 0.0859 - acc: 1.0000
Epoch 44/100
75/75 [==============================] - 0s 683us/step - loss: 0.0878 - acc: 0.9600
Epoch 45/100
75/75 [==============================] - 0s 784us/step - loss: 0.0833 - acc: 0.9867
Epoch 46/100
75/75 [==============================] - 0s 730us/step - loss: 0.0859 - acc: 0.9867
Epoch 47/100
75/75 [==============================] - 0s 747us/step - loss: 0.0850 - acc: 0.9867
Epoch 48/100
75/75 [==============================] - 0s 683us/step - loss: 0.0826 - acc: 0.9867
Epoch 49/100
75/75 [==============================] - 0s 740us/step - loss: 0.0830 - acc: 0.9733
Epoch 50/100
75/75 [==============================] - 0s 825us/step - loss: 0.0853 - acc: 0.9733
Epoch 51/100
75/75 [==============================] - 0s 699us/step - loss: 0.0860 - acc: 0.9867
Epoch 52/100
75/75 [==============================] - 0s 662us/step - loss: 0.0797 - acc: 0.9600
Epoch 53/100
75/75 [==============================] - 0s 738us/step - loss: 0.0809 - acc: 0.9867
Epoch 54/100
75/75 [==============================] - 0s 673us/step - loss: 0.0812 - acc: 0.9733
Epoch 55/100
75/75 [==============================] - 0s 678us/step - loss: 0.0853 - acc: 0.9733
Epoch 56/100
75/75 [==============================] - 0s 689us/step - loss: 0.0815 - acc: 0.9600
Epoch 57/100
75/75 [==============================] - 0s 658us/step - loss: 0.0791 - acc: 0.9733
Epoch 58/100
75/75 [==============================] - 0s 656us/step - loss: 0.0793 - acc: 0.9867
Epoch 59/100
75/75 [==============================] - 0s 668us/step - loss: 0.0795 - acc: 0.9733
Epoch 60/100
75/75 [==============================] - 0s 697us/step - loss: 0.0777 - acc: 0.9733
Epoch 61/100
75/75 [==============================] - 0s 675us/step - loss: 0.0760 - acc: 0.9867
Epoch 62/100
75/75 [==============================] - 0s 648us/step - loss: 0.0744 - acc: 0.9867
Epoch 63/100
75/75 [==============================] - 0s 659us/step - loss: 0.0758 - acc: 0.9867
Epoch 64/100
75/75 [==============================] - 0s 646us/step - loss: 0.0749 - acc: 1.0000
Epoch 65/100
75/75 [==============================] - 0s 675us/step - loss: 0.0749 - acc: 0.9733
Epoch 66/100
75/75 [==============================] - 0s 711us/step - loss: 0.0800 - acc: 0.9733
Epoch 67/100
75/75 [==============================] - 0s 664us/step - loss: 0.0794 - acc: 0.9867
Epoch 68/100
75/75 [==============================] - 0s 699us/step - loss: 0.0787 - acc: 0.9600
Epoch 69/100
75/75 [==============================] - 0s 650us/step - loss: 0.0717 - acc: 0.9867
Epoch 70/100
75/75 [==============================] - 0s 654us/step - loss: 0.0766 - acc: 0.9867
Epoch 71/100
75/75 [==============================] - 0s 872us/step - loss: 0.0738 - acc: 0.9867
Epoch 72/100
75/75 [==============================] - 0s 783us/step - loss: 0.0724 - acc: 0.9867
Epoch 73/100
75/75 [==============================] - 0s 743us/step - loss: 0.0723 - acc: 0.9867
Epoch 74/100
75/75 [==============================] - 0s 757us/step - loss: 0.0718 - acc: 0.9867
Epoch 75/100
75/75 [==============================] - 0s 693us/step - loss: 0.0711 - acc: 0.9867
Epoch 76/100
75/75 [==============================] - 0s 670us/step - loss: 0.0719 - acc: 0.9867
Epoch 77/100
75/75 [==============================] - 0s 759us/step - loss: 0.0729 - acc: 0.9867
Epoch 78/100
75/75 [==============================] - 0s 759us/step - loss: 0.0702 - acc: 0.9867
Epoch 79/100
75/75 [==============================] - 0s 728us/step - loss: 0.0742 - acc: 0.9867
Epoch 80/100
75/75 [==============================] - 0s 752us/step - loss: 0.0732 - acc: 0.9867
Epoch 81/100
75/75 [==============================] - 0s 970us/step - loss: 0.0716 - acc: 0.9867
Epoch 82/100
75/75 [==============================] - 0s 725us/step - loss: 0.0698 - acc: 0.9733
Epoch 83/100
75/75 [==============================] - 0s 724us/step - loss: 0.0716 - acc: 0.9600
Epoch 84/100
75/75 [==============================] - 0s 672us/step - loss: 0.0712 - acc: 0.9867
Epoch 85/100
75/75 [==============================] - 0s 813us/step - loss: 0.0682 - acc: 0.9867
Epoch 86/100
75/75 [==============================] - 0s 755us/step - loss: 0.0684 - acc: 0.9867
Epoch 87/100
75/75 [==============================] - 0s 686us/step - loss: 0.0680 - acc: 0.9867
Epoch 88/100
75/75 [==============================] - 0s 663us/step - loss: 0.0706 - acc: 0.9733
Epoch 89/100
75/75 [==============================] - 0s 662us/step - loss: 0.0707 - acc: 0.9733
Epoch 90/100
75/75 [==============================] - 0s 649us/step - loss: 0.0677 - acc: 0.9867
Epoch 91/100
75/75 [==============================] - 0s 765us/step - loss: 0.0666 - acc: 0.9867
Epoch 92/100
75/75 [==============================] - 0s 771us/step - loss: 0.0686 - acc: 0.9867
Epoch 93/100
75/75 [==============================] - 0s 743us/step - loss: 0.0635 - acc: 0.9867
Epoch 94/100
75/75 [==============================] - 0s 734us/step - loss: 0.0698 - acc: 0.9733
Epoch 95/100
75/75 [==============================] - 0s 785us/step - loss: 0.0653 - acc: 0.9867
Epoch 96/100
75/75 [==============================] - 0s 803us/step - loss: 0.0639 - acc: 0.9867
Epoch 97/100
75/75 [==============================] - 0s 712us/step - loss: 0.0640 - acc: 0.9867
Epoch 98/100
75/75 [==============================] - 0s 667us/step - loss: 0.0632 - acc: 0.9867
Epoch 99/100
75/75 [==============================] - 0s 655us/step - loss: 0.0673 - acc: 0.9867
Epoch 100/100
75/75 [==============================] - 0s 663us/step - loss: 0.0676 - acc: 0.9867

 
基本的な使用については、コンパイルされた keras モデルと scikit-learn 分類器間の唯一のシンタクス的な API の違いは scikit-learn .score() メソッドの Keras の同値は .evaluate() と呼称されます。

evaluate() はモデルをコンパイルするときに要求した損失関数と任意の他のメトリクスを返します。私達のケースでは、精度を要求しました、これは scikit-learn LogisticRegressionCV 分類器の .score() メソッドからの精度と比較することができます。

loss, accuracy = model.evaluate(test_X, test_y_ohe, verbose=0)
print("Accuracy = {:.2f}".format(accuracy))
Accuracy = 0.97

見て取れるように、ニューラルネットワーク・モデルのテスト精度は単純なロジスティック回帰分類器のものよりも良いです。

これは再確認させてくれますが、驚くべきことではありません。非常に単純なニューラルネットワークでさえもロジスティック回帰よりも遥かにより複雑な分類面を学習する柔軟性を持ちますので、それはもちろんロジスティック回帰よりも上手くやります。

そしてニューラルネットワークの一つの危険性のヒントを与えます: overfitting です。ここではテストセットを注意深く保持してそれでパフォーマンスを測定しましたが、97 % 精度は酷く高いように見えますので、何某かの overfitting が進んでいても驚きではありません。(Keras に組み込まれている) dropout の追加でそれに取り組めるかもしれません。それは LogisticRegression 分類器が使用する正則化のニューラルネットワークの同値です。

しかしここでは深入りはしません、というのはニューラルネットワーク・モデルはこの問題についてはやり過ぎですし、精度について心配するのは貴方に見せたいことのポイントではないからです。ポイントは Keras のような高位ライブラリの使用はニューラルネットワーク・モデルを構築し、訓練し、そして適用するために (伝統的なモデルよりも) ほんの少しだけより多くのコードだけを書く必要があることを意味することです。

 

以上