페이지

2022년 8월 3일 수요일

5.2.5 데이터 증식 사용하기

 과대적합은 학습할 샘플이 너무 적어 새로운 데이터에 일반화할 수 있는 모델을 훈련시킬 수 없기 때문에 발생합니다. 무한히 많은 데이터가 주어지면 데이터 분포의 모든 가능한 측면을 모델이 학습할 수 있을 것입니다. 데이터 증식은 기존 훈련 샘플로부터 더 많은 훈련 데이터를 생성하는 방법입니다. 이 방법은 그럴듯한 이미지를 생성하도록 여러 가지 랜덤한 변환을 적용하여 샘플을 늘립니다. 훈련할 때 모델이 정확히 같은 데이터를 두 번 만나지 않도록 하는 것이 목표입니다. 모델이 데이터의 여러 측면을 학습하면 일반화에 도움이 될 것입니다.

케라스에서는 ImageDataGenerator가 읽은 이미지에 여러 종류의 랜덤 변환을 적용하도록 설정할 수 있습니다. 예를 먼저 만들어 보죠.


datagen = ImageDataGenerator(
    rotation_range=20,
    width_shift_range=0.1,
    height_shift_range=0.1,
    shear_range=0.1,
    zoom_range=0.1,
    horizontal_flip=True,
    fill_mode='nearest'
)

추가적인 매개변수가 몇 개 더 있습니다(케라스 문서를 참고하세요). 이 코드를 간단히 살펴보죠.
1) rotation_range는 랜덤하게 사진을 회전시킬 각도 범위입니다(0-180사이).
2) width_shift_range와 height_shift_range는 사진을 수평과 수직으로 랜덤하게 평행 이동
시킬 범위입니다(전체 너비와 높이에 대한 비율).
3) zoom_range는 랜덤하게 사진을 확대할 범위입니다.
4) horizontal_flip은 랜덤하게 이미지를 수평으로 뒤집습니다. 수평 대칭을 가정할 수 있을때
사용합니다(옐르 들어 풍경/인물 사진).
5) 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=(150150))

# (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, (33), activation='relu',
                        input_shape=(1501503)))
model.add(layers.MaxPooling2D((22)))
model.add(layers.Conv2D(64, (33), activation='relu'))
model.add(layers.MaxPooling2D((22)))
model.add(layers.Conv2D(128, (33), activation='relu'))
model.add(layers.MaxPooling2D((22)))
model.add(layers.Conv2D(128, (33), activation='relu'))
model.add(layers.MaxPooling2D((22)))
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(learning_rate=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=(150150),
        batch_size=32,
        # binary_crossentropy 손실을 사용하기 때문에 이진 레이블을 만들어야 합니다
        class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150150),
        batch_size=32,
        class_mode='binary')

history = model.fit(
      train_generator,
      steps_per_epoch=100,
      epochs=100,
      validation_data=validation_generator,
      validation_steps=50)

5.4절에서 이 모델을 사용하기 위해 저장합니다.

model.save('cats_and_dogs_small_2.h5')

결과 그래프를 다시 그려 봅시다. 그림 5-12와 그림 5-13을 참고하세요. 데이터 증식과 드롭아웃
덕택에 더 이상 과대적합되지 않습니다. 훈련 곡선이 검증 곡선에 가깝게 따라가고 있습니다.
검증 데이터에서 82% 정확도를 달성했습니다. 규제하지 않은 모델과 비교했을 때 15%정도
향상되었습니다

다른 규제 기법을 더 사용하고 네트워크의 파라미터를 튜닝하면(합성곱 층의 필터 수나 네트워크
층의 수 등) 86%나 87%정도까지 더 높은 정확도를 얻을 수 있습니다. 하지만 데이터가 적기
때문에 컨브넷을 처음부터 훈련해서 더 높은 정확도를 달성하기는 어렵습니다. 이런 상황에서
정확도를 높이기 위한 다음 단계는 사전 훈련된 모델을 사용하는 것입니다. 다음 두 절에서
이에 대해 집중적으로 살펴 보겠습니다.

댓글 없음: