본문 바로가기
딥러닝 with Python

[딥러닝 with Python] 이미지 분류(Image Classification)

by CodeCrafter 2024. 6. 14.
반응형

 

Computer Vision의 기본인 이미지 분류부터 다시 기본을 잡아가고자 합니다. 

 

이번에는 이미지 분류(Image Classification)에 대해서 알아보겠습니다. 

이번에 사용할 데이터는 Kaggle에 있는 Cats vs Dogs binary classifciation dataset 입니다.

 

이미지 전처리 및 간단한 신경망 구현을 코드와 함께 알아보겠습니다.

 

먼저 사용할 기본 라이브러리를 임포트 해줍니다.

import os
import numpy as np
import keras
from keras import layers
from tensorflow import data as tf_data
import matplotlib.pyplot as plt

 

다음은 raw data를 다운로드 받아줍니다.

!curl -O https://download.microsoft.com/download/3/E/1/3E1C3F21-ECDB-4869-8368-6DEBA77B919F/kagglecatsanddogs_5340.zip

 

이후 해당 데이터의 압축을 풀어줍니다.

!unzip -q kagglecatsanddogs_5340.zip
!ls

 

이제 PetImages 라는 폴더가 나오게 되었고, 세부 항목으로는 Cat 이라는 폴더와 Dog라는 폴더가 생성된 것을 확인할 수 있습니다.

!ls PetImages

 

 

다음은, corrupted image들을 필터링 해주겠습니다.

Corrupted Image는 학습 모델의 성능을 저하시킬 수 있는데요 

이번 예시 코드에서는 해당 파일이 JFIF(JPEG File Interchange Format) 형식이면 정상이고 그렇지 않다면 Corrupted로 분류하는 단순한 로직을 적용했습니다. (해당 방법은 단순한 예시일 뿐 최선의 방법은 아닙니다)

num_skipped = 0
for folder_name in ("Cat", "Dog"):
    folder_path = os.path.join("PetImages", folder_name)
    for fname in os.listdir(folder_path):
        fpath = os.path.join(folder_path, fname)
        try:
            fobj = open(fpath, "rb")
            is_jfif = b"JFIF" in fobj.peek(10)
        finally:
            fobj.close()

        if not is_jfif:
            num_skipped += 1
            # Delete corrupted image
            os.remove(fpath)

print(f"Deleted {num_skipped} images.")

 

 

 

이제 남은 데이터들을 가지고 데이터셋을 구축해줍니다.

image_size = (180, 180)
batch_size = 128

train_ds, val_ds = keras.utils.image_dataset_from_directory(
    "PetImages",
    validation_split=0.2,
    subset="both",
    seed=1337,
    image_size=image_size,
    batch_size=batch_size,
)

 

총 23,410개의 파일이 있으며 이 중 2개의 클래스(Cats 또는 Dogs)로 구성된 것을 확인할 수 있습니다. 또한, 해당 데이터를 8:2의 비율로 train과 validation dataset으로 나누어줍니다.

 

 

이제 train 데이터 셋 내의 이미지 파일들을 시각화해봅니다.


plt.figure(figsize=(10, 10))
for images, labels in train_ds.take(1):
    for i in range(9):
        ax = plt.subplot(3, 3, i + 1)
        plt.imshow(np.array(images[i]).astype("uint8"))
        plt.title(int(labels[i]))
        plt.axis("off")

 

 개와 고양이의 이미지,  그리고 개에는 "1" , 고양이에는 "0" 이라는 Binary Class가 부여가 된 것을 확인할 수 있습니다.

 

 

다음은  학습의 성능 향상 및 안정성을 위해 Data Augmentation을 학습 데이터에 적용해줍니다. 간단한 Data Augmentation은 수평 또는 수직 이동, 회전 등이 있습니다. 이를 활용해서 모델을 학습하게 되면, 모델의 강건성(Robustness)을 높일 수 있습니다.

data_augmentation_layers = [
    layers.RandomFlip("horizontal"),
    layers.RandomRotation(0.1),
]


def data_augmentation(images):
    for layer in data_augmentation_layers:
        images = layer(images)
    return images
plt.figure(figsize=(10, 10))
for images, _ in train_ds.take(1):
    for i in range(9):
        augmented_images = data_augmentation(images)
        ax = plt.subplot(3, 3, i + 1)
        plt.imshow(np.array(augmented_images[0]).astype("uint8"))
        plt.axis("off")

 

 

이제 데이터를 정규화해줍니다. 

 

이전에 데이터의 크기를 180 x 180으로 정규화하긴 했지만, 여기에서는 추가적인 정규화를 진행합니다.

이미지 데이터는 각 채널(R,G,B) 별로[0,255]의 범위를 가진 데이터를 가지고 있습니다. 이는 Neural Network의 입력으로 활용하기에는 바람직하지않습니다. 입력 값들의 분포가 차이가 많이 난다면 학습간 Gradient Exploding 또는 Vanishing 문제를 야기할 수도 있기에 데이터 값의 스케일링을 진행해줍니다.

 

