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

#はじめに

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

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

🌟基本となるディープラーニングについて
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

# まずは、１枚で試してみる

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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672827/c84c8ec1-f05a-5e4b-9a48-c429fb148094.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
```

正解率５０%!!


```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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672827/38d2f43f-3073-292a-4440-5a53ee167d6e.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672827/5e99318d-664b-eccb-4aaf-b98233e8c9ac.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
```

正解率５４%!!

```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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672827/b5ece455-972e-f485-3824-135584e7bdc9.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
```

正解率５８%!!

```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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672827/6a77906d-f168-1d06-0aec-41d12d9437e3.png)


#おわりに

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

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