이것은 고양이일까 강아지일까? (1)

kkamyang·2022년 1월 12일
0

🔻깃허브: https://github.com/kkamyang/Aiffel_exp/blob/main/cat_dog_1.ipynb

📍 데이터 준비

✏️ 데이터 분할하기

import tensorflow as tf
import tensorflow_datasets as tfds

(raw_train, raw_validation, raw_test), metadata = tfds.load(
    'cats_vs_dogs',
    split=['train[:80%]', 'train[80%:90%]', 'train[90%:]'],
    with_info=True,
    as_supervised=True,
)

split=['train[:80%]', 'train[80%:90%]', 'train[90%:]'] 는 전체 train 데이터셋으로 설정되어 있는 데이터셋을 80%, 10%, 10% 세 부분으로 나누어 각각 train_data, validation_data, test_data로 사용하겠다는 의미이다.

print(raw_train)
print(raw_validation)
print(raw_test)

Dataset의 shape는 (image, label)의 형태로 나타낸다. 첫 번재 인자인 image는 (height, width, channel)로 나타내는데 위의 데이터에서는 (None, None, 3)으로 나타났다. height와 width가 None으로 나타난 이유는 이미지의 크기가 모두 달라 사이즈가 정해져 있지 않기 때문이다. channel은 이미지의 색을 표현하는 채널의 수를 나타낸다. 흑백 이미지의 경우에는 2로 나타내고 컬러 이미지의 경우 RGB로 나타내기 때문에 3으로 나타낸다. 저번 프로젝트에서 png파일의 channel을 4로 설정해두었던 것 같은데 아마 png는 CMYK로 나타내서 그런 듯 하다.(아닐 수도 있으니 확인해보기)두 번째 인자인 label은 특정 이미지가 무엇을 나타내는지 알려주는 단일 값이다. 1차원의 숫자 하나로 표현되기 때문에 차원이 따로 나타나지는 않는다.

✏️ 이미지 사이즈 통일하기

import matplotlib.pyplot as plt
%matplotlib inline
%config InlineBackend.figure_format = 'retina'

plt.figure(figsize=(10, 5))

get_label_name = metadata.features['label'].int2str

for idx, (image, label) in enumerate(raw_train.take(10)):  # 10개의 데이터 가져오기
    plt.subplot(2, 5, idx+1)
    plt.imshow(image)
    plt.title(f'label {label}: {get_label_name(label)}')
    plt.axis('off')

라벨을 확인해보니 고양이는 0, 강아지는 1로 설정되어 있다. 이미지를 보니 사이즈가 모두 달라 이미지를 통일시켜 주는 작업이 필요해보인다. 이미지의 사이즈는 160*160 픽셀로 통일시키려 한다.

IMG_SIZE = 160 # resize시킬 이미지의 크기

def format_example(image, label):
    image = tf.cast(image, tf.float32) 
    image = (image/127.5) - 1 # 픽셀값의 scale 수정
    image = tf.image.resize(image, (IMG_SIZE, IMG_SIZE))
    return image, label

image = tf.cast(image, tf.float32)image=float(image)과 같은 타입캐스팅의 텐서플로우 버전이다. 타입 캐스팅은 다른 데이터 타입으로 형(타입)을 바꿔주는 것을 의미한다. 정수형을 실수형으로 바꾸기 위해 float()를 사용하는 것이 타입캐스팅의 한 예다. 이미지의 원래 픽셀 값은 RGB를 나타낼 때 주로 사용되는 0 ~ 255 사이의 정수값이었다. 이 값들의 중간값인 127.5로 나누고 1을 빼주어 -1 ~ 1 사이의 실수값이 되도록 한 것이 image = (image/127.5) - 1 이다.

train = raw_train.map(format_example)
validation = raw_validation.map(format_example)
test = raw_test.map(format_example)

print(train)
print(validation)
print(test)

map()함수를 이용해 format_example()함수를 raw_train, raw_validation, raw_test에 적용시켰다. 이를 통해 동일한 모양의 train, validataion, test 데이터셋을 얻을 수 있었다. 정말 이미지 크기가 같아졌는지 이전에 출력했던 10장의 이미지를 다시 확인해보자.

plt.figure(figsize=(10, 5))


get_label_name = metadata.features['label'].int2str

for idx, (image, label) in enumerate(train.take(10)):
    plt.subplot(2, 5, idx+1)
    image = (image + 1) / 2
    plt.imshow(image)
    plt.title(f'label {label}: {get_label_name(label)}')
    plt.axis('off')

📍 모델링

✏️ Layer 쌓기

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Conv2D, Flatten, MaxPooling2D

