TensorFlow, KerasでVGG16などの学習済みモデルを利用

Posted: | Tags: Python, TensorFlow, Keras, 機械学習

KerasではVGG16やResNetといった有名なモデルが学習済みの重みとともに提供されている。TensorFlow統合版のKerasでも利用可能。

学習済みモデルの使い方として、以下の内容について説明する。

  • TensorFlow, Kerasで利用できる学習済みモデル
    • ソースコード(GitHubのリポジトリ)
    • 公式ドキュメント
  • 学習済みモデルの読み込み・ダウンロード
  • 学習済みモデルで予測(推論): 画像分類
    • 画像の形状変換
    • 画像の前処理: preprocess_input()
    • 予測(推論)を実行
    • 予測結果に対応するクラス名を取得: decode_predictions()
  • 学習済みモデルの出力層(全結合層)を変更: include_top
    • グローバルプーリング層を追加: pooling
    • 任意のレイヤーを出力層側に追加
  • 学習済みモデルの入力の形状を変更: input_shape
  • 学習済みモデルの入力に新たなレイヤーを追加: input_tensor
    • 任意のレイヤーを入力層側に追加
    • 実行例

引数include_topinput_tensorを利用した転移学習・ファインチューニングの具体例は以下の記事を参照。

以下のサンプルコードのTensorFlowのバージョンは2.1.0。TensorFlowに統合されたKerasを使う。

import tensorflow as tf
import pprint

print(tf.__version__)
# 2.1.0

スタンドアローンのKerasを使う場合、import kerasで別途Kerasをインポートして、コード中のtf.kerasの部分をkerasに置き換えれば動くかもしれないが、保証はできない。

TensorFlow, Kerasについての基礎は以下の記事を参照。

TensorFlow, Kerasで利用できる学習済みモデル

ソースコード(GitHubのリポジトリ)

Kerasにおける学習済みのモデルはapplicationsというモジュールで管理されている。

GitHubのリポジトリは以下。利用できるモデルの一覧がリファレンスの論文へのリンクとともにREADMEに記載されている。

以下のようなモデルが利用できる。

  • VGG16, VGG19
  • ResNet
  • MobileNet
  • DenseNet
  • ...

各モデルの実装は上記リポジトリのkeras_applicationsディレクトリの中のそれぞれのファイルで確認できる。Functional APIの形式で書かれているのでKerasでオリジナルのモデルを実装する際の参考にもなる。

例えば、VGG16の実装は以下。

公式ドキュメント

Kerasの公式ドキュメントは以下。日本語版は情報が古い場合があるので注意。

TensorFlowのAPIリファレンスは以下。バージョン2.1.0時点では細かい説明は記載されていない。

学習済みモデルの読み込み・ダウンロード

ここからはVGG16を例とする。他のモデルでも使い方は同じ。

tf.keras.applications.vgg16.VGG16()vgg16モジュールのVGG16()関数)でモデルを生成する。

model = tf.keras.applications.vgg16.VGG16(weights='imagenet')

model.summary()
# Model: "vgg16"
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# input_1 (InputLayer)         [(None, 224, 224, 3)]     0         
# _________________________________________________________________
# block1_conv1 (Conv2D)        (None, 224, 224, 64)      1792      
# _________________________________________________________________
# block1_conv2 (Conv2D)        (None, 224, 224, 64)      36928     
# _________________________________________________________________
# block1_pool (MaxPooling2D)   (None, 112, 112, 64)      0         
# _________________________________________________________________
# block2_conv1 (Conv2D)        (None, 112, 112, 128)     73856     
# _________________________________________________________________
# block2_conv2 (Conv2D)        (None, 112, 112, 128)     147584    
# _________________________________________________________________
# block2_pool (MaxPooling2D)   (None, 56, 56, 128)       0         
# _________________________________________________________________
# block3_conv1 (Conv2D)        (None, 56, 56, 256)       295168    
# _________________________________________________________________
# block3_conv2 (Conv2D)        (None, 56, 56, 256)       590080    
# _________________________________________________________________
# block3_conv3 (Conv2D)        (None, 56, 56, 256)       590080    
# _________________________________________________________________
# block3_pool (MaxPooling2D)   (None, 28, 28, 256)       0         
# _________________________________________________________________
# block4_conv1 (Conv2D)        (None, 28, 28, 512)       1180160   
# _________________________________________________________________
# block4_conv2 (Conv2D)        (None, 28, 28, 512)       2359808   
# _________________________________________________________________
# block4_conv3 (Conv2D)        (None, 28, 28, 512)       2359808   
# _________________________________________________________________
# block4_pool (MaxPooling2D)   (None, 14, 14, 512)       0         
# _________________________________________________________________
# block5_conv1 (Conv2D)        (None, 14, 14, 512)       2359808   
# _________________________________________________________________
# block5_conv2 (Conv2D)        (None, 14, 14, 512)       2359808   
# _________________________________________________________________
# block5_conv3 (Conv2D)        (None, 14, 14, 512)       2359808   
# _________________________________________________________________
# block5_pool (MaxPooling2D)   (None, 7, 7, 512)         0         
# _________________________________________________________________
# flatten (Flatten)            (None, 25088)             0         
# _________________________________________________________________
# fc1 (Dense)                  (None, 4096)              102764544 
# _________________________________________________________________
# fc2 (Dense)                  (None, 4096)              16781312  
# _________________________________________________________________
# predictions (Dense)          (None, 1000)              4097000   
# =================================================================
# Total params: 138,357,544
# Trainable params: 138,357,544
# Non-trainable params: 0
# _________________________________________________________________

