LoginSignup
6
3

More than 3 years have passed since last update.

【AI初心者向け】mnist_transfer_cnn.pyを1行ずつ解説していく(KerasでMNISTを学習させる)

Posted at

はじめに

この記事は全3回予定の第3回の記事です。
この記事はmnist_transfer_cnn.pyを1行ずつ解説していくだけの記事です。
前回と重なる部分もありますが、記事単体で読みやすくするため、重複して書いている内容もありますのでご了承ください。
AIに興味があるけどまだ触ったことはない人などが対象です。これを読めばディープラーニングの基本的な学習の流れが理解できるはず、と思って書いていきます。(もともとは社内で研修用に使おうと思って作成していた内容です)
1. 【AI初心者向け】mnist_mlp.pyを1行ずつ解説していく(KerasでMNISTを学習させる)
2. 【AI初心者向け】mnist_cnn.pyを1行ずつ解説していく(KerasでMNISTを学習させる)
3. 【AI初心者向け】mnist_transfer_cnn.pyを1行ずつ解説していく(KerasでMNISTを学習させる)

動作確認方法について

MNISTは画像なので、このコードを動かすにはGPUがあったほうがいいです(CPUだとちょっと辛いです)。
おすすめの方法はGoogle Colaboratoryを使う方法です。
colab.gif
やることは2つだけ。
・Python3の新しいノートブックを開く
・ランタイムからGPUを有効にする
これでGPUが使えるようになりました。
セルにコードを貼り付けて実行(ショートカットはCTRL+ENTER)するだけで動きます。

mnistについて

手書き文字画像のデータセットで、機械学習のチュートリアルでよく使用されます。
内容:0~9の手書き文字
画像サイズ:28px*28px
カラー:白黒
データサイズ:7万枚(訓練データ6万、テストデータ1万の画像とラベルが用意されています)

Fine-tuningとは

すでにある優れたモデルのパラメータを初期値として利用し、別のタスクに対応すること。こうすることで計算コストの削減と、精度の向上が望めます。

今回でいえば、

  1. 0~4の画像を分類するモデルを作成(もとになる重みを作成する)
  2. 作成したモデルの画像の特徴を抽出する層の重みを固定し、変更できないようにする
  3. 5~9の画像を学習させる(全結合層=分類する部分の重みのみを更新する)
  4. 最終的に、5~9の5種類の手書き文字を入力として受け取り、5~9のいずれであるか5種類に分類するモデルが完成する

5~9の画像を分類できるよう学習させたので、今回最終的に完成したモデルでは0~4の画像分類はできません。

コードの解説

準備

'''Trains a simple convnet on the MNIST dataset.
Gets to 99.25% test accuracy after 12 epochs
(there is still a lot of margin for parameter tuning).
16 seconds per epoch on a GRID K520 GPU.
'''
'''Transfer learning toy example.
1 - Train a simple convnet on the MNIST dataset the first 5 digits [0..4].
2 - Freeze convolutional layers and fine-tune dense layers
   for the classification of digits [5..9].
Get to 99.8% test accuracy after 5 epochs
for the first five digits classifier
and 99.2% for the last five digits after transfer + fine-tuning.
'''

# 特に必要ないコードです(Pythonのバージョンが3だが、コードがPython2で書かれている場合に必要になる)
from __future__ import print_function

# 必要なライブラリをインポート
import datetime
import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K

# 現在時刻を取得
now = datetime.datetime.now

# 定数
batch_size = 128   # バッチサイズ。1度に学習するデータサイズ
num_classes = 5    # 分類するラベル数。今回は手書き画像を5~9の5種類に分類する
epochs = 5         # エポック数。全データを何回学習するか
img_rows, img_cols = 28, 28  # 入力画像の次元数
filters = 32       # 畳み込みのフィルター数
pool_size = 2      # マックスプーリングするサイズ
kernel_size = 3    # 畳み込みのフィルター(カーネル)サイズ
# データの形
if K.image_data_format() == 'channels_first':
    input_shape = (1, img_rows, img_cols)
