ニューラルネットワークの実装(分類)
本章では、ディープラーニングフレームワーク TensorFlow を使ってディープラーニングの基礎と使い方を学んでいきます。TensorFlow とは、Google 社開発している機械学習のためのエンドツーエンドのオープンソースプラットフォームであり、こちらで活発に開発がされています。公式サイトには、チュートリアルや事例、初学者の方がゼロから学ぶためのストーリーも紹介されていますのでご覧ください。
TensorFlow が人気が高い理由として、大きく次の 3 点が挙げられます。
- 初心者にも使いやすいインターフェースで作られている
- ユーザー数が世界で多いため、世界中の人からの情報が集まるコミュニティがある
- エッジデバイスへの連携や分散処理、学習可視化ソフトなどモデル作成以外の部分も包括的なエコシステムがある
使いやすさ、ユーザー数の多さ、取り囲むエコシステムの豊富さが人気の理由だと言えます。
また、TensorFlow では高レベルな API である Keras をモデル構築で使用します。使いやすいものと今は思ってください。簡単にモデルの構築方法として、
が使用することができます。Functional API や Subclassing API を使うと、とても柔軟に複雑なモデルも記述することが可能なのですが、本章から複数続いていく TensorFlow シリーズは、入門編として Sequential API のみを使用してモデル構築していきます。また、今後様々な記法を紹介していきます。
本章の構成
- TensorFlow の基礎
- データセットの準備
- モデルの定義
- モデルの学習
- 学習済みモデルの保存と推論
TensorFlow の基礎
TensorFlow は読み込む際に tf
として省略することが慣例になっています。早速読み込んでみましょう。バージョンの確認もします。
import tensorflow as tf
# バージョンの確認
print(tf.__version__)
TensorFlow を扱っていく上で、モデル構築には TensorFlow 内部に組み込まれている Keras を使用して実装を行っていきます。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
ニューラルネットワークモデルの構築では、
- データセットの準備
- モデルの構築
- モデルの学習
- 学習済みモデルの精度を評価
- 学習済みモデルを用いて推論
以上の 5 点の流れを把握しながら、先に進んでいきましょう。
データセットの準備
本章では、こちらに格納されている wine_class.csv
を使用してクラス分類の実装を練習してみましょう。ダウンロードが終われば、アップロードしてください。どのような形式でデータを準備しておくと良いのかといった参考になるため、格納されている生のデータも確認しておきましょう。
改めて分類とは、上記の図の通り、カテゴリ毎に分けられる境界線を見つける問題でした。
今回のデータセットは CSV ファイルで用意されているため、データの取り扱いは Pandas を使って行います。ファイルのアップロードから始めていきましょう。
# ファイルのアップロード
from google.colab import files
uploaded = files.upload()
# データの読み込み
df = pd.read_csv('wine_class.csv')
# データの表示(先頭3件)
df.head(3)
Class | Alcohol | Ash | Alcalinity of ash | Magnesium | Total phenols | Flavanoids | Nonflavanoid phenols | Color intensity | Hue | Proline | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 14.23 | 2.43 | 15.6 | 127 | 2.80 | 3.06 | 0.28 | 5.64 | 1.04 | 1065 |
1 | 1 | 13.20 | 2.14 | 11.2 | 100 | 2.65 | 2.76 | 0.26 | 4.38 | 1.05 | 1050 |
2 | 1 | 13.16 | 2.67 | 18.6 | 101 | 2.80 | 3.24 | 0.30 | 5.68 | 1.03 | 1185 |
データを見てみると、0 番目のカラムにワインの等級が、1 番目以降のカラムにアルコール等のワインの特徴が入っていることがわかります。今回はこのワイン等級を正解データとして、他の特徴を元に分類を行っていきます。
入力変数と目的変数に切り分け
教師あり学習を行う上での最初のステップとして、入力変数と目的変数の切り分けがあります。毎回行うため、スムーズに出来るように練習しておきましょう。
目的変数は、df['列名']
で一列を取り出すことができます。入力変数は、目的変数で指定した以外の列を使用することになりますので、Pandas の df.drop()
で選択したくない列名を引数に入力し、axis
で行方向 (axis=0
) なのか、列方向 (axis=1
) なのかを指定します。
# 目的変数
t = df['Class']
# 入力変数
x = df.drop('Class', axis=1)
正しく切り分けられているかデータの中身を表示して確認しておきます。
x.head(3)
Alcohol | Ash | Alcalinity of ash | Magnesium | Total phenols | Flavanoids | Nonflavanoid phenols | Color intensity | Hue | Proline | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 14.23 | 2.43 | 15.6 | 127 | 2.80 | 3.06 | 0.28 | 5.64 | 1.04 | 1065 |
1 | 13.20 | 2.14 | 11.2 | 100 | 2.65 | 2.76 | 0.26 | 4.38 | 1.05 | 1050 |
2 | 13.16 | 2.67 | 18.6 | 101 | 2.80 | 3.24 | 0.30 | 5.68 | 1.03 | 1185 |
# 形の確認
x.shape
.shape
を使うことで、サンプル数 (178) と入力変数の数 (10) を確認することができます。
Keras で計算できるデータの形式に変換
Keras で計算を行うために、下記の 2 点を満たしているか確認を行っておきましょう。こちらが指定された形式となっていない場合、学習の際にエラーが出てしまいます。
- 入力変数や目標値が NumPy の ndarray で定義されているか
- 分類の場合、ラベルが 0 から始まっているか
初学者がよく遭遇しやすいエラーなので、最初は意識しましょう。こちらはフレームワークを使用する上で、必要なルールになりますので、便利なツールを使用するにはこれらを覚える必要があると今は受け入れていきましょう。
NumPy に変換
pd.read_csv
で読み込んだ場合、Pandas の DataFrame となっています。
type(x)
DataFrame の .values
属性を使用すると NumPy の ndarray を取得できます。こちらは、頻出するので覚えましょう。
type(x.values)
目標値を 0 からに変換
t
の値を確認してみましょう。np.unique
を使って、変数に含まれる値からユニークな値(被りがない値)を出力します。
# ユニークな値を確認
np.unique(t)
今回は t
が 1 から始まって 3 で終わっているため、1, 2, 3
というラベルが割り振られた 3 クラスの分類であることがわかります。目標値は 0 から始める必要があるため、1, 2, 3
→ 0, 1, 2
としましょう。
どのように値を変更するかというと NumPy では、全体に対する引き算もサポートしているため、t - 1
とすればラベル全体に 1 が引かれ、0, 1, 2
となります。この機能は NumPy のブロードキャストと呼ばれます。
# ラベルを 0 から始める
t = t.values - 1
x = x.values
t, type(t)
x, type(x)
学習用データとテスト用データに分割
次に、学習用とテスト用にデータセットを分割します。これはディープラーニング問わず、機械学習全般で使用する考え方です。
使い慣れた scikit-learn に用意されている sklearn.model_selection.train_test_split
を使用しましょう。今回は 2 分割とし、学習データを 70%、テストデータを 30% とします。
from sklearn.model_selection import train_test_split
# 学習データとテストデータの分割
x_train, x_test, t_train, t_test = train_test_split(x, t, train_size=0.7, random_state=0)
x_train.shape, x_train.dtype, x_test.shape, x_test.dtype
t_train.shape, t_train.dtype, t_test.shape, t_test.dtype
ニューラルネットワークの学習データのデータ型は 32 bit にするのが多く見受けられます。現在の 64bit からデータ型を変更する処理をキャストといいます。float
型と int
型ともに 32bit にキャストしましょう。np.array
の引数でデータ型を指定できますので np.float32
と np.int32
とします。
# 32 bit にキャスト
x_train = np.array(x_train, np.float32)
x_test = np.array(x_train, np.float32)
t_train = np.array(t_train, np.int32)
t_test = np.array(t_train, np.int32)
モデルの定義
モデルの定義では、最もシンプルな tf.keras.models.Sequential
モデルを構築します。使い方はシンプルで以下の通りです。
tf.keras.models.Sequential()
を用意する- 内部に
tf.keras.layers.層の名前()
を積み重ねて記述する 変数 (model)
に渡してインスタンス化を行う
この tf.keras.layers.層の名前()
の部分で全結合層 (Dense
) や活性化関数 (Activation
)などを追加していきます。以下のコード、コメントを参考にモデルを構築していきましょう。
モデル構築の前に、乱数のシードの固定も行います。Python の乱数、random
関数、 numpy
そして tensorflow
を固定します。乱数が固定され、CPU であれば再現性の確保が可能です。GPU を使用する場合、再現性の確保ができません(2020 年 4 月現在)。
import os, random
def reset_seed(seed=0):
os.environ['PYTHONHASHSEED'] = '0'
random.seed(seed) # random関数のシードを固定
np.random.seed(seed) # numpyのシードを固定
tf.random.set_seed(seed) # tensorflowのシードを固定
モデルを定義していきたいのですが、モデルを構築するときに頻出する 2 つを紹介します。
- tensorflow.keras.models
- モデルインスタンスの立ち上げやモデルの保存を担当
- tensorflow.keras.layers
- 全結合層 (Dense) などニューラルネットワークの層 (layer) の定義を担当
最初はこの 2 つを抑えておきましょう。
from tensorflow.keras import models, layers
今回構築するモデルは、入力層のノード数が 10、出力層のノード数が 3 のモデルです。入力変数と目的変数の数は問題設定から決まっているため間違えないようにしましょう。
中間層のノード数だけは、人間側で自由に設計する必要があります。今回は適当に中間層(隠れ層)のノード数を 10 とし、実装してみます。この値を大きくすればするほど、パラメータ数が増加し、小さくすればするほど、パラメータ数は減少します。試行錯誤を繰り返し、良い値を決めることになります。
モデルの層を定義
本節で定義したいモデル構造はこちらになります。最初に頭の中でイメージから作っていきましょう。
上記でもお伝えしましたが、層を定義する際に Keras では tf.keras.layers
を使用します。この中に全結合層や次の章で扱う畳み込み層が定義されています。
models.Sequential
内部に定義したい層をリスト形式で入れていきます。他にも .add()
を使った方法もあります。
今回は上図で表した全結合層のみを使ったモデルを構築します。Keras では全結合層を layers.Dense()
と名前が付けられています。いくつか設定しなければいけない引数があり、
layers.Dense(units, activation, input_shape)
units
: 出力の次元数activation
: 使用する活性化関数input_shape
: 入力の次元数
が主に使用する引数になります。input_shape
はモデルを構築するときの最初の層のみに必要ですが、その他の層では自動的に入力形状を受け渡してくれます。
それではモデル構築していきましょう。
# シードの固定
reset_seed(0)
# モデルの構築
model = models.Sequential([
layers.Dense(units=10, activation='relu', input_shape=(10,)),
layers.Dense(units=3, activation='softmax')
])
以下のように .add()
メソッドを使って、用意しておいたモデルの箱 (models.Sequential()
) の中に、層 (layers.Dense()
) を追加していくという方法もよくネットで見かけることもあります。どちらも同じ操作になるので、使いやすい方法を選んでください。
# モデルの構築
# model = models.Sequential()
# model.add(layers.Dense(units=10, activation='relu', input_shape=(10,)))
# model.add(layers.Dense(units=3, activation='softmax'))
こちらでモデル構築完了です。しかし、2 つ目の層の活性化関数 (activation) で softmax
を使用しています。ですので、実際のモデル構造としては以下のイメージになります。
ソフトマックス関数は主に分類で使用される活性化関数であり、
で表されます。ここで、 は変換後の出力のノード数であり、 は自然対数 を底とした指数関数を表しています。機能としては出力される値の総和を とすることが目的であり、それぞれの出力ノードの値は の間で値を取ります。
現時点では、分類には出力層の活性化関数にソフトマックス関数が使われる程度の認識で構いません。理解度が深まったときに振り返りましょう。
モデルの学習プロセスを定義
層の深さや活性化関数を定義したら、他にも学習に必要な目的関数や最適化手法などの詳細設定をします。Keras では model.compile()
で学習プロセスを構成し、3 つの重要な引数を取ります。
optimizer
:最適化手法を指定tf.keras.optimizers
モジュールからインスタンスを渡す- 代表例として
SGD
やAdam
があります
loss
:目的関数を指定tf.keras.losses
モジュールからインスタンスを渡す- 回帰:平均ニ乗誤差
MeanSquaredError : MSE
- 分類:
SparseCategoricalCrossentropy
orBinaryCrossentropy
metrics
:学習中にモニタリングする評価指標を指定tf.keras.metrics
モジュールからインスタンスを渡す- 回帰:
MeanAbsoluteError
- 分類:
Accuracy
今回は最適化手法に SGD
を使用し、目的関数には 3 クラス分類のため、SparseCategoricalCrossentropy
、評価指標には Accuracy
として正解率を指定します。
Keras ではより簡単に使用するために、モジュールからインスタンスを渡す方法の他に、文字列で渡す方法があります。本章では実装のしやすさを重視し、文字列で渡す方法を使用します。
# モデルのコンパイル
model.compile(optimizer='sgd',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# こちらでも同じ
# model.compile(optimizer=tf.keras.optimizers.SGD(),
# loss=tf.keras.losses.SparseCategoricalCrossentropy(),
# metrics=[tf.keras.metrics.Accuracy()])
モデルの学習
構築したモデルの学習を実行するには model.fit
を使います。重要な引数は以下の 3 点です。
epochs
:入力データ全体に対する 1 つの反復回数を指定しますbatch_size
:データをバッチにスライスし、バッチごとに反復処理されます。その各バッチのサイズを指定しますvalidation_data
:検証データのパフォーマンスをモニタリングするために、(入力値, 目標値)
のタプルを渡すと各エポック終わりに渡された推論モードでloss
とmetrics
の値を表示します
次章で詳しく説明しますので、本章では実装することをゴールにして一度学習を実行しましょう。
# モデルの学習
history = model.fit(x_train, t_train,
batch_size=10,
epochs=10,
validation_data=(x_test, t_test))
はじめてのディープラーニングの学習が終わりました。実現場では、もちろんさらに複雑なモデルを構築したり、データの前処理などを大量に行うことになるのですが、第一ステップとしてはこちらで大枠の把握は完了です。
学習済みモデルの精度を確認
正解率 (accuracy
) と目的関数の値 (loss
) が途中からほとんど変化しておらず、学習が上手く進んていません。文字で見てもなんとなく分かるのですが、評価しやすいようにするために文字ではなく直感的にグラフで可視化してみましょう。
学習過程の値は history.history
に格納されています。
# 学習過程
history.history
まだ確認しづらいため、Pandas の DataFrame 形式に変換します。
# Pandas 形式
result = pd.DataFrame(history.history)
result.head()
loss | accuracy | val_loss | val_accuracy | |
---|---|---|---|---|
0 | 471.618530 | 0.298387 | 1.098186 | 0.395161 |
1 | 1.097725 | 0.395161 | 1.097017 | 0.395161 |
2 | 1.097879 | 0.395161 | 1.096485 | 0.395161 |
3 | 1.097584 | 0.395161 | 1.096048 | 0.395161 |
4 | 1.096020 | 0.395161 | 1.095289 | 0.395161 |
可視化するためにはいつも Matplotlib を使っていましたが、Pandas には内部で Matplotlib が入っており、.plot()
メソッドを使用するとすぐに可視化することができます。
# 目的関数の値
result[['loss', 'val_loss']].plot();
# 正解率
result[['accuracy', 'val_accuracy']].plot();
グラフで見たほうが数字で見るよりも直感的に理解することができます。残念ながら今回出来上がったモデルはあまり良いモデルではないと言えそうです。
モデル精度の向上
データセットの準備、モデルの定義、モデルの学習までの一連の流れを説明しましたが、まだ高い精度を得ることはできていません。ここからが試行錯誤の段階で、作成したモデルを現場で活用できるように改善していく必要があります。
まず精度を向上させる方法として、バッチノーマリゼーション (Batch Normalization) が挙げられます。
バッチノーマリゼーションでは、ミニバッチごとに平均 と 標準偏差 を求め、
のように へと各変数ごとに変換を行います。ここで、 と はパラメータであり、単純な正規化のように平均 0、標準偏差 1 とするのではなく、平均 、標準偏差 となるように変換を行います。必ずしも平均 0、標準偏差 1 が良いとは限らないためです。
実装としては、各バッチ毎に平均と標準偏差を定めて標準化を行うといった非常に簡単な手法なのですが、こちらを層に加えることで各変数感のスケールによる差を吸収できます。
それでは、バッチノーマリゼーションを加えて試してみましょう。tf.keras.layers.BatchNormalization
で適用できます。
# シードの固定
reset_seed(0)
# モデルの構築
model = tf.keras.models.Sequential([
tf.keras.layers.BatchNormalization(input_shape=(10,)),
tf.keras.layers.Dense(10, activation='relu'),
tf.keras.layers.Dense(3, activation='softmax')
])
# モデルのコンパイル
model.compile(optimizer='sgd',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# モデルの学習
history = model.fit(x_train, t_train,
batch_size=10,
epochs=50,
validation_data=(x_test, t_test))
# 正解率と損失を Pandas の形式に変換
result_batchnorm = pd.DataFrame(history.history)
# 目的関数の値
result_batchnorm[['loss', 'val_loss']].plot();
# 正解率
result_batchnorm[['accuracy', 'val_accuracy']].plot();
飛躍的に正解率が向上しました。正解率の値が良くなっていれば成功です。
このようにディープラーニングでは、Batch Normalization を含めた細かな精度向上のポイントがあるため、今後も調べながら進めていきましょう。TensorFlow では、多くの機能がすでに実装されているため、上記のコードのように少し付け加えるだけでその効果を検証できるため、非常に便利です。
学習済みモデルの保存と推論
学習が終わると、学習済みモデルが得られます。TensorFlow では、2 つの方法でモデルの保存およびロードを行えます。
- 高レベル API:
model.save
&tf.keras.models.load_model
- 低レベル API:
tf.saved_model.save
&tf.saved_model.load
今回は前者の方法を指定し、保存形式が HDF5 と呼ばれるファイルフォーマットを取ります。HDF5 形式では、モデル全体を 1 つのファイルに保存することができ、モデルのアーキテクチャとモデルの重みなどの情報が入っています。
学習済みモデルには、save
メソッドがついており、model.save(filepath, save_format)
を指定します。
filepath
: 保存するファイルの場所と名前を指定save_format
: HDF5 か Saved Model のどちらかかを指定
それでは保存しましょう。
# モデルの保存
model.save(filepath='wine_model.h5', save_format='h5')
これで学習済みモデルの保存は完了です。
学習済みモデルのロード
それでは、先程保存したファイルを使用し、モデルを再構築します。models.load_model()
を使用します。
# モデルの読み込み
loaded_model = tf.keras.models.load_model('wine_model.h5')
予測値の計算
本来であれば学習を終えたモデルは新しく取得したデータに対して推論しますが、今回は試しに学習で使用したデータセットの最初のサンプルに対する予測値を計算してみましょう。
# データの準備
sample = x_train[0]
sample.shape
学習済みのモデルに新しく入力変数を入れて、値を出力する順伝播の計算を行うには .predict()
メソッドを使用します。
# 予測値の計算
loaded_model.predict(sample)
エラーが確認できました。ValueError: Error when checking input: expected batch_normalization_input to have shape (10,) but got array with shape (1,)
とあります。
(10,)
という形状を予想していたのに、(1,)
として値が入力されていますよ、と書いてあります。注意してほしいのですが、推論で使用する際には、(バッチサイズ, 入力変数の数)
という形式となっていないと上記のようなエラーが起きます。今回であれば、(1, 10)
が望ましいデータの形と言えます。
バッチサイズは次章で紹介しますが、一度に計算するサンプルの数と思っていただければ大丈夫です。
# 形を変換
sample = sample.reshape(1, 10)
sample.shape
# 予測値の計算
y = loaded_model.predict(sample)
y
3 つの値を出力するようにモデルを構築していたので上手く推論できました。結果としては、最も大きな値が入ったインデックスのみを取得したいです。このような場合にはnp.argmax()
を使用し、取得します。
# 最も値の大きなラベルを取得
np.argmax(y)
# 正解ラベル
t_train[0]
このように学習済みモデルを使用して推論を実行できました。
本章では、TensorFlow の基礎として TensorFlow のモデル構築としての使用方法を主に紹介してきました。Keras を使用するとモデル構築も非常に簡単に行うことができることが体感できたかと思います。もちろん、実際にはさらに複雑なモデルを構築することになることも多いので、その場合には Functional API や Subclassing API を使用します。しかし、気軽に試してみたい場合などには本章の流れを振り返りながら Sequential API を積極的に使用していきましょう。次章では、回帰を扱います。