引数weights='imagenet'とするとImageNetで学習済みの重みのデータが読み込まれる。重みデータ(HDF5形式のファイル)が初回実行時に~/.keras/models/にダウンロードされる。モデルによっては500MBを超えるサイズで結構時間がかかるので注意。

weights=Noneとするとランダムな重みでモデルが生成され、データのダウンロードは行われない。

デフォルトはweights='imagenet'なので、省略しても学習済みの重みデータが読み込まれる。なぜかサンプルコードでは明示的にweights='imagenet'としている場合が多い。

なお、tf.keras.applications.VGG16tf.keras.applications.vgg16.VGG16のエイリアスなのでどちらを使ってもよい。

print(tf.keras.applications.vgg16.VGG16 is tf.keras.applications.VGG16)
# True

また、以下のようにVGG16などの関数を直接インポートするサンプルコードも多い。

from tensorflow.keras.applications.vgg16 import VGG16

print(tf.keras.applications.vgg16.VGG16 is VGG16)
# True

スネークケース(例: vgg16, inception_v3)がモジュール、キャメルケース(例: VGG16, InceptionV3)がモデルを生成する関数となっている。混同しがちなので要注意。

モデル生成関数の引数include_topinput_tensorで入出力に新たな層を追加する方法については後述。

学習済みモデルで予測(推論): 画像分類

ここでは学習済みモデルの簡単な利用法として、VGG16を使った画像分類の例を示す。上のサンプルコードで読み込んだモデルを使う。

説明のための出力を省略したコード全体は以下。

画像の形状変換

画像ファイルを読み込み、NumPy配列ndarrayに変換する。VGG16の学習済みモデルのインプットサイズに合わせて(224, 224)にリサイズしている。

img_pil = tf.keras.preprocessing.image.load_img(
    '../data/img/src/baboon.jpg', target_size=(224, 224)
)

print(type(img_pil))
# <class 'PIL.Image.Image'>

img = tf.keras.preprocessing.image.img_to_array(img_pil)
print(type(img))
# <class 'numpy.ndarray'>

print(img.shape)
# (224, 224, 3)

print(img.dtype)
# float32

print(img.min(), '-', img.max())
# 0.0 - 255.0

ここでは以下の関数を使用している。読み込みにはPillow(PIL)が使われており、色の並びはRGB

TensorFlow2.1.0ではPillowは同時にインストールされないようなので、別途pipなどでインストールが必要。インストールされていない場合はエラーが発生する。

ImportError: Could not import PIL.Image. The use of `load_img` requires PIL.

他の方法で読み込んでも構わないが、OpenCVで読み込んだ場合は色の並びがBGRとなるので注意。

使用しているのはOpenCVにサンプルとして含まれているbaboon(ヒヒ)の画像。

baboon

TensorFlow, Kerasではバッチ処理を前提としているため、RGB画像の場合は(サンプル数, 縦, 横, チャンネル数)の四次元配列とする必要がある。1枚だけ処理するときも先頭に次元を追加する。

img = img[tf.newaxis, ...]
print(img.shape)
# (1, 224, 224, 3)

tf.newaxisnp.newaxisと同じで中身はNone。上の例ではimg[tf.newaxis, ...]としているが、, ...を省略してimg[tf.newaxis]としてもよい。

画像の前処理: preprocess_input()

vgg16などの各モデルのモジュールでpreprocess_input()という前処理のための関数が提供されている。

各モデルの重みデータに合わせた前処理が実行される。preprocess_input()を省略してもある程度の結果は出るが、正しい前処理を行うほうが望ましい。

img_preprocessed = tf.keras.applications.vgg16.preprocess_input(img)
print(img_preprocessed.min(), '-', img_preprocessed.max())
# -117.68 - 151.061

preprocess_input()の実体はimagenet_utils.pyで定義されている。

引数modecaffe, tf, torch)によって処理が変わっているが、これは実行時のバックエンドの種類ではなく、重みデータを訓練したときに使われたフレームワークの種類。

VGG16の重みデータはcaffeで訓練されているため、それに合わせて色の並びがRGBからBGRに変更され、ImageNetデータセットの平均値が引かれる、という処理が行われる。

