TensorFlow, Kerasで転移学習・ファインチューニング(画像分類の例)

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

TensorFlowとKerasを利用して学習済みモデルを元に転移学習(Transfer Learning)・ファインチューニング(Fine Tuning)を行う方法をサンプルコードとともに説明する。

  • 転移学習・ファインチューニングとは
  • MobileNetの学習済みモデルをCIFAR10データセットに適用
    • データの読み込み
    • モデルの実装
    • 追加した全結合層のみを学習
    • 学習済みモデルの一部を再学習(ファインチューニング)
    • 転移学習・ファインチューニングにおいて考慮する項目
  • ローカルの画像を扱う例
    • データのダウンロード
    • データの準備: ImageDataGenerator
    • モデルの実装と学習

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

import tensorflow as tf

print(tf.__version__)
# 2.1.0

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

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

転移学習・ファインチューニングとは

転移学習とファインチューニングについては様々な説明がされているようだが、ここでは書籍『直感 Deep Learning』の記述を引用する。

転移学習(transfer learning)はディープラーニングにおいてとても強力な、さまざまな分野で適用可能な手法です。転移学習のコンセプトはとても簡単な例で説明することができます。あなたが新しい言語(たとえばスペイン語)を学びたいと思ったとき、すでに学んでいる言語、たとえば英語の知識は役に立つでしょう。このように、すでに学習されたモデルを他の新しいタスクに適用することが転移学習の基本的な考え方です。
現在画像処理に携わる研究者たちは、このシンプルな考えを元に、モデルとゼロから訓練するほどデータを用意できない新しいタスクに対して事前学習済みのCNNを利用しています。このとき、ファインチューニング(fine-tuning)と呼ばれる、事前学習済みのモデルの一部を変更して再学習する手法がよく用いられます。 直感 Deep Learning P99-100

転移学習(Transfer Learning)は「すでに学習されたモデルを他の新しいタスクに適用する手法(あるいは考え方)全般」を指し、その中の一つにファインチューニング(Fine Tuning)と呼ばれる「事前学習済みのモデルの一部を変更して再学習する手法」がある…という理解だが、厳密な定義を求めているわけではないので、ここでは深追いしない。

参考になりそうなものをほかにもいくつか引用しておく。

Transfer learning (TL) is a research problem in machine learning (ML) that focuses on storing knowledge gained while solving one problem and applying it to a different but related problem. Transfer learning - Wikipedia

Fine-Tuning: Unfreeze a few of the top layers of a frozen model base and jointly train both the newly-added classifier layers and the last layers of the base model. This allows us to "fine-tune" the higher-order feature representations in the base model in order to make them more relevant for the specific task. Transfer learning with a pretrained ConvNet | TensorFlow Core

The three major Transfer Learning scenarios look as follows:
- ConvNet as fixed feature extractor. ...
- Fine-tuning the ConvNet. ...
- Pretrained models. ...
CS231n Convolutional Neural Networks for Visual Recognition

MobileNetの学習済みモデルをCIFAR10データセットに適用

転移学習・ファインチューニングの具体例として、ここでは、MobileNetV2のImageNetで学習済みのモデルをCIFAR10のデータセットに適用する。

学習済みのモデルとしてMobileNetV2を選んだのは、特に深い理由があるわけではなく以下の公式のチュートリアルに合わせただけ。VGG16やResNetなどそのほかのモデルで使い方は同じ。

上記公式チュートリアルでは犬と猫の2クラス分類を行っているが、ここではKerasのDatasetsに含まれているCIFAR10のデータを使う。MobileNetV2のImageNetで学習したモデル(= 1000クラス分類)をCIFAR10の10クラス分類に適用する、という処理となる。

サンプルコード全体は以下。

ローカルの犬猫画像で2クラス分類を行う例は後述する。

データの読み込み

CIFAR10のデータセットはtf.keras.datasets.cifar10.load_data()で読み込める。初回実行時に~/.keras/models/にデータがダウンロードされる。

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()

print(type(x_train))
# <class 'numpy.ndarray'>

print(x_train.shape, y_train.shape)
# (50000, 32, 32, 3) (50000, 1)

print(x_test.shape, y_test.shape)
# (10000, 32, 32, 3) (10000, 1)

32 x 32のRGBカラー画像が訓練用50000枚、テスト用10000枚。正解ラベルは0から9の整数。

モデルの実装

