Kerasで自作画像を用いてAlexNetで訓練するソースコード(Windows10)

Tensorflow+Kerasの環境構築を前回やってみて、無事環境構築に成功しました。

そのときはMNISTデータセットで正常な実行を確認しましたが、実用的な面を考えると、自分で学習画像を用意して訓練するというケースが多くなると思います。

そこで、予めフォルダに分類した学習画像からクラス名(及びクラス数)を自動で読み取り、各クラスの分類学習を行うソースコードを書いてみました。

また、前回からの差分として、分類のモデルはAlexNet(畳み込み層5層、全結合層3層モデル)を用いることとしました。

AlexNetのモデル部分のコードに関しては、

参考ページ:KerasによるAlexNetを用いた犬猫分類モデル – Qiita

上記のコードを参考にさせていただきました。ありがとうございます(厳密には上記のコードはLRNがBN((Batch Normalization)に変わっているので、AlexNetの論文実装とは異なっています)。

今回の環境

・OS : Windows10(64bit)
・GPU: GeForce GTX 950
・Anaconda
・CUDA 9.0
・cuDNN v7.0.5
・Tensorflow 1.11.0
・Keras 2.2.4
上記全て環境構築済

ソースコード

先にソースコードを載せちゃいます。説明は先を読んでください。

import numpy as np
import keras
from keras.utils import np_utils
from keras.models import Sequential
from keras.layers.convolutional import Conv2D, MaxPooling2D
from keras.layers.core import Dense, Dropout, Activation, Flatten
from sklearn.model_selection import train_test_split
from PIL import Image
import glob
from keras.preprocessing.image import load_img, img_to_array
from keras.initializers import TruncatedNormal, Constant
from keras.layers import Input, Dropout, Flatten, Conv2D, MaxPooling2D, Dense, Activation, BatchNormalization
from keras.optimizers import SGD
# -------------------------------------------------------------------------------------
# 初期設定部
# -------------------------------------------------------------------------------------
# GrayScaleのときに1、COLORのときに3にする
COLOR_CHANNEL = 3
# 入力画像サイズ(画像サイズは正方形とする)
INPUT_IMAGE_SIZE = 224
# 訓練時のバッチサイズとエポック数
BATCH_SIZE = 32
EPOCH_NUM = 100
# 使用する訓練画像の入ったフォルダ(ルート)
TRAIN_PATH = "..\training_dataset\training_anime1"
# 使用する訓練画像の各クラスのフォルダ名
folder = ["000_hatsune_miku", "001_kinomoto_sakura", "002_suzumiya_haruhi",
"003_fate_testarossa", "004_takamachi_nanoha", "005_lelouch_lamperouge",
"006_akiyama_mio", "007_nagato_yuki", "008_shana", "009_hakurei_reimu"]
# CLASS数を取得する
CLASS_NUM = len(folder)
print("クラス数 : " + str(CLASS_NUM))
# -------------------------------------------------------------------------------------
# 訓練画像入力部
# -------------------------------------------------------------------------------------
# 各フォルダの画像を読み込む
v_image = []
v_label = []
for index, name in enumerate(folder):
dir = TRAIN_PATH + "\" + name
files = glob.glob(dir + "\*.png")
print(dir)
for i, file in enumerate(files):
if COLOR_CHANNEL == 1:
img = load_img(file, color_mode = "grayscale", target_size=(INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE))
elif COLOR_CHANNEL == 3:
img = load_img(file, color_mode = "rgb", target_size=(INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE))
array = img_to_array(img)
v_image.append(array)
v_label.append(index)
v_image = np.array(v_image)
v_label = np.array(v_label)
# imageの画素値をint型からfloat型にする
v_image = v_image.astype('float32')
# 画素値を[0~255]⇒[0~1]とする
v_image = v_image / 255.0
# 正解ラベルの形式を変換
v_label = np_utils.to_categorical(v_label, CLASS_NUM)
# 学習用データと検証用データに分割する
train_images, valid_images, train_labels, valid_labels = train_test_split(v_image, v_label, test_size=0.20)
# -------------------------------------------------------------------------------------
# モデルアーキテクチャ定義部
# -------------------------------------------------------------------------------------
def conv2d(filters, kernel_size, strides=1, bias_init=1, **kwargs):
trunc = TruncatedNormal(mean=0.0, stddev=0.01)
cnst = Constant(value=bias_init)
return Conv2D(
filters,
kernel_size,
strides=strides,
padding='same',
activation='relu',
kernel_initializer=trunc,
bias_initializer=cnst,
**kwargs
)
def dense(units, **kwargs):
trunc = TruncatedNormal(mean=0.0, stddev=0.01)
cnst = Constant(value=1)
return Dense(
units,
activation='tanh',
kernel_initializer=trunc,
bias_initializer=cnst,
**kwargs
)
def AlexNet():
model = Sequential()
# 第1畳み込み層
model.add(conv2d(96, 11, strides=(4,4), bias_init=0, input_shape=(INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE, COLOR_CHANNEL)))
model.add(MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))
model.add(BatchNormalization())
# 第2畳み込み層
model.add(conv2d(256, 5, bias_init=1))
model.add(MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))
model.add(BatchNormalization())
# 第3~5畳み込み層
model.add(conv2d(384, 3, bias_init=0))
model.add(conv2d(384, 3, bias_init=1))
model.add(conv2d(256, 3, bias_init=1))
model.add(MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))
model.add(BatchNormalization())
# 全結合層
model.add(Flatten())
model.add(dense(4096))
model.add(Dropout(0.5))
model.add(dense(4096))
model.add(Dropout(0.5))
# 出力層
model.add(Dense(CLASS_NUM, activation='softmax'))
model.compile(optimizer=SGD(lr=0.01), loss='categorical_crossentropy', metrics=['accuracy'])
return model
# コンパイル
model = AlexNet()
# 訓練
history = model.fit(train_images, train_labels, batch_size=BATCH_SIZE, epochs=EPOCH_NUM)
# -------------------------------------------------------------------------------------
# 訓練実行&結果確認部
# -------------------------------------------------------------------------------------
# モデル構成の確認
model.summary()
score = model.evaluate(valid_images, valid_labels, verbose=0)
print(len(valid_images))
print('Loss:', score[0])
print('Accuracy:', score[1])

