茨城エンジニアのPython開発日記

茨城のITベンチャー企業ではたらく2年目エンジニア。Pythonで色々なものを作成中。

KaiNDを作っていくぞ会 第5回 --MobileNetV2をGrad-CAMで可視化--


ブログから記事を見つけたい場合はこちら

ブログ地図 - 茨城エンジニアのPython開発日記



こんにちは。
開発メンバー、AI担当のKです。

最近、毎日簡易アエロバイクをこいでいるおかげか、6 [kg] 減量しました。
この調子でスマートになります。



目次

0. 前回まで

↓前回のブログ
https://tottorisnow33.hatenablog.com/entry/2022/07/23/094350

引き続き、KaiNDシリーズを拡張・改善しています。一緒にやっているメンバーで以下のように分担しています。

松原: KaiND Web
K   : AI自体の改善
Y   :スマホアプリのUIの改善


今回は、KaiNDを作っていくぞ会 第2回 --AI改善-- - 茨城エンジニアのPython開発日記 の続きで、可視化にチャレンジです。

1. 使用するNetwork

前回は可視化のためにResNetを使っていましたが、今回はKaiNDのアプリに載せているMobileNetV2で可視化します。
MobileNetV2は事前学習済みのものを用意して、転移学習でパグとブルドッグの識別をできるようにします。
MobileNetV2の転移学習については、以下を参考にしました。
www.tensorflow.org


2. Grad-CAMで可視化するために注意したこと

Grad-CAMで可視化する場合、Conv layerの出力が必要です。
値を取得するために、Conv layerの名前を指定する必要があります。
しかし、Tensorflow公式のやり方のように tf.keras.applications.MobileNetV2() の後に自分でlayerを追加すると、Conv layerを指定できません。
model.summary()で見ると分かるのですが、MobileNetV2のところが、"mobilenetv2_1.00_224"という1つの塊になってしまいます。

# Tensorflowの公式チュートリアルを参考にしたソース
IMAGE_SIZE = (224, 224)
IMG_SHAPE = IMAGE_SIZE + (3,)
base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE, include_top=False, weights='imagenet')
base_model.trainable = False

inputs = tf.keras.Input(shape=IMG_SHAPE)
x = base_model(inputs, training=False)
# 層追加
x = tf.keras.layers.GlobalAveragePooling2D()(x)
x = tf.keras.layers.Dropout(0.2)(x)
outputs = tf.keras.layers.Dense(y_train.shape[-1],
                          kernel_regularizer=tf.keras.regularizers.l2(0.0001), activation="softmax")(x)
model = tf.keras.Model(inputs, outputs)
model.summary()


Model: "model"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_2 (InputLayer) [(None, 224, 224, 3)] 0
_________________________________________________________________
mobilenetv2_1.00_224 (Model) (None, 7, 7, 1280) 2257984
_________________________________________________________________
global_average_pooling2d (Gl (None, 1280) 0
_________________________________________________________________
dropout (Dropout) (None, 1280) 0
_________________________________________________________________
dense (Dense) (None, 3) 3843
=================================================================
Total params: 2,261,827
Trainable params: 3,843
Non-trainable params: 2,257,984
_________________________________________________________________


そこで、ソースを以下のように少し書き換えて、MobileNetV2のconv layerの名前を取得できるようにしました。
ポイントは、base_model.outputを使って、MobileNetV2の出力を追加したい層の入力とすることと、tf.keras.Modelのinputにはbase_model.inputを使うことです。

# アレンジしたソース
IMAGE_SIZE = (224, 224)
IMG_SHAPE = IMAGE_SIZE + (3,)
base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE, include_top=False, weights='imagenet')
base_model.trainable = False
x = base_model.output
# 層追加
x = tf.keras.layers.GlobalAveragePooling2D()(x)
x = tf.keras.layers.Dropout(0.2)(x)
outputs = tf.keras.layers.Dense(y_train.shape[-1],
                          kernel_regularizer=tf.keras.regularizers.l2(0.0001), activation="softmax")(x)
model = tf.keras.Model(base_model.input, outputs)
model.summary()