一方、例えばInceptionV3ではTensorFlowで訓練されているため、0 - 255-1 - 1にスケーリングするという処理が行われる。inception_v3モジュールの中でimagenet_utils.pypreprocess_input()mode='tf'で呼んでいる。

基本的には各モデルのモジュールのpreprocess_input()を実行すれば、そのモデルの重みデータに合わせた処理が実行されるので気にする必要はないが、モデルによって処理の内容が異なる場合があることは認識しておいたほうがいいだろう。

preprocess_input()の前提は以下の通り。

  • 画素値の範囲: 0 - 255
  • 色の並び: RGB
  • channel last(サンプル数, 縦, 横, チャンネル数)
    • channel first(サンプル数, チャンネル数, 縦, 横)の場合はpreprocess_input()の引数data_format='channels_first'とする

予測(推論)を実行

predict()メソッドで予測(推論)を実行する。

学習済みモデルは1000クラス分類のモデルなので、1000クラスの確率が結果として返る。以下のサンプルコードでは先頭10クラス分の確率を出力している。

predict = model.predict(img_preprocessed)
print(type(predict))
# <class 'numpy.ndarray'>

print(predict.shape)
# (1, 1000)

print(predict[0][:10])
# [4.5425102e-07 6.2950056e-07 2.4502340e-09 1.5132209e-09 1.7509529e-09
#  1.2035696e-07 2.0865437e-08 1.2301771e-04 9.0907934e-06 5.3701660e-04]

予測結果に対応するクラス名を取得: decode_predictions()

予測結果とクラス名との対応はvgg16モジュールのdecode_predictions()関数で取得できる。

例えば引数top=5とすると、確率上位5クラスのラベルと名前、確率が取得できる。

result = tf.keras.applications.vgg16.decode_predictions(predict, top=5)
pprint.pprint(result)
# [[('n02486410', 'baboon', 0.96402234),
#   ('n02484975', 'guenon', 0.013725309),
#   ('n02486261', 'patas', 0.012976606),
#   ('n02487347', 'macaque', 0.0034710427),
#   ('n02493509', 'titi', 0.0015007565)]]

ここでは結果を見やすくするためpprintを使っている。

decode_predictions()が返すのはタプルを要素とするリストを要素とするリスト。

print(type(result))
# <class 'list'>

print(type(result[0]))
# <class 'list'>

print(type(result[0][0]))
# <class 'tuple'>

print(result[0][0][1])
# baboon

baboon(ヒヒ)であると正しく分類できている。

1000クラス分の順番とラベル、クラス名の情報は以下のJSONファイルにまとめられている。

このJSONファイルがdecode_predictions()の初回実行時に~/.keras/models/にダウンロードされて使用される。

学習済みモデルの出力層(全結合層)を変更: include_top

ここからは、モデル生成関数の引数include_topinput_tensorを設定して学習済みモデルの出力層・入力層を変更する方法を示す。

変更したモデルで訓練を行う転移学習・ファインチューニングの具体例は以下の記事を参照。

VGG16()などのモデル生成関数の引数include_topFalseとすると(デフォルトはTrue)、出力層側の全結合層を含まないモデルが返される。

デフォルト時のモデルにはあったFlattenレイヤー以降が省かれている。

base_model = tf.keras.applications.vgg16.VGG16(include_top=False)

base_model.summary()
# Model: "vgg16"
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# input_1 (InputLayer)         [(None, None, None, 3)]   0         
# _________________________________________________________________
# block1_conv1 (Conv2D)        (None, None, None, 64)    1792      
# _________________________________________________________________
# block1_conv2 (Conv2D)        (None, None, None, 64)    36928     
# _________________________________________________________________
# block1_pool (MaxPooling2D)   (None, None, None, 64)    0         
# _________________________________________________________________
# block2_conv1 (Conv2D)        (None, None, None, 128)   73856     
# _________________________________________________________________
# block2_conv2 (Conv2D)        (None, None, None, 128)   147584    
# _________________________________________________________________
# block2_pool (MaxPooling2D)   (None, None, None, 128)   0         
# _________________________________________________________________
# block3_conv1 (Conv2D)        (None, None, None, 256)   295168    
# _________________________________________________________________
# block3_conv2 (Conv2D)        (None, None, None, 256)   590080    
# _________________________________________________________________
# block3_conv3 (Conv2D)        (None, None, None, 256)   590080    
# _________________________________________________________________
# block3_pool (MaxPooling2D)   (None, None, None, 256)   0         
# _________________________________________________________________
# block4_conv1 (Conv2D)        (None, None, None, 512)   1180160   
# _________________________________________________________________
# block4_conv2 (Conv2D)        (None, None, None, 512)   2359808   
# _________________________________________________________________
# block4_conv3 (Conv2D)        (None, None, None, 512)   2359808   
# _________________________________________________________________
# block4_pool (MaxPooling2D)   (None, None, None, 512)   0         
# _________________________________________________________________
# block5_conv1 (Conv2D)        (None, None, None, 512)   2359808   
# _________________________________________________________________
# block5_conv2 (Conv2D)        (None, None, None, 512)   2359808   
# _________________________________________________________________
# block5_conv3 (Conv2D)        (None, None, None, 512)   2359808   
# _________________________________________________________________
# block5_pool (MaxPooling2D)   (None, None, None, 512)   0         
# =================================================================
# Total params: 14,714,688
# Trainable params: 14,714,688
# Non-trainable params: 0
# _________________________________________________________________