models는 모델 그 자체를 구축하기 위한 함수들을 포함하고 있다. 그리고 layers에는 모델의 구성 요소인 여러 가지 종류의 레이어라는 함수들을 가지고 있다. Sequential은 말 그대로 연속적인 모델을 쌓기 위한 함수이다. Sequential 함수 안에 연속적으로 여러 가지 레이어들이 들어간다.

model = Sequential([
    Conv2D(filters=16, kernel_size=3, padding='same', activation='relu', input_shape=(160, 160, 3)),
    MaxPooling2D(),
    Conv2D(filters=32, kernel_size=3, padding='same', activation='relu'),
    MaxPooling2D(),
    Conv2D(filters=64, kernel_size=3, padding='same', activation='relu'),
    MaxPooling2D(),
    Flatten(),
    Dense(units=512, activation='relu'),
    Dense(units=2, activation='softmax')
])

model.summary()

Conv2D, MaxPooling2D, Flatten, Dense라는 네 가지 종류의 레이어를 사용했다. summary() 메소드를 통해 모델의 전체 구조를 살펴보면 레이어가 층층이 쌓여있는 것을 볼 수 있다. 딥러닝은 언제나 이렇게 층층히 쌓여 있는 구조를 보인다. 처음 이미지는 (160, 160, 3)의 크기였는데 레이어를 지날수록 형태가 달라지고 있다. 이미지는 첫 번째 레이어인 conv2d부터 여섯 번째 레이어인 max_pooling2d_2까지 지나면서 height와 width는 160 → 80 → 40 → 20 으로 점점 작아지고, channel은 16 → 32 → 64까지 커지고 있다. 처음 6개의 레이어를 지나고 난 후의 shape는 (None, height, width, channel)로 4차원이다.

None은 배치(batch) 사이즈에 따라 모델에 다른 수의 입력이 들어올 수 있음을 나타낸다. 데이터 하나의 크기는 (height, width, channel)로 3차원인데, 6개의 레이어를 지나면서 height와 width는 점점 작아지고, channel은 점점 커지다가, flatten 계층을 만나 25,600(20x20x64)이라는 하나의 숫자인 1차원으로 shape가 줄어든다. 점점 작은 feature map이 출력되다가, FlattenDense 레이어를 거쳐 1차원으로 shape이 줄어드는 네트워크는 CNN을 사용한 딥러닝 모델의 가장 대표적인 형태이다.

learning_rate = 0.0001
model.compile(optimizer=tf.keras.optimizers.RMSprop(lr=learning_rate),
              loss=tf.keras.losses.sparse_categorical_crossentropy,
              metrics=['accuracy'])

optimizer는 학습을 어떤 방식으로 시킬 것인지 결정한다. 어떻게 최적화시킬 것인지를 결정하기 때문에 최적화 함수라고 부르기도 한다.

loss는 모델이 학습해나가야 하는 방향을 결정한다. 이 문제에서는 모델의 출력은 입력받은 이미지가 고양이인지 강아지인지에 대한 확률분포로 두었으므로, 입력 이미지가 고양이(label=0)일 경우 모델의 출력이 [1.0, 0.0]에 가깝도록, 강아지(label=1)일 경우 [0.0, 1.0]에 가까워지도록 하는 방향을 제시한다.

metrics는 모델의 성능을 평가하는 척도이다. 분류 문제를 풀 때, 성능을 평가할 수 있는 지표는 정확도(accuracy), 정밀도(precision), 재현율(recall) 등이 있다. 여기서는 정확도를 사용했다.

✏️ Batch 만들기

BATCH_SIZE = 32
SHUFFLE_BUFFER_SIZE = 1000

train_batches = train.shuffle(SHUFFLE_BUFFER_SIZE).batch(BATCH_SIZE)
validation_batches = validation.batch(BATCH_SIZE)
test_batches = test.batch(BATCH_SIZE)

BATCH_SIZE로 한 스텝에 학습시킬 데이터의 개수를 의미하고 SHUFFLE_BUFFER_SIZE를 설정해둠으로써 학습 데이터를 섞어줄 수 있다. BATCH_SIZE에 따라 32개의 데이터를 랜덤으로 뿌려줄 train_batches, validation_batches, test_batches를 만들어 주었다. train_batches는 모델이 끊임없이 학습될 수 있도록 전체 데이터에서 32개를 랜덤으로 뽑아 계속 제공해준다.

for image_batch, label_batch in train_batches.take(1):
    pass

image_batch.shape, label_batch.shape

