📝

[TensorFlow Developer]#4 C1W4_Handling Complex Images

2022/08/14に公開

コース1の最終週、week4ではやや複雑な画像認識のモデル作成を学びます。
アサインメントは、ImageDataGeneratorを利用して、絵文字の画像からHappyかsadかを識別する二値分類モデルを作ります。

ちなみに、アサインメントは下記でgitにて公開されてますので誰でも自由にチャレンジすることできます。
TensorFlow Developer Certification C1W4アサインメント

ImageDataGenerator

ImageDataGeneratorは画像認識の学習を行うためのKerasに用意されたツールであり、通常は画像データを水増しする際によく使われます。
今回はデータの水増しは行いませんが、下記のように画像認識のモデル作成において便利な店があるので利用していこうと思います。

  • ディレクトリ構成に応じて自動で画像のラベリングを行ってくれる
  • rescal等の画像データの正規化を行う上での便利メソッドがある"

flow_from_directoryメソッド

ImageDataGeneratorにあるflow_from_directoryメソッドを利用することで、ディレクトリ構成に応じて自動で画像のラベリングを実施してくれます。
例えば、下記のようなディレクトリ構造の配下にそれぞれ「Horses(馬)」と「Humans(人間)」の画像が配置されてたとします。

この場合に、flow_from_directoryメソッドの引数としてValidationディレクトリのパスを指定することで、自動での「Horses(馬)」と「Humans(人間)」ディレクトリ配下の画像にラベリングを行ってくれます。

引数

メソッドの指定例は下記のような感じです。
引数はもっとたくさんありますが、アサインメントで利用する引数のみ説明します。