なお、include_top=Falseとするとデフォルト(include_top=True)のときとは異なる重みファイルが新たにダウンロードされる。

include_top=Falseとして読み込んだモデルの出力層側に新たなレイヤーを加える方法を以下に示す。

グローバルプーリング層を追加: pooling

include_top=Falseのときにさらに引数poolingを指定するとグローバルプーリング層を追加できる。

デフォルトはNoneで、上に示した例の通り。

pooling'avg'または'max'とすると、それぞれGlobalAveragePooling2DまたはGlobalMaxPooling2Dレイヤーが最後に追加される。

pooling='avg'の例。最後のレイヤーに注目。

base_model_avg = tf.keras.applications.vgg16.VGG16(
    include_top=False, pooling='avg'
)

base_model_avg.summary()
# Model: "vgg16"
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# input_2 (InputLayer)         [(None, None, None, 3)]   0         
# _________________________________________________________________
# block1_conv1 (Conv2D)        (None, None, None, 64)    1792      
# _________________________________________________________________
# block1_conv2 (Conv2D)        (None, None, None, 64)    36928     
# _________________________________________________________________
# block1_pool (MaxPooling2D)   (None, None, None, 64)    0         
# _________________________________________________________________
# block2_conv1 (Conv2D)        (None, None, None, 128)   73856     
# _________________________________________________________________
# block2_conv2 (Conv2D)        (None, None, None, 128)   147584    
# _________________________________________________________________
# block2_pool (MaxPooling2D)   (None, None, None, 128)   0         
# _________________________________________________________________
# block3_conv1 (Conv2D)        (None, None, None, 256)   295168    
# _________________________________________________________________
# block3_conv2 (Conv2D)        (None, None, None, 256)   590080    
# _________________________________________________________________
# block3_conv3 (Conv2D)        (None, None, None, 256)   590080    
# _________________________________________________________________
# block3_pool (MaxPooling2D)   (None, None, None, 256)   0         
# _________________________________________________________________
# block4_conv1 (Conv2D)        (None, None, None, 512)   1180160   
# _________________________________________________________________
# block4_conv2 (Conv2D)        (None, None, None, 512)   2359808   
# _________________________________________________________________
# block4_conv3 (Conv2D)        (None, None, None, 512)   2359808   
# _________________________________________________________________
# block4_pool (MaxPooling2D)   (None, None, None, 512)   0         
# _________________________________________________________________
# block5_conv1 (Conv2D)        (None, None, None, 512)   2359808   
# _________________________________________________________________
# block5_conv2 (Conv2D)        (None, None, None, 512)   2359808   
# _________________________________________________________________
# block5_conv3 (Conv2D)        (None, None, None, 512)   2359808   
# _________________________________________________________________
# block5_pool (MaxPooling2D)   (None, None, None, 512)   0         
# _________________________________________________________________
# global_average_pooling2d (Gl (None, 512)               0         
# =================================================================
# Total params: 14,714,688
# Trainable params: 14,714,688
# Non-trainable params: 0
# _________________________________________________________________

任意のレイヤーを出力層側に追加

出力層側に任意のレイヤーを追加したい場合、Sequential APIを用いる方法とFunctional APIを用いる方法がある。

Sequential APIとFunctional APIについては以下の記事を参照。

Sequential API

Sequential APIの場合、ベースとなるモデルを一つのレイヤーとして扱う。

model_sequential = tf.keras.Sequential([
    base_model_avg,
    tf.keras.layers.Dense(10, activation="softmax")
])

model_sequential.summary()
# Model: "sequential"
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# vgg16 (Model)                (None, 512)               14714688  
# _________________________________________________________________
# dense (Dense)                (None, 10)                5130      
# =================================================================
# Total params: 14,719,818
# Trainable params: 14,719,818
# Non-trainable params: 0
# _________________________________________________________________

ベースモデルの属性trainableで全体を訓練する・しないを切り替えられる。ベースモデルの中のレイヤーを個別に設定することも可能。

Functional API

Functional APIの場合、ベースモデルのoutputから関数をつなげて、ベースモデルのinputと定義した関数の最終段をtf.keras.Model()の引数inputsoutputsに指定する。

