본문 바로가기
Book Review/[케라스 창시자에게 배우는 딥러닝] 리뷰

CNN 훈련하기: cat / dog

by 3n952 2023. 2. 21.

이번 포스팅에서는 지난 시간 공부한 CNN을 활용하여 강아지와 고양이를 구분해보겠습니다.


 

강아지 vs 고양이 데이터 셋은 캐글에서 가져왔습니다.

해당 데이터는 케라스에 포함되어 있지 않으므로 직접 다운받아 데이터를 다운받아야 합니다.

 

-데이터 내려받기

캐글에서 파일을 직접 다운받아 일일히 내려받는 데이터를 코랩에서 적용하는 방법은 데이터가 커질수록 비효율적입니다. 

코랩에서는 캐글 api를 활용하여 캐글에 있는 데이터셋을 간편하게 다운받을 수 있도록 해줍니다.

굉장히 편리하죠! 따라서 캐글의 api를 활용하려면 키를 만들어 로컬 컴퓨터로 다운받아야합니다.

캐글 웹사이트에 접속해서 로그인한 후 account페이지로 이동하면 api섹션에서 키를 다운받을 수 있습니다.

create new API tokken 클릭!

로그인 한 후 오른쪽 상단 메뉴 바: account
account 페이지의 api 섹션

이미 api키가 있으므로 저는 expire api tokken 버튼도 있네요.

 

그렇다면 이제 api사용을 위한 준비는 끝났습니다.

코드는 다음과 같습니다.

#캐글 api파일 선택: 현재 코랩 런타임에 업로드
 
from google.colab import files
files.upload()

#현재 디렉토리에 .kaggle폴더 생성
!mkdir ~/.kaggle
#키 파일을 이 폴더에 복사
!cp kaggle.json ~/.kaggle/
#사용자가 이 파일을 읽을 수 있도록 함
!chmod 600 ~/.kaggle/kaggle.json

#캐글에서 데이터 불러오기
!kaggle competitions download -c dogs-vs-cats

마지막 코드를 실행시키면 '403 forbidden'에러가 발생할 수 있습니다.

이를 해결하기 위해서는 dogs-vs-cats/rules 페이지로 이동해서 'i understand and accept' 버튼을 누르면 됩니다.

accepted the rules

이미 동의를 했기 때문에 저는 저렇게 뜹니다. 처음하게 되면 저기에 버튼이 생성되어 있습니다.

 

마지막으로 저희가 받은 데이터셋은 압축 파일이므로 압축을 해제해야 합니다.

코드는 다음과 같습니다.

#-qq는 메세지 출력을 안한다는 명령어

!unzip -qq dogs-vs-cats
!unzip -qq train.zip

 

다운받은 데이터셋의 구조를 보면, cats_vs_dogs_small/train/ or validation/ or test/ 와 같이

세 개의 서브셋 디렉토리 구조를 가집니다.

그 안에 강아지와 고양이로 구분된 이미지가 들어있습니다.

따라서 이미지를 훈련, 검증, 테스트 디렉토리로 복사할 필요가 있습니다.

즉, 훈련, 검증, 테스트 이미지를 준비하는 것 입니다.

코드는 다음과 같습니다.

#shutil package사용하여 새로운 데이터셋 디렉토리 구조 만들기 train / validation / test (1000/500/1000)

import os, shutil, pathlib

#원본 데이터 셋이 압축되어 있는 디렉터리 경로
original_dir = pathlib.Path('train')
#서브셋 데이터를 저장할 디렉토리
new_base_dir = pathlib.Path('cats_vs_dogs_small')

def make_subset(subset_name, start_index, end_index):
  for category in ('cat', 'dog'):
    dir = new_base_dir / subset_name / category
    os.makedirs(dir)
    fnames = [f'{category}.{i}.jpg' for i in range(start_index, end_index)]
    for fname in fnames:
      shutil.copyfile(src=original_dir/fname, dst=dir/fname)

make_subset('train', start_index=0, end_index=1000)
make_subset('validation', start_index=1000, end_index=1500)
make_subset('test', start_index=1500, end_index=2500)

이렇게 되면 2000개의 훈련이미지, 1000개의 이미지, 2000개의 테스트 이미지가 만들어집니다.

데이터가 준비되었으니 이제 모델을 만들어 보겠습니다.

 

-모델 만들기

일반적으로 convnet구조는 conv2d와 maxpooling을 번갈아 쌓은 구조입니다.

이번 포스팅에서는 간단하게 컨브넷을 학습시키는 것이 목표이므로 간단하게 쌓은 모델 구조를 사용해보겠습니다.

이진 분류 문제이므로 모델은 sigmoid활성화 함수로 끝나야 할 것입니다. 이것에 주의해서 모델 구조를 만들어 주면 됩니다.

처음 input크기는 임의로 선택하여 기대되는 입력 크기로 설정해주겠습니다.

코드는 다음과 같습니다.

from tensorflow import keras
from tensorflow.keras import layers

inputs = keras.Input(shape=(180,180,3))
x = layers.Rescaling(1./255)(inputs)
x = layers.Conv2D(filters= 32, kernel_size = 3, activation = 'relu')(x)
x = layers.MaxPooling2D(pool_size=2)(x)
x = layers.Conv2D(filters = 64, kernel_size = 3, activation = 'relu')(x)
x = layers.MaxPooling2D(pool_size=2)(x)
x = layers.Conv2D(filters = 128, kernel_size = 3, activation = 'relu')(x)
x = layers.MaxPooling2D(pool_size=2)(x)
x = layers.Conv2D(filters = 256, kernel_size = 3, activation = 'relu')(x)
x = layers.MaxPooling2D(pool_size=2)(x)
x = layers.Conv2D(filters = 256, kernel_size = 3, activation = 'relu')(x)
x = layers.Flatten()(x)
outputs = layers.Dense(1, activation = 'sigmoid')(x)

