![]() |
![]() |
![]() |
![]() |
このチュートリアルでは、画像から猫または犬を分類する方法を示します。 tf.keras.Sequential
モデルを使用して画像分類器を構築し、 tf.keras.preprocessing.image.ImageDataGenerator
を使用してデータをロードします。このチュートリアルでは、以下のコンセプトにしたがって、実践的な経験と感覚を養います。
tf.keras.preprocessing.image.ImageDataGenerator
クラスを使用して データ入力パイプライン を構築し、モデルで使用するディスク上のデータを効率的に処理します。- 過学習(Overfitting) —過学習を識別および防止する方法。
- データ拡張(Data Augmentation) および ドロップアウト(dropout) —データパイプラインおよび画像分類モデルに組み込むコンピュータービジョンタスクの過学習と戦うための重要なテクニック。
このチュートリアルは、基本的な機械学習のワークフローに従います。
- データの調査及び理解
- 入力パイプラインの構築
- モデルの構築
- モデルの学習
- モデルのテスト
- モデルの改善とプロセスの繰り返し
パッケージのインポート
まずは必要なパッケージをインポートすることから始めましょう。 os
パッケージはファイルとディレクトリ構造を読み込み、 NumPy は python リストの numpy 配列への変換と必要な行列演算の実行、 matplotlib.pyplot
はグラフの描画や学習データおよび検証データに含まれる画像の表示、に利用します。
モデルの構築に必要な TensorFlow と Keras クラスをインポートします。
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Conv2D, Flatten, Dropout, MaxPooling2D
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import os
import numpy as np
import matplotlib.pyplot as plt
データの読み込み
データセットのダウンロードから始めます。このチュートリアルでは、 Kaggle の Dogs vs Cats データセットをフィルタリングしたバージョンを使用します。データセットのアーカイブバージョンをダウンロードし、"/tmp/"ディレクトリに保存します。
_URL = 'https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip'
path_to_zip = tf.keras.utils.get_file('cats_and_dogs.zip', origin=_URL, extract=True)
PATH = os.path.join(os.path.dirname(path_to_zip), 'cats_and_dogs_filtered')
Downloading data from https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip 68608000/68606236 [==============================] - 2s 0us/step
データセットのディレクトリ構造は次のとおりです:
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 ...]
データの内容を抽出した後、学習および検証セットのための適切なファイルパスで変数を設定します。
train_dir = os.path.join(PATH, 'train')
validation_dir = os.path.join(PATH, 'validation')
train_cats_dir = os.path.join(train_dir, 'cats') # 学習用の猫画像のディレクトリ
train_dogs_dir = os.path.join(train_dir, 'dogs') # 学習用の犬画像のディレクトリ
validation_cats_dir = os.path.join(validation_dir, 'cats') # 検証用の猫画像のディレクトリ
validation_dogs_dir = os.path.join(validation_dir, 'dogs') # 検証用の犬画像のディレクトリ
データの理解
学習および検証ディレクトリの中にある猫と犬の画像の数を見てみましょう:
num_cats_tr = len(os.listdir(train_cats_dir))
num_dogs_tr = len(os.listdir(train_dogs_dir))
num_cats_val = len(os.listdir(validation_cats_dir))
num_dogs_val = len(os.listdir(validation_dogs_dir))
total_train = num_cats_tr + num_dogs_tr
total_val = num_cats_val + num_dogs_val
print('total training cat images:', num_cats_tr)
print('total training dog images:', num_dogs_tr)
print('total validation cat images:', num_cats_val)
print('total validation dog images:', num_dogs_val)
print("--")
print("Total training images:", total_train)
print("Total validation images:", total_val)
total training cat images: 1000 total training dog images: 1000 total validation cat images: 500 total validation dog images: 500 -- Total training images: 2000 Total validation images: 1000
便宜上、データセットの前処理およびネットワークの学習中に使用する変数を設定します。
batch_size = 128
epochs = 15
IMG_HEIGHT = 150
IMG_WIDTH = 150
データの準備
モデルにデータを送る前に、画像を適切に前処理された浮動小数点テンソルにフォーマットします。
1.ディスクから画像を読み取ります。 2.これらの画像のコンテンツをデコードし、RGB値にしたがって適切なグリッド形式に変換します。 3.それらを浮動小数点テンソルに変換します。 4.ニューラルネットワークは小さな入力値を扱う方が適しているため、テンソルを0〜255の値から0〜1の値にリスケーリングします。
幸い、これらすべてのタスクは、 tf.keras
によって提供される ImageDataGenerator
クラスで実行できます。この ImageDataGenerator
はディスクから画像を読み取り、適切なテンソルに前処理を行います。さらに、これらの画像をテンソルのバッチに変換するジェネレータをセットアップします。これは、ネットワーク学習時に便利です。
train_image_generator = ImageDataGenerator(rescale=1./255) # 学習データのジェネレータ
validation_image_generator = ImageDataGenerator(rescale=1./255) # 検証データのジェネレータ
学習および検証画像のジェネレータを定義したのち、 flow_from_directory
メソッドはディスクから画像をロードし、リスケーリングを適用し、画像を必要な大きさにリサイズします。
train_data_gen = train_image_generator.flow_from_directory(batch_size=batch_size,
directory=train_dir,
shuffle=True,
target_size=(IMG_HEIGHT, IMG_WIDTH),
class_mode='binary')
Found 2000 images belonging to 2 classes.
val_data_gen = validation_image_generator.flow_from_directory(batch_size=batch_size,
directory=validation_dir,
target_size=(IMG_HEIGHT, IMG_WIDTH),
class_mode='binary')
Found 1000 images belonging to 2 classes.
学習用画像の可視化
学習用のジェネレータから画像バッチを抽出して可視化します。(この例では32個の画像を抽出し、そのうち5つを matplotlib
で描画します。)
sample_training_images, _ = next(train_data_gen)
next
関数はデータセットからバッチを返します。 next
関数の返り値は (x_train、y_train)
の形式で、 x_train
は学習用の特徴量、 y_train
はそのラベルです。ラベルを破棄して、学習用画像の可視化のみを行います。
# この関数は、1行5列のグリッド形式で画像をプロットし、画像は各列に配置されます。
def plotImages(images_arr):
fig, axes = plt.subplots(1, 5, figsize=(20,20))
axes = axes.flatten()
for img, ax in zip( images_arr, axes):
ax.imshow(img)
ax.axis('off')
plt.tight_layout()
plt.show()
plotImages(sample_training_images[:5])
モデルの構築
モデルはmax pooling層を伴う3つの畳み込みブロックからなります。さらに relu
活性化関数によるアクティベーションを伴う512ユニットの全結合層があります。モデルは、シグモイド活性化関数による2値分類に基づいてクラスに属する確率を出力します。
model = Sequential([
Conv2D(16, 3, padding='same', activation='relu', input_shape=(IMG_HEIGHT, IMG_WIDTH ,3)),
MaxPooling2D(),
Conv2D(32, 3, padding='same', activation='relu'),
MaxPooling2D(),
Conv2D(64, 3, padding='same', activation='relu'),
MaxPooling2D(),
Flatten(),
Dense(512, activation='relu'),
Dense(1, activation='sigmoid')
])
モデルのコンパイル
このチュートリアルでは、 ADAM オプティマイザーと binary cross entropy 損失関数を選択します。各学習エポックの学習と検証の精度を表示するために、metrics
引数を渡します。
model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])
モデルの概要
すべてのネットワークのレイヤーを見るには、モデルの summary
メソッドを利用します:
model.summary()
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d (Conv2D) (None, 150, 150, 16) 448 _________________________________________________________________ max_pooling2d (MaxPooling2D) (None, 75, 75, 16) 0 _________________________________________________________________ conv2d_1 (Conv2D) (None, 75, 75, 32) 4640 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 37, 37, 32) 0 _________________________________________________________________ conv2d_2 (Conv2D) (None, 37, 37, 64) 18496 _________________________________________________________________ max_pooling2d_2 (MaxPooling2 (None, 18, 18, 64) 0 _________________________________________________________________ flatten (Flatten) (None, 20736) 0 _________________________________________________________________ dense (Dense) (None, 512) 10617344 _________________________________________________________________ dense_1 (Dense) (None, 1) 513 ================================================================= Total params: 10,641,441 Trainable params: 10,641,441 Non-trainable params: 0 _________________________________________________________________
モデルの学習
ImageDataGenerator
クラスの fit_generator
メソッドを使用して、ネットワークを学習します。
history = model.fit_generator(
train_data_gen,
steps_per_epoch=total_train // batch_size,
epochs=epochs,
validation_data=val_data_gen,
validation_steps=total_val // batch_size
)
/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/keras/engine/training.py:1844: UserWarning: `Model.fit_generator` is deprecated and will be removed in a future version. Please use `Model.fit`, which supports generators. warnings.warn('`Model.fit_generator` is deprecated and ' Epoch 1/15 15/15 [==============================] - 12s 643ms/step - loss: 0.8856 - accuracy: 0.4860 - val_loss: 0.6931 - val_accuracy: 0.5112 Epoch 2/15 15/15 [==============================] - 8s 570ms/step - loss: 0.6916 - accuracy: 0.5540 - val_loss: 0.6875 - val_accuracy: 0.5022 Epoch 3/15 15/15 [==============================] - 9s 581ms/step - loss: 0.6826 - accuracy: 0.5819 - val_loss: 0.6595 - val_accuracy: 0.6663 Epoch 4/15 15/15 [==============================] - 9s 578ms/step - loss: 0.6528 - accuracy: 0.6310 - val_loss: 0.6152 - val_accuracy: 0.6864 Epoch 5/15 15/15 [==============================] - 9s 592ms/step - loss: 0.6265 - accuracy: 0.6550 - val_loss: 0.6152 - val_accuracy: 0.6641 Epoch 6/15 15/15 [==============================] - 8s 566ms/step - loss: 0.6012 - accuracy: 0.6812 - val_loss: 0.5844 - val_accuracy: 0.7031 Epoch 7/15 15/15 [==============================] - 8s 579ms/step - loss: 0.5555 - accuracy: 0.7325 - val_loss: 0.5687 - val_accuracy: 0.7154 Epoch 8/15 15/15 [==============================] - 8s 566ms/step - loss: 0.4960 - accuracy: 0.7628 - val_loss: 0.5671 - val_accuracy: 0.7065 Epoch 9/15 15/15 [==============================] - 8s 567ms/step - loss: 0.4730 - accuracy: 0.7813 - val_loss: 0.6202 - val_accuracy: 0.6875 Epoch 10/15 15/15 [==============================] - 8s 566ms/step - loss: 0.4734 - accuracy: 0.7731 - val_loss: 0.5731 - val_accuracy: 0.6953 Epoch 11/15 15/15 [==============================] - 8s 564ms/step - loss: 0.4651 - accuracy: 0.7746 - val_loss: 0.5433 - val_accuracy: 0.7321 Epoch 12/15 15/15 [==============================] - 8s 572ms/step - loss: 0.3982 - accuracy: 0.8185 - val_loss: 0.5666 - val_accuracy: 0.7277 Epoch 13/15 15/15 [==============================] - 8s 563ms/step - loss: 0.3867 - accuracy: 0.8214 - val_loss: 0.5566 - val_accuracy: 0.7210 Epoch 14/15 15/15 [==============================] - 8s 546ms/step - loss: 0.3389 - accuracy: 0.8492 - val_loss: 0.5779 - val_accuracy: 0.7321 Epoch 15/15 15/15 [==============================] - 8s 527ms/step - loss: 0.3242 - accuracy: 0.8663 - val_loss: 0.5690 - val_accuracy: 0.7366
学習結果の可視化
ネットワークを学習した後、結果を可視化します。
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs_range = range(epochs)
plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
プロットからわかるように、学習セットの精度と検証セットの精度は大幅に外れており、モデルは検証セットで約70%の精度しか達成していません。
何がうまくいかなかったかを見て、モデル全体のパフォーマンスを向上してみましょう。
過学習
上記のプロットでは、学習セットの精度は時間とともに直線的に向上していますが、検証セットの精度は学習プロセスの中で約70%あたりで頭打ちになっています。そして、学習と検証の精度の違いが顕著です。これは 過学習 のサインです。
学習サンプルが少ない場合、モデルは学習サンプルに含まれるノイズや不要な詳細から学習してしまい、これによって新しいサンプルに対するモデルの性能に悪影響を与えることがあります。この現象は、過学習として知られています。過学習とは、モデルが新しいデータセットに対して汎化するのが難しい状態をいいます。
学習プロセスにおいて過学習に対抗する手段はいくつかあります。このチュートリアルでは、データ拡張(data Augmentation) を使用し、さらにモデルに ドロップアウト(dropout) を追加します。
データ拡張(Data augmentation)
過学習は一般に、学習サンプルが少ない場合に発生します。この問題を解決する方法の1つは、十分な数の学習サンプルが含まれるようにデータセットを拡張することです。データ拡張は、既存の学習サンプルに対してランダムな変換を行い、データセットとして利用できそうな画像を生成するアプローチをとります。このデータ拡張の目的は、学習中にモデルがまったくおなじ画像を2回利用しないようにすることです。これによってモデルをデータのより多くの特徴を利用し、より汎化することができます。
tf.keras
においては、このデータ拡張を ImageDataGenerator
クラスを使用して実装します。データセットに対するさまざまな変換を指定することによって、学習プロセス中にそれが適用されます。
データの拡張と可視化
最初に、ランダムな水平反転による拡張をデータセットに適用し、それぞれの画像が変換後にどのように見えるかを確認します。
水平反転の適用
このデータ拡張を適用するためには、 ImageDataGenerator
クラスの引数として horizontal_flip
を渡し、 True
を設定します。
image_gen = ImageDataGenerator(rescale=1./255, horizontal_flip=True)
train_data_gen = image_gen.flow_from_directory(batch_size=batch_size,
directory=train_dir,
shuffle=True,
target_size=(IMG_HEIGHT, IMG_WIDTH))
Found 2000 images belonging to 2 classes.
学習サンプルから1つのサンプル画像を取得する作業を5回繰り返して、おなじ画像に5回データ拡張が適用されるようにします。
augmented_images = [train_data_gen[0][0][0] for i in range(5)]
# 上で学習用画像の可視化のために定義、使用されたおなじカスタムプロット関数を再利用する
plotImages(augmented_images)
画像のランダムな回転
回転のデータ拡張を利用して学習用サンプルをランダムに左右45度の範囲で回転させてみましょう。
image_gen = ImageDataGenerator(rescale=1./255, rotation_range=45)
train_data_gen = image_gen.flow_from_directory(batch_size=batch_size,
directory=train_dir,
shuffle=True,
target_size=(IMG_HEIGHT, IMG_WIDTH))
augmented_images = [train_data_gen[0][0][0] for i in range(5)]
Found 2000 images belonging to 2 classes.
plotImages(augmented_images)
ズームによるデータ拡張の適用
データセットにズームによるデータ拡張を適用して、画像をランダムに最大50%拡大します。
image_gen = ImageDataGenerator(rescale=1./255, zoom_range=0.5)
train_data_gen = image_gen.flow_from_directory(batch_size=batch_size,
directory=train_dir,
shuffle=True,
target_size=(IMG_HEIGHT, IMG_WIDTH))
augmented_images = [train_data_gen[0][0][0] for i in range(5)]
Found 2000 images belonging to 2 classes.
plotImages(augmented_images)
すべてのデータ拡張を同時に利用する
ここまでで紹介したすべてのデータ拡張機能を適用します。ここでは、学習用画像に対して、リスケール、45度の回転、幅シフト、高さシフト、水平反転、ズームを適用しました。
image_gen_train = ImageDataGenerator(
rescale=1./255,
rotation_range=45,
width_shift_range=.15,
height_shift_range=.15,
horizontal_flip=True,
zoom_range=0.5
)
train_data_gen = image_gen_train.flow_from_directory(batch_size=batch_size,
directory=train_dir,
shuffle=True,
target_size=(IMG_HEIGHT, IMG_WIDTH),
class_mode='binary')
Found 2000 images belonging to 2 classes.
これらのデータ拡張がデータセットにランダムに適用されたときに、一つの画像に対して5回の個別の適用を行った際にそれぞれどのように見えるかを可視化します。
augmented_images = [train_data_gen[0][0][0] for i in range(5)]
plotImages(augmented_images)
検証データジェネレータの構築
一般に、データ拡張は学習サンプルのみに適用します。今回は、 ImageDataGenerator
を使用して検証画像に対してリスケールのみを実施し、バッチに変換します。
image_gen_val = ImageDataGenerator(rescale=1./255)
val_data_gen = image_gen_val.flow_from_directory(batch_size=batch_size,
directory=validation_dir,
target_size=(IMG_HEIGHT, IMG_WIDTH),
class_mode='binary')
Found 1000 images belonging to 2 classes.
ドロップアウト(dropout)
過学習を避けるもう一つの方法は、ネットワークに ドロップアウト を導入することです。これは、ネットワークにおいて重みを小さくする正則化の方式で、これによって重みの値の分布がより規則的になり、少ない学習データに対する過学習を減らすことができます。ドロップアウトはこのチュートリアルで利用される正則化手法の一つです。
ドロップアウトをレイヤーに適用すると、学習プロセス中に適用されたレイヤーのうちランダムに出力ユニットをドロップアウト(ゼロに設定)します。ドロップアウトは、入力値として0.1、0.2、0.4といった形式の小数をとります。これは、適用されたレイヤーからランダムに出力単位の10%、20%、または40%をドロップアウトすることを意味します。
特定のレイヤーに0.1ドロップアウトを適用すると、各学習エポックにおいて出力ユニットの10%がランダムに0にされます。
この新しいドロップアウト機能を使用したネットワークアーキテクチャを作成し、異なる畳み込みレイヤーや全接続レイヤーに適用してみましょう。
ドロップアウトを追加した新しいネットワークの構築
ここでは、ドロップアウトを最初と最後の max pool 層に適用します。ドロップアウトを適用すると、各学習エポック中にニューロンの20%がランダムにゼロに設定されます。これにより、学習データセットに対する過学習を避けることができます。
model_new = Sequential([
Conv2D(16, 3, padding='same', activation='relu',
input_shape=(IMG_HEIGHT, IMG_WIDTH ,3)),
MaxPooling2D(),
Dropout(0.2),
Conv2D(32, 3, padding='same', activation='relu'),
MaxPooling2D(),
Conv2D(64, 3, padding='same', activation='relu'),
MaxPooling2D(),
Dropout(0.2),
Flatten(),
Dense(512, activation='relu'),
Dense(1, activation='sigmoid')
])
モデルのコンパイル
ネットワークにドロップアウトを導入した後、モデルをコンパイルし、レイヤーの概要を表示します。
model_new.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])
model_new.summary()
Model: "sequential_1" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d_3 (Conv2D) (None, 150, 150, 16) 448 _________________________________________________________________ max_pooling2d_3 (MaxPooling2 (None, 75, 75, 16) 0 _________________________________________________________________ dropout (Dropout) (None, 75, 75, 16) 0 _________________________________________________________________ conv2d_4 (Conv2D) (None, 75, 75, 32) 4640 _________________________________________________________________ max_pooling2d_4 (MaxPooling2 (None, 37, 37, 32) 0 _________________________________________________________________ conv2d_5 (Conv2D) (None, 37, 37, 64) 18496 _________________________________________________________________ max_pooling2d_5 (MaxPooling2 (None, 18, 18, 64) 0 _________________________________________________________________ dropout_1 (Dropout) (None, 18, 18, 64) 0 _________________________________________________________________ flatten_1 (Flatten) (None, 20736) 0 _________________________________________________________________ dense_2 (Dense) (None, 512) 10617344 _________________________________________________________________ dense_3 (Dense) (None, 1) 513 ================================================================= Total params: 10,641,441 Trainable params: 10,641,441 Non-trainable params: 0 _________________________________________________________________
モデルの学習
学習サンプルにデータ拡張を導入し、ネットワークにドロップアウトを追加した後、この新しいネットワークを学習します:
history = model_new.fit_generator(
train_data_gen,
steps_per_epoch=total_train // batch_size,
epochs=epochs,
validation_data=val_data_gen,
validation_steps=total_val // batch_size
)
Epoch 1/15 15/15 [==============================] - 17s 1s/step - loss: 1.0632 - accuracy: 0.5034 - val_loss: 0.6900 - val_accuracy: 0.5011 Epoch 2/15 15/15 [==============================] - 16s 1s/step - loss: 0.6937 - accuracy: 0.5047 - val_loss: 0.6879 - val_accuracy: 0.5100 Epoch 3/15 15/15 [==============================] - 16s 1s/step - loss: 0.6915 - accuracy: 0.5020 - val_loss: 0.6747 - val_accuracy: 0.5982 Epoch 4/15 15/15 [==============================] - 16s 1s/step - loss: 0.6823 - accuracy: 0.5673 - val_loss: 0.6491 - val_accuracy: 0.6306 Epoch 5/15 15/15 [==============================] - 16s 1s/step - loss: 0.6703 - accuracy: 0.5955 - val_loss: 0.6376 - val_accuracy: 0.6339 Epoch 6/15 15/15 [==============================] - 16s 1s/step - loss: 0.6562 - accuracy: 0.6101 - val_loss: 0.6325 - val_accuracy: 0.6473 Epoch 7/15 15/15 [==============================] - 16s 1s/step - loss: 0.6392 - accuracy: 0.6311 - val_loss: 0.6077 - val_accuracy: 0.6562 Epoch 8/15 15/15 [==============================] - 16s 1s/step - loss: 0.6243 - accuracy: 0.6401 - val_loss: 0.6300 - val_accuracy: 0.6105 Epoch 9/15 15/15 [==============================] - 16s 1s/step - loss: 0.6383 - accuracy: 0.6371 - val_loss: 0.5926 - val_accuracy: 0.6696 Epoch 10/15 15/15 [==============================] - 16s 1s/step - loss: 0.6091 - accuracy: 0.6547 - val_loss: 0.5801 - val_accuracy: 0.6830 Epoch 11/15 15/15 [==============================] - 17s 1s/step - loss: 0.5997 - accuracy: 0.6740 - val_loss: 0.5935 - val_accuracy: 0.6752 Epoch 12/15 15/15 [==============================] - 16s 1s/step - loss: 0.5832 - accuracy: 0.6871 - val_loss: 0.6069 - val_accuracy: 0.6518 Epoch 13/15 15/15 [==============================] - 16s 1s/step - loss: 0.6012 - accuracy: 0.6713 - val_loss: 0.5615 - val_accuracy: 0.6998 Epoch 14/15 15/15 [==============================] - 15s 1s/step - loss: 0.5679 - accuracy: 0.7146 - val_loss: 0.5349 - val_accuracy: 0.7087 Epoch 15/15 15/15 [==============================] - 15s 1s/step - loss: 0.5502 - accuracy: 0.7230 - val_loss: 0.5663 - val_accuracy: 0.6897
モデルの可視化
学習後に新しいモデルを可視化すると、過学習が前回よりも大幅に少ないことがわかります。より多くのエポックでモデルを学習すると、精度はさらに向上するはずです。
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs_range = range(epochs)
plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()