x = base_model_avg.output
x = tf.keras.layers.Dense(10, activation="softmax")(x)

model_functional = tf.keras.Model(inputs=base_model_avg.input, outputs=x)

model_functional.summary()
# Model: "model"
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# input_2 (InputLayer)         [(None, None, None, 3)]   0         
# _________________________________________________________________
# block1_conv1 (Conv2D)        (None, None, None, 64)    1792      
# _________________________________________________________________
# block1_conv2 (Conv2D)        (None, None, None, 64)    36928     
# _________________________________________________________________
# block1_pool (MaxPooling2D)   (None, None, None, 64)    0         
# _________________________________________________________________
# block2_conv1 (Conv2D)        (None, None, None, 128)   73856     
# _________________________________________________________________
# block2_conv2 (Conv2D)        (None, None, None, 128)   147584    
# _________________________________________________________________
# block2_pool (MaxPooling2D)   (None, None, None, 128)   0         
# _________________________________________________________________
# block3_conv1 (Conv2D)        (None, None, None, 256)   295168    
# _________________________________________________________________
# block3_conv2 (Conv2D)        (None, None, None, 256)   590080    
# _________________________________________________________________
# block3_conv3 (Conv2D)        (None, None, None, 256)   590080    
# _________________________________________________________________
# block3_pool (MaxPooling2D)   (None, None, None, 256)   0         
# _________________________________________________________________
# block4_conv1 (Conv2D)        (None, None, None, 512)   1180160   
# _________________________________________________________________
# block4_conv2 (Conv2D)        (None, None, None, 512)   2359808   
# _________________________________________________________________
# block4_conv3 (Conv2D)        (None, None, None, 512)   2359808   
# _________________________________________________________________
# block4_pool (MaxPooling2D)   (None, None, None, 512)   0         
# _________________________________________________________________
# block5_conv1 (Conv2D)        (None, None, None, 512)   2359808   
# _________________________________________________________________
# block5_conv2 (Conv2D)        (None, None, None, 512)   2359808   
# _________________________________________________________________
# block5_conv3 (Conv2D)        (None, None, None, 512)   2359808   
# _________________________________________________________________
# block5_pool (MaxPooling2D)   (None, None, None, 512)   0         
# _________________________________________________________________
# global_average_pooling2d (Gl (None, 512)               0         
# _________________________________________________________________
# dense_1 (Dense)              (None, 10)                5130      
# =================================================================
# Total params: 14,719,818
# Trainable params: 14,719,818
# Non-trainable params: 0
# _________________________________________________________________

Functional APIでは、Sequential APIのようにベースモデルが一つのレイヤーとして扱われない。

学習済みモデルの入力の形状を変更: input_shape

VGG16()などのモデル生成関数の引数input_shapeで入力画像の形状shapeを指定できる。

が、デフォルト(weights='imagenet', include_top=True)では規定の形状で学習済みの重みを使用しようとするためinput_shapeは指定できない。

# model = tf.keras.applications.vgg16.VGG16(input_shape=(150, 150, 3))
# ValueError: When setting `include_top=True` and loading `imagenet` weights, `input_shape` should be (224, 224, 3).

これは、全結合層Denseのパラメータ数が入力の形状に依存するため。入力の形状を変更するとDenseのパラメータ数も変わり、学習済みのパラメータ数と一致しなくなってしまう。

一方、畳み込み層Conv2Dのパラメータ数は入力の形状に依存しない。include_top=Falseとした場合は全結合層Denseが省略され、入力画像のサイズを制限するものはなくなるため、引数input_shapeで入力の形状shapeを指定できる。

各層のOutput Shapeがデフォルト時と変わっていることに注目。

model = tf.keras.applications.vgg16.VGG16(
    include_top=False, input_shape=(150, 150, 3)
)

