Kerasを利用した画像2クラス分類CNN

woman typing writing programming

Kerasを利用した画像分類を業務内で扱うことがあり,まれに実装を忘れてしまう。いつ・どこからでも参照できるように,備忘録としてブログ上にポストしておく。本ページでは,転移学習(base_model = VGG16)を利用した,画像の2クラス分類モデルの実装を扱う。

(紹介コードは github にて公開)

開発環境

python: 3.7.9
keras: 2.3.1
matplotlib: 3.3.4
numpy: 1.19.2

ソースコード

「データ読み込み」「ImageDataGenerator作成」「モデル学習」「history可視化」処理全体のソースコードは以下に示す通り。

import numpy as np
import matplotlib.pyplot as plt
from keras.models import Model
from keras.datasets import cifar10
from keras.applications import VGG16
from keras.layers import Input, Flatten, Dense
from keras.preprocessing.image import ImageDataGenerator


def get_input():
    # CIFAR10データの読み込み
    (X_train, y_train), (X_test, y_test), = cifar10.load_data()
    y_train = y_train.reshape(-1)
    y_test = y_test.reshape(-1)

    # 0番目のクラスと1番目のクラスのデータを結合
    X_train = np.concatenate([X_train[y_train == 0], X_train[y_train == 1]], axis=0)
    y_train = np.concatenate([y_train[y_train == 0], y_train[y_train == 1]], axis=0)
    X_test = np.concatenate([X_test[y_test == 0], X_test[y_test == 1]], axis=0)
    y_test = np.concatenate([y_test[y_test == 0], y_test[y_test == 1]], axis=0)

    return X_train, y_train, X_test, y_test


def get_datagen():
    train_datagen = ImageDataGenerator(
        rescale=1. / 255,
        rotation_range=90,
        width_shift_range=0.2,
        height_shift_range=0.2,
        horizontal_flip=True
    )

    test_datagen = ImageDataGenerator(
        rescale=1. / 255
    )

    return train_datagen, test_datagen


def define_model(height, width):
    # 入力サイズの定義
    input = Input(shape=(height, width, 3))

    # ベースモデル(VGG16)のインスタンスを生成
    base_model = VGG16(input_shape=(height, width, 3), include_top=False, weights='imagenet', input_tensor=input)
    
    # ベースモデルとtrainable属性をFalseに設定
    for layer in base_model.layers:
        layer.trainable = False

    # ベースモデルの出力を取得
    base_output = base_model.output

    # ベースモデルの出力を一次元化
    base_flatten = Flatten()(base_output)

    # 出力層を追加する
    classifier = Dense(1, activation='sigmoid')(base_flatten)

    # モデルを定義する
    model = Model(inputs=input, outputs=classifier)

    # モデルをコンパイルする
    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=["accuracy"])

    return model


if __name__ == '__main__':
    # 訓練・検証データを作成
    X_train, y_train, X_test, y_test = get_input()

    # KerasのImageDataGeneratorを作成
    train_generator, test_generator = get_datagen()

    # 学習モデルを作成
    model = define_model(height=X_train.shape[1], width=X_train.shape[2])

    # モデル構造を出力する
    model.summary()

    # 訓練を実行 
    history = model.fit_generator(
        train_generator.flow(X_train, y_train, batch_size=32),
        validation_data=test_generator.flow(X_test, y_test, batch_size=32),
        epochs=5)
    
    # Accurayの変遷をプロット
    plt.figure(figsize=(4, 5))
    plt.plot(history.history['accuracy'])
    plt.plot(history.history['val_accuracy'])
    plt.title('Model accuracy')
    plt.ylabel('Accuracy')
    plt.xlabel('Epoch')
    plt.legend(['Train', 'Test'], loc='upper left')
    plt.show()
    
    # Lossの変遷をプロット
    plt.figure(figsize=(4, 5))
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.title('Model loss')
    plt.ylabel('Loss')
    plt.xlabel('Epoch')
    plt.legend(['Train', 'Test'], loc='upper left')
    plt.show()

