📝

[TensorFlow Developer ]#5 C2W1_Using CNN's with the Cats vs Dogs

2022/08/15に公開

コース2のweek1ではCNNを利用して、猫と犬の画像の識別を行います。
利用する「Cats vs Dogs Dataset」はKaggle等でも利用される有名なデータセットのようです。

今回のアサインメントではコース1に比べ、ディレクトリの作成やデータの振分け部分も自らコーディングを行う必要があります。コース2になって若干レベルが上がったということでしょうか。

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

アサインメントの内容

「Cats vs Dogs Dataset」にある猫、犬それぞれ12,500件ずつの画像を利用してモデルの学習と検証を行います。学習でaccuracyが95%以上、検証でaccuracyが85%以上のモデルを作ることが合格条件です。

全体の流れ

今回はコーディング量が多いので先に全体の流れを記載します。

1.学習と検証用のファイル配置ディレクトリの作成
2.ファイルチェックとディレクトリへのファイルコピー
3.ImageDataGeneratorの作成
4.モデル定義&コンパイル
5.フィッティング

アサインメントの実施

1.学習と検証用のファイル配置ディレクトリの作成

ここでは学習と検証用に必要なデータを格納するためのディレクトリを作成します。
最終的に作りたいディレクトリのイメージは下記のような感じです。

/tmp/cats-v-dogs/training
/tmp/cats-v-dogs/validation
/tmp/cats-v-dogs/training/cats
/tmp/cats-v-dogs/training/dogs
/tmp/cats-v-dogs/validation/cats
/tmp/cats-v-dogs/validation/dogs

コードは下記のような感じです。
os.path.joinメソッドでパスを作成して、os.makedirsメソッドでディレクトリを作成します。

# Define root directory
root_dir = '/tmp/cats-v-dogs'

# Empty directory to prevent FileExistsError is the function is run several times
if os.path.exists(root_dir):
  shutil.rmtree(root_dir)#配下のフォルダとファイルを全削除

# GRADED FUNCTION: create_train_val_dirs
def create_train_val_dirs(root_path):
  """
  Creates directories for the train and test sets
  
  Args:
    root_path (string) - the base directory path to create subdirectories from
  
  Returns:
    None
  """  
  ### START CODE HERE

  # HINT:
  # Use os.makedirs to create your directories with intermediate subdirectories
  # Don't hardcode the paths. Use os.path.join to append the new directories to the root_path parameter

  # ルートパスのディレクトリ作成
  os.makedirs(root_path)
  # トレーニング用と検証用のディレクトリパスを作成
  train_dir = os.path.join(root_path, 'training')
  val_dir = os.path.join(root_path, 'validation')

  # Directory with training and valuation cat/dog pictures
  train_cats_dir = os.path.join(train_dir, 'cats')
  train_dogs_dir = os.path.join(train_dir, 'dogs')
  val_cats_dir = os.path.join(val_dir, 'cats')
  val_dogs_dir = os.path.join(val_dir, 'dogs')
  
  os.makedirs(train_cats_dir)
  os.makedirs(train_dogs_dir)
  os.makedirs(val_cats_dir)
  os.makedirs(val_dogs_dir)
  pass

  ### END CODE HERE
  
try:
  create_train_val_dirs(root_path=root_dir)
except FileExistsError:
  print("You should not be seeing this since the upper directory is removed beforehand")

2.ファイルチェックとディレクトリへのファイルコピー

ディレクトリが作成できたら、今度はファイルのチェックと学習用、検証用のファイルの振分、ディレクトリのコピーを行ってきます。

2.1.ファイルチェック

SOURCE_DIRにあるファイルを全チェックして0ファイルでない場合配列にパスを格納、0ファイルの場合はファイル名を出力を"filename is zero length, so ignoring."のように出力します。

最初にファイルのパスを格納するリストを宣言します。
次にSOURCE_DIR配下のファイルに対してos.path.getsizeメソッドでファイルサイズのチェックを行い、サイズが0であった場合はメッセージを出力し、0より大きい場合は配列にファイルのパスを設定していきます。

  file_list = [] #ファイルのパスを格納するリスト

  for file in os.listdir(SOURCE_DIR):
    file_path = os.path.join(SOURCE_DIR,file)
    if(os.path.getsize(file_path) == 0):#ファイルのサイズチェックを行い、0の場合はファイル名を表示
      print(file + " is zero length, so ignoring.")
    else:
      file_list.append(file_path)#ファイルサイズが0より大きい場合は配列にパス名を格納

2.2.ファイルの振分

SPLIT_SIZE(学習用に振分るデータの割合、このアサインでは0.9が設定される)に応じて、データを学習用と検証用に振り分けます。
この振分にはrandom.sampleメソッドを利用しますが、第一引数にファイルパスが格納されたリスト、第二引数にサンプリングしたいデータの個数をint型で設定します。
SPLIT_SIZEをかけた値はFLOAT型になってしまうのでroundを使ったあとint型でキャストした値を引数に設定します。

そして、学習用のファイルをtrain_listに設定し、それ以外を検証用データとしてval_listに設定する必要があります。このval_listへのデータ設定は、setメソッドを利用してset(ファイル全体)-set(学習データ)の結果をlist化することで設定することができます。

  train_num_items = int(round(len(file_list) * SPLIT_SIZE, 0)) #SPLIT_SIZEを全体データ数にかけint型でキャストする
  train_list = random.sample(file_list, train_num_items) #random.sampleの第二引数はint型で振分けるデータの個数を指定する
  val_list = list(set(file_list) - set(train_list))