model.summary()
# Model: "vgg16"
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# input_1 (InputLayer)         [(None, 150, 150, 3)]     0         
# _________________________________________________________________
# block1_conv1 (Conv2D)        (None, 150, 150, 64)      1792      
# _________________________________________________________________
# block1_conv2 (Conv2D)        (None, 150, 150, 64)      36928     
# _________________________________________________________________
# block1_pool (MaxPooling2D)   (None, 75, 75, 64)        0         
# _________________________________________________________________
# block2_conv1 (Conv2D)        (None, 75, 75, 128)       73856     
# _________________________________________________________________
# block2_conv2 (Conv2D)        (None, 75, 75, 128)       147584    
# _________________________________________________________________
# block2_pool (MaxPooling2D)   (None, 37, 37, 128)       0         
# _________________________________________________________________
# block3_conv1 (Conv2D)        (None, 37, 37, 256)       295168    
# _________________________________________________________________
# block3_conv2 (Conv2D)        (None, 37, 37, 256)       590080    
# _________________________________________________________________
# block3_conv3 (Conv2D)        (None, 37, 37, 256)       590080    
# _________________________________________________________________
# block3_pool (MaxPooling2D)   (None, 18, 18, 256)       0         
# _________________________________________________________________
# block4_conv1 (Conv2D)        (None, 18, 18, 512)       1180160   
# _________________________________________________________________
# block4_conv2 (Conv2D)        (None, 18, 18, 512)       2359808   
# _________________________________________________________________
# block4_conv3 (Conv2D)        (None, 18, 18, 512)       2359808   
# _________________________________________________________________
# block4_pool (MaxPooling2D)   (None, 9, 9, 512)         0         
# _________________________________________________________________
# block5_conv1 (Conv2D)        (None, 9, 9, 512)         2359808   
# _________________________________________________________________
# block5_conv2 (Conv2D)        (None, 9, 9, 512)         2359808   
# _________________________________________________________________
# block5_conv3 (Conv2D)        (None, 9, 9, 512)         2359808   
# _________________________________________________________________
# block5_pool (MaxPooling2D)   (None, 4, 4, 512)         0         
# =================================================================
# Total params: 14,714,688
# Trainable params: 14,714,688
# Non-trainable params: 0
# _________________________________________________________________

最小の入力サイズは畳み込み層Conv2Dkernel_sizeに依存し、例えばVGG16の場合、32 x 32が最小。それより小さいとエラーとなる。

# model = tf.keras.applications.vgg16.VGG16(
#     include_top=False, input_shape=(31, 31, 3)
# )
# ValueError: Input size must be at least 32x32; got `input_shape=(31, 31, 3)`

なお、weights=Noneの場合はすべてのパラメータがランダムな重みでモデルが生成される。学習済みの重みのパラメータ数にに合わせる必要がないため、input_shapeを指定できる。その形状に合わせて全結合層Denseのサイズが決定される。

model = tf.keras.applications.vgg16.VGG16(
    weights=None, input_shape=(150, 150, 3)
)

model.summary()
# Model: "vgg16"
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# input_2 (InputLayer)         [(None, 150, 150, 3)]     0         
# _________________________________________________________________
# block1_conv1 (Conv2D)        (None, 150, 150, 64)      1792      
# _________________________________________________________________
# block1_conv2 (Conv2D)        (None, 150, 150, 64)      36928     
# _________________________________________________________________
# block1_pool (MaxPooling2D)   (None, 75, 75, 64)        0         
# _________________________________________________________________
# block2_conv1 (Conv2D)        (None, 75, 75, 128)       73856     
# _________________________________________________________________
# block2_conv2 (Conv2D)        (None, 75, 75, 128)       147584    
# _________________________________________________________________
# block2_pool (MaxPooling2D)   (None, 37, 37, 128)       0         
# _________________________________________________________________
# block3_conv1 (Conv2D)        (None, 37, 37, 256)       295168    
# _________________________________________________________________
# block3_conv2 (Conv2D)        (None, 37, 37, 256)       590080    
# _________________________________________________________________
# block3_conv3 (Conv2D)        (None, 37, 37, 256)       590080    
# _________________________________________________________________
# block3_pool (MaxPooling2D)   (None, 18, 18, 256)       0         
# _________________________________________________________________
# block4_conv1 (Conv2D)        (None, 18, 18, 512)       1180160   
# _________________________________________________________________
# block4_conv2 (Conv2D)        (None, 18, 18, 512)       2359808   
# _________________________________________________________________
# block4_conv3 (Conv2D)        (None, 18, 18, 512)       2359808   
# _________________________________________________________________
# block4_pool (MaxPooling2D)   (None, 9, 9, 512)         0         
# _________________________________________________________________
# block5_conv1 (Conv2D)        (None, 9, 9, 512)         2359808   
# _________________________________________________________________
# block5_conv2 (Conv2D)        (None, 9, 9, 512)         2359808   
# _________________________________________________________________
# block5_conv3 (Conv2D)        (None, 9, 9, 512)         2359808   
# _________________________________________________________________
# block5_pool (MaxPooling2D)   (None, 4, 4, 512)         0         
# _________________________________________________________________
# flatten (Flatten)            (None, 8192)              0         
# _________________________________________________________________
# fc1 (Dense)                  (None, 4096)              33558528  
# _________________________________________________________________
# fc2 (Dense)                  (None, 4096)              16781312  
# _________________________________________________________________
# predictions (Dense)          (None, 1000)              4097000   
# =================================================================
# Total params: 69,151,528
# Trainable params: 69,151,528
# Non-trainable params: 0
# _________________________________________________________________

学習済みモデルの入力に新たなレイヤーを追加: input_tensor

VGG16()などのモデル生成関数の引数input_tensorを使って、入力層側にあらたなレイヤーを追加できる。input_tensorにはテンソルを指定する。

任意のレイヤーを入力層側に追加

