안 쓰던 블로그
소규모 데이터셋에서 CNN 훈련하기(Kaggle - Dogs vs. Cats) 본문
kaggle - Dogs vs. Cats
1. 소규모 데이터셋에서 CNN 훈련하기(Dogs vs. Cats 데이터셋) https://foxtrotin.tistory.com/473
2. 사전 훈련된 CNN 사용하기 (ImageNet 데이터셋, VGG16 모델) https://foxtrotin.tistory.com/486
3. 미세 조정 https://foxtrotin.tistory.com/507
케라스 창사자에게 배우는 딥러닝 5장을 개인적으로 공부한 내용을 덧붙여 정리한 글입니다.
컴퓨터 비전 작업 중 매우 적은 데이터를 사용해 이미지 분류 모델을 훈련하는 일은 흔하게 만날 수 있을 것이다. '적은' 샘플이란 보통 수백 개~수만 개 사이를 의미한다. 이번 글에서는 4,000개의 강아지와 고양이 사진(2,000개는 강아지, 2,000개는 고양이)으로 구성된 데이터셋에서 강아지와 고양이 이미지를 분류해 보겠다. 훈련을 위해 2,000개의 사진을 사용하고 검증과 테스트에 각각 1,000개의 사진을 사용하겠다
다음과 같은 과정을 거치겠다.
1. 2,000개의 훈련 샘플에서 작은 CNN을 어떤 규제 방법도 사용하지 않고 훈련하여 기준이 되는 기본 성능을 만든다. 이 방법의 분류 정확도를 측정한다. 이 방법의 주요 이슈는 과대 적합이 될 것이다.
2. 컴퓨터 비전에서 과대적합을 줄이기 위한 강력한 방법인 데이터 증식을 적용하여 네트워크의 성능을 향상시켜 본다.
데이터셋 준비
이 데이터셋은 25,000개의 강아지와 고양이 이미지(클래스마다 12,500개)를 담고 있다. 이 데이터셋으로 세 개의 서브셋이 들어 있는 새로운 데이터셋을 만들 것이다. 캐글에는 별도의 테스트 케이스가 있으므로 제출을 할 거면 만들지 않아도 되지만, 공부 목적이니까 훈련, 검증, 테스트 세트를 만들어 완전한 실습을 하겠다. (또한 책에서도 이렇게 진행한다)
클래스마다 1,000개의 샘플로 이루어진 훈련 세트, 클래스마다 500개의 샘플로 이루어진 검증 세트, 클래스마다 500개의 샘플로 이루어진 테스트 세트로 나눈다.
사용한 케라스 버전은 2.4.3이다.
-코랩 실습 시 에러 해결-
로컬이 아닌 구글 코랩에서 실습한다면 구글 드라이브 기준으로 폴더 위치를 변경해 주어야 한다. 그냥 os만 사용하면 분명히 같은 폴더 내에 있음에도 폴더를 찾을 수 없다는 에러가 뜬다.
from google.colab import drive
drive.mount('/content/gdrive')
이 코드를 추가해서 구글 드라이브와 코랩을 연결한다. 실행하면 나오는 링크를 눌러 코드를 받아 입력한다. 매번 초기화되므로 접속할 때마다 마운트 작업을 다시 해 주어야 한다.
# 원본 데이터셋을 압축 해제한 디렉터리 경로
original_dataset_dir = '/content/gdrive/My Drive/Kaggle/Keras/datasets/cats_and_dogs/train'
# 소규모 데이터셋을 저장할 디렉터리
base_dir = '/content/gdrive/My Drive/Kaggle/Keras/datasets/cats_and_dogs_small'
if os.path.exists(base_dir): # 반복적인 실행을 위해 디렉토리를 삭제합니다.
shutil.rmtree(base_dir) # 이 코드는 책에 포함되어 있지 않습니다.
os.mkdir(base_dir)
또한 구글 드라이브의 디폴트 경로는 '/content/gdrive/My Drive/ ' 이다. 내 드라이브라고 뜨는 그곳이 My Drive인 것이다. 나는 내 드라이브 밑에 Kaggle 밑에 Keras 밑에 datasets 폴더가 있으므로 위에처럼 절대경로로 바꾸어 준다.
또한 kaggle의 dogs vs. cats 데이터셋은 용량이 크지는 않지만 이미지가 많아서 구글 드라이브 내에서 압축해제를 하거나, 로컬에서 압축해제 한 폴더 전체를 한 번에 업로드하면 오류가 나며 업로드되지 않는다. 또는 비정상적으로 오래 걸린다. 로컬에서 압축 해제한 뒤, 구글 드라이브에 직접 폴더를 만들고 소분해서 업로드하면 훨씬 빠르다. 약간의 노가다 작업이 필요하다. 이렇게 train 폴더가 준비되었다면 아래 코드로 cats_and_dogs_small 폴더를 만들어서 이미지를 분류한다.
# 원본 데이터셋을 압축 해제한 디렉터리 경로
original_dataset_dir = '/content/gdrive/My Drive/Kaggle/Keras/datasets/cats_and_dogs/train'
# 소규모 데이터셋을 저장할 디렉터리
base_dir = '/content/gdrive/My Drive/Kaggle/Keras/datasets/cats_and_dogs_small'
if os.path.exists(base_dir): # 반복적인 실행을 위해 디렉토리를 삭제합니다.
shutil.rmtree(base_dir) # 이 코드는 책에 포함되어 있지 않습니다.
os.mkdir(base_dir)
# 훈련, 검증, 테스트 분할을 위한 디렉터리
train_dir = os.path.join(base_dir, 'train')
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir, 'validation')
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir, 'test')
os.mkdir(test_dir)
# 훈련용 고양이 사진 디렉터리
train_cats_dir = os.path.join(train_dir, 'cats')
os.mkdir(train_cats_dir)
# 훈련용 강아지 사진 디렉터리
train_dogs_dir = os.path.join(train_dir, 'dogs')
os.mkdir(train_dogs_dir)
# 검증용 고양이 사진 디렉터리
validation_cats_dir = os.path.join(validation_dir, 'cats')
os.mkdir(validation_cats_dir)
# 검증용 강아지 사진 디렉터리
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
os.mkdir(validation_dogs_dir)
# 테스트용 고양이 사진 디렉터리
test_cats_dir = os.path.join(test_dir, 'cats')
os.mkdir(test_cats_dir)
# 테스트용 강아지 사진 디렉터리
test_dogs_dir = os.path.join(test_dir, 'dogs')
os.mkdir(test_dogs_dir)
# 처음 1,000개의 고양이 이미지를 train_cats_dir에 복사합니다
fnames = ['cat.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_cats_dir, fname)
shutil.copyfile(src, dst)
# 다음 500개 고양이 이미지를 validation_cats_dir에 복사합니다
fnames = ['cat.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(validation_cats_dir, fname)
shutil.copyfile(src, dst)
# 다음 500개 고양이 이미지를 test_cats_dir에 복사합니다
fnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_cats_dir, fname)
shutil.copyfile(src, dst)
# 처음 1,000개의 강아지 이미지를 train_dogs_dir에 복사합니다
fnames = ['dog.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_dogs_dir, fname)
shutil.copyfile(src, dst)
# 다음 500개 강아지 이미지를 validation_dogs_dir에 복사합니다
fnames = ['dog.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(validation_dogs_dir, fname)
shutil.copyfile(src, dst)
# 다음 500개 강아지 이미지를 test_dogs_dir에 복사합니다
fnames = ['dog.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_dogs_dir, fname)
shutil.copyfile(src, dst)
이 코드를 실행하면 2000개의 훈련 이미지, 1000개의 검증 이미지, 1000개의 테스트 이미지가 준비된다. (각 이미지 그룹마다 강아지와 고양이 사진 반반씩)
base_dir = '/content/gdrive/My Drive/Kaggle/Keras/datasets/cats_and_dogs_small'
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
test_dir = os.path.join(base_dir, 'test')
train_cats_dir = os.path.join(train_dir, 'cats')
train_dogs_dir = os.path.join(train_dir, 'dogs')
validation_cats_dir = os.path.join(validation_dir, 'cats')
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
test_cats_dir = os.path.join(test_dir, 'cats')
test_dogs_dir = os.path.join(test_dir, 'dogs')
print('훈련용 고양이 이미지 전체 개수:', len(os.listdir(train_cats_dir)))
print('훈련용 강아지 이미지 전체 개수:', len(os.listdir(train_dogs_dir)))
print('검증용 고양이 이미지 전체 개수: ', len(os.listdir(validation_cats_dir)))
print('검증용 강아지 이미지 전체 개수: ', len(os.listdir(validation_dogs_dir)))
print('테스트용 고양이 이미지 전체 개수: ', len(os.listdir(test_cats_dir)))
print('테스트용 강아지 이미지 전체 개수: ', len(os.listdir(test_dogs_dir)))
분할된 각 데이터는 클래마다 동일한 개수의 샘플을 포함한다. 이는 균형잡힌 이진 분류 문제이므로 정확도를 사용해 성공을 측정한다.
네트워크 구성하기
Conv2D(relu 활성화 함수 사용)와 MaxPooling2D 층을 번갈아 쌓은 컨브넷을 만들 것이다. 이전 글에서 했던 MNIST 숫자 분류보다는 이미지가 크고 복잡한 문제이기 때문에 네트워크를 좀 더 크게 만든다.
Conv2D+MaxPooling2D 단계를 하나 더 추가한다. (MaxPooling2D -> 최대 풀링 연산 층으로, 특성 맵을 강제적으로 다운샘플링 하여, 처리할 특성 맵의 가중치 개수를 줄인다. 과대적합을 방지한다)
이렇게 하면 네트워크의 용량을 늘리고 Flatten 층의 크기가 너무 커지지 않도록 특성 맵의 크기를 줄일 수 있다.
여기에서는 150x150 크기(임의로 결정한 크기)의 입력으로 시작해서 Flatten 층 이전에 7x7 크기의 특성 맵으로 줄인다.
(특성 맵의 깊이는 네트워크에서 점진적으로 증가하지만(32에서 128까지), 특성 맵의 크기는 감소한다(150x150에서 7x7까지). 이는 거의 모든 컨브넷에서 볼 수 있는 전형적인 패턴이다)
이진 분류 문제이므로 네트워크는 하나의 유닛(크기가 1인 Dense 층)과 sigmoid 활성화 함수로 끝난다. 이 유닛은 한 클래스에 대한 확률을 인코딩할 것이다.
from keras import layers
from keras import models
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu',
input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
층들을 거치면서 특성 맵의 차원이 다음과 같이 변했다. MaxPooling2D을 거치면서 특성 맵의 크기가 작아지고, 네트워크의 깊이는 증가한다.
컴파일 단계에서는 RMSprop 옵티마이저를 선택하겠다. 네트워크의 마지막이 하나의 시그모이드 유닛이기 때문에 이진 크로스엔트로피(binary crossentropy)를 손실로 사용한다. (참고: foxtrotin.tistory.com/469)
from keras import optimizers
model.compile(loss='binary_crossentropy',
optimizer=optimizers.RMSprop(lr=1e-4),
metrics=['acc'])
데이터 전처리
데이터는 네트워크에 주입되기 전에 부동 소수 타입의 텐서로 적절하게 전처리되어 있어야 한다. 지금은 데이터가 JPEG 파일로 되어 있으므로 네트워크에 주입하려면 대략 다음 과정을 거친다.
1. 사진 파일을 읽는다.
2. JPEG 콘텐츠를 RGB 픽셀 값으로 디코딩한다.
3. 부동 소수 타입의 텐서로 변환한다.
4. 픽셀 값(0에서 255 사이)의 스케일을 [0, 1] 사이로 조정한다. (신경망은 작은 입력 값을 선호하기 때문)
케라스는 이런 단계를 자동으로 처리하는 유틸리티를 가지고 있다. 케라스는 keras.preprocessing.image에 이미지 처리를 위한 헬퍼 도구들을 가지고 있다. 특히 ImageDataGenerator 클래스는 디스크에 있는 이미지 파일을 전처리된 배치 텐서로 자동으로 바꾸어주는 파이썬 제너레이터를 만들어 준다. 이 클래스를 사용하겠다.
from keras.preprocessing.image import ImageDataGenerator
# 모든 이미지를 1/255로 스케일을 조정
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
# 타깃 디렉터리
train_dir,
# 모든 이미지를 150 × 150 크기로
target_size=(150, 150),
batch_size=20,
# binary_crossentropy 손실을 사용하기 때문에 이진 레이블이 필요하다
class_mode='binary')
validation_generator = test_datagen.flow_from_directory(
validation_dir,
target_size=(150, 150),
batch_size=20,
class_mode='binary')
중간에 class_mod 매개변수는, 다중 분류일 때는 categorical(원-핫 인코딩된 2차원 배열 반환) 또는 sparse(정수 레이블을 담은 1차원 배열 반환), 이진 분류일 때는 binary(0또는 1로 채워진 1차원 배열 반환)를 사용한다. 아니면 입력을 타깃으로 하는 경우에는 input값을 주면 된다. 기본값은 categorical이다.
이 제너레이터들은 150x150 RGB 이미지의 배치((20, 150, 150, 3) 크기)와 이진 레이블의 배치((20,) 크기)를 출력한다. 각 배치에는 20개의 샘플(배치 크기)이 있다. 제너레이터는 이 배치를 무한으로 만들어 낸다. 타깃 폴더에 있는 이미지를 끝없이 반복합니다. 따라서 반복 루프안의 어디에선가 break 문을 사용해야 한다. 다음과 같이 break를 걸면 된다.
for data_batch, labels_batch in train_generator:
print('배치 데이터 크기:', data_batch.shape)
print('배치 레이블 크기:', labels_batch.shape)
break
제너레이터를 사용한 데이터에 모델을 훈련시켜 보겠다. 훈련할 때 사용할 fit_generator 메서드는 fit 메서드와 동일하지만 데이터 제너레이터를 사용할 수 있다는 차이가 있다. 이 메서드는 첫 번째 매개변수로 입력과 타깃의 배치를 끝없이 반환하는 파이썬 제너레이터를 받는다.
데이터가 끝없이 생성되기 때문에 케라스 모델에 하나의 에포크를 정의하기 위해 제너레이터로부터 얼마나 많은 샘플을 뽑을 것인지 알려 주어야 한다. steps_per_epoch 매개변수에서 이를 설정할 수 있다. 제너레이터로부터 steps_per_epoch 개의 배치만큼 뽑은 다음, 즉 steps_per_epoch 횟수만큼 경사 하강법 단계를 실행한 다음에 훈련 프로세스는 다음 에포크로 넘어간다. 여기서는 20개의 샘플이 하나의 배치이므로 2,000개의 샘플을 모두 처리할 때까지 100개의 배치를 뽑게 될 것이다.
fit_generator를 사용할 때 fit 메서드와 마찬가지로 validation_data 매개변수를 전달할 수 있다. 이 매개변수에는 데이터 제너레이터도 가능하지만 넘파이 배열의 튜플도 가능하다. validation_data로 제너레이터를 전달하면 검증 데이터의 배치를 끝없이 반환하므로, 검증 데이터 제너레이터에서 얼마나 많은 배치를 추출하여 평가할지 validation_steps 매개변수에 지정해야 한다. 위에서 만든 validation_generator의 배치를 20개로 지정했으니까 전체 검증 데이터(1,000개)를 사용하려면 validation_steps를 50으로 설정하면 된다.
history = model.fit_generator(
train_generator,
steps_per_epoch=100,
epochs=30,
validation_data=validation_generator,
validation_steps=50)
전체 검증을 하므로 시간이 조금 걸린다. 여기까지 한 뒤에 모델을 저장한다. (내 디렉토리 기준 코드이다)
from keras.models import load_model
model.save('/content/gdrive/My Drive/Kaggle/Keras/cats_and_dogs_small_1.h5')
저장한 모델은 이렇게 불러올 수 있다.
from keras.models import load_model
model = load_model('/content/gdrive/My Drive/Kaggle/Keras/cats_and_dogs_small_1.h5')
model.summary()
그리고 훈련 데이터와 검증 데이터에 대한 모델의 손실과 정확도를 그래프로 나타내면 다음과 같다.
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(acc))
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
위의 그래프는 훈련 정확도와 검증 정확도, 아래 그래프는 훈련 손실과 검증 손실이다.
훈련 정확도가 시간이 지남에 따라 선형적으로 증가해서 거의 100%에 도달한 반면에 검증 정확도는 70~72%에서 멈추었다. 검증 손실은 다섯 번의 에프크만에 최솟값에 다다른 이후에 더 이상 진전되지 않았다. 반면 훈련 손실은 거의 0에 도달할 때까지 선형적으로 계속 감소했다. 과대적합의 특성이다.
비교적 훈련 샘플의 수(2,000개)가 적기 때문에 과대적합이 가장 중요한 문제이다. 과대적합을 감소시킬 수 있는 드롭아웃이나 가중치 감소(L2 규제)와 같은 여러 가지 기법들이 있다. 여기에서는 컴퓨터 비전에 특화되어 있어서 딥러닝으로 이미지를 다룰 때 매우 일반적으로 사용되는 새로운 방법인 데이터 증식을 시도하겠다.
데이터 증식 사용하기
과대적합은 학습할 샘플이 너무 적어 새로운 데이터에 일반화할 수 있는 모델을 훈련시킬 수 없기 때문에 발생한다. 무한히 많은 데이터가 주어지면 데이터 분포의 모든 가능한 측면을 모델이 학습할 수 있을 것이다. 데이터 증식은 기존의 훈련 샘플로부터 더 많은 훈련 데이터를 생성하는 방법이다. 이 방법은 그럴듯한 이미지를 생성하도록 여러 가지 랜덤한 변환을 적용하여 샘플을 늘린다. 훈련 시에 모델이 정확히 같은 데이터를 두 번 만나지 않도록 하는 것이 목표이다. 모델이 데이터의 여러 측면을 학습하면 일반화에 도움이 될 것이다.
케라스에서는 ImageDataGenerator가 읽은 이미지에 여러 종류의 랜덤 변환을 적용하도록 설정할 수 있다. 예제를 먼저 만들어 본다.
datagen = ImageDataGenerator(
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest')
- rotation_range는 랜덤하게 사진을 회전시킬 각도 범위이다. -rotation_range ~ +rotation_range 범위를 가진다.
- width_shift_range와 height_shift_range는 사진을 수평과 수직으로 랜덤하게 평행 이동시킬 범위이다(전체 넓이와 높이에 대한 비율). 정수가 입력되면 (-width_shift_range ~ + width_shift_range)범위, 실수가 입력되면 [-width_shift_range ~ width_shift_range)범위이다.
- shear_range는 랜덤하게 전단 변환을 적용할 각도 범위이다. rotation_range로 회전할 때 y축 방향으로 각도를 증가시킨다.
- zoom_range는 랜덤하게 사진을 확대할 범위이다. 1-zoom_range ~ 1+zoom_range범위를 가진다.
- horizontal_flip은 랜덤하게 이미지를 수평으로 뒤집는다. 수평 대칭을 가정할 수 있을 때 사용한다(예를 들어, 풍경/인물 사진). 뒤집힌 글자 같은 경우는 학습에 도움이 되지 않아 의미가 없다.
- fill_mode는 회전이나 가로/세로 이동으로 인해 새롭게 생성해야 할 픽셀을 채울 전략이다. 증식된 이미지 샘플을 한 번 출력해 본다.
#이미지 전처리 유틸리티 모듈
from keras.preprocessing import image
fnames = sorted([os.path.join(train_cats_dir, fname) for fname in os.listdir(train_cats_dir)])
#증식할 이미지 선택
img_path = fnames[3]
#이미지를 읽고 크기 변경
img = image.load_img(img_path, target_size=(150, 150))
#(150, 150, 3) 크기의 넘파이 배열로 변환
x = image.img_to_array(img)
#(1, 150, 150, 3) 크기로 변환
x = x.reshape((1,) + x.shape)
#flow() 메서드는 랜덤하게 변환된 이미지의 배치를 생성한다
#무한 반복되기 때문에 어느 지점에서 중지해야 한다
i = 0
for batch in datagen.flow(x, batch_size=1):
plt.figure(i)
imgplot = plt.imshow(image.array_to_img(batch[0]))
i += 1
if i % 4 == 0:
break
plt.show()
데이터 증식을 사용하여 새로운 네트워크를 훈련시킬 때 네트워크에 같은 입력 데이터가 두 번 주입되지 않는다. 하지만 적은 수의 원본 이미지에서 만들어졌기 때문에 여전히 입력 데이터들 사이에 상호 연관성이 크다. 즉, 새로운 정보를 만들어낼 수 없고 단지 기존 정보의 재조합만 가능하다. 때문에 완전히 과대적합을 제거하기에 충분하지 않을 수 있다. 과대적합을 더 억제하기 위해 완전 연결 분류기(합성곱에서 추출한 특성 맵을 사용하여 클래스를 분류한다는 의미로 합성곱 층 위에 놓인 완전 연결 층을 말한다) 직전에 Dropout 층(최대 풀링 연산)을 추가할 수 있다.
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu',
input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy',
optimizer=optimizers.RMSprop(lr=1e-4),
metrics=['acc'])
데이터 증식과 드롭아웃까지 포함하여 네트워크를 훈련시켜 본다.
train_datagen = ImageDataGenerator(
rescale=1./255,
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,)
# 검증 데이터는 증식되어서는 안 됩니다!
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
# 타깃 디렉터리
train_dir,
# 모든 이미지를 150 × 150 크기로 바꿉니다
target_size=(150, 150),
batch_size=32,
# binary_crossentropy 손실을 사용하기 때문에 이진 레이블을 만들어야 합니다
class_mode='binary')
validation_generator = test_datagen.flow_from_directory(
validation_dir,
target_size=(150, 150),
batch_size=32,
class_mode='binary')
history = model.fit_generator(
train_generator,
steps_per_epoch=100,
epochs=100,
validation_data=validation_generator,
validation_steps=50)
훈련이 끝나면 재사용할 때를 대비하여 모델을 저장한다.
model.save('cats_and_dogs_small_2.h5')
결과 그래프를 다시 그려본다.
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(acc))
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
데이터 증식과 드롭아웃 덕택에 더이상 과대적합되지 않는다. 훈련 곡선이 검증 곡선에 가깝게 따라가고 있다. 검증 데이터에서 82% 정확도를 달성하였다. 규제하지 않은 모델과 비교했을 때 15% 정도 향상되었다!
여기서 다른 규제 기법을 더 사용하고 네트워크의 파라미터를 튜닝하면(합성곱 층의 필터 수나 네트워크의 층의 수 등) 86%나 87% 정도까지 더 높은 정확도를 얻을 수도 있지만, 데이터가 적기 때문에 컨브넷을 처음부터 훈련해서 더 높은 정확도를 달성하기는 어렵다. 이런 상황에서 더 정확도를 높이기 위해서는 사전 훈련된 모델을 사용할 수 있다.
'머신러닝 > 머신러닝' 카테고리의 다른 글
다중 슬롯머신 문제 (Multi-Amred Bandits, MAB) with UCB1(Upper Confidence Bound1) (0) | 2021.06.24 |
---|---|
다중 슬롯머신 문제 (Multi-Amred Bandits, MAB) with Epsilon-Greedy (0) | 2021.06.24 |
CNN(convolutional neural network - 합성곱 신경망) (2) | 2021.04.08 |
머신 러닝의 기본 개념들 (1) | 2021.04.05 |
회귀 문제: 케라스로 주택 가격 예측하기 (0) | 2021.04.05 |