LoginSignup
2
5

More than 3 years have passed since last update.

畳み込みニューラルネットワーク(CNN)の実装

Last updated at Posted at 2021-02-13

はじめに

今回は、「畳み込みニューラルネットワーク」を実装していきます。

ディープラーニングや畳み込みニューラルネットワークの基本については、下記の記事を参照してください。

🌟基本となるディープラーニングについて
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種類のラベルから分類するものです。

データの前処理

必要なライブラリーをインポート

python.py
import numpy as np
import pandas as pd
from PIL import Image
import glob

目的変数と説明変数の処理

目的変数を読み込んでダミー変数へ変換

python.py
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」を使って読み込めるが画像データの並びがバラバラに読み込まれてしまう。そのため、読み込み後に小さい数字の順番に並び替える。

python.py
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」までの数値の集まりに変換する。
(✅ディープラーニングは、データの数値の幅が大きいと学習がうまくいかない)

python.py
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)

スクリーンショット 2021-02-07 9.25.24.png

全ての画像を一括で処理。

※※CPU環境では学習に時間がかかる場合があるため、「image = load_img(image, target_size = (32, 32)) 」とすれば画像サイズを小さくできるので計算時間が減る。(予測精度は悪くなる可能性あり)

python.py
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]]]

学習用データ、検証用データ、テストデータへ分割

python.py
# データの分割
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)

畳み込みニューラルネットワークモデルの構築

最初は単純なモデルで精度を確認する。

python.py
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
_________________________________________________________________
python.py
# 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) #検証用データ
               )

結果の確認①

python.py
# テスト用データ(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%!!

python.py
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()

スクリーンショット 2021-02-07 9.51.50.png

予測精度は低く、早い段階で過学習をしている。

原因として考えられるのは、

✅学習用データが少ないこと。

そこで学習用データの拡張と水増しをしてみる。

学習用データの拡張と水増し

✅学習用データの拡張と水増しが必要な理由

・対象の物体が逆さまや斜めになったデータも学習させることで、応用力をつけさせるため(データの拡張)
・必要な量の学習データが準備できていない(データの水増し)

python.py
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)                   

スクリーンショット 2021-02-07 13.54.22.png

python.py
#学習用データの状態確認
#3200 / 32
print(len(train_data_gen))

100

print(type(train_data_gen))

tensorflow.python.keras.preprocessing.image.NumpyArrayIterator

検証用データを学習用データの状態に合わせる

python.py
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

学習用データを拡張して学習

python.py
%%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)

結果の確認②

python.py
# 予測
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%!!

python.py
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()

スクリーンショット 2021-02-07 13.35.27.png

正解率は上昇したもの、まだまだ過学習しています。
最後に、過学習対策を施したモデルを構築します。

過学習対策をしたモデルを構築

python.py
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
_________________________________________________________________

python.py
%%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)

結果の確認③

python.py
# 予測
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%!!

python.py
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()

スクリーンショット 2021-02-07 13.42.27.png

おわりに

正解率はあまり向上しなかったが、いい感じに学習が進んでいるグラフになっている。

さらなる精度向上を目指すなら、
・モデルの構築をより複雑にする
・転移学習
・学習用データをもっと集める

2
5
1

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
2
5