model = keras.Model(inputs = inputs, outputs = outputs)

두번째 줄에서 이미지 입력의 [0,255]스케일에서 [0,1]스케일로 변환해주었습니다.

컴퓨터 비전에서 이미지를 처리할때 필요한 과정입니다.

 

이제 모델 만들기의 마지막으로 컴파일 단계가 남았습니다.

rmsprop옵티마이저를 선택하겠습니다.

코드는 다음과 같습니다.

model.compile(loss = 'binary_crossentropy, optimizer = 'rmsprop', metrics = ['acccuracy])

 

 

-데이터 전처리

이미지 데이터는 convnet 네트워크에 주입되기 전에 부동 소수점 텐서의 형태로 적절하게 전처리되어 있어야합니다.

데이터가 jpeg로 되어 있으므로 다음과 같은 전처리 과정이 필요할 것입니다.

 

1.사진 파일 읽기

2. jpeg파일을 rgb 픽셀값으로 디코딩

3. 부동 소수점 텐서로 변환

4. 동일한 크기의 이미지로 변환

5. 배치(batch) 형성

 

다행히도 이 복잡한 과정을 간단하게 구현할 수 있는 케라스 유틸리티가 있습니다.

바로 image_dataset_from_directory()함수 입니다.

()안에 디렉토리 넣어 호출하면 그 디렉토리의 서브 디렉토리를 찾습니다.

그 다음 서브 디렉토리에 있는 이미지 파일을 인덱싱합니다.

이런 파일을 읽고, 순서를 섞고, 텐서로 디코딩하고, 동일 크기로 변경하고, 배치로 묶어줍니다.

참 간편하죠?

 

여기서는 이전에 사용했던 new_base_dir의 디렉토리로 서브셋을 저장한 디렉토리를 불러오겠습니다.

이를 활용하는 코드는 다음과 같습니다.

#데이터 전처리
'''image_dataset_from_directory()함수를 사용하여 디스크에 있는 이미지 파일을 자동으로 전처리된 텐서의 배치로 변환하는 파이프라인 구성'''
#image_dataset_from_directory(directory) -> tf.data.Dataset객체를 만들어 반환

from tensorflow.keras.utils import image_dataset_from_directory

train_dataset = image_dataset_from_directory(
    new_base_dir/'train',
    image_size = (180,180),
    batch_size = 32)

val_dataset = image_dataset_from_directory(
    new_base_dir/'validation',
    image_size = (180,180),
    batch_size = 32)

test_dataset = image_dataset_from_directory(
    new_base_dir/'test',
    image_size = (180,180),
    batch_size = 32)

이렇게 되면 Dataset객체의 출력은 180 * 180 RGB이미지의 배치를 가집니다.

(  이미지의 배치: (32,180,180,3)크기 / 정수 레이블의 배치: (32,)크기  )

 

 

-모델 훈련하기

모델 구성도 끝나고 데이터 전처리과정도 끝났다면 이제 훈련 시키기만 하면 됩니다.

 

modelcheckpoint 콜백을 사용하여 에포크가 끝날 때 마다 모델을 저장합니다.

저장할 모델이름은 conv.keras이고 val_loss의 성능을 지표로 가장 낮은 값의 모델을 저장합니다.

 

코드는 다음과 같습니다.

callbacks = [keras.callbacks.ModelCheckpoint(
    filepath = 'conv.keras',
    save_best_only = True,
    monitor = 'val_loss'
)]

history = model.fit(
    train_dataset, epochs=30, validation_data = val_dataset, callbacks = callbacks
)

해당 컨브넷의 훈련과 검증 지표를 시각화 하면 과대적합의 특성을 보여주는 것을 확인 할 수 있습니다.

코드는 다음과 같습니다.

#훈련과정의 정확도와 손실 그래프 그리기
import matplotlib.pyplot as plt

accuracy = history.history['accuracy']
val_accuracy = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(accuracy)+1)
plt.plot(epochs, accuracy, 'bo', label = 'training accuracy')
plt.plot(epochs, val_accuracy, 'b', label = 'test accuracy')
plt.title('accuracy')
plt.legend()
plt.figure()

plt.plot(epochs, loss, 'bo', label = 'training loss')
plt.plot(epochs, val_loss, 'b', label = 'test loss')
plt.legend()
plt.show()

accuracy monitoring

 

loss monitoring

 

따라서 테스트 데이터 셋으로 테스트 정확도를 확인하려면

저장한 모델로 과대적합 되기 전의 상태를 가지고 평가해야합니다.

코드는 다음과 같습니다.

#테스트 정확도

test_model = keras.models.load_model('conv.keras')
test_loss, test_acc = test_model.evaluate(test_dataset)
print(f'test acc = {test_acc:.3f}')

각자 미묘하게 상이하지만 테스트 정확도는 67%정도를 상회할 것입니다.

신경망의 랜덤한 초기화 때문입니다.

비교적 훈련 샘플 개수가 적기 때문에 과대적합이 쉽게 일어납니다.

따라서 다양한 기법으로 과대적합을 줄이고 성능이 좋은 모델을 만드는 다양한 기법을 사용하는 것이 중요합니다.