else:
    input_shape = (img_rows, img_cols, 1)

データの形の部分ですが、詳細は【AI初心者向け】mnist_cnn.pyを1行ずつ解説していく(KerasでMNISTを学習させる)で確認してください。データの前処理の部分に記載しています。

KerasのバックエンドがTheano(channels_first)か tensorflow(channels_last)かで画像の書式が異なるのを判断しています。今回はtensorflowなので、(28, 28, 1)になります。

データの前処理

# mnistのデータを読み込み、訓練データ(6万件)とテストデータ(1万件)に分割する
(x_train, y_train), (x_test, y_test) = mnist.load_data()

# ラベルが5以上か未満かで分けたデータセットを作成
# ラベル値が5未満の訓練画像
x_train_lt5 = x_train[y_train < 5]
# ラベル値が5未満の訓練ラベル
y_train_lt5 = y_train[y_train < 5]
# ラベル値が5未満のテスト画像
x_test_lt5 = x_test[y_test < 5]
# ラベル値が5未満のテストラベル
y_test_lt5 = y_test[y_test < 5]

# ラベル値が5以上の訓練画像
x_train_gte5 = x_train[y_train >= 5]
# ラベル値が5以上の訓練ラベルから5を引いたデータセット(5~9⇒0~4にする)
y_train_gte5 = y_train[y_train >= 5] - 5
# ラベル値が5以上のテスト画像
x_test_gte5 = x_test[y_test >= 5]
# ラベル値が5以上のテストラベルから5を引いたデータセット(5~9⇒0~4にする)
y_test_gte5 = y_test[y_test >= 5] - 5

まず、0~4の画像を分類するモデルを作成するために、0~4のデータと5~9のデータに分けておきます。

次に、5~9のデータに関してはラベルを5~9⇒0~4に変更しておきます。

モデルの定義

# モデルの定義(.add()メソッドを使わないパターン)

# 畳み込み処理で特徴を学習
feature_layers = [
    # 畳み込み層(フィルター:32枚、フィルターサイズ:(3, 3)、受け取る入力サイズ:(28, 28, 1))
    Conv2D(filters, kernel_size,
           # padding='valid'で0パディングしない。0パディングするときは'same'を指定する
           padding='valid',
           input_shape=input_shape),
    # 活性化関数:Relu
    Activation('relu'),
    # 畳み込み層(フィルター:32枚、フィルターサイズ:(3, 3))
    Conv2D(filters, kernel_size),
    # 活性化関数:Relu
    Activation('relu'),
    # プーリング層
    MaxPooling2D(pool_size=pool_size),
    # 0.25の確率でドロップアウト
    Dropout(0.25),
    # データを1次元に変換
    Flatten(),
]

# 全結合層で分類を学習
classification_layers = [
    # 全結合層(128ユニット)
    Dense(128),
    # 活性化関数:relu
    Activation('relu'),
    # 0.5の確率でドロップアウト
    Dropout(0.5),
    # 全結合層(5ユニット)
    Dense(num_classes),
    # 活性化関数:softmax(分類問題のため)
    Activation('softmax')
]

# Sequentialクラスにfeature_layersとclassification_layersを渡したものをインスタンス化
model = Sequential(feature_layers + classification_layers)

今回は第1回、第2回と違い.add()メソッドを使わずにモデルを定義しています。
Kerasでは、層を順番に並べたリストをSequential()に渡してインスタンス化することでもモデルの定義ができます。

なぜこのような書き方をしたのかというと、Fine-tuningでは、学習時に特定の層のみ重みを変更しない(または変更する)ということをします。そのためにあえてこのような書き方をしています。

画像の特徴を抽出する層と、分類をする層を分けて定義しておくことで、どちらかの重みのみを更新するということが簡単にできるようになります。

学習させる関数

