深層学習入門:MNISTの分類①(NNの作成)


深層学習によってMNIST(手書き数字)を10クラスに分類する方法(Classification)を紹介します。
内容も盛りだくさんなので投稿を分ける予定ですが、よろしくお願いします。

なお今回のプログラムはGoogle Colab上で作成しています。
"深層学習の勉強をしたいけど高性能なPCを持っていない"という方も、Google ColabはGPUが無料で使用できるのでぜひトライしてみてください。

作成したコード

#モジュールのインポート
import keras
from keras.datasets import mnist
from keras.utils import to_categorical
from keras import models
from keras import Input
from keras.layers import Activation, Dense
import matplotlib.pyplot as plt

#mnistのロード
(x_train,y_train),(x_val,y_val)=mnist.load_data()

# mnistデータの前処理
def preprocess(data, label=False):
    if label:
        #One-Hotベクトル化
        data = to_categorical(data)
    else:
        #画素値を0~1に変換
        data = data.astype('float32') / 255
        # (ミニバッチサイズ、横幅、縦幅)→(ミニバッチサイズ、横幅、縦幅、チャネル数)に変換
        data = data.reshape((-1, 28, 28, 1))
    return data

x_train = preprocess(x_train)
x_val = preprocess(x_val)
y_train = preprocess(y_train, label=True)
y_val = preprocess(y_val, label=True)

#モデルの構築
model = keras.Sequential([keras.layers.Flatten(input_shape=(28, 28,1)),
                          keras.layers.Dense(1, activation='relu'),
                          keras.layers.Dense(10, activation='softmax')])

#モデルを保存している場合、下記コマンドでロードする
#model = keras.models.load_model('model.h5', compile=False)

#モデルの重みを保存している場合下記コマンドでロードする
#model.load_weights('weights*.hdf5')

#学習方法の設定
model.compile(optimizer='adam',loss='categorical_crossentropy',metrics=['accuracy'])

#学習の実施
history = model.fit(x_train, y_train,epochs=30,batch_size=128,validation_data=(x_val, y_val))

#モデルと重みの保存
model.save('model.h5')
model.save_weights('weights_mnist.hdf5')

#グラフの描画
#accuracyのグラフ
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
epochs = range(1, len(acc)+1)
plt.plot(epochs, acc, 'b', label='Training accuracy')
plt.plot(epochs, val_acc, 'r', label='Val accuracy')
plt.legend()
plt.show()
#lossのグラフ
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(loss)+1 )
plt.plot(epochs, loss, 'b', label='Training loss')
plt.plot(epochs, val_loss, 'r', label='Val loss')
plt.legend()
plt.show()

処理の解説

はじめに

kerasでAI作成を実施する場合、バージョンが変わるとプログラムの記述方法が大きく変わるので、注意してください。
今回のプログラムはver.2.3.1のkerasで作成しています。
kerasのバージョンは以下のコマンドで確認できます。
プログラムが実行できない場合は、確認してみてください!
print(keras.__version__)
Google Colabでは、keras等のよく使うモジュールが標準でインストールされているので、場合によっては削除&任意のバージョンの再インストールを行う必要があります。
この方法も後日記事にしようかなと思います。

処理のフロー

次の図が今回の処理フローです。




これから個々の処理を解説します。

MNISTのダウンロード

まずは教師画像となるMNISTをロードします。
#mnistのダウンロード
(x_train,y_train),(x_val,y_val)=mnist.load_data()
MNISTとは、教師あり学習の最も一般的なデータセットで、手書き数字の画像が学習(training)用に60000枚、検証(validation)用に10000枚用意されています。
それぞれの画像には、正解ラベルがついており、画像に何の数字が書いてあるかが分かるようになっています。

MNISTはClassificationの基礎となるデータセットです。
これからディープラーニングを勉強する方は、何度も取り扱うことになるかと思います!

データの前処理

tensolflowで学習を行うために、MNISTデータに前処理を施します。
前処理は数字画像、ラベルデータ両方に対して実施します。
# mnistデータの前処理
def preprocess(data, label=False):
    if label:
        #One-Hotベクトル化
        data = to_categorical(data)
    else:
        #画素値を0~1に変換
        data = data.astype('float32') / 255
        # (ミニバッチサイズ、横幅、縦幅)→(ミニバッチサイズ、横幅、縦幅、チャネル数)に変換
        data = data.reshape((-1, 28, 28, 1))
    return data

x_train = preprocess(x_train)
x_val = preprocess(x_val)
y_train = preprocess(y_train, label=True)
y_val = preprocess(y_val, label=True)
【One-hotベクトルへの変換】
まずラベルデータをOne-hotベクトルに変換します。
今回は0~9の10クラスの分類ということで、one-hotベクトルの次元数は10になります。

画像が0である場合、one-hotベクトルの1つ目の値が1、それ以外が0となっていますが、これは入力画像が0である確率が1(100%)、0以外である確率が0(0%)だということを意味しています。

ニューラルネットワークの分類問題では、ある画像を入力した際の出力結果が確率になっていて、入力画像が0の確率が○○%、1である確率が●●%という形で計算を行い、最もパーセンテージが大きいラベルを予測値とします。

学習の際には、ある画像を入力した際の出力が、このone-hotベクトルの値に近づくように、モデルのパラメータを更新していきます。

【画像の正規化】
さらに画像データは0から1までの範囲にスケールを変更します。
スケールを変換する理由は、学習におけるモデルのパラメータ更新時に、結果の振れ幅を小さくするためと言われています。
ちゃんと調べていないので、使わなくても結局同じじゃない?と思う節もありますが、形式的に実行しています。