테스트로 하나의 batch를 뽑아보았다. image_batch의 shape는 (160, 160, 3)의 shape인 32개의 데이터가 존재한다는 뜻이다. 또한 label은 강아지이면 1, 고양이이면 0으로 정답 label을 나타내기 때문에 한 batch에 데이터가 32개라면 label은 0 또는 1의 32개의 숫자로만 구성된다.

다음은 본격적인 모델을 만들기에 앞서 만든 초기 모델이다. 검증(validation)을 위한 데이터셋인 validation_batches를 이용해 20번의 예측을 해 보고, 평균 loss와 평균 accuracy를 확인한다.

validation_steps = 20
loss0, accuracy0 = model.evaluate(validation_batches, steps=validation_steps)

print("initial loss: {:.2f}".format(loss0))
print("initial accuracy: {:.2f}".format(accuracy0))

initial loss는 0.70이 나왔고 initial accuracy는 0.52이 나왔다. loss는 모델이 얼마나 틀렸는지를 나타내기 때문에 당연히 낮게 나올수록 좋다. accuracy는 정확도를 나타내는데 우리가 만든 모델은 강아지와 고양이 둘 중 하나를 분류하는 모델인데 찍어도 50%의 정확도가 나오므로 52%라는 정확도는 이 모델이 의미없는 예측을 했다고 판단할 수 있다. 이유는 아직 모델을 학습시키지 않았기 때문이다. 그러면 이번에는 10epoch를 학습시켜 정확도가 변하는 것을 확인해볼 예정이다.

EPOCHS = 10
history = model.fit(train_batches,
                    epochs=EPOCHS,
                    validation_data=validation_batches)

accuracy는 학습하고 있는 데이터에 대한 정확도를 나타내고 val_accuracy는 학습하지 않은 검증 데이터에 대한 정확도이다. 당연히 훈련 데이터에 대한 정확도가 높게 나온다. 위에서 accuracy는 약 97.56%가 나왔고 val_accuracy는 78.46이 나왔다. 학습 단계에 따른 정확도를 살펴보면 다음과 같다.

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=(12, 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()
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()
plt.title('Training and Validation Loss')
plt.show()

training accuracy는 현재 학습하는 데이터셋에 대한 정확도이기 때문에 모델의 구조나 데이터셋 등에 문제가 없다면 일반적으로 학습하면 할수록 꾸준히 계속 오른다. 반면 validation accuracy는 학습하지 않은 데이터셋에 대한 정확도이기 때문에 일정수준까지 오른 후에는 계속 오를지 장담할 수 없다. 심지어 loss 그래프에서 training loss는 계속 안정적으로 줄어들지만, validation loss값은 특정 순간 이후로 다시 커지는 모습을 보인다. 모델의 성능이 제대로 올라가려면 학습하지 않은 데이터에 대해서도 성능이 좋아야 하는데, 훈련 데이터만으로 계속 학습하다 보니 그 데이터에만 과도하게 적합(overfitting) 되어서 일반화 능력이 떨어지게 되는 것이다.

✏️ test data로 예측하기

마지막으로 이 모델을 이용해 test data를 적용시켜 모델의 예측 결과를 확인해보려 한다.

for image_batch, label_batch in test_batches.take(1):
    images = image_batch
    labels = label_batch
    predictions = model.predict(image_batch)
    pass

predictions

model.predict를 활용해 모델의 예측 결과를 확인해보면 소수점들로 이루어져 있는 것을 확인해볼 수 있는데, [1.0, 0.0]에 가까울수록 label이 0인 고양이로, [0.0, 1.0]에 가까울수록 label이 1인 강아지로 예측했다고 볼 수 있다. 보기 쉽게 라벨과 이미지로 변환해보면 다음과 같다.

import numpy as np

predictions = np.argmax(predictions, axis=1)
predictions

plt.figure(figsize=(20, 12))

for idx, (image, label, prediction) in enumerate(zip(images, labels, predictions)):
    plt.subplot(4, 8, idx+1)
    image = (image + 1) / 2
    plt.imshow(image)
    correct = label == prediction
    title = f'real: {label} / pred :{prediction}\n {correct}!'
    if not correct:
        plt.title(title, fontdict={'color': 'red'})
    else:
        plt.title(title, fontdict={'color': 'blue'})
    plt.axis('off')

count = 0   # 정답을 맞춘 개수
for image, label, prediction in zip(images, labels, predictions):
    correct = label == prediction
    if correct:
        count = count + 1

print(count / 32 * 100)


32개의 이미지에 대한 예측의 정확도는 75%로 나왔다. 조금은 아쉬운 정확도였다. 다음에는 정확도를 높일수 있는 모델을 가져와 활용해보려 한다.

profile
공부 정리정돈 📚 💻

0개의 댓글