ここでは、引数input_tensorを指定して、モデルの入力層側にリサイズと前処理を行うレイヤーを組み込む。モデルに組み込まず、外部で処理してからモデルに入力する方式でも構わない。

さらに、出力層側を元のモデルのものから10クラス分類用の全結合層に変更する。include_top=FalseとしてSequential APIで全結合層を追加する。Functional APIの例は後述。

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

base_model = tf.keras.applications.mobilenet_v2.MobileNetV2(
    weights='imagenet', input_tensor=x, input_shape=(160, 160, 3),
    include_top=False, pooling='avg'
)

model = tf.keras.Sequential([
    base_model,
    tf.keras.layers.Dense(10, activation='softmax')
])

model.summary()
# Model: "sequential"
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# mobilenetv2_1.00_160 (Model) (None, 1280)              2257984   
# _________________________________________________________________
# dense (Dense)                (None, 10)                12810     
# =================================================================
# Total params: 2,270,794
# Trainable params: 2,236,682
# Non-trainable params: 34,112
# _________________________________________________________________

summary()の出力でも分かるように、Sequential APIの場合はベースモデル(MobileNetV2の学習済みモデル)が一つのレイヤーとして扱われる。入れ子になっているようなイメージ。

print(len(model.layers))
# 2

print(model.layers[0].name)
# mobilenetv2_1.00_160

print(len(model.layers[0].layers))
# 158

引数input_tensor, include_topによる入力層・出力層の変更や学習済みモデルの前処理などについての詳細は以下の記事を参照。

上の記事の最後に書いたように、TensorFlow2.1.0ではVGG16やVGG19などのmode='caffe'preprocess_inputを使ったLambdaレイヤーを含むモデルでfit()predict()を実行するとエラーが発生するので注意。

なお、MobileNetV2では入力画像サイズが224 x 224, 192 x 192, 160 x 160, 128 x 128, 96 x 96の場合の学習済み重みデータが提供されている。

input_tensorを指定した場合もinput_shapeを省略せず指定しないと所望の重みデータが使われない模様(バージョン、環境によって違うかもしれない)。

上の例では160 x 160にリサイズしているが、これは適当に決めたもので特別な理由があるわけではない。

追加した全結合層のみを学習

まずは追加した全結合層のみを学習する。

ベースモデルのtrainable属性をFalseとし、Freeze(凍結)する。ベースモデルの各レイヤーの重みが更新されなくなる(= 学習されなくなる)。

base_model.trainable = False

model.summary()
# Model: "sequential"
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# mobilenetv2_1.00_160 (Model) (None, 1280)              2257984   
# _________________________________________________________________
# dense (Dense)                (None, 10)                12810     
# =================================================================
# Total params: 2,270,794
# Trainable params: 12,810
# Non-trainable params: 2,257,984
# _________________________________________________________________

summary()の出力結果のTrainable params(学習されるパラメータの数)が追加した全結合層の分のみになっていることに注目。

なお、summary()の出力結果には反映されているが、実際に設定を有効にするにはcompile()する必要があるので注意。compile()のあとでtrainableを変更した場合、再度compile()しなければならない。

trainable属性についての詳細は以下の記事を参照。

オプティマイザー、損失関数、評価関数を設定してcompile()。学習率learning_rateは上記の公式チュートリアルの値を使った。