# 学習させる関数を作成
def train_model(model, train, test, num_classes):

    # データの前処理
    # データの形式をreshapeして合わせておく
    x_train = train[0].reshape((train[0].shape[0],) + input_shape)  # (30596, 28, 28) -> reshape(30596, 28, 28, 1)
    x_test = test[0].reshape((test[0].shape[0],) + input_shape)     # (5139, 28, 28)  -> reshape(5139, 28, 28, 1)
    # 画像データは0~255の値をとるので255で割ることでデータを標準化する
    x_train = x_train.astype('float32')
    x_test = x_test.astype('float32')
    # .astype('float32')でデータ型を変換する。(しないと割ったときにエラーが出るはず)
    x_train /= 255
    x_test /= 255
    print('x_train shape:', x_train.shape)
    print(x_train.shape[0], 'train samples')
    print(x_test.shape[0], 'test samples')

    # ラベルデータをone-hot-vector化
    '''one-hot-vectorのイメージはこんな感じ
    label  0 1 2 3 4 
    0:    [1,0,0,0,0]
    3:    [0,0,0,1,0]'''
    y_train = keras.utils.to_categorical(train[1], num_classes)
    y_test = keras.utils.to_categorical(test[1], num_classes)

    # 学習プロセスを設定する
    model.compile(loss='categorical_crossentropy',  # 損失関数を設定。今回は分類なのでcategorical_crossentropy
                  optimizer='adadelta',  # 最適化アルゴリズムをadadeltaにしている
                  metrics=['accuracy'])  # 評価関数を指定

    # 学習開始時間を取得
    t = now()
    # 学習させる
    model.fit(x_train, y_train,       # 学習データ、ラベル
              batch_size=batch_size,  # バッチサイズ(128)
              epochs=epochs,          # エポック数(5)
              verbose=1,              # 学習の進捗をリアルタムに棒グラフで表示(0で非表示)
              validation_data=(x_test, y_test))  # テストデータ(エポックごとにテストを行い誤差を計算するため)
    # 学習にかかった時間を出力
    print('Training time: %s' % (now() - t))

    # 評価
    # テストデータを渡す(verbose=0で進行状況メッセージを出さない)
    score = model.evaluate(x_test, y_test, verbose=0)
    # 汎化誤差を出力
    print('Test score:', score[0])
    # 汎化性能を出力
    print('Test accuracy:', score[1])

今回、5~9の画像を分類するモデルを作るために2回学習させます。

学習

# 上で作成した関数を使い学習させる
# 5未満の画像に0~4のラベルを与えて学習(分類させる)
train_model(model,
            (x_train_lt5, y_train_lt5),
            (x_test_lt5, y_test_lt5), num_classes)

# trainable=Falseで、レイヤーに学習をさせなくする
# 畳み込みを行う部分であるfeature_layersのパラメータ更新をしないよう設定し、classification_layersのパラメータのみ更新する
# 変更を有効にするには、プロパティの変更後のモデルでcompile()を呼ぶ必要がある
for l in feature_layers:
    l.trainable = False

# 上で作成した関数を使い学習させる
# 5以上の画像に0~4のラベルを与えて学習(分類させる)
train_model(model,
            (x_train_gte5, y_train_gte5),
            (x_test_gte5, y_test_gte5), num_classes)

いよいよ学習です。

  1. 0~4の画像を分類するモデルを作成(もとになる重みを作成する)
  2. feature_layersの重みを固定し、変更できないようにする
  3. 5~9の画像を学習させる(全結合層=分類する部分の重みのみを更新する)
  4. 最終的に、5~9の5種類の手書き文字を入力として受け取り、5~9のいずれであるか5種類に分類するモデルの完成!

おわりに

以上で全3回のソース解説記事は終わりです。
解説は終わりですが、おまけとして次回、モデルの保存とロード、利用方法の記事をアップ予定です。
作っただけで保存をしないと、せっかく作ったのに消えちゃうので・・・。

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3