パッケージのインポート

「numpy」は学習・訓練データ準備用。「matplotlib」はhistoryの可視化用。「keras」は学習モデル準備,学習実行用。

import numpy as np
import matplotlib.pyplot as plt
from keras.models import Model
from keras.datasets import cifar10
from keras.applications import VGG16
from keras.layers import Input, Flatten, Dense
from keras.preprocessing.image import ImageDataGenerator

get_input():データ読み込み

検証用のデータセットとして,今回はCIFAR10を利用。2クラス分類の実装を対象とするため,ここではclass=0,class=1のみを選択し,訓練・検証データのみを抽出。

def get_input():
    # CIFAR10データの読み込み
    (X_train, y_train), (X_test, y_test), = cifar10.load_data()
    y_train = y_train.reshape(-1)
    y_test = y_test.reshape(-1)

    # 0番目のクラスと1番目のクラスのデータを結合
    X_train = np.concatenate([X_train[y_train == 0], X_train[y_train == 1]], axis=0)
    y_train = np.concatenate([y_train[y_train == 0], y_train[y_train == 1]], axis=0)
    X_test = np.concatenate([X_test[y_test == 0], X_test[y_test == 1]], axis=0)
    y_test = np.concatenate([y_test[y_test == 0], y_test[y_test == 1]], axis=0)

    return X_train, y_train, X_test, y_test

get_datagen():ジェネレータ作成

学習モデルにデータを与えるため,keras.preprocessing.imageのImageDataGeneratorを利用する。汎化性能を高める意図で,学習用のジェネレータ(train_datagen)にのみ回転やシフト,フリップの処理を加えている。

def get_datagen():
    train_datagen = ImageDataGenerator(
        rescale=1. / 255,
        rotation_range=90,
        width_shift_range=0.2,
        height_shift_range=0.2,
        horizontal_flip=True
    )

    test_datagen = ImageDataGenerator(
        rescale=1. / 255
    )

    return train_datagen, test_datagen

define_model():モデル定義

KerasのFunctional APIを利用した実装。転移学習元のベースモデルとしてVGG16を選択しているが,MobileNetでも,InceptionResNetV2でも何でもよい。

ファインチューニングの実装を目的としていないので,ベースモデルのtrainable属性はFalseとしている。

2クラス分類なので,最終出力層のユニット数は「1」,活性化関数は「sigmoid」を選択している。keras.utils.to_categorical()を利用して,正答データ1をone-hot encodingするのであれば,最終出力層のユニット数を「2」,活性化関数を「softmax」でも可能。後者の場合,モデルコンパイル時の損失(loss)は,「categorical_crossentropy」にする必要がある。要するに,多クラス分類時の実装と同じになる。

def define_model(height, width):
    # 入力サイズの定義
    input = Input(shape=(height, width, 3))

    # ベースモデル(VGG16)のインスタンスを生成
    base_model = VGG16(input_shape=(height, width, 3), include_top=False, weights='imagenet', input_tensor=input)
    
    # ベースモデルとtrainable属性をFalseに設定
    for layer in base_model.layers:
        layer.trainable = False

    # ベースモデルの出力を取得
    base_output = base_model.output

    # ベースモデルの出力を一次元化
    base_flatten = Flatten()(base_output)

    # 出力層を追加する
    classifier = Dense(1, activation='sigmoid')(base_flatten)

    # モデルを定義する
    model = Model(inputs=input, outputs=classifier)

    # モデルをコンパイルする
    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=["accuracy"])

    return model

実行処理

上記の関数を利用し,データ作成からモデルの学習,学習historyのプロットまでの処理。各処理の意味するところはコメントアウトの通り。