model.compile(optimizer=tf.keras.optimizers.RMSprop(learning_rate=0.0001),
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

追加した全結合層はランダムな重みで初期化されているだけなので、当然、学習前のこの時点ではまったく分類できない。参考までにevaluate()で評価してみると、正解率は10%前後となる。10クラス分類なので適当に予測して偶然当たっているだけの正解率。

print(model.evaluate(x_test, y_test, verbose=0))
# [2.9224756198883055, 0.1132]

fit()で学習しevaluate()で評価する。CPU環境だとかなり時間がかかるので注意。

model.fit(x_train, y_train, epochs=6, validation_split=0.2, batch_size=256)
# Train on 40000 samples, validate on 10000 samples
# Epoch 1/6
# 40000/40000 [==============================] - 23s 571us/sample - loss: 1.9849 - accuracy: 0.3234 - val_loss: 1.5291 - val_accuracy: 0.4970
# Epoch 2/6
# 40000/40000 [==============================] - 21s 537us/sample - loss: 1.2436 - accuracy: 0.6140 - val_loss: 1.0953 - val_accuracy: 0.6405
# Epoch 3/6
# 40000/40000 [==============================] - 22s 540us/sample - loss: 0.9540 - accuracy: 0.6974 - val_loss: 0.9669 - val_accuracy: 0.6762
# Epoch 4/6
# 40000/40000 [==============================] - 21s 534us/sample - loss: 0.8236 - accuracy: 0.7321 - val_loss: 0.8732 - val_accuracy: 0.7070
# Epoch 5/6
# 40000/40000 [==============================] - 22s 541us/sample - loss: 0.7538 - accuracy: 0.7530 - val_loss: 0.8641 - val_accuracy: 0.7090
# Epoch 6/6
# 40000/40000 [==============================] - 22s 546us/sample - loss: 0.7110 - accuracy: 0.7629 - val_loss: 0.8390 - val_accuracy: 0.7204
# 
# <tensorflow.python.keras.callbacks.History at 0x7f79f9f37630>

print(model.evaluate(x_test, y_test, verbose=0))
# [0.8526914182662964, 0.7186]

学習済みモデルの一部を再学習(ファインチューニング)

学習済みのベースモデルの一部を再学習する。

MobileNetV2はblock_1_xxxからblock_16_xxxまで16のブロックに分かれているが、ここではblock_12_xxx以降を再学習することにする。

ブロック12の最初のレイヤーであるblock_12_expandのインデックス(何層目か)を取得する。

layer_names = [l.name for l in base_model.layers]
idx = layer_names.index('block_12_expand')
print(idx)
# 110

ベースモデルのtrainable属性をTrueとし、全体をUnfreeze(解凍)してから、ブロック11までのレイヤー(block_12_expandの一つ前までのレイヤー)のtrainableFalseとしFreeze(凍結)する。

base_model.trainable = True

for layer in base_model.layers[:idx]:
    layer.trainable = False

スライス[start:stop]stopを含まないことに注意。

なお、ベースモデルのtrainableTrueとしないと、その内部のレイヤーのtrainableTrueとしてもUnfreeze(解凍)されず学習されないので注意。この例のようにベースモデルが一つのレイヤーとして扱われている場合、ベースモデルのtrainableFalseだと、内部のレイヤーのtrainableTrueであっても学習対象とならない。

上述のように、trainableを変更した後は再度compile()する必要がある。ここで、上記の公式チュートリアルを参考に学習率learning_rateを初回の学習時より小さくしている。

model.compile(optimizer=tf.keras.optimizers.RMSprop(learning_rate=0.00001),
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

model.summary()
# Model: "sequential"
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# mobilenetv2_1.00_160 (Model) (None, 1280)              2257984   
# _________________________________________________________________
# dense (Dense)                (None, 10)                12810     
# =================================================================
# Total params: 2,270,794
# Trainable params: 1,812,426
# Non-trainable params: 458,368
# _________________________________________________________________

この状態で学習すると正解率がさらに改善することが確認できる。

model.fit(x_train, y_train, epochs=6, validation_split=0.2, batch_size=256)
# Train on 40000 samples, validate on 10000 samples
# Epoch 1/6
# 40000/40000 [==============================] - 29s 714us/sample - loss: 0.6117 - accuracy: 0.7946 - val_loss: 0.7145 - val_accuracy: 0.7577
# Epoch 2/6
# 40000/40000 [==============================] - 26s 651us/sample - loss: 0.4992 - accuracy: 0.8292 - val_loss: 0.6788 - val_accuracy: 0.7719
# Epoch 3/6
# 40000/40000 [==============================] - 26s 656us/sample - loss: 0.4307 - accuracy: 0.8522 - val_loss: 0.6632 - val_accuracy: 0.7744
# Epoch 4/6
# 40000/40000 [==============================] - 26s 651us/sample - loss: 0.3784 - accuracy: 0.8713 - val_loss: 0.6444 - val_accuracy: 0.7792
# Epoch 5/6
# 40000/40000 [==============================] - 26s 650us/sample - loss: 0.3377 - accuracy: 0.8857 - val_loss: 0.6478 - val_accuracy: 0.7790
# Epoch 6/6
# 40000/40000 [==============================] - 27s 671us/sample - loss: 0.3038 - accuracy: 0.8981 - val_loss: 0.6257 - val_accuracy: 0.7865
# 
# <tensorflow.python.keras.callbacks.History at 0x7f79f9dbcf98>

print(model.evaluate(x_test, y_test, verbose=0))
# [0.6538689835548401, 0.7845]

転移学習・ファインチューニングにおいて考慮する項目

上の例では転移学習・ファインチューニングの流れを説明することを目的として正解率(精度)は特に気にしていないが、実際に正解率を改善するには様々なことを考慮する必要がある。

再び『直感 Deep Learning 』から引用する。

Kerasに組み込まれた事前学習済みのモデルを使用して転移学習を行えば、ゼロから学習するよりも多くの時間を節約することができます。もちろん、実際に良い精度を出すには再学習させるレイヤーの数や学習のエポック数など、調整すべきパラメータが多くあります。 直感 Deep Learning P102

例えば、上の例では再学習させるレイヤーを適当に決めたが、これが最善とは限らない。

一般的に、畳み込み層を重ねるモデルの場合、層が深くなるにつれて抽象的な特徴が抽出され、出力層に近づくと分類するクラス(物体)に対応した重みを学習していく。中間層で抽出される抽象的な特徴はどのようなクラスに分類するとしても共通であるから後半のレイヤーのみを再学習すればよい、というのが画像分類におけるファインチューニングの考え方である。

どのレイヤーまでが共通する特徴を抽出する部分で、どのレイヤーからが分類するクラスに依存する部分というのははっきり分けられるものではなく、いくつかのパターンを試してみるというのが現実的だろう。

また、上の例では最後に全結合層Denseを一層のみ追加したが、隠れ層(中間層)を加える構成も考えられる。

当然ながら、通常のモデルの学習と同様に、学習率やバッチサイズ、エポック数などのハイパーパラメータも調整しなければいけない。

ローカルの画像を扱う例

次に、ローカルの画像を扱うサンプルコードを示す。

画像をすべて読み込んで形状が(サンプル数, 縦, 横, チャンネル数)numpy.ndarrayとすれば上の例と同じように扱えるが、画像が大量でメモリに乗り切らないような場合は、ローカルのディレクトリから画像ファイルを逐次読み込む必要がある。

ここではtf.keras.preprocessing.image.ImageDataGeneratorを用いる。

サンプルコード全体は以下。

データのダウンロード

例として、以下の公式チュートリアルで紹介されている犬と猫の画像データを使用する。あくまでもお試し用なので枚数は多くない。

tf.keras.utils.get_file()でZIPファイルをダウンロードし展開する。この例ではデフォルトの~/.keras/datasets/以下にダウンロード、展開される。

path_to_zip = tf.keras.utils.get_file(
    fname='cats_and_dogs_filtered.zip',
    origin='https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip',
    extract=True
)

以下のようなディレクトリ構造で、犬と猫の画像に分けられ、さらにtrainvalidationに振り分けられている。

cats_and_dogs_filtered
|__ train
    |______ cats: [cat.0.jpg, cat.1.jpg, cat.2.jpg ....]
    |______ dogs: [dog.0.jpg, dog.1.jpg, dog.2.jpg ...]
|__ validation
    |______ cats: [cat.2000.jpg, cat.2001.jpg, cat.2002.jpg ....]
    |______ dogs: [dog.2000.jpg, dog.2001.jpg, dog.2002.jpg ...]

上記の公式チュートリアルではディレクトリ名の通り、訓練データと検証(validation)データとして使っているが、以下のサンプルコードではtrainを訓練データと検証データ、validationをテストデータとして使う。

tf.keras.utils.get_file()はダウンロード先のディレクトリのパスを返す。そこからtrain, validationの各ディレクトリへのパス文字列を生成する。

path_to_dir = os.path.join(os.path.dirname(path_to_zip), 'cats_and_dogs_filtered')

train_dir = os.path.join(path_to_dir, 'train')
test_dir = os.path.join(path_to_dir, 'validation')

データの準備: ImageDataGenerator

まずImageDataGeneratorのインスタンスを生成する。

引数preprocessing_functionに前処理を行う関数、ここではMobileNetV2の前処理関数preprocess_inputを指定する。

訓練データの方は訓練用と検証用に分割するため引数validation_splitを指定する。

train_datagen = tf.keras.preprocessing.image.ImageDataGenerator(
    preprocessing_function=tf.keras.applications.mobilenet_v2.preprocess_input,
    validation_split=0.2
)

test_datagen = tf.keras.preprocessing.image.ImageDataGenerator(
    preprocessing_function=tf.keras.applications.mobilenet_v2.preprocess_input
)

Data Augmentation(画像の水増し)を行う場合はImageDataGenerator()のその他の引数を設定するが、今回は行わない。なお、validation_splitを設定した場合、訓練用と検証用の両方に対してData Augmentationが行われるので注意。

訓練用、検証用、テスト用の各ジェネレータイテレータをflow_from_directory()メソッドで生成する。

訓練用と検証用は引数subsetをそれぞれ'training', 'validation'とする。また、引数target_sizeに画像のサイズを設定するとリサイズされる。

batch_size = 64
height = 160
width = 160

train_generator = train_datagen.flow_from_directory(
    batch_size=batch_size,
    directory=train_dir,
    target_size=(height, width),
    class_mode='binary',
    subset='training'
)
# Found 1600 images belonging to 2 classes.

valid_generator = train_datagen.flow_from_directory(
    batch_size=batch_size,
    directory=train_dir,
    target_size=(height, width),
    class_mode='binary',
    subset='validation'
)
# Found 400 images belonging to 2 classes.

test_generator = test_datagen.flow_from_directory(
    batch_size=batch_size,
    directory=test_dir,
    target_size=(height, width),
    class_mode='binary'
)
# Found 1000 images belonging to 2 classes.

モデルの実装と学習

上の例と同じくMobileNetV2の学習済みモデルをベースモデルとして使う。リサイズを含む前処理はImageDataGeneratorの設定で行っているため、ここではinput_shapeを設定するのみ。

base_model = tf.keras.applications.mobilenet_v2.MobileNetV2(
    weights='imagenet', input_shape=(height, width, 3),
    include_top=False, pooling='avg'
)

x = base_model.output
x = tf.keras.layers.Dense(1, activation='sigmoid')(x)

model = tf.keras.Model(inputs=base_model.input, outputs=x)

上の例のようにSequential APIでモデルを生成してもいいが、参考までにここではFunctional APIを用いる。

Sequential APIではベースモデルが一つのレイヤーとして扱われるが、Functional APIの場合はそのような入れ子の形にはならない。

print(len(model.layers))
# 157

ここではmodel.summary()の結果は省略するが、サンプルコード全体のリンク先で参照されたい。

入れ子構造ではないが、ベースモデルbase_modelの各レイヤーと新たに構築したmodelの各レイヤーは同じオブジェクトを指している。ベースモデルbase_modeltrainableを変更すると、その中の各レイヤーのtrainableも変更されるため、上のSequential APIでの例と同じく一括で設定可能。

print(model.layers[0] is base_model.layers[0])
# True

print(base_model.layers[0].trainable)
# True

print(model.layers[0].trainable)
# True

base_model.trainable = False

print(base_model.layers[0].trainable)
# False

print(model.layers[0].trainable)
# False

compile()でオプティマイザー、損失関数、評価関数を設定。

model.compile(optimizer=tf.keras.optimizers.RMSprop(learning_rate=0.0001),
              loss='binary_crossentropy',
              metrics=['accuracy'])

新たに追加した全結合層の学習前にevaluate()でモデルを評価すると、正解率は50%程度。2クラス分類なので、まったく分類できていないことが確認できる。

print(model.evaluate(test_generator, verbose=0))
# WARNING:tensorflow:sample_weight modes were coerced from
#   ...
#     to  
#   ['...']
# [0.7827205918729305, 0.473]

なお、以前のバージョンではジェネレータを用いる場合はevaluate_generator()メソッドを使っていたが、新しいバージョンではevaluate()にジェネレータを指定できるようになった。fit()fit_generator()predict()predict_generator()についても同様。

fit()で学習。ImageDataGeneratorは無限にイテレーションするので引数steps_per_epochおよびvalidation_stepsを明示的に設定する。

model.fit(
    train_generator,
    steps_per_epoch=train_generator.n // batch_size,
    validation_data=valid_generator,
    validation_steps=valid_generator.n // batch_size,
    epochs=6
)
# WARNING:tensorflow:sample_weight modes were coerced from
#   ...
#     to  
#   ['...']
# WARNING:tensorflow:sample_weight modes were coerced from
#   ...
#     to  
#   ['...']
# Train for 25 steps, validate for 6 steps
# Epoch 1/6
# 25/25 [==============================] - 4s 164ms/step - loss: 0.7280 - accuracy: 0.5475 - val_loss: 0.6718 - val_accuracy: 0.6120
# Epoch 2/6
# 25/25 [==============================] - 2s 85ms/step - loss: 0.6413 - accuracy: 0.6306 - val_loss: 0.5767 - val_accuracy: 0.7292
# Epoch 3/6
# 25/25 [==============================] - 2s 84ms/step - loss: 0.5740 - accuracy: 0.7044 - val_loss: 0.4978 - val_accuracy: 0.7917
# Epoch 4/6
# 25/25 [==============================] - 2s 85ms/step - loss: 0.5162 - accuracy: 0.7600 - val_loss: 0.4349 - val_accuracy: 0.8385
# Epoch 5/6
# 25/25 [==============================] - 2s 87ms/step - loss: 0.4672 - accuracy: 0.8012 - val_loss: 0.3834 - val_accuracy: 0.8620
# Epoch 6/6
# 25/25 [==============================] - 2s 86ms/step - loss: 0.4255 - accuracy: 0.8350 - val_loss: 0.3423 - val_accuracy: 0.8750
# 
# <tensorflow.python.keras.callbacks.History at 0x7fd8cbfa9240>

print(model.evaluate(test_generator, verbose=0))
# WARNING:tensorflow:sample_weight modes were coerced from
#   ...
#     to  
#   ['...']
# [0.3087761905044317, 0.912]

ファインチューニングのために、ベースモデルの後半のレイヤーのtrainableTrueとする。trainableを変更したあとは忘れずにcompile()

idx = [l.name for l in base_model.layers].index('block_12_expand')

for layer in base_model.layers[idx:]:
    layer.trainable = True

model.compile(optimizer=tf.keras.optimizers.RMSprop(learning_rate=0.00001),
              loss='binary_crossentropy',
              metrics=['accuracy'])

ベースモデルbase_modeltrainableFalseのままだが、ベースモデルが一つのレイヤーとして扱われていないので、ベースモデルのtrainableの値によらず内部のレイヤーのtrainableの値が反映される。

なお、ベースモデルのtrainableを変更するとその中のレイヤーのtrainableも一括で変更されるので、上のSequential APIの例のように、ベースモデルのtrainableTrueにしてから前半のレイヤーのtrainableFalseにしても同じ結果になる。

再学習を行う。

model.fit(
    train_generator,
    steps_per_epoch=train_generator.n // batch_size,
    validation_data=valid_generator,
    validation_steps=valid_generator.n // batch_size,
    epochs=6
)
# WARNING:tensorflow:sample_weight modes were coerced from
#   ...
#     to  
#   ['...']
# WARNING:tensorflow:sample_weight modes were coerced from
#   ...
#     to  
#   ['...']
# Train for 25 steps, validate for 6 steps
# Epoch 1/6
# 25/25 [==============================] - 5s 208ms/step - loss: 0.3144 - accuracy: 0.8944 - val_loss: 0.2101 - val_accuracy: 0.9375
# Epoch 2/6
# 25/25 [==============================] - 2s 86ms/step - loss: 0.2024 - accuracy: 0.9538 - val_loss: 0.1632 - val_accuracy: 0.9453
# Epoch 3/6
# 25/25 [==============================] - 2s 87ms/step - loss: 0.1433 - accuracy: 0.9762 - val_loss: 0.1352 - val_accuracy: 0.9505
# Epoch 4/6
# 25/25 [==============================] - 2s 87ms/step - loss: 0.1029 - accuracy: 0.9869 - val_loss: 0.1181 - val_accuracy: 0.9609
# Epoch 5/6
# 25/25 [==============================] - 2s 92ms/step - loss: 0.0744 - accuracy: 0.9944 - val_loss: 0.1072 - val_accuracy: 0.9661
# Epoch 6/6
# 25/25 [==============================] - 2s 87ms/step - loss: 0.0540 - accuracy: 0.9975 - val_loss: 0.1002 - val_accuracy: 0.9688
# 
# <tensorflow.python.keras.callbacks.History at 0x7fd8cbe39588>

print(model.evaluate(test_generator, verbose=0))
# WARNING:tensorflow:sample_weight modes were coerced from
#   ...
#     to  
#   ['...']
# [0.06722519337199628, 0.978]

正解率の改善が確認できる。

関連カテゴリー

関連記事