学習済モデルを利用しないで作るKerasを用いたVGG16の実装

## この記事を読者層について
DeepLearningの畳み込みニューラルネットワーク(以下CNN)の基礎知識があり、以下のような言葉の意味がわかる方
例)
- 畳み込み
- MaxPooling
- フィルター


# VGG16とは
画像を解析するDeepLearningの手法の一つです。
2014年に発表されたモデルで、現在でも使われるほど、性能が高いモデルです。
学習済のモデルもKerasから呼び出して利用する事もできます。

オリジナルの記事は[こちら](http://www.robots.ox.ac.uk/~vgg/research/very_deep/)です。

VGG16を作成するにあたり以下が重要になります。
- 畳み込みに使うフィルターのサイズは3×3
- 複数回の畳み込みを行った際にMaxPoolingを行う。
- MaxPoolingの後にフィルターのチャンネル数を倍にする。

## 動作環境
GoogleColaboratory

# サンプルプログラム
```
# 必要なライブラリーのインストール
import tensorflow.keras as keras
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D
from tensorflow.keras.layers import MaxPooling2D
from tensorflow.keras.layers import Flatten
from tensorflow.keras.layers import Dense
from tensorflow.keras.datasets import cifar10

# CIFAR10のデータを取得して、ベクトルに変換するクラス
class CIFAR10Dataset():
  def __init__(self):
    self.image_shape = (32, 32, 3)
    self.num_classes = 10

  # 学習データとテストデータを取得する。
  def get_batch(self):
    (x_train, y_train), (x_test, y_test) = cifar10.load_data()
    x_train, x_test = [self.change_vec(img_data) for img_data in [x_train, x_test]]
    y_train, y_test = [self.change_vec(img_data, label_data=True) for img_data in [y_train, y_test]]
    return x_train, y_train, x_test, y_test

  # 目的変数の場合は、クラスベクトルに変更する。説明変数は標準化する。
  def change_vec(self, img_data, label=False):
    if label:
      data = keras.utils.to_categorical(img_data, self.num_classes)
    else:
      img_data = img_data.astype("float32")
      img_data /= 255
      shape = (img_data.shape[0],) + self.image_shape
      img_data = img_data.reshape(shape)
    return img_data

# ディープラーニングのモデルを設定して返す関数
def network(input_shape, num_classes):
  model = Sequential()
  model.add(Conv2D(32, kernel_size=3, padding="same", input_shape=input_shape, activation="relu"))
  model.add(Conv2D(32, kernel_size=3, padding="same", activation="relu"))
  model.add(MaxPooling2D())
  model.add(Conv2D(64, kernel_size=3, padding="same", activation="relu"))
  model.add(Conv2D(64, kernel_size=3, padding="same",  activation="relu"))
  model.add(MaxPooling2D())
  model.add(Conv2D(128, kernel_size=3, padding="same", activation="relu"))
  model.add(Conv2D(128, kernel_size=3, padding="same",  activation="relu"))
  model.add(Conv2D(128, kernel_size=3, padding="same",  activation="relu"))
  model.add(MaxPooling2D())
  model.add(Flatten())
  model.add(Dense(1024, activation="relu"))
  model.add(Dense(1024, activation="relu"))
  model.add(Dense(num_classes, activation="softmax"))
  print(model.summary())
  return model

# モデルを学習させるクラス
class Trainer():
  # モデルをコンパイルして、学習するための設定をプライベートプロパティに設定する。
  def __init__(self, model, loss, optimizer):
    self._model = model
    self._model.compile(
        loss=loss,
        optimizer=optimizer,
        metrics=["accuracy"]
    )
    self._verbose = 1
    self._batch_size = 128
    self._epochs = 30
  
  # 実際の学習
  def fit(self, x_train, y_train, x_test, y_test):
    self._model.fit(
        x_train,
        y_train,
        batch_size=self._batch_size,
        epochs=self._epochs,
        verbose=self._verbose,
        validation_data=(x_test, y_test)
    )
    return self._model

dataset = CIFAR10Dataset() # データを取得するためのCIFAR10Datasetのインスタンス化
model = network(dataset.image_shape, dataset.num_classes) #モデルの取得

x_train, y_train, x_test, y_test = dataset.get_batch()  # 学習データとテストデータの取得
trainer = Trainer(model, loss="categorical_crossentropy", optimizer="adam") # モデルとロス関数、最適化アルゴリズムを引数にして、Trainerのインスタンス化
model = trainer.fit(x_train, y_train, x_test, y_test) # モデルの学習

# モデルの評価
score = model.evaluate(x_test, y_test, verbose=0)
print('Test loss: ', score[0])
print('Test accuracy: ', score[1])
```

## 参考文献
[直感DeepLearning](https://www.oreilly.co.jp/books/9784873118260/)