여기서는 [0,255]의 각 채널별 값을 [0,1]로 축소해줍니다. 

inputs = keras.Input(shape=input_shape)
x = data_augmentation(inputs)
x = layers.Rescaling(1./255)(x)
...  # Rest of the model

 

 

이제 앞서 정의한 Data Augmentation을 학습 데이터에 적용해주고, 디스크에서 데이터를 불러올 때 입축력 작업이 학습 과정을 방해하지 않도록 버퍼링과 프리패칭(prefetching)을 적용해줍니다.

# Apply `data_augmentation` to the training images.
train_ds = train_ds.map(
    lambda img, label: (data_augmentation(img), label),
    num_parallel_calls=tf_data.AUTOTUNE,
)
# Prefetching samples in GPU memory helps maximize GPU utilization.
train_ds = train_ds.prefetch(tf_data.AUTOTUNE)
val_ds = val_ds.prefetch(tf_data.AUTOTUNE)

 

 

이제 학습 데이터는 준비가 끝났습니다.

 

이제 모델을 구축해보겠습니다. 모델은 간단한 Xception Net을 활용해주겠습니다.

 

- Xception Net은 깊고 효율적인 신경망 아키텍처로 이미지 분류작업에 많이 활용됩니다. 주요 특징은 아래와 같습니다.

 * Depthwise Seperable Convolution : 각 입력 채널에 대해 개별적으로 합성곱을 수행하는 Depthwise Convolution을 한 뒤 1x1 합성곱을 사용하여 채널 간의 결합을 해주는 Pointwise Convolution을 수행해줍니다.

 * Residual Connection : ResNet에서 입증되었듯 Residual Connection을 활용하여 깊은 네트워크에서도 Gradient 가 소실되는 문제를 최소화해줍니다.

 


def make_model(input_shape, num_classes):
    inputs = keras.Input(shape=input_shape)

    # Entry block
    x = layers.Rescaling(1.0 / 255)(inputs)
    x = layers.Conv2D(128, 3, strides=2, padding="same")(x)
    x = layers.BatchNormalization()(x)
    x = layers.Activation("relu")(x)

    previous_block_activation = x  # Set aside residual

    for size in [256, 512, 728]:
        x = layers.Activation("relu")(x)
        x = layers.SeparableConv2D(size, 3, padding="same")(x)
        x = layers.BatchNormalization()(x)

        x = layers.Activation("relu")(x)
        x = layers.SeparableConv2D(size, 3, padding="same")(x)
        x = layers.BatchNormalization()(x)

        x = layers.MaxPooling2D(3, strides=2, padding="same")(x)

        # Project residual
        residual = layers.Conv2D(size, 1, strides=2, padding="same")(
            previous_block_activation
        )
        x = layers.add([x, residual])  # Add back residual
        previous_block_activation = x  # Set aside next residual

    x = layers.SeparableConv2D(1024, 3, padding="same")(x)
    x = layers.BatchNormalization()(x)
    x = layers.Activation("relu")(x)

    x = layers.GlobalAveragePooling2D()(x)
    if num_classes == 2:
        units = 1
    else:
        units = num_classes

    x = layers.Dropout(0.25)(x)
    # We specify activation=None so as to return logits
    outputs = layers.Dense(units, activation=None)(x)
    return keras.Model(inputs, outputs)


model = make_model(input_shape=image_size + (3,), num_classes=2)
keras.utils.plot_model(model, show_shapes=True)

 

 

이번에는 예제이므로 25번의 반복만 진행해줍니다.

epochs = 25

callbacks = [
    keras.callbacks.ModelCheckpoint("save_at_{epoch}.keras"),
]
model.compile(
    optimizer=keras.optimizers.Adam(3e-4),
    loss=keras.losses.BinaryCrossentropy(from_logits=True),
    metrics=[keras.metrics.BinaryAccuracy(name="acc")],
)
model.fit(
    train_ds,
    epochs=epochs,
    callbacks=callbacks,
    validation_data=val_ds,
)

 

학습 결과 train accuracy는 0.9638 / validation accuracy는 0.9382를 얻게 되었습니다.

 

 

이렇게 학습된 모델을 가지고 추론(Inference)를 진행해보겠습니다. 고양이 이미지 한장을 가지고 해보겠습니다.

img = keras.utils.load_img("PetImages/Cat/6779.jpg", target_size=image_size)
plt.imshow(img)

img_array = keras.utils.img_to_array(img)
img_array = keras.ops.expand_dims(img_array, 0)  # Create batch axis

predictions = model.predict(img_array)
score = float(keras.ops.sigmoid(predictions[0][0]))
print(f"This image is {100 * (1 - score):.2f}% cat and {100 * score:.2f}% dog.")

 

결과는 아래에서 보시는 것처럼 고양이일 확률이 94.3%로 예측하였습니다. Threshold를 0.9로 주더라도 해당 모델은 다음 그림을 고양이로 분별할 것입니다.

반응형

댓글