TensorFlow, KerasでVGG16などの学習済みモデルを利用
KerasではVGG16やResNetといった有名なモデルが学習済みの重みとともに提供されている。TensorFlow統合版のKerasでも利用可能。
学習済みモデルの使い方として、以下の内容について説明する。
- TensorFlow, Kerasで利用できる学習済みモデル
- ソースコード(GitHubのリポジトリ)
- 公式ドキュメント
- 学習済みモデルの読み込み・ダウンロード
- 学習済みモデルで予測(推論): 画像分類
- 画像の形状変換
- 画像の前処理:
preprocess_input()
- 予測(推論)を実行
- 予測結果に対応するクラス名を取得:
decode_predictions()
- 学習済みモデルの出力層(全結合層)を変更:
include_top
- グローバルプーリング層を追加:
pooling
- 任意のレイヤーを出力層側に追加
- グローバルプーリング層を追加:
- 学習済みモデルの入力の形状を変更:
input_shape
- 学習済みモデルの入力に新たなレイヤーを追加:
input_tensor
- 任意のレイヤーを入力層側に追加
- 実行例
引数include_top
やinput_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.VGG16
はtf.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_top
やinput_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
。
- tf.keras.preprocessing.image.load_img | TensorFlow Core v2.1.0
- tf.keras.preprocessing.image.img_to_array | TensorFlow Core v2.1.0
TensorFlow2.1.0
ではPillowは同時にインストールされないようなので、別途pip
などでインストールが必要。インストールされていない場合はエラーが発生する。
ImportError: Could not import PIL.Image. The use of `load_img` requires PIL.
他の方法で読み込んでも構わないが、OpenCVで読み込んだ場合は色の並びがBGR
となるので注意。
使用しているのはOpenCVにサンプルとして含まれているbaboon(ヒヒ)の画像。
TensorFlow, Kerasではバッチ処理を前提としているため、RGB画像の場合は(サンプル数, 縦, 横, チャンネル数)
の四次元配列とする必要がある。1枚だけ処理するときも先頭に次元を追加する。
img = img[tf.newaxis, ...]
print(img.shape)
# (1, 224, 224, 3)
tf.newaxis
はnp.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
で定義されている。
引数mode
(caffe
, tf
, torch
)によって処理が変わっているが、これは実行時のバックエンドの種類ではなく、重みデータを訓練したときに使われたフレームワークの種類。
VGG16の重みデータはcaffeで訓練されているため、それに合わせて色の並びがRGB
からBGR
に変更され、ImageNetデータセットの平均値が引かれる、という処理が行われる。
一方、例えばInceptionV3ではTensorFlowで訓練されているため、0 - 255
を-1 - 1
にスケーリングするという処理が行われる。inception_v3
モジュールの中でimagenet_utils.py
のpreprocess_input()
をmode='tf'
で呼んでいる。
基本的には各モデルのモジュールのpreprocess_input()
を実行すれば、そのモデルの重みデータに合わせた処理が実行されるので気にする必要はないが、モデルによって処理の内容が異なる場合があることは認識しておいたほうがいいだろう。
preprocess_input()
の前提は以下の通り。
- 画素値の範囲:
0 - 255
- 色の並び:
RGB
- channel last
(サンプル数, 縦, 横, チャンネル数)
- channel first
(サンプル数, チャンネル数, 縦, 横)
の場合はpreprocess_input()
の引数data_format='channels_first'
とする
- channel 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_top
やinput_tensor
を設定して学習済みモデルの出力層・入力層を変更する方法を示す。
変更したモデルで訓練を行う転移学習・ファインチューニングの具体例は以下の記事を参照。
VGG16()
などのモデル生成関数の引数include_top
をFalse
とすると(デフォルトは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
レイヤーが最後に追加される。
- tf.keras.layers.GlobalAveragePooling2D | TensorFlow Core v2.1.0
- tf.keras.layers.GlobalMaxPooling2D | TensorFlow Core v2.1.0
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()
の引数inputs
とoutputs
に指定する。
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
# _________________________________________________________________
最小の入力サイズは畳み込み層Conv2D
のkernel_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という違いもあるが、どちらかに合わせても完全に同一処理ではないため微妙に異なる結果となる。