KerasでInceptionとか色々な自作Modelを作ってみる話【Colab付き】

# はじめに

今回の記事では、TF2.0付属のKerasModuleを用いて、様々な内容の
この記事はColabratoryで直接動かすことができます。
動かしながら確認したい方は是非利用してみてください。
[Colabはこちら](https://colab.research.google.com/github/shamoji101/TensorFlow2.0Gym/blob/master/Make_Inception_Network_with_TF20.ipynb)
# 新しく統合されたKerasと3つのAPIについて

KerasはTF2.0からcontribレイヤーから正式なTFの仲間入りをしました。
代わりにTF1.xにあった共有レイヤー機能などは削除されています。（つまりKerasで書け。ということみたいです）  
Kerasには色々なAPIがありますが、Model作成に関係するのは以下の3つのAPIです。

- Sequencial API
- Functional API
- SubClass API

これを使えば基本的に全てのモデルを作れるといってもいいくらい自由度が高いAPIになっています。
これらをCIFAR10モデルを例にチュートリアルしていこうと思います。

# 最初に入れておいてほしいModule

チュートリアルでは以下のModuleを必要とします。
インストールされていない場合は各自pipなりで環境を構築するようにお願いします。

```python:import
import tensorflow as tf
import tensorflow.keras as keras
import matplotlib.pyplot as plt
import numpy as np 

print("TensorFlow Version:",tf.__version__)

```

```
TensorFlow Version: 2.0.0
```

# CIFAR10を扱う

今回チュートリアルで用いるのは皆さんお馴染みの[CIFAR10データセット](https://www.cs.toronto.edu/~kriz/cifar.html)です。
データは(32,32,3)でのテンソルで表現されています。ダウンロードは`tf.keras`から行うことができます。

## データセットをダウンロード

```python:データセットをダウンロード

(Train_X,Train_Y),(Test_X,Test_Y) = keras.datasets.cifar10.load_data()
Train_X,Test_X = Train_X/255.0, Test_X/255.0
```

##データの表示

一度表示させてみましょう。

```python:CIFAR10の画像をランダムで表示
import random

X = random.choice(Train_X)
print(X.shape)
plt.figure(facecolor="white")
plt.imshow(X)
plt.colorbar()
plt.grid(False)
plt.title("CIFAR10 sample")
plt.show()
```

```:結果
(32,32,3)
```

![CIFAR10-dog.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145843/b0dcbaef-9e7d-cd45-838b-6882bb18af5a.png)

次にランダムに沢山表示させてみましょう。

```python:ランダムに表示
labels = np.array([
    'airplane',
    'automobile',
    'bird',
    'cat',
    'deer',
    'dog',
    'frog',
    'horse',
    'ship',
    'truck'])
plt.figure(figsize=(10,30),facecolor="white")
index=random.randint(0,Train_Y.shape[0]-25)
for i in range(75):
    plt.subplot(15,5,i+1)
    plt.xticks([])
    plt.yticks([])
    plt.grid(False)
    plt.imshow(Train_X[i+index])
    plt.xlabel(labels[Train_Y[i+index][0]])
plt.show()

```
![CIFAR10-random.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145843/9a555cbe-1f6a-9480-ba59-812b760c140f.png)

（ちょっと長すぎましたね💦)

分布を表示させてみましょう。

```python:データ分布を表示
import collections
c=collections.Counter(Train_Y.reshape(-1))
c=sorted(c.items(),key=lambda x:x[0])
left = [label for label,count in c]
hight = [count for label,count in c]

plt.figure(facecolor="white")
plt.bar(left,hight,tick_label=left)
plt.show()
```
![CIFAR10-Distribute.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145843/d9d3e00e-07b2-0fe5-c4b2-bfc09195d139.png)

各ラベル、5000枚均一のようですね。

# チュートリアル１：KerasのSequentialAPIで問題を解く。

## SequentialAPI

SequentialAPIは、Kerasの中ではもっとも簡単なDNNModelの書き方で、
その分直列一直線にしかModelを組めません。

まずは複雑なモデルは組まず、単純なConv2Dモデルでやってみましょう。

SequentialAPIは基本的に以下のように記述します。

```python:SequentialAPI
model=keras.Sequential()
model.add(keras.layers.Conv2D(filters=16,kernel_size=(3,3),padding="same",input_shape=(32,32,3)))
model.add(keras.layers.Conv2D(filters=16,kernel_size=(3,3),padding="same"))
model.add(keras.layers.MaxPool2D())
model.add(keras.layers.Conv2D(filters=64,kernel_size=(3,3),padding="same"))
model.add(keras.layers.Conv2D(filters=64,kernel_size=(3,3),padding="same"))
model.add(keras.layers.MaxPool2D())
model.add(keras.layers.Conv2D(filters=256,kernel_size=(3,3),padding="same"))
model.add(keras.layers.Conv2D(filters=256,kernel_size=(3,3),padding="same"))
model.add(keras.layers.MaxPool2D())
model.add(keras.layers.Flatten())
model.add(keras.layers.Dense(128,activation="relu"))
model.add(keras.layers.Dense(10,activation="softmax"))
model.compile(optimizer="adam",loss="sparse_categorical_crossentropy",metrics=["accuracy"])
model.summary()
```

```:結果
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 32, 32, 16)        448       
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 32, 32, 16)        2320      
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 16, 16, 16)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 16, 16, 64)        9280      
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 16, 16, 64)        36928     
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 8, 8, 64)          0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 8, 8, 256)         147712    
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 8, 8, 256)         590080    
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 4, 4, 256)         0         
_________________________________________________________________
flatten (Flatten)            (None, 4096)              0         
_________________________________________________________________
dense (Dense)                (None, 128)               524416    
_________________________________________________________________
dense_1 (Dense)              (None, 10)                1290      
=================================================================
Total params: 1,312,474
Trainable params: 1,312,474
Non-trainable params: 0
_________________________________________________________________
```

このようにaddしていくことでDNNModelを構築することができます。

## 実際に学習させてみる

学習させてみましょう。やり方は簡単で、modelインスタンスの`.fit`を使用すればいいだけです。

```python:学習
history=model.fit(Train_X,Train_Y,validation_split=0.2,epochs=10,batch_size=128)
```

```:結果
Train on 40000 samples, validate on 10000 samples
Epoch 1/10
40000/40000 [==============================] - 12s 293us/sample - loss: 1.4603 - accuracy: 0.4796 - val_loss: 1.1121 - val_accuracy: 0.6115
Epoch 2/10
40000/40000 [==============================] - 11s 271us/sample - loss: 0.9884 - accuracy: 0.6554 - val_loss: 0.9367 - val_accuracy: 0.6795
Epoch 3/10
40000/40000 [==============================] - 11s 271us/sample - loss: 0.7948 - accuracy: 0.7233 - val_loss: 0.8491 - val_accuracy: 0.7138
Epoch 4/10
40000/40000 [==============================] - 11s 270us/sample - loss: 0.6479 - accuracy: 0.7741 - val_loss: 0.8719 - val_accuracy: 0.7037
Epoch 5/10
40000/40000 [==============================] - 11s 268us/sample - loss: 0.5212 - accuracy: 0.8179 - val_loss: 0.8546 - val_accuracy: 0.7215
Epoch 6/10
40000/40000 [==============================] - 11s 269us/sample - loss: 0.4168 - accuracy: 0.8551 - val_loss: 0.9460 - val_accuracy: 0.7288
Epoch 7/10
40000/40000 [==============================] - 11s 271us/sample - loss: 0.3216 - accuracy: 0.8884 - val_loss: 1.0214 - val_accuracy: 0.7089
Epoch 8/10
40000/40000 [==============================] - 11s 271us/sample - loss: 0.2616 - accuracy: 0.9078 - val_loss: 1.1193 - val_accuracy: 0.7123
Epoch 9/10
40000/40000 [==============================] - 11s 269us/sample - loss: 0.2134 - accuracy: 0.9260 - val_loss: 1.2778 - val_accuracy: 0.7148
Epoch 10/10
40000/40000 [==============================] - 11s 270us/sample - loss: 0.1822 - accuracy: 0.9372 - val_loss: 1.2681 - val_accuracy: 0.7187
```
ちなみに、ColabのようにGPUがあるような場合、自動的にGPUモードで学習してくれます。

評価は以下のように行うことができます。

```python:モデル最終評価
model.evaluate(Test_X,Test_Y,batch_size=128,verbose=0)
```

```:結果
[1.2933093170166015, 0.7155]
```
左はloss、右はAccuracyを示しています。

## グラフの表示
グラフを表示させてみましょう。

```python:グラフを表示
# Accuracyを表示
plt.figure(figsize=(7,5),facecolor="white")
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('Model accuracy')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['Train', 'Test'], loc='upper left')
plt.show()

# lossを表示
plt.figure(figsize=(7,5),facecolor="white")
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Model loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Train', 'Test'], loc='upper left')
plt.show()
```

![CIFAR10-seq-graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145843/5d625d60-0839-92a7-176d-0af24b298ee6.png)
![CIFAR10-seq-graph2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145843/f7077b8c-a30c-03c1-b813-9573aef54a5a.png)

お手本のような過学習が起きています。  
次こそはいいモデルを作るために、InceptionV3と呼ばれるモデルを作成してみましょう。

# チュートリアル2:Functional API

## Inceptionを構成する
InceptionV3は非常に複雑なモデルですが、FunctionalAPIを用いれば簡単に構築できます。
InceptionV3の名前はInceptionModuleから来ています。InceptionModuleは[参考文献](https://arxiv.org/abs/1512.00567)から引用すると、以下のように構成されています。(他にも様々なバリエーションがあります。)
![Inception](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145843/8238d321-1ddb-b228-d0df-6332c8c7b8ce.png)


CIFAR10と論文中のコンペとでは画像サイズが違うため、一部改造してInceptionModuleをFunctionalAPIで組んで行きましょう。
4つ伸びている線を左からCell1,Cell2,Cell3,Cell4と名前をつけます。
実際にInceptionModuleを組んでいくと以下のようなモデルになります。

```python:InceptionModule

input_x=keras.Input((32,32,3))
x = keras.layers.Conv2D(32,3,padding="same")(input_x)
out_x = keras.layers.Conv2D(64,3,strides=2,padding="same")(x)

output_filter = 128
#--inceptionModuleStart---#
cell1 = keras.layers.Conv2D(output_filter//4,1,padding="same")(out_x)
cell1 = keras.layers.Conv2D(output_filter//4,3,padding="same")(cell1)
cell1 = keras.layers.Conv2D(output_filter//4,3,padding="same")(cell1)

cell2 = keras.layers.Conv2D(output_filter//4,1,padding="same")(out_x)
cell2 = keras.layers.Conv2D(output_filter//4,3,padding="same")(cell2)

cell3 = keras.layers.MaxPool2D(pool_size=(2,2),strides=1,padding="same")(out_x)
cell3 = keras.layers.Conv2D(output_filter//4,1,padding="same")(cell3)

cell4 = keras.layers.Conv2D(output_filter//4,1,padding="same")(out_x)

x = keras.layers.Concatenate()([cell1,cell2,cell3,cell4])
#--inceptionModuleEnd---#
x = keras.layers.Flatten()(x)
x = keras.layers.Dense(128,activation="relu")(x)
x = keras.layers.Dropout(0.4)(x)
output_x = keras.layers.Dense(10,activation="softmax")(x)

model=keras.Model(input_x,output_x)
model.compile(optimizer="adam",loss="sparse_categorical_crossentropy",metrics=["accuracy"])
model.summary()
```
カッコが二つ！と驚かれると思いますが、これはコンストラクタの()と関数としての()という意味で別になります。
どちらかというと、tensorflowというよりも、Kerasの仕様に近いですね。

こんな風にして数珠つなぎをすることができます。注目してほしいのは、どのCellのはじめには`out_x`が入っています。
こうする事により、同じ入力を別の4つのNNに入力することがFunctionalAPIではできるのです。

## FunctionalAPIの解説
だだーっと解説しましたが、FunctionalAPIのポイントは、

```python:解説
input_x = keras.Input(shape) #バッチを除いた単一のデータ入力サイズを指定したInputLayer
x = keras.layers.Dense(dense_size, activation="relu")(input_x) #NN中間層
output_x = keras.layers.Dense(1,activation="sigmoid")(x)#NN最終層
model = keras.Model(inputs=input_x, outputs=output_x)#入力と出力を設定してモデルインスタンスを作成
```
となります。ちなみに複数のInput、Outputも設定できます。その場合はTupleでinputs/outputs引数に入れれば自動でfitしてくれます。
(詳細については後日カレンダーが空いていたらやろうかと思います。)


さらに以下のことを加えます。

* GlovalAveragePoolingを使用
* Convolution後にReLUを使用
* DropOutを適用

そうすると最終形として、以下のようになります。

```python:Inception_for_CIFAR10
input_x=keras.Input((32,32,3))
x = keras.layers.Conv2D(32,3,padding="same",activation="relu")(input_x)
x = keras.layers.Dropout(0.1)(x)
x = keras.layers.Conv2D(32,3,padding="same",activation="relu")(x)
x = keras.layers.Dropout(0.1)(x)
x = keras.layers.Conv2D(64,3,padding="same",activation="relu")(x)
x = keras.layers.Dropout(0.1)(x)
out_x = keras.layers.MaxPool2D()(x)

output_filter = 128
#--inceptionModule--#
cell1 = keras.layers.Conv2D(output_filter//4,1,padding="same",activation="relu")(out_x)
cell1 = keras.layers.Conv2D(output_filter//4,3,padding="same",activation="relu")(cell1)
cell1 = keras.layers.Conv2D(output_filter//4,3,padding="same",activation="relu")(cell1)

cell2 = keras.layers.Conv2D(output_filter//4,1,padding="same",activation="relu")(out_x)
cell2 = keras.layers.Conv2D(output_filter//4,3,padding="same",activation="relu")(cell2)

cell3 = keras.layers.MaxPool2D(pool_size=(2,2),strides=1,padding="same")(out_x)
cell3 = keras.layers.Conv2D(output_filter//4,1,padding="same",activation="relu")(cell3)

cell4 = keras.layers.Conv2D(output_filter//4,1,padding="same",activation="relu")(out_x)

out_x = keras.layers.Concatenate()([cell1,cell2,cell3,cell4])

output_filter = 128
#--inceptionModule--#
cell1 = keras.layers.Conv2D(output_filter//4,1,padding="same",activation="relu")(out_x)
cell1 = keras.layers.Conv2D(output_filter//4,3,padding="same",activation="relu")(cell1)
cell1 = keras.layers.Conv2D(output_filter//4,3,padding="same",activation="relu")(cell1)

cell2 = keras.layers.Conv2D(output_filter//4,1,padding="same",activation="relu")(out_x)
cell2 = keras.layers.Conv2D(output_filter//4,3,padding="same",activation="relu")(cell2)

cell3 = keras.layers.MaxPool2D(pool_size=(2,2),strides=1,padding="same")(out_x)
cell3 = keras.layers.Conv2D(output_filter//4,1,padding="same",activation="relu")(cell3)

cell4 = keras.layers.Conv2D(output_filter//4,1,padding="same",activation="relu")(out_x)

out_x = keras.layers.Concatenate()([cell1,cell2,cell3,cell4])

output_filter = 256

#--inception_reduction#
cell1 = keras.layers.Conv2D(output_filter//2,1,padding="same",activation="relu")(out_x)
cell1 = keras.layers.Conv2D(output_filter//2,3,padding="same",activation="relu")(cell1)
cell1 = keras.layers.Conv2D(output_filter//2,3,strides=2,padding="same",activation="relu")(cell1)

cell2 = keras.layers.Conv2D(output_filter//2,1,padding="same",activation="relu")(out_x)
cell2 = keras.layers.Conv2D(output_filter//2,3,strides=2,padding="same",activation="relu")(cell2)

cell3 = keras.layers.MaxPool2D(pool_size=(2,2),strides=2,padding="same")(out_x)


x = keras.layers.Concatenate()([cell1,cell2,cell3])

x = keras.layers.GlobalAveragePooling2D()(x)

x = keras.layers.Dense(200,activation="relu")(x)
x = keras.layers.Dropout(0.4)(x)
output_x = keras.layers.Dense(10,activation="softmax")(x)

model=keras.Model(input_x,output_x)
model.compile(optimizer="adam",loss="sparse_categorical_crossentropy",metrics=["accuracy"])
model.summary()
```

## 学習させてみる
学習させてみましょう。

```python:モデルを学習
history=model.fit(Train_X,Train_Y,validation_split=0.2,epochs=10,batch_size=128)
```

```:結果
Train on 40000 samples, validate on 10000 samples
Epoch 1/10
40000/40000 [==============================] - 38s 947us/sample - loss: 1.9527 - accuracy: 0.2422 - val_loss: 1.7093 - val_accuracy: 0.3506
Epoch 2/10
40000/40000 [==============================] - 35s 871us/sample - loss: 1.6131 - accuracy: 0.3867 - val_loss: 1.4641 - val_accuracy: 0.4547
Epoch 3/10
40000/40000 [==============================] - 35s 870us/sample - loss: 1.4118 - accuracy: 0.4748 - val_loss: 1.3792 - val_accuracy: 0.4960
Epoch 4/10
40000/40000 [==============================] - 35s 871us/sample - loss: 1.2831 - accuracy: 0.5302 - val_loss: 1.1916 - val_accuracy: 0.5602
Epoch 5/10
40000/40000 [==============================] - 35s 874us/sample - loss: 1.1909 - accuracy: 0.5660 - val_loss: 1.1827 - val_accuracy: 0.5674
Epoch 6/10
40000/40000 [==============================] - 35s 871us/sample - loss: 1.0971 - accuracy: 0.6045 - val_loss: 1.0484 - val_accuracy: 0.6162
Epoch 7/10
40000/40000 [==============================] - 35s 871us/sample - loss: 1.0318 - accuracy: 0.6278 - val_loss: 0.9390 - val_accuracy: 0.6616
Epoch 8/10
40000/40000 [==============================] - 35s 872us/sample - loss: 0.9674 - accuracy: 0.6546 - val_loss: 0.8948 - val_accuracy: 0.6769
Epoch 9/10
40000/40000 [==============================] - 35s 868us/sample - loss: 0.9134 - accuracy: 0.6742 - val_loss: 0.8558 - val_accuracy: 0.6945
Epoch 10/10
40000/40000 [==============================] - 35s 870us/sample - loss: 0.8641 - accuracy: 0.6929 - val_loss: 0.8625 - val_accuracy: 0.6975
```

```python:評価
model.evaluate(Test_X,Test_Y,batch_size=128,verbose=0)
```

```:結果
[0.8861876195907593, 0.6829]
```
うーん、悪くなってしまったように思えます。　グラフをみてみましょう。

![CIFAR10-func-graph1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145843/ecf5f2dd-4a29-eb7c-cd12-ee56d5942f38.png)

![CIFAR10-func-graph2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145843/60667162-9fa3-4bef-ca4d-d9a89f838b27.png)

過学習が非常に抑えられていていいですね、もっとepoch数を伸ばしたらもっとよくなりそうですね。

```python:epochを100に
history=model.fit(Train_X,Train_Y,validation_split=0.2,epochs=100,batch_size=128)
print(model.evaluate(Test_X,Test_Y,batch_size=128,verbose=0))
```

```:結果
Train on 40000 samples, validate on 10000 samples
Epoch 1/100
40000/40000 [==============================] - 37s 934us/sample - loss: 1.9353 - accuracy: 0.2507 - val_loss: 1.6810 - val_accuracy: 0.3510
Epoch 2/100
40000/40000 [==============================] - 35s 871us/sample - loss: 1.5842 - accuracy: 0.4001 - val_loss: 1.4552 - val_accuracy: 0.4647
Epoch 3/100
40000/40000 [==============================] - 35s 870us/sample - loss: 1.3558 - accuracy: 0.4974 - val_loss: 1.3613 - val_accuracy: 0.5091
Epoch 4/100
40000/40000 [==============================] - 35s 868us/sample - loss: 1.2095 - accuracy: 0.5586 - val_loss: 1.0845 - val_accuracy: 0.6082
Epoch 5/100
40000/40000 [==============================] - 35s 869us/sample - loss: 1.1131 - accuracy: 0.5954 - val_loss: 1.0509 - val_accuracy: 0.6200
...(中略)
Epoch 97/100
40000/40000 [==============================] - 34s 859us/sample - loss: 0.0869 - accuracy: 0.9710 - val_loss: 1.0830 - val_accuracy: 0.8110
Epoch 98/100
40000/40000 [==============================] - 34s 860us/sample - loss: 0.0830 - accuracy: 0.9718 - val_loss: 1.1919 - val_accuracy: 0.8027
Epoch 99/100
40000/40000 [==============================] - 34s 858us/sample - loss: 0.0843 - accuracy: 0.9722 - val_loss: 1.0689 - val_accuracy: 0.8040
Epoch 100/100
40000/40000 [==============================] - 34s 858us/sample - loss: 0.0864 - accuracy: 0.9709 - val_loss: 1.1582 - val_accuracy: 0.8007
[1.1903886857509614, 0.8022]
```

![CIFAR10-func-graph3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145843/49e5eef8-e444-d72c-9782-31befa3fee39.png)

![CIFAR10-func-graph4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145843/6a2daa5b-e432-8b22-ee68-d90418089b4c.png)

いい感じに伸びましたね！

# チュートリアル3: SubClass API

## SubClass APIとは？

SubClass APIとは、ざっくり言うとレイヤーやモデルを自作できる機能です。
先ほどのInceptionを一つのLayerとして自作してみましょう。

```python:Inception
class Inception(keras.layers.Layer):
    def __init__(self, output_filter=64, **kwargs):
        super(Inception, self).__init__(output_filter, **kwargs)

        self.c1_conv1 = keras.layers.Conv2D(output_filter//4,1,padding="same",name="c1_conv1")
        self.c1_conv2 = keras.layers.Conv2D(output_filter//4,3,padding="same",name="c1_conv2")
        self.c1_conv3 = keras.layers.Conv2D(output_filter//4,3,padding="same",name="c1_conv3")

        self.c2_conv1 = keras.layers.Conv2D(output_filter//4,1,padding="same",name="c2_conv1")
        self.c2_conv2 = keras.layers.Conv2D(output_filter//4,3,padding="same",name="c2_conv2")

        self.c3_MaxPool = keras.layers.MaxPool2D(pool_size=(2,2), strides=1, padding="same",name="c3_MaxPool")
        self.c3_conv = keras.layers.Conv2D(output_filter//4,1,padding="same",name="c3_conv")

        self.c4_conv = keras.layers.Conv2D(output_filter//4,1,padding="same",name="c4_conv")
        
        self.concat = keras.layers.Concatenate()
    def call(self, input_x, training=False):
        
        x1 = self.c1_conv1(input_x)
        x1 = self.c1_conv2(x1)
        cell1 = self.c1_conv3(x1)

        x2 = self.c2_conv1(input_x)
        cell2 = self.c2_conv2(x2)
        
        x2 = self.c3_MaxPool(input_x)
        cell3 = self.c3_conv(x2)

        cell4 = self.c4_conv(input_x)

        return self.concat([cell1,cell2,cell3,cell4]) 
   
```

![Inception](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145843/8238d321-1ddb-b228-d0df-6332c8c7b8ce.png)

これがひとかたまりになったInceptionModuleとなります。これだけではトレーニングできませんので、Modelを作る必要があります。
ModelでInceptionを使用したい場合は、Sequential APIでもFunctionalAPIでも、既存のレイヤーと同様にこのLayerClassは使うことができます。

```python:sequentialAPI
model = keras.models.Sequential()
model.add(keras.layers.Conv2D(32, 3,padding="same",kernel_initializer="he_normal",name="conv_0",input_shape=(32,32,3)))
model.add(Inception(64,name="inception1"))
model.add(Inception(128,name="inception2"))
model.add(Inception(256,name="inception3"))
model.add(keras.layers.GlobalAvgPool2D())
model.add(keras.layers.Dense(10,activation="softmax",name="output_layer"))
model.compile(optimizer="adam",loss="sparse_categorical_crossentropy",metrics=["accuracy"])
model.summary()
```

また、Model自体もLayerと同様にSubClassAPIで作成することができます。上のモデルを再現すると、

```python:SubClassAPI_Model

class InceptionNet(keras.models.Model):
    def __init__(self,**kwargs):
        super(InceptionNet, self).__init__(**kwargs)
        self.conv0 = keras.layers.Conv2D(32, 3,padding="same",kernel_initializer="he_normal",name="conv_0")
        self.inception1 = Inception(64,name="Inception1")
        self.inception2 = Inception(128,name="inception2")
        self.inception3 = Inception(256,name="inception3")
        self.GAP = keras.layers.GlobalAveragePooling2D()
        self.dense2 = keras.layers.Dense(10, activation="softmax",name="output_layer")


    def call(self, x, training=False):#trainingを引数に自動で選択してくれる
        x = self.conv0(x)
        x = self.inception1(x)
        x = self.inception2(x)
        x = self.inception3(x)
        x = self.GAP(x)
        x = self.dense2(x)
        return x


    def build_graph(self, input_shape): 
        input_shape_nobatch = input_shape[1:]
        self.build(input_shape)
        inputs = tf.keras.Input(shape=input_shape_nobatch)
        
        if not hasattr(self, 'call'):
            raise AttributeError("User should define 'call' method in sub-class model!")
        
        _ = self.call(inputs)

model = InceptionNet(name="InceptionNet")
model.build_graph((None,32,32,3))
model.compile(optimizer="adam",loss="sparse_categorical_crossentropy",metrics=["accuracy"])
model.summary()

```
こういった書き方になります。つまり、ModelでもLayerでも、if文やprintといったことを適宜挟むことができます。

## 層を深くしてみる。
さらにGoingDeeperしてみましょう。

```python:InceptionA
class InceptionA(keras.layers.Layer):
    def __init__(self, output_filter=64, **kwargs):
        super(InceptionA, self).__init__(output_filter, **kwargs)

        self.c1_conv1 = keras.layers.Conv2D(output_filter//4,1,padding="same",name="c1_conv1")
        self.c1_conv2_1 = keras.layers.Conv2D(output_filter//4,(1,3),padding="same",name="c1_conv2_1")
        self.c1_conv2_2 = keras.layers.Conv2D(output_filter//4,(3,1),padding="same",name="c1_conv2_2")
        
        self.c1_conv3_1 = keras.layers.Conv2D(output_filter//4,(1,3),padding="same",name="c1_conv3_1")
        self.c1_conv3_2 = keras.layers.Conv2D(output_filter//4,(3,1),padding="same",name="c1_conv3_2")
        
        self.c2_conv1 = keras.layers.Conv2D(output_filter//4,1,padding="same",name="c2_conv1")
        self.c2_conv2_1 = keras.layers.Conv2D(output_filter//4,(1,3),padding="same",name="c2_conv2_1")
        self.c2_conv2_2 = keras.layers.Conv2D(output_filter//4,(3,1),padding="same",name="c2_conv2_2")
        
        self.c3_MaxPool = keras.layers.MaxPool2D(pool_size=(2,2), strides=1, padding="same",name="c3_MaxPool")
        self.c3_conv = keras.layers.Conv2D(output_filter//4,1,padding="same",name="c3_conv")

        self.c4_conv = keras.layers.Conv2D(output_filter//4,1,padding="same",name="c4_conv")
        
        self.concat = keras.layers.Concatenate()
    def call(self, input_x, training=False):
        
        x1 = self.c1_conv1(input_x)
        x1 = self.c1_conv2_1(x1)
        x1 = self.c1_conv2_2(x1)
        x1 = self.c1_conv3_1(x1)
        cell1 = self.c1_conv3_2(x1)

        x2 = self.c2_conv1(input_x)
        x2 = self.c2_conv2_1(x2)
        cell2 = self.c2_conv2_2(x2)

        
        x2 = self.c3_MaxPool(input_x)
        cell3 = self.c3_conv(x2)

        cell4 = self.c4_conv(input_x)

        return self.concat([cell1,cell2,cell3,cell4]) 
   
```
![InceptionA](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145843/fe45dce5-d697-5bd3-a7d6-7942d7384842.png)
[参考文献](https://arxiv.org/abs/1512.00567)のFigure.6から引用(n=3とした)

```python:InceptionB
class InceptionB(keras.layers.Layer):
    def __init__(self, output_filter=64, **kwargs):
        super(InceptionB, self).__init__(output_filter, **kwargs)

        self.c1_conv1_1 = keras.layers.Conv2D(output_filter//6,1,padding="same",name="c1_conv1_1")
        self.c1_conv1_2 = keras.layers.Conv2D(output_filter//6,3,padding="same",name="c1_conv1_2")
        self.c1_conv2_1 = keras.layers.Conv2D(output_filter//6,(1,3),padding="same",name="c1_conv2_1")
        self.c1_conv2_2 = keras.layers.Conv2D(output_filter//6,(3,1),padding="same",name="c1_conv2_2")
        
        self.c2_conv1 = keras.layers.Conv2D(output_filter//6,1,padding="same",name="c2_conv1")
        self.c2_conv2_1 = keras.layers.Conv2D(output_filter//6,(1,3),padding="same",name="c2_conv2_1")
        self.c2_conv2_2 = keras.layers.Conv2D(output_filter//6,(3,1),padding="same",name="c2_conv2_2")
        
        self.c3_MaxPool = keras.layers.MaxPool2D(pool_size=(2,2), strides=1, padding="same",name="c3_MaxPool")
        self.c3_conv = keras.layers.Conv2D(output_filter//6,1,padding="same",name="c3_conv")

        self.c4_conv = keras.layers.Conv2D(output_filter//6,1,padding="same",name="c4_conv")
        
        self.concat = keras.layers.Concatenate()
    def call(self, input_x, training=False):
        
        x1 = self.c1_conv1_1(input_x)
        x1 = self.c1_conv1_2(x1)
        cell1_1 = self.c1_conv2_1(x1)
        cell1_2 = self.c1_conv2_2(x1)

        x2 = self.c2_conv1(input_x)
        cell2_1 = self.c2_conv2_1(x2)
        cell2_2 = self.c2_conv2_2(x2)

        
        x2 = self.c3_MaxPool(input_x)
        cell3 = self.c3_conv(x2)

        cell4 = self.c4_conv(input_x)

        return self.concat([cell1_1,cell1_2,cell2_1,cell2_2,cell3,cell4]) 
```
![InceptionB](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145843/d0f56470-4b4a-d979-5057-b3001d207318.png)
[参考文献](https://arxiv.org/abs/1512.00567)のFigure.7から引用

Model作成はFunctionalAPIを使って書いてみましょう。

```python:Model
input_x=keras.Input((32,32,3))
x = keras.layers.Conv2D(32,3,padding="same",activation="relu")(input_x)
x = keras.layers.Dropout(0.1)(x)
x = keras.layers.Conv2D(32,3,padding="same",activation="relu")(x)
x = keras.layers.Dropout(0.1)(x)
x = keras.layers.Conv2D(64,3,padding="same",activation="relu")(x)
x = keras.layers.Dropout(0.1)(x)
x = keras.layers.MaxPool2D()(x)
x = Inception(64,name="inception1")(x)
x = Inception(64,name="inception2")(x)
x = Inception(128,name="inception3")(x)
x = keras.layers.MaxPool2D()(x)
x = InceptionA(128,name="inceptionA1")(x)
x = InceptionA(128,name="inceptionA2")(x)
x = InceptionA(128,name="inceptionA3")(x)
x = InceptionA(128,name="inceptionA4")(x)
x = InceptionA(256,name="inceptionA5")(x)
x = keras.layers.MaxPool2D()(x)
x = InceptionB(256,name="inceptionB1")(x)
x = InceptionB(512,name="inceptionB2")(x)
x = keras.layers.GlobalAvgPool2D()(x)
x = keras.layers.Dense(200,activation="relu")(x)
x = keras.layers.Dropout(0.4)(x)
output_x = keras.layers.Dense(10,activation="softmax")(x)
model=keras.Model(input_x,output_x)

```

## 学習してみる

```python:結果

history=model.fit(Train_X,Train_Y,validation_split=0.2,epochs=100,batch_size=128)
print(model.evaluate(Test_X,Test_Y,batch_size=128,verbose=0))
# Plot training & validation accuracy values
plt.figure(figsize=(7,5),facecolor="white")
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('Model accuracy')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['Train', 'Test'], loc='upper left')
plt.show()

# Plot training & validation loss values
plt.figure(figsize=(7,5),facecolor="white")
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Model loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Train', 'Test'], loc='upper left')
plt.show()
```

```:結果
Train on 40000 samples, validate on 10000 samples
Epoch 1/100
40000/40000 [==============================] - 21s 528us/sample - loss: 1.7499 - accuracy: 0.3451 - val_loss: 1.5629 - val_accuracy: 0.4344
Epoch 2/100
40000/40000 [==============================] - 14s 358us/sample - loss: 1.3234 - accuracy: 0.5164 - val_loss: 1.0947 - val_accuracy: 0.6033
Epoch 3/100
40000/40000 [==============================] - 14s 354us/sample - loss: 1.1026 - accuracy: 0.6058 - val_loss: 0.9439 - val_accuracy: 0.6604
Epoch 4/100
40000/40000 [==============================] - 14s 348us/sample - loss: 0.9771 - accuracy: 0.6535 - val_loss: 0.9520 - val_accuracy: 0.6610
Epoch 5/100
40000/40000 [==============================] - 14s 339us/sample - loss: 0.8966 - accuracy: 0.6862 - val_loss: 0.8594 - val_accuracy: 0.6891
Epoch 6/100
40000/40000 [==============================] - 13s 331us/sample - loss: 0.8242 - accuracy: 0.7118 - val_loss: 0.8313 - val_accuracy: 0.7049
Epoch 7/100
40000/40000 [==============================] - 13s 331us/sample - loss: 0.7646 - accuracy: 0.7346 - val_loss: 0.7673 - val_accuracy: 0.7348

...(中略)
Epoch 98/100
40000/40000 [==============================] - 13s 328us/sample - loss: 0.2433 - accuracy: 0.9205 - val_loss: 0.7278 - val_accuracy: 0.8055
Epoch 99/100
40000/40000 [==============================] - 13s 328us/sample - loss: 0.2573 - accuracy: 0.9159 - val_loss: 0.7851 - val_accuracy: 0.7935
Epoch 100/100
40000/40000 [==============================] - 13s 328us/sample - loss: 0.2532 - accuracy: 0.9165 - val_loss: 0.8414 - val_accuracy: 0.7903
[0.8851639811515808, 0.7773]
```
![CIFAR10-sub-graph1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145843/748a0768-2f80-db9a-0aad-97b17d9b3436.png)
![CIFAR10-sub-graph2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145843/d082989d-bb00-be79-8b6e-a9c5e5349770.png)

思うように精度向上とは行きませんでしたが、Lossは過学習が抑えられているとわかります。

# おわりに

精度をもっとあげたい！という方は、パラメータチューニングを行うか、以下の記事を参考にされるとよろしいかと思われます。
[CIFAR-10でaccuracy95%--CNNで精度を上げるテクニック--](https://qiita.com/yy1003/items/c590d1a26918e4abe512)

今回、DataAugmentationを行なわなかったりしている関係で、精度も過学習になっています。(次の日のTF2.0AdventCalendarにて紹介)
CNNで精度を上げるテクニックに関してはTF2.0アドベントカレンダーにも今後載せようかと考えています。

ではまた明日！