「ゼロから作るDeep Learning」自習メモ（その16）SimpleConvNet を Keras で構築してみた

「ゼロから作るDeep Learning」(斎藤 康毅　著　オライリー・ジャパン刊)を読んでいる時に、参照したサイト等をメモしていきます。　[その１５](https://qiita.com/slow_learner/items/f9ba7f84053684da750a)← → [その１７](https://qiita.com/slow_learner/items/675fbedc932e850db4e8)

Google Colab がふつうに使えるので、この本の内容を TensorFlow でやってみることにします。

TensorFlow のサイト　https://www.tensorflow.org/?hl=ja の初心者向けチュートリアル「はじめてのニューラルネットワーク」をやって、本の５章めくらいまではカバーできたかと思います。
なので、７章のSimpleConvNetに相当するものを keras で構築してみようと思います。

##Conv1D ? 2D ? 3D ?
畳み込みには　Convナントカ　を使うんだろうな、というのは察しがつくのですが、1D 2D 3D と種類があって、 D はたぶん Dimension 次元 のことなんでしょうから、画像の処理は２次元 2D でいい、ということなんでしょうか？
そういうことなら、それでいいんですが、

が

1次元って、どんなものがあるの？　３次元って？
ということが気になります。

###1D は時系列データ等で

>Keras Documentationより
このレイヤーを第一層に使う場合，キーワード引数としてinput_shape（整数のタプルかNone．例えば10個の128次元ベクトルの場合ならば(10, 128)，任意個数の128次元ベクトルの場合は(None, 128)）を指定してください．

ということです
こんな例がありました。 
[時系列予測を一次元畳み込みを使って解く](https://qiita.com/niisan-tokyo/items/a94dbd3134219f19cab1)
[時系列データに対する１次元畳み込み層の出力を可視化](https://www.kabuku.co.jp/developers/visualize_intermidiate_conv1d_output)

###2D は画像等で

>このレイヤーをモデルの第1層に使うときはキーワード引数input_shape （整数のタプル，サンプル軸を含まない）を指定してください． 例えば，data_format="channels_last"のとき，128x128 RGB画像ではinput_shape=(128, 128, 3)となります．

###3D は高さを含めた空間等で

>このレイヤーをモデルの第一層に使うときはキーワード引数input_shape （整数のタプル，サンプル軸を含まない）を指定してください． 例えばdata_format="channels_last"の場合，シングルチャネルの128x128x128の立体はinput_shape=(128, 128, 128, 1)です．

空間内での移動方向のデータも３次元になるので、こんな例がありました。
[加速度センサーで行動分類](http://bluewidz.blogspot.com/2017/10/cnn.html)


##Conv2D Convolution2D
パラメータ
filters,     畳み込みにおける出力フィルタの数
kernel_size, 畳み込みフィルタの幅と高さを指定します. 単一の整数の場合は正方形のカーネルになります
strides=(1, 1), 畳み込みの縦と横のストライドをそれぞれ指定できます．単一の整数の場合は幅と高さが同様のストライドになります
padding='valid',   出力のサイズが入力と違う（小さい）"valid"か、同じ"same"のどちらかを指定します
data_format=None,
dilation_rate=(1, 1),
groups=1,
activation=None,
use_bias=True,
kernel_initializer='glorot_uniform',　重みの初期化方法
bias_initializer='zeros',　バイアスの初期化方法
kernel_regularizer=None,
bias_regularizer=None,
activity_regularizer=None,
kernel_constraint=None,
bias_constraint=None,
**kwargs


>data_format
"channels_last"（デフォルト）か"channels_first"のどちらかを指定します．これは入力における次元の順序です． "channels_last"の場合，入力のshapeは"(batch, height, width, channels)"となり，"channels_first"の場合は"(batch, channels, height, width)"となります．

これ、逆に考えると、Keras では　channels_last 入力のshapeが(batch, height, width, channels)　というのがデフォルトだよ、と言っているわけです。

で

注意しないといけないのが、「ゼロから作るDeep Learning」で扱っているMNISTデータは、(batch, channels, height, width)  channels_first だということ。
ところが、このパラメータに "channels_first" を指定しても、エラーが出てしまいます。
結局、データを channels_last に変換して処理する事にしました。

padding については、こちらを参照しました→ [Tensorflow - padding = VALID/SAMEの違いについて](https://qiita.com/tadOne/items/48302a399dcad44c69c8)


##SimpleConvNet

本のP229から説明されている SimpleConvNet を Keras で構築してみます。

Google Drive に保存している MNISTデータを使うので、ドライブのマウントやドライブにあるフォルダへのパスを定義します。

```python
from google.colab import drive
drive.mount('/content/drive')

import sys, os
sys.path.append('/content/drive/My Drive/Colab Notebooks/deep_learning/common')
sys.path.append('/content/drive/My Drive/Colab Notebooks/deep_learning/dataset')
```

```python
# TensorFlow と tf.keras のインポート
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from keras.layers import Dense, Activation, Flatten, Conv2D, MaxPooling2D

# ヘルパーライブラリのインポート
import numpy as np
import matplotlib.pyplot as plt
```
ドライブに保存してあるMNISTデータを読み込みます。

```python
from mnist import load_mnist
# データの読み込み
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=False)
x_train.shape
```
>(60000, 1, 28, 28)

(batch, channels, height, width) channels_first　の形式です。
これを(batch, height, width, channels) channels_last に変換します。

```python
X_train = x_train.transpose(0,2,3,1)
X_test = x_test.transpose(0,2,3,1)

X_train.shape
```
>(60000, 28, 28, 1)

channel_last になりました。
また、ラベル t_train は整数の目的値なので、損失関数には sparse_categorical_crossentropyを使います。

>「ゼロから作るDeep Learning」P230
図7-23 に示すように、ネットワークの構成は、「Convolution - ReLU - Pooling - Affine - ReLU - Affine - Softmax」という流れです。
![g7-23.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/686010/175c93a8-0e5d-6318-7762-68fe3497ac60.jpeg)

これをKerasで構築しました。活性化関数に relu を使うので、重みの初期値には he_normal を使っています。

```python
input_shape=(28,28,1)
filter_num = 30
filter_size = 5
filter_stride = 1
pool_size_h=2
pool_size_w=2
pool_stride=2
hidden_size=100
output_size=10

model = keras.Sequential(name="SimpleConvNet")
model.add(Conv2D(filter_num, filter_size, activation="relu", strides=filter_stride, kernel_initializer='he_normal', input_shape=input_shape))
model.add(MaxPooling2D(pool_size=(pool_size_h, pool_size_w),strides=pool_stride))
model.add(Flatten())
model.add(Dense(hidden_size, activation="relu", kernel_initializer='he_normal')) 
model.add(keras.layers.Dense(output_size, activation="softmax"))

#モデルのコンパイル
model.compile(loss="sparse_categorical_crossentropy", 
              optimizer="adam", 
              metrics=["accuracy"])

model.summary()
```
>Model: SimpleConvNet
Layer 　　　　(type)　　　　　　Output Shape　　　　　Param 
_________________________________________________________________
conv2d 　　　　(Conv2D)　　　　(None, 24, 24, 30) 　　780       
max_pooling2d (MaxPooling2D)　(None, 12, 12, 30)　　　　0         
flatten 　　　　(Flatten) 　　　(None, 4320)　　　　　　　0         
dense 　　　　　(Dense)　　　　　(None, 100)　　　　　432100    
dense_1 　　　　(Dense)　　　　　(None, 10)　　　　　　　1010      
_________________________________________________________________
Total params: 433,890
Trainable params: 433,890
Non-trainable params: 0

モデルを訓練します。

```python
model.fit(X_train, t_train,  epochs=5, batch_size=128)
```
>Epoch 1/5
469/469 [==============================] - 27s 58ms/step - loss: 0.2050 - accuracy: 0.9404
Epoch 2/5
469/469 [==============================] - 27s 57ms/step - loss: 0.0614 - accuracy: 0.9819
Epoch 3/5
469/469 [==============================] - 26s 56ms/step - loss: 0.0411 - accuracy: 0.9875
Epoch 4/5
469/469 [==============================] - 27s 58ms/step - loss: 0.0315 - accuracy: 0.9903
Epoch 5/5
469/469 [==============================] - 27s 57ms/step - loss: 0.0251 - accuracy: 0.9927
tensorflow.python.keras.callbacks.History at 0x7f5167581748

かなり高い正解率になりました。

```python
#予測する
predictions = model.predict(X_test)

class_names = ['0', '1', '2', '3', '4', 
               '5', '6', '7', '8', '9']

def plot_image(i, predictions_array, t_label, img):
    predictions_array = predictions_array[i]
    img = img[i].reshape((28, 28))
    true_label = t_label[i]
    plt.grid(False)
    plt.xticks([])
    plt.yticks([])

    plt.imshow(img, cmap=plt.cm.binary)

    predicted_label = np.argmax(predictions_array)
    if predicted_label == true_label:
        color = 'blue'
    else:
        color = 'red'

    plt.xlabel("{} {:2.0f}% ({})".format(class_names[predicted_label],
                                    100*np.max(predictions_array),
                                    class_names[true_label]),
                                    color=color)

def plot_value_array(i, predictions_array, t_label):
    predictions_array = predictions_array[i]
    true_label = t_label[i]
    plt.grid(False)
    plt.xticks([])
    plt.yticks([])
    thisplot = plt.bar(range(10), predictions_array, color="#777777")
    plt.ylim([0, 1]) 
    predicted_label = np.argmax(predictions_array)

    thisplot[predicted_label].set_color('red')
    thisplot[true_label].set_color('blue')

# X個のテスト画像、予測されたラベル、正解ラベルを表示します。
# 正しい予測は青で、間違った予測は赤で表示しています。
num_rows = 5
num_cols = 3
num_images = num_rows*num_cols
plt.figure(figsize=(2*2*num_cols, 2*num_rows))
for i in range(num_images):
    plt.subplot(num_rows, 2*num_cols, 2*i+1)
    plot_image(i, predictions, t_test, X_test)
    plt.subplot(num_rows, 2*num_cols, 2*i+2)
    plot_value_array(i, predictions, t_test)
plt.show()
```
>![g7-24.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/686010/5583121f-571a-cfb0-f67d-5bc17d8ad48b.jpeg)

かなり難しい９番目の 5 もちゃんと判別できました。

レイヤーを個々に積み上げていく書き方もあります。

```python
model = keras.Sequential(name="SimpleConvNet")
model.add(keras.Input(shape=input_shape))
model.add(keras.layers.Convolution2D(filter_num, filter_size, strides=filter_stride, kernel_initializer='he_normal'))
model.add(keras.layers.Activation(tf.nn.relu)) 
model.add(keras.layers.MaxPooling2D(pool_size=(pool_size_h, pool_size_w),strides=pool_stride))
model.add(keras.layers.Flatten())
model.add(keras.layers.Dense(hidden_size))
model.add(keras.layers.Activation(tf.nn.relu)) 
model.add(keras.layers.Dense(output_size))
model.add(keras.layers.Activation(tf.nn.softmax)) 
```

　[その１５](https://qiita.com/slow_learner/items/f9ba7f84053684da750a)← → [その１７](https://qiita.com/slow_learner/items/675fbedc932e850db4e8)
メモの目次等はこちらから　[読めない用語集](https://qiita.com/slow_learner/items/03eb3367119e98b5a22c)