flow_from_directory(directory, target_size=(256, 256), class_mode='categorical', batch_size=32)
  • 引数1_directory:ラベリング対象のディクトリと画像データを含むディレクトリを指定
  • 引数2_target:識別対象画像のサイズ、ここで指定したサイズに全画像はリサイズされる(デフォルト:256×256
  • 引数3_class_mode:"categorical"か"binary"か"sparse"か"input"か"None"のいずれか1つを指定、返すラベルの配列のshapeを決定する(デフォルト:categorical)
    • "categorical"は2次元のone-hotにエンコード化されたラベル
    • "binary"は1次元の2値ラベル
    • "sparse"は1次元の整数ラベル
    • "input"は入力画像と同じ画像になる(主にオートエンコーダで用いられます)
    • Noneであれば,ラベルを返さない
  • 引数4_batch_size:データのバッチのサイズ(デフォルト:32)

rescale

これまで学習してきたように扱うデータは0-1の範囲にスケールした方が学習効率があがることが知られてます。
ImageDataGeneratorでは下記のように記載すると対象のデータを指定した値、下記であれば255で割って正規化してくれます。

ImageDataGenerator(rescale=1/255)

アサインメントの内容

今回のアサインメントは、80枚の絵文字データの中にある40のsmile画像と40のsad画像を識別するモデルの作成です。accuracy99.9%以上で合格となります。

ちなみにsmileとsadのサンプル画像は下記のよう感じです。

1.識別対象の画像データを知る

まずは、対象となる画像の情報を知ることが重要です。

from tensorflow.keras.preprocessing.image import img_to_array
# Load the first example of a happy face
sample_image  = load_img(f"{os.path.join(happy_dir, os.listdir(happy_dir)[0])}")
# Convert the image into its numpy array representation
sample_array = img_to_array(sample_image)
print(f"Each image has shape: {sample_array.shape}")
print(f"The maximum pixel value used is: {np.max(sample_array)}")

sample_array.shapeで画像データの次元を調べ、np.max(sample_array)でデータのmaxの値が知ることができます。データのmaxの値は正規化するために必要となります。

Each image has shape: (150, 150, 3)
The maximum pixel value used is: 255.0

コールバック関数の作成はいつも通りなので省きます。

2.ImageDataGeneratorの作成

ImageDataGeneratorを利用して学習データを作成するメソッドを作成します。

from tensorflow.keras.preprocessing.image import ImageDataGenerator

# GRADED FUNCTION: image_generator
def image_generator():
    ### START CODE HERE

    # Instantiate the ImageDataGenerator class.
    # Remember to set the rescale argument.
    train_datagen = ImageDataGenerator(rescale=1/255)

    # Specify the method to load images from a directory and pass in the appropriate arguments:
    # - directory: should be a relative path to the directory containing the data
    # - targe_size: set this equal to the resolution of each image (excluding the color dimension)
    # - batch_size: number of images the generator yields when asked for a next batch. Set this to 10.
    # - class_mode: How the labels are represented. Should be one of "binary", "categorical" or "sparse".
    #               Pick the one that better suits here given that the labels are going to be 1D binary labels.
    train_generator = train_datagen.flow_from_directory(directory=base_dir,
                                                        target_size=(150, 150),
                                                        batch_size=10,
                                                        class_mode="binary")
    ### END CODE HERE

    return train_generator

train_datagen.flow_from_directoryメソッドの引数に、学習させるディレクトリのパス、ステップ1で調べた画像のサイズ(今回だと150×150)、バッチサイズ(コメントにある通り今回は10で設定)、class_modeは二値分類(smile or sad)なので"binary"を選択します。

3.学習メソッド

下記のように、ImageDataGeneratorを引数とする学習用メソッドを定義し、このメソッドの中にモデル定義、コンパイル、フィッティングの処理を記載していきます。

from tensorflow.keras import optimizers, losses
from tensorflow.keras.optimizers import RMSprop

def train_happy_sad_model(train_generator):

    # Instantiate the callback
    callbacks = myCallback()

    ### 中身は省略
    #1.モデル定義
    #2.コンパイル
    #3.フィッティング
    
    return history

4.モデル定義

モデル定義はConv2DとMaxPooling2Dの組み合わせを3層重ねます。
最初のConv2Dで今回の対象のデータサイズをinput_shape=(150,150,3)として与えます。
最終出力は二値分類なのでactivationにはsigmoidを指定してます。

    # Define the model
    model = tf.keras.models.Sequential([
            # This is the first convolution
            tf.keras.layers.Conv2D(16, (3,3), activation='relu', input_shape=(150, 150, 3)),
            tf.keras.layers.MaxPooling2D(2, 2),
            # The second convolution
            tf.keras.layers.Conv2D(32, (3,3), activation='relu'),
            tf.keras.layers.MaxPooling2D(2,2),
            # The third convolution
            tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
            tf.keras.layers.MaxPooling2D(2,2),
        
            # Flatten the results to feed into a DNN
            tf.keras.layers.Flatten(),
            # 512 neuron hidden layer
            tf.keras.layers.Dense(512, activation='relu'),
            # Only 1 output neuron. It will contain a value from 0-1 where 0 for 1 class ('smile') and 1 for the other ('sad')
            tf.keras.layers.Dense(1, activation='sigmoid')
    ])

5.コンパイル

コンパイルでは、損失関数にbinary_crossentropy、オプティマイザにRMSpropを指定してコンパイルします。二値分類なので損失関数がbinary_crossentropyなのはわかるのですが、オプティマイザとしてRMSpropがなぜいいのかはよくわかってないです。。
詳しい人いたら教えて欲しいです。

    # Compile the model
    # Select a loss function compatible with the last layer of your network
    model.compile(loss='binary_crossentropy',
                  optimizer=RMSprop(learning_rate=0.001),
                  metrics=['accuracy']) 

6.フィッティング

fitメソッドの最初の引数としてImageDataGeneratorのインスタンスを渡すことで学習を行うことができます。これまでは説明変数(識別対象画像)、目的変数(ラベル)と2つの引数を渡していたと思いますが、ImageDataGeneratorを引数として渡すだけでうまいこと学習してくれます!

    # Train the model
    # Your model should achieve the desired accuracy in less than 15 epochs.
    # You can hardcode up to 20 epochs in the function below but the callback should trigger before 15.
    history = model.fit(x=train_generator,
                        epochs=15,
                        callbacks=[callbacks]
                       ) 

7.実行結果

下記の通り12回目のエポックでaccuracyが99.9%I を超えました!

Epoch 1/15
8/8 [==============================] - 4s 413ms/step - loss: 1.5127 - accuracy: 0.4625
Epoch 2/15
8/8 [==============================] - 3s 387ms/step - loss: 0.5527 - accuracy: 0.7625
Epoch 3/15
8/8 [==============================] - 3s 384ms/step - loss: 0.2373 - accuracy: 0.9625
Epoch 4/15
8/8 [==============================] - 3s 359ms/step - loss: 0.4790 - accuracy: 0.8625
Epoch 5/15
8/8 [==============================] - 3s 384ms/step - loss: 0.1382 - accuracy: 0.9250
Epoch 6/15
8/8 [==============================] - 3s 398ms/step - loss: 0.1296 - accuracy: 0.9625
Epoch 7/15
8/8 [==============================] - 3s 373ms/step - loss: 0.1303 - accuracy: 0.9500
Epoch 8/15
8/8 [==============================] - 3s 373ms/step - loss: 0.0893 - accuracy: 0.9625
Epoch 9/15
8/8 [==============================] - 3s 371ms/step - loss: 0.0849 - accuracy: 0.9625
Epoch 10/15
8/8 [==============================] - 3s 371ms/step - loss: 0.0428 - accuracy: 0.9875
Epoch 11/15
8/8 [==============================] - 3s 383ms/step - loss: 0.0659 - accuracy: 0.9750
Epoch 12/15
8/8 [==============================] - ETA: 0s - loss: 0.0107 - accuracy: 1.0000
Reached 99.9% accuracy so cancelling training!
8/8 [==============================] - 3s 373ms/step - loss: 0.0107 - accuracy: 1.0000

Discussion