画像をリサイズするレイヤーと、上述のpreprocess_input()を行うレイヤーを追加する例を示す。実行例は後述。

任意の関数をレイヤー化できるtf.keras.layers.Lambdaを使う。

リサイズする関数は以下のものを使う。

Sequential API

追加したいレイヤーからなるモデルをSequential APIで生成した場合、そのoutput属性を引数input_tensorに指定する。

model_in = tf.keras.Sequential([
    tf.keras.Input(shape=(None, None, 3)),
    tf.keras.layers.Lambda(lambda img: tf.image.resize(img, (224, 224)), name='resize'),
    tf.keras.layers.Lambda(tf.keras.applications.vgg16.preprocess_input, name='preprocess')
])

model = tf.keras.applications.vgg16.VGG16(input_tensor=model_in.output)

model.summary()
# Model: "vgg16"
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# input_1 (InputLayer)         [(None, None, None, 3)]   0         
# _________________________________________________________________
# resize (Lambda)              (None, 224, 224, 3)       0         
# _________________________________________________________________
# preprocess (Lambda)          (None, 224, 224, 3)       0         
# _________________________________________________________________
# block1_conv1 (Conv2D)        (None, 224, 224, 64)      1792      
# _________________________________________________________________
# block1_conv2 (Conv2D)        (None, 224, 224, 64)      36928     
# _________________________________________________________________
# block1_pool (MaxPooling2D)   (None, 112, 112, 64)      0         
# _________________________________________________________________
# block2_conv1 (Conv2D)        (None, 112, 112, 128)     73856     
# _________________________________________________________________
# block2_conv2 (Conv2D)        (None, 112, 112, 128)     147584    
# _________________________________________________________________
# block2_pool (MaxPooling2D)   (None, 56, 56, 128)       0         
# _________________________________________________________________
# block3_conv1 (Conv2D)        (None, 56, 56, 256)       295168    
# _________________________________________________________________
# block3_conv2 (Conv2D)        (None, 56, 56, 256)       590080    
# _________________________________________________________________
# block3_conv3 (Conv2D)        (None, 56, 56, 256)       590080    
# _________________________________________________________________
# block3_pool (MaxPooling2D)   (None, 28, 28, 256)       0         
# _________________________________________________________________
# block4_conv1 (Conv2D)        (None, 28, 28, 512)       1180160   
# _________________________________________________________________
# block4_conv2 (Conv2D)        (None, 28, 28, 512)       2359808   
# _________________________________________________________________
# block4_conv3 (Conv2D)        (None, 28, 28, 512)       2359808   
# _________________________________________________________________
# block4_pool (MaxPooling2D)   (None, 14, 14, 512)       0         
# _________________________________________________________________
# block5_conv1 (Conv2D)        (None, 14, 14, 512)       2359808   
# _________________________________________________________________
# block5_conv2 (Conv2D)        (None, 14, 14, 512)       2359808   
# _________________________________________________________________
# block5_conv3 (Conv2D)        (None, 14, 14, 512)       2359808   
# _________________________________________________________________
# block5_pool (MaxPooling2D)   (None, 7, 7, 512)         0         
# _________________________________________________________________
# flatten (Flatten)            (None, 25088)             0         
# _________________________________________________________________
# fc1 (Dense)                  (None, 4096)              102764544 
# _________________________________________________________________
# fc2 (Dense)                  (None, 4096)              16781312  
# _________________________________________________________________
# predictions (Dense)          (None, 1000)              4097000   
# =================================================================
# Total params: 138,357,544
# Trainable params: 138,357,544
# Non-trainable params: 0
# _________________________________________________________________

Functional API

追加したいレイヤーをFunctional APIで定義した場合、その最終段を引数input_tensorに指定する。

inputs = tf.keras.Input(shape=(None, None, 3))
x = tf.keras.layers.Lambda(lambda img: tf.image.resize(img, (224, 224)), name='resize')(inputs)
x = tf.keras.layers.Lambda(tf.keras.applications.vgg16.preprocess_input, name='preprocess')(x)

model = tf.keras.applications.vgg16.VGG16(input_tensor=x)