使用するデータセットと準備

今回は自作画像……といいつつ、自作画像を用意するのは面倒なので、JPGやPNGなどの一般的な画像形式で提供されている公開データセットを使うことにしました。

ただ、今回のソースコードに関しては、自分で学習画像を収集して、フォルダに分類し、そのフォルダ名をソースコード内に入れるだけで使っていただけるような構成になっています。

では、今回のソースコードを動かすためのデータセットの準備方法を説明します。

今回、「animeface-character-dataset」というアニメキャラの顔画像のデータセットを使用しています。以下のページからダウンロード可能です。

外部サイト:animeface-character-dataset

ダウンロードしたZIPファイルを解凍すると、以下のように各キャラクター別に分類された画像が入ったフォルダ群が展開されます。

多数のキャラクターがあるのですが、学習にあまり時間を掛けたくないという事情から、10キャラ分のフォルダだけを、訓練データ配置用のフォルダへ移動します。

訓練データ配置用のフォルダに関してはソースコードの方から変更可能ですが、私は実行ファイルからの相対パスで見て「..\training_dataset\training_anime1」のフォルダに画像を置きました。

あとは、ソースコードの29-35行目に訓練データ配置フォルダへのパスと、各クラスごとの画像が入ったフォルダ名を指定して、認識を行うことができます。

# 使用する訓練画像の入ったフォルダ(ルート)
TRAIN_PATH = "..\\training_dataset\\training_anime1"
# 使用する訓練画像の各クラスのフォルダ名
folder = ["000_hatsune_miku", "001_kinomoto_sakura", "002_suzumiya_haruhi",
"003_fate_testarossa", "004_takamachi_nanoha", "005_lelouch_lamperouge",
"006_akiyama_mio", "007_nagato_yuki", "008_shana", "009_hakurei_reimu"]

コードの説明

一部コードをかいつまんで説明します。

カラー画像とグレースケール画像への対応

今回は最初の方でCOLOR_CHANNELという変数を定義し、これを1のときは画像をグレースケールとしてネットワークへ入力、3のときはカラー画像としてネットワークに入力できるようにしました。

# GrayScaleのときに1、COLORのときに3にする
COLOR_CHANNEL = 3

以下の部分のコードにて、 グレースケール入力とカラー入力を自動で切り替えるようにしています。入力が元々グレースケールだとカラーで入れても意味がない気がしますが、元々カラー画像で用意されている場合は、グレースケールに変換して入力してくれます。

if COLOR_CHANNEL == 1:
img = load_img(file, color_mode = "grayscale", target_size=(INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE))
elif COLOR_CHANNEL == 3:
img = load_img(file, color_mode = "rgb", target_size=(INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE))

また、ネットワークの入力の部分にも、COLOR_CHANNELを変数として与えて、グレースケールとカラーの場合で入力の形が自動で変わるようにしています。

# 第1畳み込み層
model.add(conv2d(96, 11, strides=(4,4), bias_init=0, input_shape=(INPUT_IMAGE_SIZE, INPUT_IMAGE_SIZE, COLOR_CHANNEL)))

分類クラス数の自動取得

今回は10人のキャラクターの顔を分類するので、当然クラスは10になります。

フォルダごとに分類した学習画像を用意すれば、クラス数はフォルダ数と同一になるため、以下のようにフォルダ数から自動でクラス数をカウントし、

# CLASS数を取得する
CLASS_NUM = len(folder)
print("クラス数 : " + str(CLASS_NUM))

ネットワークの出口のところの数はクラス数に合わせないとエラーが起こります。

# 出力層
model.add(Dense(CLASS_NUM, activation='softmax'))

実行結果の確認

結構画像数の少ないデータセットなので、少々実験としては心許ない部分がありますが、実際に実行をしてみました。

今回はカラーチャンネルを変更し、グレースケール化した訓練画像で認識する場合と、カラー化した訓練画像で認識する場合を比較してみましょう。

エポックは100、バッチサイズは32で統一しました。

COLOR_CHANNEL = 1 の結果

COLOR_CHANNEL = 1でプログラムを回したところ、以下のような結果が出ました。

上の190は検証に用いた画像の枚数で、Lossは損失を示し、低ければ低いほど良い結果であることを示します。

そして、肝心の精度は81.6%というところでした。

今回は10クラス分類なので、ランダムで正解当てをした場合は精度は10%ですが、81.6%とモデルの訓練が成功していることがわかります。

また、以下がsummary()関数で表示したモデルの構成です。

COLOR_CHANNEL = 3 の結果

次に、COLOR_CHANNELを3に変えて実験をしました。

結果は以下となりました。

精度は88.4%と、グレースケールの場合よりも精度が高まりました。

これは、アニメのキャラクターの分類には髪の色などの色情報が非常に重要な情報であり、色があることで認識率が上がりそう……という直感的な予想にも見合う結果となりました。

まとめ

今回は自作画像から学習を行う準備を整えると共に、実際にモデルを作って精度を確認してみました。

今回はAlexNetを扱いましたが、他のネットワーク等も試してみようと思います。

スポンサーリンク

シェアする

フォローする