モデルの構築

次のコマンドにより、モデルを構築します。
#モデルの構築
model = keras.Sequential([keras.layers.Flatten(input_shape=(28, 28,1)),
                          keras.layers.Dense(1, activation='relu'),
                          keras.layers.Dense(10, activation='softmax')])

#モデルを保存している場合、下記コマンドでロードする
#model = keras.models.load_model('model.h5', compile=False)

#モデルの重みを保存している場合下記コマンドでロードする
#model.load_weights('weights*.hdf5')
今回は入門ということで1層のニューラルネットワークのモデルを組んでいます。

これはSequetialモデルと呼ばれる記述法で、入門的な書き方になります。
ただ単純に順伝播していくようなモデルではSequentialモデルで良いと思いますが、複雑なモデルを書く場合はFunctional APIと呼ばれる手法で記述しなければいけないのでご注意ください。

inputは(28,28,1)となっていますが、これはMNISTの画像の28px×28px×1chに準拠しています。

中間層には、ユニット数が1の全結合層を1層だけ置いています。

用いた活性化関数はreluです。用いるデータが画像の場合、基本的に中間層の活性化関数にはreluが使用されます。

今回は計算を速くするためにユニット数も層数も1にしていますが、精度を上げたい場合は色々と増やして実験してみてください。
ただ全結合層のみで構成したニューラルネットワークでは限界があるため、CNNに変更した方が、手軽に精度向上を見込めると思います。

出力層にはSoftmaxを用いています。
Classificationのニューラルネットワークの出力層は間違いなくこれです。
Softmax関数を用いることで、入力画像がどのクラス(MNISTの場合は数字)に当たるかを確率で示してくれます。

例えば、MNISTだと次のような、出力が得られます。
[0.4 0.1 0.1 0.05 0.05 0.05 0.05 0.05 0.05 0.1]
これは上述のone-hotベクトルと同じ次元のデータとなっており、この例の場合は最大値である1つ目の要素のラベル0が予測値として出力されます。総和を1として出力してくれるのもSoftmax関数の大きな特徴です。

また、以前に作成したモデルや重みのデータがある場合はロードすることもできます。
MNISTではあまり必要のある機能ではありませんが、実務とかでは必ず使うので記しています。

学習方法の設定

学習方法を次のコマンドで設定します。
#学習方法の設定
model.compile(optimizer='adam',loss='categorical_crossentropy',metrics=['accuracy'])
まずoptimizerにより最適化関数を決定します。
最適化関数は色々と種類がありますが、最初のうちはそこまで気にしなくても良いです。立ち位置的には、精度や学習効率向上に向けての最後の一押しぐらいの感じだと思っていても良い気がします。

次にlossにより損失関数を決定します。
損失関数はクロスエントロピーと呼ばれるものを扱っています。簡単に言うと、one-hotベクトルの1を1、0を0と言い当てられたら低くなるような関数です。

最後にmetricsによって、学習時の評価基準を定めます。
precisionやrecallなど色々と設定できるみたいですが、とりあえずaccuracyにしておいて不都合はないと思います。

学習の実施

次のコマンドで学習を実施します。
#学習の実施
history = model.fit(x_train, y_train,epochs=30,batch_size=128,validation_data=(x_val, y_val))
学習の際は、学習や精度検証に使用するデータのほかに、エポック数やバッチサイズなどを指定します。
エポック数は学習の繰り返し回数、バッチサイズは一度に学習に用いるデータ数を指します。

モデル・重みの保存

次のコマンドで、学習に使用したモデルと学習結果である重みを保存します。
#モデルと重みの保存
model.save('model.h5')
model.save_weights('weights_mnist.hdf5')

モデルは学習時用いたプログラムをコピペすることで再現できますが、学習結果である重みは後から再現するのは大変なので、長時間の学習を実施する際は重みを必ず保存しましょう。
(MNISTではあまり保存する必要はありませんが・・・)

今回は適当に作成しているため、学習終了時のモデルが保存されるようになっていますが、過学習してる際などは途中のモデルを使用すべきとなるので、本当は毎エポック(もしくはAccuracy or Lossが改善した際のエポック)保存する必要があります。
この方法も今後記事にしたいと思います。

ちなみに、Google Colabでデータ等を保存する場合は、事前にフォルダのマウントなどの処理が必要です。
やり方はこちらにまとめていますのでご覧ください。

学習曲線の表示

#グラフの描画
#accuracyのグラフ
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
epochs = range(1, len(acc)+1)
plt.plot(epochs, acc, 'b', label='Training accuracy')
plt.plot(epochs, val_acc, 'r', label='Val accuracy')
plt.legend()
plt.show()
#lossのグラフ
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(loss)+1 )
plt.plot(epochs, loss, 'b', label='Training loss')
plt.plot(epochs, val_loss, 'r', label='Val loss')
plt.legend()
plt.show()
最後に学習曲線を表示しました。

学習曲線は学習の精度を確認する重要な指標になります。
Training accuracyは学習に用いたデータの識別精度、Val accuracyは学習に用いていないデータの識別精度になります。

エポックを過度に増やすと、学習に用いたデータの識別に特化するあまり、学習に用いていないデータの識別精度が低下する過学習と呼ばれる現象が発生します。
もちろんTraining accuracyがどの程度の精度を持つのか確認することも大事ですが、Training accuracyは基本的に単調増加していくので、Val accuracyの値をよく確認することが最良モデルを選定する鍵となります。

今回は以上です!
今後はこのプログラムを肉付けするような形で、色々と改善していきたいと思います!

コメント