model.summary()
# Model: "vgg16"
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# input_2 (InputLayer)         [(None, None, None, 3)]   0         
# _________________________________________________________________
# resize (Lambda)              (None, 224, 224, 3)       0         
# _________________________________________________________________
# preprocess (Lambda)          (None, 224, 224, 3)       0         
# _________________________________________________________________
# block1_conv1 (Conv2D)        (None, 224, 224, 64)      1792      
# _________________________________________________________________
# block1_conv2 (Conv2D)        (None, 224, 224, 64)      36928     
# _________________________________________________________________
# block1_pool (MaxPooling2D)   (None, 112, 112, 64)      0         
# _________________________________________________________________
# block2_conv1 (Conv2D)        (None, 112, 112, 128)     73856     
# _________________________________________________________________
# block2_conv2 (Conv2D)        (None, 112, 112, 128)     147584    
# _________________________________________________________________
# block2_pool (MaxPooling2D)   (None, 56, 56, 128)       0         
# _________________________________________________________________
# block3_conv1 (Conv2D)        (None, 56, 56, 256)       295168    
# _________________________________________________________________
# block3_conv2 (Conv2D)        (None, 56, 56, 256)       590080    
# _________________________________________________________________
# block3_conv3 (Conv2D)        (None, 56, 56, 256)       590080    
# _________________________________________________________________
# block3_pool (MaxPooling2D)   (None, 28, 28, 256)       0         
# _________________________________________________________________
# block4_conv1 (Conv2D)        (None, 28, 28, 512)       1180160   
# _________________________________________________________________
# block4_conv2 (Conv2D)        (None, 28, 28, 512)       2359808   
# _________________________________________________________________
# block4_conv3 (Conv2D)        (None, 28, 28, 512)       2359808   
# _________________________________________________________________
# block4_pool (MaxPooling2D)   (None, 14, 14, 512)       0         
# _________________________________________________________________
# block5_conv1 (Conv2D)        (None, 14, 14, 512)       2359808   
# _________________________________________________________________
# block5_conv2 (Conv2D)        (None, 14, 14, 512)       2359808   
# _________________________________________________________________
# block5_conv3 (Conv2D)        (None, 14, 14, 512)       2359808   
# _________________________________________________________________
# block5_pool (MaxPooling2D)   (None, 7, 7, 512)         0         
# _________________________________________________________________
# flatten (Flatten)            (None, 25088)             0         
# _________________________________________________________________
# fc1 (Dense)                  (None, 4096)              102764544 
# _________________________________________________________________
# fc2 (Dense)                  (None, 4096)              16781312  
# _________________________________________________________________
# predictions (Dense)          (None, 1000)              4097000   
# =================================================================
# Total params: 138,357,544
# Trainable params: 138,357,544
# Non-trainable params: 0
# _________________________________________________________________

実行例

上で示したモデルを実際に使用する例は以下の通り。

なお、TensorFlow2.1.0ではVGG16やVGG19などのmode='caffe'preprocess_inputを使ったLambdaレイヤーを含むモデルでfit()predict()を実行するとエラーが発生する。

以下のIssueにあるように既知のバグでtf-nightly(開発版)では修正されている。

以下はtf-nightlyで実行した結果。512 x 512のサイズのままmodel.predict()に入力しても正しく予測できている。

print(tf.__version__)
# 2.2.0-dev20200309

inputs = tf.keras.Input(shape=(None, None, 3))
x = tf.keras.layers.Lambda(lambda img: tf.image.resize(img, (224, 224)))(inputs)
x = tf.keras.layers.Lambda(tf.keras.applications.vgg16.preprocess_input)(x)

model = tf.keras.applications.vgg16.VGG16(input_tensor=x)

img_pil = tf.keras.preprocessing.image.load_img('../data/img/src/baboon.jpg')
img = tf.keras.preprocessing.image.img_to_array(img_pil)[tf.newaxis, ...]
print(img.shape)
# (1, 512, 512, 3)

pprint.pprint(tf.keras.applications.vgg16.decode_predictions(model.predict(img), top=5))
# [[('n02486410', 'baboon', 0.9816024),
#   ('n02484975', 'guenon', 0.007312194),
#   ('n02486261', 'patas', 0.0072130407),
#   ('n02487347', 'macaque', 0.0026990667),
#   ('n02493509', 'titi', 0.00031297794)]]

同様の処理をしてから元のモデル(前処理レイヤーなし)で予測した場合、まったく同じ結果となることが確認できる。

model_org = tf.keras.applications.vgg16.VGG16()

img2 = tf.image.resize(img, (224, 224))
img2 = tf.keras.applications.vgg16.preprocess_input(img2)
print(img2.shape)
# (1, 224, 224, 3)

pprint.pprint(tf.keras.applications.vgg16.decode_predictions(model_org.predict(img2), top=5))
# [[('n02486410', 'baboon', 0.9816024),
#   ('n02484975', 'guenon', 0.007312194),
#   ('n02486261', 'patas', 0.0072130407),
#   ('n02487347', 'macaque', 0.0026990667),
#   ('n02493509', 'titi', 0.00031297794)]]

ちなみに、細かく比較すると「学習済みモデルで予測(推論): 画像分類」のところで示したコードと結果の値が異なっているが、これはtf.keras.preprocessing.image.load_img()tf.image.resize()のリサイズ処理の違いによるもの。デフォルトの補間方法が、前者はNearest neighbor、後者はBilinearという違いもあるが、どちらかに合わせても完全に同一処理ではないため微妙に異なる結果となる。

関連カテゴリー

関連記事