if __name__ == '__main__':
    # 訓練・検証データを作成
    X_train, y_train, X_test, y_test = get_input()

    # KerasのImageDataGeneratorを作成
    train_generator, test_generator = get_datagen()

    # 学習モデルを作成
    model = define_model(height=X_train.shape[1], width=X_train.shape[2])

    # モデル構造を出力する
    model.summary()

    # 訓練を実行 
    history = model.fit_generator(
        train_generator.flow(X_train, y_train, batch_size=32),
        validation_data=test_generator.flow(X_test, y_test, batch_size=32),
        epochs=5)
    
    # Accurayの変遷をプロット
    plt.plot(history.history['accuracy'])
    plt.plot(history.history['val_accuracy'])
    plt.title('Model accuracy')
    plt.ylabel('Accuracy')
    plt.xlabel('Epoch')
    plt.legend(['Train', 'Test'], loc='upper left')
    plt.show()
    
    # Lossの変遷をプロット
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.title('Model loss')
    plt.ylabel('Loss')
    plt.xlabel('Epoch')
    plt.legend(['Train', 'Test'], loc='upper left')
    plt.show()

得られる出力は下記を参考。

Model: "model_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 32, 32, 3)         0         
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 32, 32, 64)        1792      
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 32, 32, 64)        36928     
_________________________________________________________________
block1_pool (MaxPooling2D)   (None, 16, 16, 64)        0         
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 16, 16, 128)       73856     
_________________________________________________________________
block2_conv2 (Conv2D)        (None, 16, 16, 128)       147584    
_________________________________________________________________
block2_pool (MaxPooling2D)   (None, 8, 8, 128)         0         
_________________________________________________________________
block3_conv1 (Conv2D)        (None, 8, 8, 256)         295168    
_________________________________________________________________
block3_conv2 (Conv2D)        (None, 8, 8, 256)         590080    
_________________________________________________________________
block3_conv3 (Conv2D)        (None, 8, 8, 256)         590080    
_________________________________________________________________
block3_pool (MaxPooling2D)   (None, 4, 4, 256)         0         
_________________________________________________________________
block4_conv1 (Conv2D)        (None, 4, 4, 512)         1180160   
_________________________________________________________________
block4_conv2 (Conv2D)        (None, 4, 4, 512)         2359808   
_________________________________________________________________
block4_conv3 (Conv2D)        (None, 4, 4, 512)         2359808   
_________________________________________________________________
block4_pool (MaxPooling2D)   (None, 2, 2, 512)         0         
_________________________________________________________________
block5_conv1 (Conv2D)        (None, 2, 2, 512)         2359808   
_________________________________________________________________
block5_conv2 (Conv2D)        (None, 2, 2, 512)         2359808   
_________________________________________________________________
block5_conv3 (Conv2D)        (None, 2, 2, 512)         2359808   
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 1, 1, 512)         0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 512)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 513       
=================================================================
Total params: 14,715,201
Trainable params: 513
Non-trainable params: 14,714,688
_________________________________________________________________

Epoch 1/5
313/313 [==============================] - 7s 22ms/step - loss: 0.4736 - accuracy: 0.7852 - val_loss: 0.3620 - val_accuracy: 0.8595
Epoch 2/5
313/313 [==============================] - 5s 17ms/step - loss: 0.3763 - accuracy: 0.8418 - val_loss: 0.4377 - val_accuracy: 0.8775
Epoch 3/5
313/313 [==============================] - 5s 18ms/step - loss: 0.3504 - accuracy: 0.8506 - val_loss: 0.2776 - val_accuracy: 0.8820
Epoch 4/5
313/313 [==============================] - 5s 17ms/step - loss: 0.3292 - accuracy: 0.8657 - val_loss: 0.2275 - val_accuracy: 0.8930
Epoch 5/5
313/313 [==============================] - 5s 17ms/step - loss: 0.3234 - accuracy: 0.8650 - val_loss: 0.1502 - val_accuracy: 0.8935
  1. この場合,y_train/y_testを意味

コメント