#はじめに
今回は、「畳み込みニューラルネットワーク」を実装していきます。
ディープラーニングや畳み込みニューラルネットワークの基本については、下記の記事を参照してください。
🌟基本となるディープラーニングについて
https://qiita.com/hara_tatsu/items/c0e59b388823769f9704
🌟ディープラーニングの実装及び過学習対策
https://qiita.com/hara_tatsu/items/b7423e90574cf7730978
🌟「畳み込みニューラルネットワーク(CNN)まとめ」
https://qiita.com/hara_tatsu/items/8dcd0a339ad2f67932e7
#畳み込みニューラルネットワークを実装する
今回は題材として、「【SIGNATE】画像ラベリング(10種類)」を使います。
https://signate.jp/competitions/133
画像データに映っているものを10種類のラベルから分類するものです。
##データの前処理
###必要なライブラリーをインポート
import numpy as np
import pandas as pd
from PIL import Image
import glob
###目的変数と説明変数の処理
目的変数を読み込んでダミー変数へ変換
train_Y = pd.read_csv('train_master.tsv', delimiter='\t')
train_Y = train_Y.drop('file_name', axis=1)
# カテゴリー変数へ変換
from tensorflow.keras.utils import to_categorical
Y = to_categorical(train_Y)
print(Y.shape)
print(Y[:5])
(5000, 10)
array([[0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 1., 0., 0., 0., 0.],
[0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 1., 0., 0., 0.],
[0., 0., 0., 1., 0., 0., 0., 0., 0., 0.]], dtype=float32)
説明変数(画像データ)は、「glob」を使って読み込めるが画像データの並びがバラバラに読み込まれてしまう。そのため、読み込み後に小さい数字の順番に並び替える。
train_file = glob.glob('train_images/t*')
# 0埋めでない数値を小さい順に並び替える関数
import re
from collections import OrderedDict
def sortedStringList(array=[]):
sortDict=OrderedDict()
for splitList in array:
sortDict.update({splitList:[int(x) for x in re.split("(\d+)",splitList)if bool(re.match("\d*",x).group())]})
return [sortObjKey for sortObjKey,sortObjValue in sorted(sortDict.items(), key=lambda x:x[1])]
# 小さい順に並び替え
sort_file = sortedStringList(train_file)
print(sort_file[:5])
['train_images/train_0.jpg',
'train_images/train_1.jpg',
'train_images/train_2.jpg',
'train_images/train_3.jpg',
'train_images/train_4.jpg']
###説明変数(画像データ)の前処理
現在は、「glob」で画像データのファイル名を取得している状態。
PIL形式で読み込み後にnumpy化及び正規化をする。
※画像データは「0〜255」までの数値の集まり。そのため、全体を「255.0」で割ることで、「0〜1」までの数値の集まりに変換する。
(✅ディープラーニングは、データの数値の幅が大きいと学習がうまくいかない)
from tensorflow.keras.preprocessing.image import load_img, img_to_array, array_to_img
# まずは、1枚で試してみる
train = sort_file[0]
# 画像ファイルからPIL形式で読み込み
train = load_img(train)
# PIL形式からnumpy形式へ + 正規化
train = img_to_array(train) / 255.0
print(train.shape)
(96, 96, 3)
# 画像表示
plt.imshow(train)
全ての画像を一括で処理。
※※CPU環境では学習に時間がかかる場合があるため、「image = load_img(image, target_size = (32, 32)) 」とすれば画像サイズを小さくできるので計算時間が減る。(予測精度は悪くなる可能性あり)
X = []
for image in sort_file:
# 画像ファイルのPIL形式で読み込み
image = load_img(image)
#image = load_img(image, target_size = (32, 32)) 学習時間を短縮したい場合
# PIL形式からnumpy形式へ + 正規化
image = img_to_array(image) / 255.0
# データをリストへ格納
X.append(image)
#Xはリスト型のためnumpy型へ変換
X_np = np.array(X)
print(X_np.shape)
print(X_np.dtype)
print(X_np[0])
(5000, 96, 96, 3)
float32
[[[0.57254905 0.5529412 0.42745098]
[0.5764706 0.5568628 0.43137255]
[0.5803922 0.56078434 0.43529412]
[0.6509804 0.627451 0.49411765]
[0.64705884 0.62352943 0.49019608]
...
[0.6431373 0.61960787 0.4862745 ]]
[[0.57254905 0.5529412 0.42745098]
[0.57254905 0.5529412 0.42745098]
[0.5764706 0.5568628 0.43137255]
...
[0.5568628 0.4862745 0.43137255]]]
###学習用データ、検証用データ、テストデータへ分割
# データの分割
from sklearn.model_selection import train_test_split
X_train, X_test, Y_train, Y_test = train_test_split(X_np, Y, test_size=0.2, random_state=0)
X_train, X_valid, Y_train, Y_valid = train_test_split(X_train, Y_train, test_size=0.2, random_state=0)
# 形状を確認
print("Y_train=", Y_train.shape, ", X_train=", X_train.shape)
print("Y_valid=", Y_valid.shape, ", X_valid=", X_valid.shape)
print("Y_test=", Y_test.shape, ", X_test=", X_test.shape)
Y_train= (3200, 10) , X_train= (3200, 96, 96, 3)
Y_valid= (800, 10) , X_valid= (800, 96, 96, 3)
Y_test= (1000, 10) , X_test= (1000, 96, 96, 3)
##畳み込みニューラルネットワークモデルの構築
最初は単純なモデルで精度を確認する。
from tensorflow import keras
from tensorflow.keras.layers import Dense, Activation, Conv2D, Flatten, MaxPooling2D
# モデルの初期化
model = keras.Sequential()
# 入力層and畳み込み層
model.add(Conv2D(32, # フィルター数
kernel_size= 3, # フィルターのサイズ(3×3)
input_shape=(96, 96, 3,), # 入力データ(画像)のサイズ
padding="same", #ゼロパディング(画像サイズを小さくしない)
activation="relu" #活性化関数
))
# プーリング層
model.add(MaxPooling2D())
# 1次元に変換
model.add(Flatten())
# 全結合層
model.add(Dense(128, activation="relu"))
# 出力層
model.add(Dense(10, activation='softmax'))
# モデルの構築
model.compile(optimizer = "rmsprop", #誤差逆伝播
loss='categorical_crossentropy', #損失関数
metrics=['accuracy'] #訓練時に監視する指標
)
# モデルの表示
model.summary()
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_12 (Conv2D) (None, 96, 96, 32) 896
_________________________________________________________________
max_pooling2d_12 (MaxPooling (None, 48, 48, 32) 0
_________________________________________________________________
flatten_8 (Flatten) (None, 73728) 0
_________________________________________________________________
dense_17 (Dense) (None, 128) 9437312
_________________________________________________________________
dense_18 (Dense) (None, 10) 1290
=================================================================
Total params: 9,439,498
Trainable params: 9,439,498
Non-trainable params: 0
_________________________________________________________________
# EarlyStopping用
from tensorflow.keras import regularizers
%%time
# 学習の実施
log = model.fit(X_train, Y_train, #学習用データ
epochs=5000, #繰り返し計算する回数
batch_size=64, #ミニバッチ勾配降下法で分割するデータのかたまり
verbose=True, #計算過程を表示
#過学習していると判断したら学習を止める
callbacks=[keras.callbacks.EarlyStopping(monitor='val_loss',#監視する値
min_delta=0, #改善とみなされる最小の量
patience=30, #設定したエポック数改善がないと終了
verbose=1,
mode='auto' #監視する値が増減どうなったら終了か自動で推定
)],
validation_data=(X_valid, Y_valid) #検証用データ
)
###結果の確認①
# テスト用データ(Y_test)をダミー変数から通常の数値へ復元
Y_test_ = np.argmax(Y_test, axis=1)
# モデルでの予測
Y_pred = model.predict_classes(X_test)
# モデルの評価
from sklearn.metrics import classification_report
print(classification_report(Y_test_, Y_pred))
precision recall f1-score support
0 0.63 0.80 0.70 93
1 0.47 0.44 0.45 100
2 0.72 0.66 0.69 99
3 0.32 0.34 0.33 103
4 0.46 0.50 0.48 117
5 0.23 0.20 0.22 93
6 0.46 0.52 0.49 101
7 0.56 0.36 0.44 110
8 0.62 0.68 0.65 88
9 0.55 0.54 0.55 96
accuracy 0.50 1000
macro avg 0.50 0.50 0.50 1000
weighted avg 0.50 0.50 0.50 1000
正解率50%!!
acc = log.history['accuracy']
val_acc = log.history['val_accuracy']
loss = log.history['loss']
val_loss = log.history['val_loss']
epochs_range = range(37)
plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Accuracy')
plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Loss')
plt.show()
予測精度は低く、早い段階で過学習をしている。
原因として考えられるのは、
✅学習用データが少ないこと。
そこで学習用データの拡張と水増しをしてみる。
##学習用データの拡張と水増し
✅学習用データの拡張と水増しが必要な理由
・対象の物体が逆さまや斜めになったデータも学習させることで、応用力をつけさせるため(データの拡張)
・必要な量の学習データが準備できていない(データの水増し)
from tensorflow.keras.preprocessing.image import ImageDataGenerator
# 学習用データの拡張設定
image_gen = ImageDataGenerator(rotation_range=45, #45°回転
horizontal_flip = True, #左右反転
)
# 拡張データの生成
train_data_gen = image_gen.flow(X_train, Y_train, batch_size = 32, shuffle = False)
# 拡張データを表示する関数
def plotImages(images_arr):
fig, axes = plt.subplots(1, 5, figsize=(20,20))
axes = axes.flatten()
for img, ax in zip( images_arr, axes):
ax.imshow(img)
ax.axis('off')
plt.tight_layout()
plt.show()
augmented_images = [train_data_gen[0][0][0] for i in range(5)]
plotImages(augmented_images)
#学習用データの状態確認
#3200 / 32
print(len(train_data_gen))
100
print(type(train_data_gen))
tensorflow.python.keras.preprocessing.image.NumpyArrayIterator
検証用データを学習用データの状態に合わせる
valid_gen = ImageDataGenerator()
valid_data_gen = valid_gen.flow(X_valid, Y_valid, batch_size = 32)
#800 / 32
print(len(valid_data_gen))
25
print(type(valid_data_gen))
tensorflow.python.keras.preprocessing.image.NumpyArrayIterator
##学習用データを拡張して学習
%%time
# 学習の実施
log = model.fit_generator(train_data_gen, #学習用データ
steps_per_epoch = 100,
epochs = 5000, #繰り返し計算する回数
callbacks = [keras.callbacks.EarlyStopping(monitor='val_loss',#監視する値
min_delta=0, #改善とみなされる最小の量
patience=30, #設定したエポック数改善がないと終了
verbose=1,
mode='auto' #監視する値が増減どうなったら終了か自動で推定
)],
validation_data = valid_data_gen, #検証用データ
validation_steps = 25)
###結果の確認②
# 予測
Y_pred = model.predict_classes(X_test)
# モデルの評価
print(classification_report(Y_test_, Y_pred))
precision recall f1-score support
0 0.62 0.86 0.72 93
1 0.38 0.70 0.49 100
2 0.70 0.79 0.74 99
3 0.47 0.19 0.27 103
4 0.74 0.37 0.49 117
5 0.39 0.39 0.39 93
6 0.51 0.56 0.54 101
7 0.49 0.54 0.51 110
8 0.72 0.66 0.69 88
9 0.61 0.43 0.50 96
accuracy 0.54 1000
macro avg 0.56 0.55 0.53 1000
weighted avg 0.56 0.54 0.53 1000
正解率54%!!
acc = log.history['accuracy']
val_acc = log.history['val_accuracy']
loss = log.history['loss']
val_loss = log.history['val_loss']
epochs_range = range(69)
plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Accuracy')
plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Loss')
plt.show()
正解率は上昇したもの、まだまだ過学習しています。
最後に、過学習対策を施したモデルを構築します。
##過学習対策をしたモデルを構築
from tensorflow.keras.layers import Dropout
# モデルの初期化
model = keras.Sequential()
# 入力層
model.add(Conv2D(
32, kernel_size=3,
input_shape=(96, 96, 3,),
padding="same",
activation="relu",
))
# プーリング層
model.add(MaxPooling2D())
model.add(Dropout(0.25))
# 層のユニットの繰り返し
model.add(Conv2D(64, kernel_size=3, padding="same", activation="relu"))
model.add(MaxPooling2D())
model.add(Dropout(0.25))
# 1次元に変換
model.add(Flatten())
model.add(Dense(128, activation="relu"))
model.add(Dense(64, activation="relu"))
model.add(Dropout(0.5))
# 出力層
model.add(Dense(10, activation='softmax'))
# モデルの構築
model.compile(optimizer = "rmsprop", loss='categorical_crossentropy', metrics=['accuracy'])
model.summary()
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_4 (Conv2D) (None, 96, 96, 32) 896
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 48, 48, 32) 0
_________________________________________________________________
dropout_3 (Dropout) (None, 48, 48, 32) 0
_________________________________________________________________
conv2d_5 (Conv2D) (None, 48, 48, 64) 18496
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 24, 24, 64) 0
_________________________________________________________________
dropout_4 (Dropout) (None, 24, 24, 64) 0
_________________________________________________________________
flatten_3 (Flatten) (None, 36864) 0
_________________________________________________________________
dense_7 (Dense) (None, 128) 4718720
_________________________________________________________________
dense_8 (Dense) (None, 64) 8256
_________________________________________________________________
dropout_5 (Dropout) (None, 64) 0
_________________________________________________________________
dense_9 (Dense) (None, 10) 650
=================================================================
Total params: 4,747,018
Trainable params: 4,747,018
Non-trainable params: 0
_________________________________________________________________
%%time
# 学習の実施
log = model.fit_generator(train_data_gen, #学習用データ
steps_per_epoch = 100,
epochs = 5000, #繰り返し計算する回数
callbacks = [keras.callbacks.EarlyStopping(monitor='val_loss',#監視する値
min_delta=0, #改善とみなされる最小の量
patience=30, #設定したエポック数改善がないと終了
verbose=1,
mode='auto' #監視する値が増減どうなったら終了か自動で推定
)],
validation_data = valid_data_gen, #検証用データ
validation_steps = 25)
###結果の確認③
# 予測
Y_pred = model.predict_classes(X_test)
# モデルの評価
print(classification_report(Y_test_, Y_pred))
precision recall f1-score support
0 0.73 0.77 0.75 93
1 0.45 0.59 0.51 100
2 0.87 0.63 0.73 99
3 0.49 0.42 0.45 103
4 0.57 0.59 0.58 117
5 0.38 0.23 0.28 93
6 0.54 0.75 0.63 101
7 0.64 0.42 0.51 110
8 0.69 0.76 0.72 88
9 0.56 0.73 0.63 96
accuracy 0.58 1000
macro avg 0.59 0.59 0.58 1000
weighted avg 0.59 0.58 0.58 1000
正解率58%!!
acc = log.history['accuracy']
val_acc = log.history['val_accuracy']
loss = log.history['loss']
val_loss = log.history['val_loss']
epochs_range = range(69)
plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Accuracy')
plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Loss')
plt.show()
#おわりに
正解率はあまり向上しなかったが、いい感じに学習が進んでいるグラフになっている。
さらなる精度向上を目指すなら、
・モデルの構築をより複雑にする
・転移学習
・学習用データをもっと集める