Model: "model"
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
============================================================
input_1 (InputLayer) [(None, 224, 224, 3) 0
__________________________________________________________________________________________________
Conv1_pad (ZeroPadding2D) (None, 225, 225, 3) 0 input_1[0][0]
__________________________________________________________________________________________________
Conv1 (Conv2D) (None, 112, 112, 32) 864 Conv1_pad[0][0]
__________________________________________________________________________________________________
bn_Conv1 (BatchNormalization) (None, 112, 112, 32) 128 Conv1[0][0]
__________________________________________________________________________________________________
Conv1_relu (ReLU) (None, 112, 112, 32) 0 bn_Conv1[0][0]
__________________________________________________________________________________________________
expanded_conv_depthwise (Depthw (None, 112, 112, 32) 288 Conv1_relu[0][0]
__________________________________________________________________________________________________
expanded_conv_depthwise_BN (Bat (None, 112, 112, 32) 128 expanded_conv_depthwise[0][0]
__________________________________________________________________________________________________
expanded_conv_depthwise_relu (R (None, 112, 112, 32) 0 expanded_conv_depthwise_BN[0][0]
__________________________________________________________________________________________________
expanded_conv_project (Conv2D) (None, 112, 112, 16) 512 expanded_conv_depthwise_relu[0][0
__________________________________________________________________________________________________
expanded_conv_project_BN (Batch (None, 112, 112, 16) 64 expanded_conv_project[0][0]
__________________________________________________________________________________________________
block_1_expand (Conv2D) (None, 112, 112, 96) 1536 expanded_conv_project_BN[0][0]
__________________________________________________________________________________________________
block_1_expand_BN (BatchNormali (None, 112, 112, 96) 384 block_1_expand[0][0]
__________________________________________________________________________________________________
block_1_expand_relu (ReLU) (None, 112, 112, 96) 0 block_1_expand_BN[0][0]
__________________________________________________________________________________________________
block_1_pad (ZeroPadding2D) (None, 113, 113, 96) 0 block_1_expand_relu[0][0]
__________________________________________________________________________________________________
block_1_depthwise (DepthwiseCon (None, 56, 56, 96) 864 block_1_pad[0][0]
__________________________________________________________________________________________________
block_1_depthwise_BN (BatchNorm (None, 56, 56, 96) 384 block_1_depthwise[0][0]
__________________________________________________________________________________________________
block_1_depthwise_relu (ReLU) (None, 56, 56, 96) 0 block_1_depthwise_BN[0][0]
__________________________________________________________________________________________________
block_1_project (Conv2D) (None, 56, 56, 24) 2304 block_1_depthwise_relu[0][0]
__________________________________________________________________________________________________
block_1_project_BN (BatchNormal (None, 56, 56, 24) 96 block_1_project[0][0]
__________________________________________________________________________________________________


.....[中略]......


block_16_expand (Conv2D) (None, 7, 7, 960) 153600 block_15_add[0][0]
__________________________________________________________________________________________________
block_16_expand_BN (BatchNormal (None, 7, 7, 960) 3840 block_16_expand[0][0]
__________________________________________________________________________________________________
block_16_expand_relu (ReLU) (None, 7, 7, 960) 0 block_16_expand_BN[0][0]
__________________________________________________________________________________________________
block_16_depthwise (DepthwiseCo (None, 7, 7, 960) 8640 block_16_expand_relu[0][0]
__________________________________________________________________________________________________
block_16_depthwise_BN (BatchNor (None, 7, 7, 960) 3840 block_16_depthwise[0][0]
__________________________________________________________________________________________________
block_16_depthwise_relu (ReLU) (None, 7, 7, 960) 0 block_16_depthwise_BN[0][0]
__________________________________________________________________________________________________
block_16_project (Conv2D) (None, 7, 7, 320) 307200 block_16_depthwise_relu[0][0]
__________________________________________________________________________________________________
block_16_project_BN (BatchNorma (None, 7, 7, 320) 1280 block_16_project[0][0]
__________________________________________________________________________________________________
Conv_1 (Conv2D) (None, 7, 7, 1280) 409600 block_16_project_BN[0][0]
__________________________________________________________________________________________________
Conv_1_bn (BatchNormalization) (None, 7, 7, 1280) 5120 Conv_1[0][0]
__________________________________________________________________________________________________
out_relu (ReLU) (None, 7, 7, 1280) 0 Conv_1_bn[0][0]
__________________________________________________________________________________________________
global_average_pooling2d (Globa (None, 1280) 0 out_relu[0][0]
__________________________________________________________________________________________________
dropout (Dropout) (None, 1280) 0 global_average_pooling2d[0][0]
__________________________________________________________________________________________________
dense (Dense) (None, 3) 3843 dropout[0][0]
============================================================
Total params: 2,261,827
Trainable params: 3,843
Non-trainable params: 2,257,984
__________________________________________________________________________________________________


これで、Conv layerの名前を取得できるようになりました。


後は↓の参考ソースのように、Conv layerのLayer Name (とモデルのoutput) から勾配を取る感じです。
Grad CAM implementation with Tensorflow 2 · GitHub

3. MobileNetV2でGrad-CAMをやってみた結果

自作ResNetでやってみた結果は↓です。



4. 今後

Grad-CAMを使って可視化をすることができるようになったので、
次回からは推論結果を定量的に評価する機構も作りたいと思います。
そして、どんな画像が苦手なのかについて、Grad-CAMの結果と定量的評価結果から調べ、苦手そうなデータを学習データに追加したり、MobileNetV2よりも性能が良いとされるモデルならどうか評価してみたり、、、
などをしてKaiNDの性能を上げていきたいです。