Keras Python Tensorflow ガジェット部 研究部

Keras 学習済みCNNモデルの中間層の出力の可視化方法

学習済みモデルの中間層の出力を可視化したい

Kerasの学習済みモデルの中間層の活性を取得したかったので,その方法を紹介してみます.

使っている tf.keras, Keras のバージョンは以下. ちょっと古めな環境です.

tensorflow : 2.0.0

Keras : 2.3.1

ここでは, tf.keras でも スタンドアローンでもどっちでも大丈夫です.

必要なライブラリ

必要最低限(のつもり)のライブラリです.

import matplotlib.pyplot as plt
import cv2
import numpy as np
from tensorflow.keras import models

#jupyter なら %matplotlib inline

今回は,Keras が学習済みのパラメータを提供しているモデルを使いました.

実際はMobileNetを使ってたんですけど,ここでは説明しやすいVGG16をインポートします.

from tensorflow.keras.applications.vgg16 import VGG16
from tensorflow.keras.applications.vgg16 import preprocess_input, decode_predictions

モデルの定義とか

モデルを定義していきます.「 weights = 'imagenet' 」を指定することで,ImageNetの学習済みパラメータを適用できます.

分類とかしたいわけでなく,中間層の出力だけみたいので 「 include_top = False 」とします.

「 include_top = False 」ならば,入力画像の縦横はなんでも良いのですが,分類時と同じ (224,224,3) にしています.

model = VGG16(input_shape=(224,224,3),
         weights='imagenet',
               include_top = False)

layer の名前取得と,見たいlayerの名前を指定します.pooling 層前の出力を見てみます.

#print([layer.name for layer in model.layers]) 
# ['input_1', 'block1_conv1', 'block1_conv2', 'block1_pool', 
# 'block2_conv1', 'block2_conv2', 'block2_pool', 
# 'block3_conv1', 'block3_conv2', 'block3_conv3', 'block3_pool', 
# 'block4_conv1', 'block4_conv2', 'block4_conv3', 'block4_pool', 
# 'block5_conv1', 'block5_conv2', 'block5_conv3', 'block5_pool']

layer_names = ['block1_conv2','block2_conv2','block3_conv3','block4_conv3','block5_conv3']

特定の層の出力を得るモデルを新しく定義

models.Model で新しいモデルを定義します.複数の入力・出力はリストで指定します.

actmodel = models.Model(inputs = model.input, 
                        outputs=[model.get_layer(name).output for name in layer_names])

ここでは,先にターゲットの layer の name を指定した後,get_layerメソッドで layer の名前で呼び出しています.ちなみに get_layer では name ではなく,layer のインデックスの整数を指定しても呼び出せます.

print(model.get_layer(index=0).name)
#'input_1'
print(model.get_layer(index=-1).name)
#'block5_pool'
print(model.get_layer('block5_pool',index=0).name)
#'input_1'
# 異なるnameとindexを指定すると,indexが優先される.

出力の shape は以下のよう. batchsize x width x height x channel

actmodel.output_shape
# [(None, 224, 224, 64),
# (None, 112, 112, 128),
# (None, 56, 56, 256),
# (None, 28, 28, 512),
# (None, 14, 14, 512)]

画像を入力.出力の活性をみる

画像はなんでも良いですが,下の画像を入力しました

input image
ex_img = plt.imread('./test.jpg')
plt.imshow(ex_img)
plt.show()
ex_img = cv2.resize(ex_img,(224,224)) # (224,224,3) にリサイズ
ex_img = ex_img[np.newaxis,:,:,:] # バッチサイズ次元を設ける.shapeは(1,224,224,3)になる
ex_img_input = preprocess_input(ex_img) # ネットワークに入力する前の前処理

あとは,ネットワークに通して出力を得るだけ.predict メソッドを用いる.

activations = actmodel.predict(ex_img_input)

activations はリストで出力される.

activations[0]ならば 'block1_conv2' layer の出力,activations[-1] ならば 'block5_conv3' layer の出力が得られる.

それぞれの layer の出力の最初の 14 枚の特徴量を描写してみる.ただし,それぞれの特徴量は,(224,224)サイズにリサイズしている.

activation1 = activations[0][0][:,:,:14]
num_of_rows = 2
num_of_culm = 7
fig, axes = plt.subplots(num_of_rows, ncols=num_of_culm,figsize=(24.0,7.0),dpi=200)

for i in range(14):
    row = i%num_of_rows
    culm = i//num_of_rows
    axes[row][culm].imshow(activation1[:,:,i],cmap='jet')
    axes[row][culm].grid(False)
    
plt.subplots_adjust(wspace=0.2, hspace=0.1)
plt.savefig('sample1.jpg')
plt.show
'block1_conv2' の出力する特徴量
'block2_conv2' の出力する特徴量
'block3_conv3' の出力する特徴量
'block4_conv3' の出力する特徴量
'block5_conv3' の出力する特徴量

入力側では,入力したイメージが分かるような特徴量が得られている.

出力側に行くほど,より抽象化された特徴量が得られていることがわかる.

まとめ

models.Model を用いて,特定の層の出力だけを得るモデルを再定義することで,簡単に目的の特徴量のみが得られます.

おまけ〜出力を辞書型で管理したい〜

layer の名前を使って get_layer していたので,出力も layer name で呼べたら良いなと思う.

とりあえず出力を辞書型で指定してみる.

actmodel = models.Model(inputs = model.input, 
              outputs={name : model.get_layer(name).output for name in layer_names})

これ tf.keras (tf-ver 2.0.0) なら通るんだけど,スタンドアローンな Keras(2.3.1)だと

'tuple' object has no attribute 'layer'

ってエラーが出てしまう.

やっぱ tf.keras じゃないといけないんか.と思いつつ,tf.keras の方で predict すると...

actmodel = models.Model(inputs = model.input, 
                       outputs={name : model.get_layer(name).output for name in layer_names})
print(type(actmodel.output))
# dict

activations = actmodel.predict(ex_img_input)
print(type(activations))
# list

結局出力はリストで帰ってくる.

新しい tf.keras だったら predict 後も辞書で返ってくるのだろうか.

それともなんかやり方があるのかな?ちょっと調査が必要.

(2020年12月21日追記) tensorflow ver 2.4.0 でやっても同じ挙動.predict 後も辞書型で返してしまうのは何か弊害があるのかな.それともやり方あるのかな.ちょっとワカンナイ.(まあ,これで特別困ることがあるわけでもないんだけどね)

-Keras, Python, Tensorflow, ガジェット部, 研究部