2.3.ファイルコピー

振分けたファイルをそれぞれ、TRAINING_DIR、VALIDATION_DIRにコピーします。
コピーにはcopyfileメソッドを利用します。

  for f in train_list:
    copyfile(f,TRAINING_DIR+os.path.basename(f))
  for f in val_list:
    copyfile(f,VALIDATION_DIR+os.path.basename(f))
  pass

3.ImageDataGeneratorの作成

ImageDataGeneratorを学習用と検証用でそれぞれ作成していきます。
ここでも、コース1のweek4でも利用したflow_from_directoryメソッドを利用していきます。
下記にflow_from_directoryメソッドの説明を再掲します。

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)

今回のgeneratorの定義は下記のような感じにしました。
ここで、ポイントなのはbatch_sizeです。
何回かトライしたのですが学習のaccurasyが95%に届かず、ここのbatch_sizeを20から50に変えてやっとaccuracyが95%以上となりました。
batch_sizeが精度に影響するんですね。勉強になります。

# GRADED FUNCTION: train_val_generators
def train_val_generators(TRAINING_DIR, VALIDATION_DIR):
  """
  Creates the training and validation data generators
  
  Args:
    TRAINING_DIR (string): directory path containing the training images
    VALIDATION_DIR (string): directory path containing the testing/validation images
    
  Returns:
    train_generator, validation_generator - tuple containing the generators
  """
  ### START CODE HERE

  # Instantiate the ImageDataGenerator class (don't forget to set the rescale argument)
  train_datagen = ImageDataGenerator( rescale = 1.0/255. )

  # Pass in the appropiate arguments to the flow_from_directory method
  train_generator = train_datagen.flow_from_directory(directory=TRAINING_DIR,
                                                      batch_size=50,
                                                      class_mode='binary',
                                                      target_size=(150, 150))

  # Instantiate the ImageDataGenerator class (don't forget to set the rescale argument)
  validation_datagen = ImageDataGenerator( rescale = 1.0/255. )

  # Pass in the appropiate arguments to the flow_from_directory method
  validation_generator = validation_datagen.flow_from_directory(directory=VALIDATION_DIR,
                                                                batch_size=50,
                                                                class_mode='binary',
                                                                target_size=(150, 150))
  ### END CODE HERE
  return train_generator, validation_generator

4.モデル定義&コンパイル

モデル定義を行います。
少なくとも3層のConvolutionレイヤーを利用することとコメントにあったので4層で組んでみました。

コンパイル時のオプティマイザは、RMSprop(learning_rate=0.001)を設定し、損失関数は二値分類なのでbinary_crossentropyを指定しました。

from tensorflow.keras.optimizers import RMSprop
# GRADED FUNCTION: create_model
def create_model():
  # DEFINE A KERAS MODEL TO CLASSIFY CATS V DOGS
  # USE AT LEAST 3 CONVOLUTION LAYERS

  ### START CODE HERE

  model = tf.keras.models.Sequential([ 
    # Note the input shape is the desired size of the image 150x150 with 3 bytes color
    tf.keras.layers.Conv2D(16, (3,3), activation='relu', input_shape=(150, 150, 3)),
    tf.keras.layers.MaxPooling2D(2,2),
    tf.keras.layers.Conv2D(32, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2), 
    tf.keras.layers.Conv2D(64, (3,3), activation='relu'), 
    tf.keras.layers.MaxPooling2D(2,2),
    tf.keras.layers.Conv2D(128, (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 ('cats') and 1 for the other ('dogs')
    tf.keras.layers.Dense(1, activation='sigmoid')  
  ])

  
  model.compile(optimizer=RMSprop(learning_rate=0.001),
                loss='binary_crossentropy',
                metrics=['accuracy']) 
    
  ### END CODE HERE

  return model

5.フィッティング

学習用と検証用のImageDataGeneratorを指定してephochsは元のソースのまま15で実施しました。

# Get the untrained model
model = create_model()

# Train the model
# Note that this may take some time.
history = model.fit(train_generator,
                    epochs=15,
                    verbose=1,
                    validation_data=validation_generator)

6.実行結果

学習で、accuracy: 0.9687、検証でval_accuracy: 0.8716と合格基準を満たすことができました。

Epoch 1/15
153/450 [=========>....................] - ETA: 45s - loss: 0.6916 - accuracy: 0.5765/usr/local/lib/python3.7/dist-Epoch 11/15
450/450 [==============================] - 74s 165ms/step - loss: 0.1071 - 

途中省略・・・

accuracy: 0.9620 - val_loss: 0.4581 - val_accuracy: 0.8776
Epoch 12/15
450/450 [==============================] - 75s 167ms/step - loss: 0.0989 - accuracy: 0.9673 - val_loss: 0.4744 - val_accuracy: 0.8808
Epoch 13/15
450/450 [==============================] - 75s 167ms/step - loss: 0.0954 - accuracy: 0.9687 - val_loss: 0.5591 - val_accuracy: 0.8808
Epoch 14/15
450/450 [==============================] - 81s 179ms/step - loss: 0.0898 - accuracy: 0.9697 - val_loss: 0.5094 - val_accuracy: 0.8836
Epoch 15/15
450/450 [==============================] - 77s 171ms/step - loss: 0.0941 - accuracy: 0.9687 - val_loss: 0.5789 - val_accuracy: 0.8716

最後に用意されているセルを実行してhistoryのpklファイルをダウンロードして、notebookファイルとあわせてアップロードして完了です。

Discussion