페이지

2022년 7월 31일 일요일

5.2.3 네트워크 구성하기

 이전 예제에서 MNIST를 위해 간단한 컨브넷을 만들었습니다. 이제 컨브넷에 친숙해졌을 것입니다. 여기서 사용할 구조도 일반적으로 동일합니다. Conv2D(relu 활성화 함수 사용)와  MaxPooling2D층을 번갈아 쌓은 컨브넷을 만들겠습니다.

이전보다 이미지가 크고 복잡한 문제이기 때문에 네트워크를 좀 더 크게 만들겠습니다. Conv2D + MaxPooling2D 단계를 하나 더 추가합니다. 이렇게 하면 네트워크의 용량을 늘리고 Flatten층의 크기가 너무 커지지 않도록 특성 맵의 크기를 줄일 수 있습니다. 150 * 150 크기(임의로 선택한 것입니다)의 입력으로 시작해서  Flatten 층 이전에 7 * 7 크기의 특성 맵으로 줄어듭니다.

특성 맵의 깊이는 네트워크에서 점진적으로 증가하지만(32에서 128까지). 특성 맵의 크기는 감소합니다.(150 * 150 에서 7 * 7까지). 이는 거의 모든 컨브넷에서 볼 수 있는 전형적인 패턴입니다.

이진 분류 문제이크로 네트워크는 하나의 유닛(크기가 1인 Dense 층)과 sigmoid활성화 함수로 끝납니다. 이 유닛은 한 클래스에 대한 확률을 인코딩할 것입니다.


from keras import layers
from keras import models

model = models.Sequential()
model.add(layers.Conv2D(32, (33), activation='relu',input_shape=(1501503)))
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, (33), activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(128, (33), activation='relu'))
model.add(layers.MaxPooling2D((22)))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

층들을 거치면서 특성 맵의 차원이 어떻게 변하는지 살펴보겠습니다.
>>>model.summary()
Model: "sequential_1" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d_4 (Conv2D) (None, 148, 148, 32) 896 max_pooling2d_4 (MaxPooling (None, 74, 74, 32) 0 2D) conv2d_5 (Conv2D) (None, 72, 72, 64) 18496 max_pooling2d_5 (MaxPooling (None, 36, 36, 64) 0 2D) conv2d_6 (Conv2D) (None, 34, 34, 128) 73856 max_pooling2d_6 (MaxPooling (None, 17, 17, 128) 0 2D) conv2d_7 (Conv2D) (None, 15, 15, 128) 147584 max_pooling2d_7 (MaxPooling (None, 7, 7, 128) 0 2D) flatten_1 (Flatten) (None, 6272) 0 dense_1 (Dense) (None, 512) 3211776 dense_2 (Dense) (None, 1) 513 ================================================================= Total params: 3,453,121 Trainable params: 3,453,121 Non-trainable params: 0 _________________________________________________________________

컴파일 단계에서 이전과 같이 RMSprop 옵티마이저를 선택하겠습니다. 네트워의 마지막의 하나의
시그모이드 유닛이기 때문에 이진 크로스엔트로피(binary crossentropy)를 손실로 사용합니다
(표 4-1에서 다양한 경우에 사용할 수 있는 손실 함수 목록을 볼수 있습니다).

from tensorflow.keras import optimizers

model.compile(loss='binary_crossentropy'
              optimizer=optimizers.RMSprop(learning_rate=1e-4),
              metrics=['acc'])

5.2.2 데이터 내려받기

 여기서 사용할 강아지 vs. 고양이 데이터셋(Dogs vs. Cat dataset)은 케라스에 포함되어 있지 않습니다. 컨브넷이 주류가 되기 전인 2013년 후반에 캐글에서 컴퓨터 비전 경연 대회의 일환으로 이 데이터셋을 만들었습니다. 원본 데이터셋을 https://www.kaggle.com/c/dogs-vs-cats/data 에서 내려받을 수 있습니다(캐글 계정이 없다면 하나 만들어야 하지만 계정을 만드는 과정은 간단합니다).

이 사진들은 중간 정도의 해상도를 가진 컬러 JPEG파일입니다. 그림 5-8에서 샘플 몇 개를 확인 할 수 있습니다.

당연히 2013년 강아지 vs. 고양이 캐글 경연은 컨브넷을 사용한 참가자가 우승했습니다. 최고 성능은 95%의 정확도를 달성했습니다. 이 예제로 (다음 절에서) 참가자들이 상요했던 데이터의 10%보다 적은 양으로 모델을 훈련하고도 이와 아주 근접한 정확도를 달성해 보겠습니다.

이 데이터셋은 2만 5,000개의 강아지와 고양이 이미지(클래스마다 1만 2,500개)를 담고 있고(앞축해서) 543MB 크기입니다. 내려받아 앞축을 해제한 후 3개의 서브셋이 들어 있는 새로운 데이터셋을 만들 것입니다. 클래스마다 1,000개의 샘플로 이루어진 훈련 세트, 클래스마다 500개의 샘플로 이루어진 검증 세트, 클래스마다 500개의 샘플로 이루어진 테스트 세트입니다.

다음은 이를 처리하는 코드입니다.

    import os, shutil

    origianl_dataset_dir = './datasets/cats_and_dogs/train'.....원본 데이터셋을 압축 해제한 디렉터리 경로

    base_dir = './datasets/cats_ands_small' ........소규모 데이터셋을 저장할 디렉터리

    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_cat_dir)


    train_dogs_dir = os.path.join(validation_dir, 'dogs')

    os.mkdir(train_dogs_dir)


    validation_cats_dir = os.path.join(validation_dir, 'cats')

    os.mkdir(validation_cat_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)


    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)


    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)


    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_catas_dir, fname)

        shutilcopyfile(src, dst)


    fnames = ['dog.{}.jpg'.format(i) for i in range(1000)]

    for fname in fnames:

        src = os.path.join(original_dataet_dir, fname)

        dst = os.path.join(train_dogs_dir, fname)

        shutil.copyfile(src, dst)


    fnames = ['dog.{}.jpg'.format(i) for i in range(1500, 2000)]

    for fname in fnames:

        src = os.path.join(origianl_dataset_dir, fname)

        dst = os.path.join(test_dogs_dir, fname)

        shutil.copyfile(src, dst)

복사가 잘 되었는지 확인하기 위해 각 분할(훈련/검증/테스트)에 들어 있는 사진이 개수를 카운트 해보죠.

>>> print('훈련용 고양이 이미지 전체 개수:', len(os.listdir(train_cats_dir)))

훈련용 고양이 이미지 전체 개수: 1000

>>> print('훈련용 강아지 이미지 전체 개수:', len(os.listdir(train_dogs_dir)))

훈련용 강아지 이미지 전체 개수 : 1000

>>> print('검증용 공양이 이미지 전체 개수:', len(os.listdir(validation_cats_dir)))

검증용 공양이 이미지 전체 갯수: 500

>>> print('검증용 강아이 이미지 전체 개수:', len(os.listdir(validation_dogs_dir)))

검증용 강아지 이미지 전체 갯수:500

>>> print('테스트용 고양이 이미지 전체 개수:', len(os.listdir(test_cats_dir)))

테스트용 고양이 이미지 전체 개수 : 500

>>> print('테스트용 강아지 이미지 전체 개수:', len(os.listdir(test_dogs_dir)))

테스트용 강아지 이미지 전체 개수: 500


이제 2,000개의 훈련 이미지, 1,000 개의 검증이미지, 1,000개의 테스트 이미지가 준비되었씁니다. ㅈ분할된 각 데이터는 클래스마다 동일한 개수의 샘플을 포함합니다. 균형 잡힌 이진 분류 문제이므로 정확도를 사용하여 성공을 측정하겠습니다.



    

    

2022년 7월 30일 토요일

5.2.1 작은 데이터셋 문제에서 딥러닝의 타당성

 딥러닝은 데이터가 풍부할 때만 작동한단느 말을 이따금 듣습니다. 부분적으로 맞습니다. 딥러닝의 근본적인 특징은 훈련 데이터에서 특성 공학의 수작업 없이 흥미로운 특성을 찾을 수 있는 것입니다. 이는 훈련 샘플이 많아야만 가능합니다. 입력 샘플이 이미지처럼 매우 고차원적인 문제에서는 특히 그렇습니다. 하지만 많은 샘플이 의미하는 것은 상대적입니다. 우선 훈련하는 네트워크의 크기와 깊이에 상대적입니다. 복잡한 문제를 푸는 컨브넷을 수십 개의 샘플만 사용해서 훈련하는 것은 불가능합니다. 하지만 모델이 작고 규제가 잘 되어 있으며 간단한 작업이라면 수백 개의 샘플로도 충분할 수 있습니다. 컨브넷은 지역적이고 평행 이동으로 변하지 않는 특성을 학습하기 때문에 지각에 관한 문제에서 매우 효율적으로 데이터를 사용합니다. 매우 작은 이미지 데이터셋에서 어떤 종류의 특성 공학을 사용하지 않고 컨브넷을 처음부터 훈련해도 납득할 만한 결과를 만들 수 있습니다. 이 절에서 실제로 이런 결과를 보게 될 것입니다.

거기에 더하여 딥러닝 모델은 태생적으로 매우 다목적입니다. 말하자면 대규모 데이터셋에서 훈련시킨 이미지 분류 모델이나 스피치-투-텍스트(speech-to-text) 모델을 조금만 변경해서 완전히 다른 문제에 재사용할 수 있습니다. 특히 컴퓨터 비전에서는(보통 ImageNet 데이터셋에서 훈련된) 사전 훈련된 모델을 만드는 데 사용할 수 있습니다. 바로 다음 절에서 우리가 해 볼것입니다. 먼저 데이터를 구하는 것보터 시작해 보죠.


5.2 소규모 데이터셋에서 밑바닥부터 컨브넷 훈련하기

 매우 적은 데이터를 사용하여 이미지 분류 모델을 훈련하는 일은 흔한 경우입니다. 여러분이 전문적인 컴퓨터 비전 작업을 한다면 실제로 이런 상황을 마주치게 될 가능성이 높습니다. 보통'적은'샘플이란 수백 개에서 수만 개 사이를 의미합니다. 실용적인 예제로 4,000개의 강아지와 고양이사진(2,000개는 강아지, 2,000개는 고양이)으로 구성된 데이터셋에서 강아지와 고양이 이미지를 분류해 보겠습니다. 훈련을 위해 2,000개의 사진을 사용하고 검증과 테스트에 각각 1,000개의 사진을 사용합니다.

이 절에서 문제를 해결하기 위해 기본적인 전략 하나를 살펴볼것 입니다. 보유한 소규모 데이터셋을 사용하여 처음부터 새로운 모델을 훈련한느 것입니다. 2,000개의 훈련 샘플에서 작은 컨브넷을 어떤 규제 방법도 사용하지 않고 훈련하여 기분이 되는 기본 성능을 만들겠습니다. 이 밥ㅇ법은 71%의 분류 정확도를 달성할 것입니다. 이 방법의 주요 이슈는 과대적합이 될 것입니다. 그 다음 컴퓨터 비전에서 과대적합을 줄이기 위한 강력한 방법인 데이터 증식(data augmentation)을 소개하겠습니다. 데이터 증식을 통해 네트워크의 성능을 82% 정확도로 향상시킬 것입니다.

다음 절에서 작은 데이터셋에 딥러닝을 적용하기 위한 핵심적인 기술 두 가지를 살펴보겠습니다. 사전 훈련된 네트워크로 특성을 추출하는 것(90%의 정확도를 얻게 됩니다)과 사전 훈련된 네트워크를 세밀하게 튜닝하는 것입니다(최종 모델은 92% 정확도를 얻을 것입니다). 이런 세가지 전략(처음부터 작은 모델을 훈련하기, 사전 훈련된 모델을 사용하여 특성 추출하기, 사전 훈련된 모델을 세밀하게 튜닝하기)은 작은 데이터셋에서 이미지 분류 문제를 수행할 때 여러분의 도구 상자에 표함되어 있어야 합니다.

5.1.2 최대 풀링 연산

 앞선 컨브넷 예제에서 특성 맵의 크기가 MaxPooling2D층마다 절반으로 둘어들었습니다. 얘ㅖㄹ르 들어 첫 번째 MaxPooling2D 층 이전에 특성 뱁의 크기는 26 * 26이었ㄴ느데 최대 풀링 연산으로 13 * 13으로 줄어들었습니다. 스트라이드 합성곱과 매우 비슷하게 강제적으로 특성 맵을 다운샘프링하는 것이 최대 풀링의 역할입니다.

최대 풀링은 입력 특성 맵에서 원도우에 맞는 패치를 추출하고 각 채널별로 최댓값을 출력합니다.

합성곱과 개념적으로 비슷하지만 추출한 패치에 학습된 선형 변환(합성곱 채널)을 적용하는 대신 하드코딩된 최댓값 추출 연산을 사용합니다. 합성곱과 가장 큰 차이점은 최대 풀링은 보통 2 * 2원도우와 스트라이드 2 를 사용하여 특성 맵을 절반 크기로 다운샘플링한다는 것입니다. 이에 반해 합성곱은 전형적으로 3 * 3 원도우와 스트라이드 1을 사용합니다.

왜 이런 식으로 특성 맵을 다운샘플링할까요? 왜 최대 풀링 층을 빼고 큰 특성 맵을 계속 유지하지 않을 까요? 이런 방식을 한번 테스트해 보죠, 합성곱으로만 이루어진 모델은 다음과 같습니다.

model_no_max_pool = models.Sequential()

model_no_max_pool.add(layers.Conv2D(32, (3, 3), activation='relu',

                            input_shape=(28, 28, 1)))

model_no_max_pool.add(layers.Conv2D(64, (3, 3), activation='relu'))

model_no_max_pool.add(layers.Conv2D(64, (3, 3), activation='relu'))

모델 구조는 다음과 같습니다.

>>> model_no_max_pool.summary()

Layer(type)                ouput Shape            Param #

=======================================================

conv2d_4 (Conv2D)        (None, 26, 26, 32)        320

conv2d_5 (Conv2D)        (None, 24, 24, 64)        18496

conv2d_6 (Conv2D)        (None, 22, 22, 64)        36928

========================================================

Total params: 55,744

Trainable params: 55,744

Non-trainable params:0

이 설정에서 무엇이 문제일까요? 두가지가 있습니다.

1) 특성의 공간적 계층 구조를 학습하는 데 도움이 되지 않습니다. 세 번째 층이 3 * 3원도우는 초기 입력의 7 * 7원도우 영역에 대한 정보만 담고 있습니다. 컨브넷에 의해 학습된 고수준 패넡은 초기 입력에 관한 정보가 아주 적어 숫자 분류를 학습하기에 충분하지 ㅇㄶ을 것입니다(7 * 7픽셀 크기의 창으로 숫자를 보고 분류해 보세요!). 마지막 합성곱 층의 특성이 전체 입력에 대한 정보를 가지고 있어야 합니다.

2) 최종 특성 맵은 22 * 22 * 64 = 30,976개의 원소를 가집니다. 아주 많습니다. 이 컨브넷을 펼친 후 512 크기의Dense츠과 연결한다면 약 15.8배만 개의 가중치 파라미터가 생깁니다. 작은 모델치고는 너무 많은 가중치고, 심각한 과대적합이 발생할 것입니다.


간단히 말해서 단운샘플링을 사용한느 이유는 처리할 특성 맵의 가중치 개술르 줄이기 위해서입니다. 또 연속적인 합성곱 층이(원본 입력에서 커버되는 영역 측면에서)점점 커진 원도우를 통해 바라보도록 만들어 필터의 공간적인 계층 구조를 구성합니다.

최대 폴링이 다운샘플링을 할 수 있는 유일한 방법은 아닙니다. 이미 알고 있듯이 앞선 합성곱 층에서 스트라이드를 사용할 수 있습니다. 최댓값을 취하는 최대 폴링 대신에 입력 패치의 채널별 평균값을 계산하여 변환하는 평균 풀링(average pooling)을 사용할 수도 있습니다. 하지만 최대 풀링이 다른 방법들보다 더 잘 작도하는 편입니다. 그 이유는 특성 맵의 각 타일에서 어떤 패턴이나 개면이 존재 여부를 인코딩하는 경향이 있기 때문입니다(그래서 특성의 지도(맵)입니다). 따라서 특성의 평균값보다 여러 특성 중 최댓값을 사용하는 것이 더 유용합니다. 가장 납득할 만한 서브 샘플링(subsampling)전략은 먼저(스트라이드가 없는 삽성곱으로)조밀한 특성 맵을 만들고 그 다음 작은 배치에 대해서 최대로 활성화된 특성을 고르는 것입니다. 이런 방법이 입력에 대해(스트라이드 합성곱으로) 듬성듬성 원도우를 슬라이딩하거나 입력 패치를 평균해서 특성 정보를 놓치거나 희석시키는 것보다 낫습니다.

이제 커브넷의 특성 맵과 합성곱 그리고 최대 풀링에 대한 기본 개념을 이해했을 것입니다. MNIST숫자 이미지 분류처럼 간단한 문제를 풀기 위해 작은 컨브넷을 만드는 법을 배웠습니다. 그럼 좀 더 유용하고 현실적인 애플리케이션을 만들어 보죠.

5장부터 등장하는 예제는 CPU만 사용할 경우 컴퓨터 사양에 따라 실행 시간이 다소 오래 걸릴수 있습니다. 부로 C 를 참고하여 아마조 GPU 인스턴스를 사용하거나 구글의 코랩 (Colab, https://colab.research.google.com/)을 사용할 수 있습니다. 코랩은 구글이 만든 교육과 연구를 위한 주피터 노트북 환경으로 구글 클라우드의 컴퓨팅 자원을 무료로 사용할 수 있습니다.

5.1.1 합성곱 연산

 완전 연결 층과 합성곱 층 사이의 근본적인 차이는 다음과 같습니다. Dense층은 입력 특성 공간에 있는 전역 패턴(예를 들어 MNIST숫자 이미지에는 모든 픽셀에 걸친 패턴)을 학습하지만 합성곱 층은 지역 패턴을 학습합니다(그림 5-1 참고). 이미지일 경우 작은 2D 원도우(window)로 입력에서 태펀을 찾습니다. 앞의 예에서 이 원도우는 모두 3 * 3크기였습니다.

이 핵심 특징은 컨브넷에 두가지 흥미로운 성질을 제공합니다.

1) 학습된 패턴은 평행 이동 불변성(translation invariant)을 가집니다. 컨브넷이 이미지의 오른쪽 아래 모서리에서 어떤 패턴을 학습했다면 다른 곳(예를 들어 왼쪽 위 모서리)에서도 이 패턴을 인식할 수 있습니다. 완전 연결 네트워크는 새로운 위치에 나타난 것은 새로운 패턴으로 학습해야 합니다. 이런 성질은 컨브넷이 이미지를 효율적으로 처리하게 만들어 줍니다(근본저ㅏㄱ으로 우리가 보는 세상은 평행 이동으로 인해 다르게 인식되지 않습니다). 적은 수의 훈련 샘플을 사용해서 일반화 능력을 가진 표현을 학습할 수 있습니다.

2) 컨브넷은 패넡의 공간적 계층 구조를 학습할 수 있습니다. 첫 번째 합성곱층이 에지 같은 작은 지역 패턴을 학습합니다. 두 번째 합성 곱층은 첫 번째 층의 특성으로 수겅되 더 큰 패턴을 학습하는 식입니다. 이런 방식을 사용하여 컨브넷은 매우 복잡하고 추상적인 시가적 개념을 효과적으로 학습할 수 있습니다.(근본적으로 우리가 보는 세상은공간적 계ㅒ츨 구조를 가집니다).


합성곱 연산은 특성 맵(featreu map)이라고 부르는 3D 텐서에 적용됩니다. 이 텐서는 2개의 공간축(높이와 너비)과 깊이 축(채널 축이라고도 합니다)으로 구성됩니다. RGB 이미지는 3개의 컬러채널(빨간색, 녹색, 파란색)을 가지므로 깊이 축의 차원이 3이 됩니다. MNIST 숫자처럼 흑백이미지는 깊의 축 차원이 1(회색 톤) 입니다. 합성곱 연산은 입력 특성 맵에서 작은 패치(patch)들을 추출하고 이런 모든 패치에 같은 변환을 적용하여 출력 특성 맵(output feature map)을 만듭니다.

출력 특성 맵도 높이와 너비를 가진 3D 텐서입니다. 출련 텐서의 깊이는 층의 매개변수로 결정되기 때문에 상황에 따라 다릅니다. 이렇게 되면 깊이 축의 채널은 더 이상 RGB 입력처럼 특정 컬러를 의미하지 않습니다. 그 대신 일종의 필터(filter)를 의미합니다. 필터는 입력 데ㅐ이터의 어떤 특성을 인코딩합니다. 예를 들어 고수준으로 보면 하나의 필터가 '입력에 얼굴이 있는지'를 인코딩할 수 있습니다.

MNIST 예제에서는 첫 번째 합성곱 층이 (28, 28, 1)크기의 특성 맵을 입력으로 받아 (26, 26, 32)크기의 특성 맵을 출력합니다. 즉 입력에 대해 32개의 필터를 적용합니다. 32개의 출력 채널 각각은 26 * 26 크기의 배열 값을 가집니다. 이 값은 입력에 대한 필터의 응답 맵(response map)입니다. 입력의 각 위치에서 필터 패턴에 대한 응답을 나타냅이다. 특성 맵이란 말이 의미하는 것은 다음과 같습니다. 깊이 축에 있는 각 차원은 하나의 특서(또는 필터)이고, 2D텐서 output[:, :, n]은 입력에 대한 이 필터 응답을 나타내는 2D 공간상의 맵입니다.

합성곱은 색십적인 2개의 파라미터로 정의됩니다.

1) 입력으로부터 뽑아낼 패치의 크기: 전형적으로 3 * 3 또는 5 * 5 크기를 사용합니다. 이 예에서는 일반적으로 많이 사용하는 3 * 3 크기를 사용했습니다.

2) 특성 맵의 출력 깊이: 합성곱으로 계샇할 필터의 수입니다. 이 예에서는 깊이 32로 시작해서 깊이 64로 끝났습니다.


케라스 Conv2D층에서 이 파리미터는 Conv2D(output_depth, (window_height, window_width))처럼  첫 번째와 두 번째 매개변수로 전달됩니다.

3D 입력 특성 맵 위를 3 * 3 또는 5 * 5 크기의 원도우가 슬라이딩(sliding)하면서 모든 위치에서 3D 특성 패치((window_height, window_width, input_depth) 크기)를 추출하는 방식으로 합성곱이 작동합니다. 이런 3D패치는 (output_depth,)크기의 1D 벡터로 변환됩니다(합성곱 커널(convolution kernel)이라고 불리는 하나의 학습된 가중치 행렬과 텐서 곱셈을 통하여 변환됩니다). 변환된 모든 벡터는 (height, width, output_depth)크그의 3D특성 맵으로 재구성됩니다. 출력 특성 맵의 공간상 위치는 입력 특성 맵의 같은 위치에 대응됩니다(예를 들어 출력의 오른쪽 아래 모서리는 입력의 오른쪽 아래 부근에 해당하는 정보를 담고 있습니다). 3 * 3 원도우를 사용하면 3D패치 input[i-1: i+2, j-1:j+2, :]로부터 벡터 output[i, j,  :]가 만들어집니다. 


출력 높이와 너비느 입력의 높이, 너비와 다를 수 있습니다. 여기에는 두가지 이유가 있습니다.
1) 경계문제, 입력 특성 맵에 패딩을 추가하여 대응할 수 있습니다.
2) 잠시 후에 설명할 스트라이드(stride)의 사용 여부에 따라 다릅니다.

- 경계 문제와 패딩 이해하기
5 * 5 크기의 특성 맵을 생각해 보겠습니다(총 25개의 타일이 있다고 생각합니다). 3 * 3크기인 원도우의 중앙을 맞출 수 있는 타일은 3 * 3격자를 형성하믄 9개뿐입니다(그림 5-5참고). 따라서 출력 특성 맵은 3 * 3 크기가 됩니다. 크기가 조금 줄어들었습니다. 여기에선느 높이와 너비차원을 따라 정확히 2개의 타일이 줄어들었습니다. 앞선 예에서도 이런 경계 문제를 볼수 있습니다. 첫 번째 합성곱 층에서 28 * 28 크기의 입력이 26 * 26 크기가 되었습니다.
입력과 동일한 높이와 너비를 가진 출력 특성 맵을 얻고 싶다면 패딩(padding)을 사용할 수 있습니다. 패딩은 입력 특성 맵의 가장자리에 적절한 개수의 행과 열을 추가합니다. 그래서 모든 입력 타일에 합성곱 원도우의 중앙을 위치시킬 수 있습니다. 3 * 3원도우라면 아래에 하나의 행을 추가하고 오른쪽, 왼졲에 하나의 열을 추가합니다. 5 * 5원도우라면 2개의 행과 열을 추가합니다.
Conv2D 층에서 패딩은 padding 매개변수로 설정할 수 있습니다. 2개의 값이 가능합니다. "valid"는 패딩을 사용하지 않는다는 뜻입이다(원도우를 놓을 수 있는 위치만 사용합니다). "same"은 "입력과 동일한 높이와 너비를 가진 출력을 만들기 위해 피딩한다." 라는 뜻입니다. padding 매개변수의 기본값은 "valid" 입니다.

- 합성곱 스트라이드 이해하기
출력 크기에 영향을 미치는 다른 요소는 스트라이드 입니다. 지금까지 합성곱에 대한 설명은 합성곱 원도우의 중앙 타일이 연속적으로 자나간다고 가정한 것입니다. 두 번째 연속적인 원도우 사이의 거리가 스트라이드라고 불리는 삽성곱의 파라미터입니다. 스트라이드의 기본값은 1입니다. 스트라이드가 1보가 큰 스트라이드 합성곱도 가능합니다. 그림 5-7에서 5 * 5 크기의 입력(패딩 없음)에 스트라이드 2를 사용한 3 * 3크기의 원도우로 합성곱하여 추출한 패치를 볼수 있습니다.
스트라이드 2를 사용했다는 것은 특성 맵의 너비와 높이가 2의 배수로 다운샘플링되었다는 뜻입니다(경계문제가 있다면 더 줄어듭니다). 스트라이드 합성곱은 실정에서 드물게 사용됩니다. 하지만 어떤 모델에서는 유용하게 사용될 수 있으므로 잘 알아 둘 필요가 있습니다.
특성 뱁을 다운샘플링하기 위해서 스트라이드 대신에 첫 번째 컨브넷 예제에 상요된 최대 풀링(max pooling)연산을 사용하는 경우가 많습니다. 최대 풀링에 대해 좀 더 자세히 알아보겠습니다.


5.1 합성곱 신경망 소개

 컨브넷 정의와 컨브넷이 컴퓨터 비전 관련 작업에 잘 맞는 이유에 대해 이론적 배경을 알아봅시다. 하지만 먼저 간단한 컨브넷 예제를 둘러보죠. 2장에서 환전 연결 네트워크(densely connected network)로 풀었던(이 방식의 테스트 정확도는 97.8%였습니다) MNIST숫자 이미지 분류에 컨브넷을 사용해 보겠습니다. 기본적이 컨브넷이라도 2장에서 다룬 완전 연결된 모델의 성능을 훨씬 앞지를 것입니다.

다음 코드는 기본거인 커브넷의 모습입니다. Conv2D와 MaxPooling2D층을 쌓아 올렸습니다. 잠시 후에 이들이 무성인지 배웁니다.


    from keras import layers

    from keras import models

    

    model = moldes.Sequential()

    model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))

    model.add(layers.MaxPooling2D((2, 2)))

    model.add(layers.Conv2D(643, (3, 3), activation='relu'))

    model.add(layers.MaxPooling2D((2, 2)))

    model.add(layers.Conv2D(64, (3, 3), activation='relue'))


컨브넷이 (image_height, image_width, image_channels) 크기의 입력 텐설르 사용한다는 점이 중요합니다(배치 차원은 포함하지 않습니다). 이 예제에서는 MNIST 이미지 포맷인 (28, 28, 1) 크기의 입력을 처리하도록 컨브넷을 설정해야 합니다. 이 때문에 첫 번째 층의 매개 변수로 input_shape=(28, 28, 1)을 전달했습니다.

지금까지의 컨브넷 구조를 출력해 보죠

>>> model.summary()

--------------------------------------------------------------------------------------------

Layer(type)                    Ouput Shape                Params #

========================================================

conv2d_1(Conv2D)        (None, 26, 26, 32)            320

---------------------------------------------------------------------------------------------

maxpooling2D_1(MaxPooling2D)        (None, 13, 13, 32)        0

----------------------------------------------------------------------------------------------

conv2d_2(Conv2D)        (None, 11, 11, 64)            18496

-----------------------------------------------------------------------------------------------

maxpooling2d_2 (MaxPooling2D)        (None, 5, 5, 64)        0

------------------------------------------------------------------------------------------------

conv2d_3 (Conv2D)        (None, 3, 3, 64)                36928

==========================================================

Total params: 55,744

Trainable params: 55,744

Non-trainable params: 0

Conv2D와 MaxPooling2D 층의 출력은 (height, width, channels) 크기의 3D텐서입니다. 높이와 너비 차원은 네트워크가 깊어질수록 작아지는 경향이 있습니다. 채널의 수는 Conv2D층에 전달된 첫 번째 매개변수에 의해 조절됩니다(32개 또는 64개).

다음 단계에서 마지막 층의 ((3, 3, 64) 크기인) 출력 텐서를 완전 연결 네트워크에 주입합니다. 이 네트워크는 이미 익숙하게 보았던 Dense 층을 쌓은 분류기입니다. 이 분류기는 1D벡터를 처리하는데, 이전 층의 출력이 3D 텐서입니다. 그래서 먼저 3D출력을 1D텐서로 펼쳐야 합니다. 그다음 몇 개의 Dense층을 추가합니다.

    

    model.add(layersd.Flatten())

    model.add(layers.Dense(64, activation='relu'))

    model.add(layers.Dense(10, activation='softmax'))

10개의 클래스를 분류하기 위해 마지막 층의 출력 크기를 10으로 하고 소프트맥스 활성화함수를 사용합니다. 이제 전체 네트워크는 다음과 같습니다.

>>> model.summary()

Layer(type)                               Output Shape                Param #

==========================================================

conv2d_1 (Conv2D)                (None, 26, 26, 32)                320

maxpooling2d_1 (MaxPooling2D)        (None, 13, 13, 32)            0

conv2d_2 (Conv2D)                (None, 11, 11, 64)                18496

maxpooling2d_2 (MaxPooling2D)        (None, 5, 5, 64)                0

conv2d_3 (Conv2D)                (None, 3, 3, 64)                    36928

flatten_1 (Flatten)                    (None, 576)                            0

dense_1 (Dense)                    (None, 64)                            36928

dense_2 (Dense)                    (None, 10)                            650

===========================================================

Total params:93,322

Trainable params: 93,322

Non-trainable params: 0

여기에서 볼 수 있듯이 (3, 3, 64) 출력이 (576,)크기의 벡터로 펼쳐진 후 Dense 층으로 주입되었습니다.

이제 MNIST숫자 이미지에 이 컨브넷을 훈련합니다. 2장의 MNIST예제 코드를 많이 재상요하겠습니다.


        from keras.datasets import mnist

        from keras.utils import to_categorical

    

        (train_iamges, train_labels), (test_images, test_labels) = mnist.load_data()

        train_images = train_images.reshape((60000, 28, 28, 1))

        train_images = train_images.astype('float32') / 255


        test_images = test_images.reshape((10000, 28, 28, 1))

        test_images = test_images.astype('float32') / 255


        train_labels = to_catetorical(train_labels)

        test_labels = to_catetorical(test_labels)


        model.compile(optimizer='resprop', 

                        loss='categorical_crossentropy',

                        metrics=['accuracy'])

        model.fit(train_images, train_labels, epochs=5, batch_size=64)


테스트 데이터에서 모델을 평가해 보죠.

>>> test_loss, test_acc = model.evaluate(test_images, test_labels)

>>> test_acc

0.9921

2장의 완전 연결 네트워크는 97.8%의 테스트 정확도를 얻은 반면에 기본적인 컨브넷은 99.2%의 테스트 정확도를 얻었습니다. 에러율이 (상태적으로) 64%나 줄었습니다. 나쁘지 않군요!

완전 연결된 모델보다 왜 간단한 컨브넷이 더 잘 작동할까요? 이에 대해 알아보기 위해 Conv2D 와 MaxPooling2D 층의 어떤 일을 하는지 살펴보겠 습니다.





5. 컴퓨터 비전을 위한 딥러닝

 이 장에서 다룰 핵심 내용

1) 합성곱 신경망(컨브넷)의 이해

2) 과대적합을 줄이기 위해 데이터 증식 기법 사용

3) 특성 추출을 위해 사전 훈련된 컨브넷 사용

4) 사전 훈련된 컨브넷을 미세 조정하는 방법

5) 컨브넷에서 학습된 것과 컨브넷의 분류 결정 방식을 시각화하는 방법


이 장에서는 컨브넷(convnet)이라고도 불리는 합성곱 신경망(convolutional neural network)을 소개합니다. 이 딥러닝 모델은 거의 대부분의 컴퓨터 비전(computer vision) 애플리케이션에 사용됩니다. 거대 IT회사가 아니라면 대부분 작은 데이터셋을 다루므로 여기에서도 작은 훈련 데이터셋을 사용한 이미지 분류 문제에 컨브넷을 적용하는 법을 배우겠습니다.

4.6 요약

 1) 주어진 문제와 훈련할 데이터를 정의 합니다. 이 데이터를 수집하고 필요하면 레이블을 태킹합니다.

2) 성공을 어떻게 측정할지 선택하세요. 검증 데이터에서 모니터링할 지표는 무엇인가요?

3) 평가 방법을 결정하세요. 홀드아웃 검증이나 K-겹 교차 검증인가요? 검증에 사용해야 할 데이터의 양은 얼마나 되나요?

4) 단순한 랜덤 선택 모델보다 나은 통꼐적 검정려이 있는 첫 번째 모델을 만듭니다.

5) 과대적합된 모델을 만듭니다.

6) 검증 데이터ㅢ 성능에 기초하여 모델에 규제를 적용하고 하이퍼파라미터를 튜닝합니다. 머신러닝 연구의 대부분은 이단계에 집중됩니다. 하지만 큰 그림을 놓치지 마세요.

4.5.7 모델 규제와 하이퍼파라미터 튜닝

 이 단계가 대부분의 시간을 차지합니다. 반복적으로 모델을 수정하고 훈련하고 검증 데이터에서 평가합니다(이때 테스트 데이터를 사용하지 않습니다). 그리고 다시 수정하고 가능한 좋은 모델을 얻을 때까지 반복합니다. 적용해 볼 것은 다음과 같습니다.

1) 드롭아웃을 추가합니다.

2) 층을 추가하거나 제거해서 다른 구졸르 시도해 봅니다.

3)L1이나 L2또는 두 가지 모두 차가합니다.

4) 최적의 설정을 찾기 위해 하이퍼파라미터를 바꾸어 시도해 봅니다(층의 유닛 수나 옵티마이저의 학습률 등).

5) 선택적으로 특성 공학을 시도해 봅니다. 새로운 특성을 추가하거나 유용하지 않을 것 같은 특성을 제거합니다.

다음사항을 유념하세요. 검증 과정에서 얻은 피드백을 사용하여 모델을 튜닝할 때마다 검증 과정에 대한 정보를 모델에 누설하고 있다는 것입니다. 몇 번만 반복하는 것은 큰 문제가 되지 않습니다. 하지만 많이 반복하게 되면 결국 모델이 검증 과정에 과대적합될 것입니다(모델이 검증 데이터에서 전혀 훈련되지 않는데도 말입니다). 이는 검증 과정의 신뢰도를 감소시킵니다.

만족할 만한 모델 설정을 얻었다면 가용한 모든 데이터(훈려 데이터와 검증 데이터)를 사용해서 제품에 투입할 최종 모델을 훈련시킵니다. 그리고 마지막에 딱 한번 테스트 세트에서 평가합니다. 테스트 세트의 성능이 검증 데이터에서 측정한 것보다 많이 나쁘다면, 검증 과정에 전혀 신뢰성이 없거나 모델의 하이퍼파라미터를 튜닝하는 동안 검증 데이터에 과대적합된 것입니다. 이런 경우에는 좀 더 신뢰할 만한 평가 방법으로 바꾸는 것이 좋습니다(반복 K-겹 교차검증)

4.5.6 몸집 키우기: 과대적합 모델 구축

 통계적 검정력을 가진 모델을 얻었다면 이제 모델이 충분히 성능을 내느지 질문해 보야야 합니다. 주어진 문제를 적절히 모델링하기에 충분한 층과 파라미터가 있나요? 예를 들어 2개의 유닛을 가진 하나의 은닉 층으로 구성된 ㅔㄴ트워크가 있다고 가정합시다. 이 네트워크가 MNIST데이터셋에서 통계적 검정력을 가질 수 있지만 문제를 잘 해결하기에는 충분하지 않을 것입니다. 머신 러닝은 최적화와 일반화 사아의 줄다리기라는 점을 기억하세요. 과서적합과 과대적합 사이, 즉 과서용량과 과대용량의 경계에 적절히 위치한 모델이 이상적입니다. 이 경계가 어디에 위치하는 지 찾기 위해서는 먼저 지나쳐 보아야 합니다.

얼마나 큰 모델을 만들어야 하는지 알기 위해서는 과대적합된 모델을 만들어야 합니다. 이는 아주 쉽습니다.

1) 층을 추가합니다.

2) 층의 크기를 키웁니다.

3) 더 많은 에포크 동안 훈련합니다.

관심 대상인 훈련과 검증 지표는 물론 항상 훈련 손실과 검증 손실을 모니터링하세요. 검증 데이터에서 모델 성능이 감소하기 시작했을 때 과대적합에 도달한 것입니다.

다음 단계에서 규제와 모델 튜닝을 시작하여 과소적합도 아니고 과대적합도 아닌 이상적인 모델에 가능한 가깝도록 만듭니다.

4.5.5 기본보다 나은 모델 훈련하기

 이 단계의 목표는 통계적 검정력(statistial power)을 달성하는 것입니다. 즉 아주 단순한 모델보다 나은 수준의 작은 모델을 개발합니다. MNIST숫자 이미지 분류 예에서는 0.1보다 높은 정확도를 내는 모델이 통계적 검정력을 가졌다고 말할 수 있습니다. IMDB예에서는 0.5보다 높은 정확도를 갖는 것입니다. 

통계적 검정력을 달성하는 것이 항상 가능하지는 않습니다. 여러 개의 타당성 있는 네트워크 구조를 시도해 보고 무작위로 예측하는 모델보다 낫지 않다면 입력 데이터에 존재하지 않는 것을 얻으려고 한다는 신호일 것입니다. 2개의 가설이 있다는 것을 기억하세요.

1) 주어진 입력으로 출력을 예측할 수 있다고 가설을 세웁니다.

2) 가용한 데이터에 입력과 출력 사이의 관계를 학습하는 데 충분한 정보가 있다고 가설을 세웁니다.

이가설이 잘못된 것일 수 있습니다. 이때는 기획부터 다시 해야 합니다.

일이 잘 진행되다고 가정하면 첫 번째 모델을 만들기 위해 세가지 중요한 선택을 해야 합니다.

1) 마지막 층의 활성화 함수: 네트워크의 출력에 필요한 제한을 가합니다. 예를 들어 IMDB분류 예는 마지막 츠에 시그모이드 함수를 사용합니다. 회귀 예에서는 마지막 층에 활성화 함수를 사용하지 않습니다.

2) 손실 함수: 툴려고 하는 문제의 종류에 적합해야 합니다. 예를 들어 IMDB 예제는 binary_crossentrop를 사용하고, 회귀 예제는 mse를 사용하는 식입니다.

3) 최적화 설정: 어떤 옵티마이저를 사용하나요? 학습률은 얼마인가요? 대부분의 경우 rmsprop과 기본 학습률을 사용하는 것이 무난합니다.

손실 함수의 선택에 대ㅑ해서 언급할 것은 주어진 문제의 성고 지표를 직접 최적화하는 것이 항상 가능하지 않다는 점입니다. 때로는 이 지표를 손실 함수로 바꿀 수 있는 방법이 없습니다. 무엇보다도 손실 함수는 주어진 미니 배치 데이터에서 계산 가능해야 하고(이상적으로 손실 함수는 하나의 데이터 포인트에서 계산 가능해야 합니다), 미분 가능해야 합니다(그렇지 않으면 역전파 알고리즘을 사용하여 네트워크를 훈련 시킬수 없습니다). 예를 들어 널리 사용되는 분류 지표인 ROC AUC는 직접 최적화 될 수 없습니다. 그래서 분류 작업에는 크로스엔트로피처럼 ROC AUC를 대신할 지표를 최적홧하는 것이 보통입니다. 일반적으로 크로스엔트로피가 낮을수록 ROC AUC가 높다고 기대할 수 있습니다.

표 4-1 에 자주 등장하는 문제 유형에 따라 선택할 수 있는 마지막 츠의 활성화 함수와 손실 함수를 정리했습니다.

문제유형    마지막 층의 활성화 함수    손실함수

이진 분류    시그로이드    binary_crossentropy

단일 레이블 다중 분류    소프트맥스    categorical__crossentropy

다중 레이블 다중 분류    시그모이드     binary_crossentropy

임의 값에 대한 회귀    없음        mse

0과 1사이 값에 대한 회귀    시그모이드    mse또는 binary_crossentropy

4.5.4 데이터 준비

 무엇을 훈련할지와 무엇을 최적화할지, 그리고 어떻게 평가할지를 정했다면 거의 모델을 훈련시킬 준비가 되었습니다. 하지만 먼저 머신 러닝 모델에 주입할 데이터를 구성해야 합니다. 여기에서는 이 머신 러닝 모델을 심층 신경망이라고 가정합니다.

1) 앞서 보았듯이 텐서로 구성됩니다.

2) 이 텐서에 있는 값은 일반적으로 작은 값으로 스케일 조정되어 있습니다. 예를 들어 [-1,1]이나 [0,1] 범위입니다.

3) 특성마다 범위가 다르면(여러 종류의 값으로 이루어진 데이터라면) 정규화 해야 합니다.

4) 특성 공학을 수행할 수 있습니다. 특히 데이터가 적을 때입니다.

입력 데이터와 타깃 데이터의 텐서가 준비되면 모델을 훈련시킬 수 있습니다.

4.5.3 평가 방법 선택

 목표를 ㅅ정했다면 현재의 진척 상황을 평가할 방법을 정해야 합니다. 앞서 잘 알려진 세 가지의 평가 방식을 소개했습니다.

1) 홀드아웃 검증 세트 분리: 데이터가 풍부할 때 사용합니다.

2) K-겹 교차 검증: 홀드아웃 검증을 사용하기에 샘플의 수가 너무 적을 때 사용합니다.

3) 반복 K-겹 교차 검증: 데이터가 적고 매우 정확한 모델 평가가 필요할 때 사용합니다.

이 중에 하나를 선택하면 됩니다. 대부분의 경우 첫 번째로 충분할 것입니다.

4.5.2 성공 지표 선택

 어떤 것을 제어하려면 관측할 수 있어야 합니다. 성공하기 위해서는 성공은 무엇인가를 정의해야 합니다. 정확도일까요? 정밀도나 재현유일까요? 고객 재방문율일까요? 성공의 지표가 모델의 최적화활 손실 함수를 선택하는 기준이 됩니다. 비즈니스 성고처럼 고수준의 목표와 직접적으로 연결되어 있어야 합니다.

클래스 분포가 균일한 분류 문제에서는 정확도와 ROC AUC 가 일반적이 지표입니다. 클래스 분포가 균일하지 않은 문제에서는 정밀도와 재현율을 사용할 수 있습니다. 랭킹 문제나 다중 레이블 문제에는 평균 정밀도를 사용할 수 있습니다. 성공을 측정하기 위해 자신만의 지표를 정의하는 일은 일반적이지 않습니다. 머신 러닝의 다양한 성공 지표가 여러 가지 종류의 문제에 어떻게 관련되어 있는지 알고 싶다면 캐글(https://kaggle.com)의 데이터 과학 경연 대회를 살펴보는 것이 도움이 됩니다. 캐글에서 굉장이 다양한 문제들과 측정 지표들을 볼 수 있습니다.

4.5.1 문제 정의 와 데이터셋 수집

 먼저 주어진 문제를 정의 해야 합니다.

1) 입력 데이터는 무엇인가요? 어떤 것을 예측하려고 하나요? 가용한 휸련 데이터가 있어야 어떤 것을 예측하도록 학습할 수 있습니다. 예를 들어 영화 리뷰와 감성 레이블이 태깅되어 있어야 영화 리뷰의 감성 분류를 학습할 수 있습니다. 이런 식으로 보통 가용데이터의 유무는 이 단계에서 제한 요소가 됩니다(데이터를 수집할 비용이 없다면).

2) 당면한 문제가 어떤 종류인가요? 이진 분류인가요? 다중 분류인가요? 스칼라 회귀인가요? 베터 회귀인가요? 다중 레이블 다중 분류인가요? 아니면 군집, 생성 또는 강화 학습 같은 다른 문제 인가요? 문제의 유형을 식별하면 모델의 구조와 손실 함수등을 선택하는 데 도움이 됩니다.


입력과 출력이 무엇인지와 어떤 데이터를 사용할 것인지 알기 전까지는 다음 단계로 넘어갈 수 없습니다. 이 단계에서 가설을 세워야 합니다.

3) 주어진 입력으로 출력을 예측할 수 있다고 가설을 세웁니다.

4) 가용한 데이터에 입력과 출력 사이의 관계를 학습하는 데 충분한 정보가 있다고 4가설을 세웁니다.

모델이 작동하기 전까지 이는 가설에 불과합니다. 검증될지 아닐지 기다려 보아야 합니다. 모든 문제가 해결되지는 않습니다. 입력 X와 타깃 Y의 샘플을 수집했다고 X에 Y를 예측하기에 충분한 정보가 있는것은 아닙니다. 예를 들어 주식 시장의 최근 가격 변공 정보를 가지고 주가를 예측한다면 실패할 가능성이 높ㅊ습니다. 과거 가격 정보에는 예측에 활용할 정보가 많지 않기 때문입니다.

풀기 어려운 종류의 문제는 시간에 따라 변하는 문제(nonstationary problem)입니다. 옷을 위한 추천 엔진을 구축한다고 가정합시다. 한 달치(8월)의 데이터로 훈련하고 겨율에 이 추천 엔진을 사용하려고 합니다. 여기서 문제는 사람들이 계정에 따라 구매하려는 옷의 종류가 바뀐다는 것입니다. 의류 구매는 몇 달에 걸쳐 보면 시간에 따라 바뀌어 유동적입니다. 시간에 따라 모델을 바꾸기 위해 어떻게 해야 할까요? 이런 경우에 최근의 데이터로 주기적으로 모델을 다시 훈련하거나 시간 분포에 맞게 데이터를 수집하여 시간에 따라 변하지 않는 무넺로 바꿉니다. 의류 구매처럼 순환성이 있는 문제는 몇 년치의 데이터를 모으면 결정릐 변활르 감지하는 데 충분할 것입니다. 1년 중 언제인지 기록한 시간도 메델에 입력해야 한다는 것을 잊지 마세요!

머신 러닝은 훈련 데이터에 있는 패턴을 기억하기 위해서만 사용한다는 것을 유념하세요. 이미 보았던 것만 인식할 수 있습니다. 미래를 예ㅖ측하기 위해 과거 데이터에서 훈련한 머신러닝을 사용하는 것은 미래가 과거처럼 움직인다고 가정한 것입니다. 사실 대부분 그렇지는 않습니다.


4.5 보편적인 머신 러닝 작업 흐름

 이 절에서 머신 러닝 문제를 해결하기 위해 사용할 수 있는 보편적인 청사진을 제시하겠습니다. 이 청사진은 이장에서 배운 문제 정의, 평가, 특성 공학, 과대적합의 개념과 연결되어 있습니다.

4.4.3 드롭아웃 추가

 드럽아웃(dropout)은 토론토 대한의 제프리 힌튼과 그의 학생들이 개발했습니다. 신경망을 위해 사용되는 규제 기법 중에 가장 효과적이고 널리 사용되는 방법 중 하나입니다. 네트워크 층에 드롭아웃을 적용하면 훈련하는 동안 무작위로 층의 일부 출력 특성을 제외시킵니다(0으로 만든빈다). 한 층이 정상적으로 훈련하는 동안에는 어떤 입력 샘플에 대해 [0.2, 0.5, 1.3, 0.8, 1.1] 백터를 출력한다고 가정합시다. 드롭아웃을 적용하면 이 벡터의 일부가 무작위로 0으로 바뀝니다. 예를 들어 [0, 0.5, 1.3, 0, 1.1]이 됩니다. 드롭아웃 비율은 0이 될 특성의 비율입니다. 봍농 0.2에서 0.5 사이로 지정됩니다. 테스트 단계에선느 어떤 유닛도 드롭아웃되지 않습니다. 그 대신에 층의 출력을 드롭아웃 비율에 비례하여 줄여 줍니다. 훈련할 때보다 더 많은 유닛이 활성화되기 때문입니다.

크기가 (batch_size, features)인 한 층의 출력을 담고 있는 넘파이 행렬을 생각해 보겠습니다. 훈련할 때는 이 행렬 값의 일부가 랜덤하게 0이 됩니다.

layer_output *=  np.ramdom.randint(0, high=2, size=layer_output.shape)..훈련할 때 유닛의 출력중

                                                                                         50%를 버립니다.

테스트 할 때는 드롭아웃 비율로 출력을 낮추어 주어야 합니다. 여기에서는 0.5배만큼 스케일을 조정했습니다(앞에서 절반의 유닛을 드롭아웃했으므로).

layer_output *= 0.5 ....테스트 단계

훈련 단계에 이 두 연산을 포함시켜 테스트 단계에는 출력을 그대로 두도록 구현할 수 있습니다. 실제로 종종 이런 방식으로 구현합니다(그림 4-8 참고).

layer_output *= np.random.randint(0, high=2, size=layer_output.shape) ..훈련단계

layer_output /= 0.5 .........여기에서 스케일을 낮추는 대신 높입니다.

이 기업이 이상하고 무 계획으로 보일 수 있습니다. 왜 드롭아웃이 과대 적합을 줄이는 데 도움이 될까요? 힌튼은 은행에서 사용하는 부정 방지 매커니즘에서 착안했다고 합니다. 그의 말을 빌리면 "은행에 갔을 때 행원들이 계속 바뀌길래 왜 그런지를 물었습니다. 자신들도 이유는 모르지만 자주 업무가 바뀐다고 했습니다. 나는 은행에서 부정 행위를 하려면 직원들 사이의 유대가 필요하기 때문이라 판단했습니다. 각 샘플에 대해 뉴런의 일부를 무작위하게 제거하면 뉴런의 부정한 협업을 방지하고 결국 과대 적합을 감소시킨다는 것을 깨달았습니다." 핵심 아이디어는 층의 출력 값에 노이즈를 추가하여 중요하지 않은 우연한 패턴(힌튼이 이야기한 부정한 협업)을 깨뜨리는 것입니다. 노이즈가 없다면 네트워크가 이 패턴을 기억하기 시작할 것입니다.

케라스에서는 층의 출려 바로 뒤에 Dropout 층을 추가하여 네트워크에 드롭아웃을 적용할 수 있습니다.

model.add(layers.Dropout(0.5))

IMDB 네트워크에 2개의 Dropout 층을 추가하고 과대적합을 얼마나 줄여 주는지 확인해 보겠습니다.

    model = models.Sequential()

    model.add(layers.Dense(16, activation='relu', input_shape(1000, )))

    model.add(layers.Dropout(0.5))

    model.add(layers.Dense(16, activastion='relu'))

    model.add(layers.Dropout(0.5))

    model.add(layers.Dense(1, activation='sigmoid'))

정리하면 신경망에서 과대적합을 방지하기 위해 가장 널리 사용하는 방법은 다음과 같습니다.

1) 훈련 데이터를 더 모읍니다.

2) 네트워크의 용량을 감소시킵니다.

3) 가중치 규제를 추가합니다.

4) 드롭아웃을 추가합니다.


4.4.2 가중치 규제 추가

 오캄의 면도날(Occam's razor) 이론을 알고 있을지 모르겠습니다. 어떤 것에 대한 두 가지의 설명이 있다면 더 작은 가정이 필요한 간단한 설명이 옳을 것이라는 이론입니다. 이 개념은 신경망으로 학습되는 모델에도 적용됩니다. 어떤 훈련 데이터와 네트워크 구조가 주어졌을 때 데이터를 설명할 수 있는 가중치 값의 집합은 여러 개(여러 개의 모델)입니다. 간단한 모델의 복잡한 모델보다 덜 과대적합될 가능성이 높습니다.

여기에서 간단한 모델은 파라미터 값 분포의 엔트로피가 작은 모델입니다(또는 앞 절에서 본 것처럼 작은 수의 파라미터를 가진 모델입니다). 그러므로 과대적합을 완화하기 위한 일반적인 방법은 네트워크의 복잡도에 제한을 두어 가중치가 작은 값을 가지도록 강제하는 것입니다. 가중체 값의 분포가 더 균일하게 됩니다. 이를 가중치 규제(weight regularization)라고 하며, 네트워크의 손실함수에 큰 가중치에 연관된 비용을 추가합니다. 두 가지 형태의 비용이 있습니다.

1) L1규제: 가중치의 절댓값에 비례하는 비용이 추가됩니다(가중치의 L1 노름(norm)).

2) L2규제: 가중치의 제곱에 비례하는 비용이 추가 됩니다(가중치의 L2 노름). L2규제는 신경망에서 가중치 감쇠(weight decay)라고도 부릅니다. 이름이 다르지만 혼동하지 마세요 가중치 감쇠는 수학적으로 L2규제와 동일합니다.

케라스에서 가중치 규제 책체를 층의 키워드 매개변수로 전달하여 가중치 규제를 추가할 수 있습니다. 영화 리뷰 분류 네트워크에 L2가중치 규제를 추가해 보죠.


    from keras import regularizers


    model = models.Sequential()

    model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001),

                                    activation=;relu', input_shape=(10000,)))

    model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001),

                                    activation='relu'))

    model.add(layers.Dense(1, activation='sigmoid'))

l2(0.001)는 가중치 행렬의 모든 원소를 제곱하여 0.01을 곱하여 네트워크의 전체 손실에 더해진다는 의미입니다. 이 패널티(penalry) 항은 훈련할 때만 추가됩니다. 이 네트워크의 손실은 테스트보다 훈련할 때 더 높을 것입니다.

그림 4-7은 L2규제 패널티의 효과를 보여 줍니다. 여기서 볼수 있듯이 두 모델의 동일한 파라키너 수를 가지고 있더라도 L2규제를 사용한 모델(점)이 기본 모델(덧셈 기호)보다 훨씬 더 과대적합에 잘 견디고 있습니다.

케라스에서는 L2규제 대신에 다음 가중치 규제 중 하나를 사용할 수 있습니다.

     from keras import regularizers

    regularizers.l1(0.001) ..... L1 규제

    regularizers.l1_l2(l1=0.001, l2=0.001) ......  L1과 L2 규제 병행

4.4.1 네트워크 크기 축소

 과대적합을 맏는 가장 단순한 방법은 모델의 크기, 즉 모델에 있는 학습 파라미터의 수를 줄이는 것입니다. 파라미터의 수는 층의 수와 각 층의 유ㅜ닛 수에 의해 결정됩니다. 딥러닝에서 몰델에 잇는 학습 파라미터의 수를 종종 모델의 용량(capacity)이라고 말합니다. 당연하게 파라미터가 많은 모델이 기억 용량이 더 많습니다. 훈련 샘플과 ㅌ나깃 사이를 딕셔너리 같은 일대일 매핑으로 완벽하게 학습할 수도 있습니다. 이런 매핑은 일반화 능력이 없습니다. 옐르 들어50만개의 이진 파라미터가 있는 모델은 MNIST 훈련 세트의 숫자 이미지 클래스를 모두 쉽게 학습할 수 있습니다. 5만 개의 숫자 이미지 하나마다 10개의 이진 파라미터만 있으면 됩니다. 하지반 이런 모델은 새로운 숫자 샘플을 분류하는 용도는 쓸모가 없습니다. 항상 유념해야 할 것은 딥러닝 모델은 훈련 데이터에 잘 맞추려는 경향이 있다는 점입니다. 하지만 진짜 문제는 최적화가 아니고 일반화입니다.

다른 한편으로 네트워크가 기억 용량에 제한이 있다면 이런 매핑을 쉽게 학습하지 못할 것입니다. 따라서 손실을 최소화하기 위해 타깃에 대한 예측 성능을 가진 압축된 표현을 학습해야 ㅎ바니다.

정확히 이런 표현이 우리 관심 대상입니다. 동시에 기억해야 할 것은 과소적합되지 않도록 충분한 파라미너틀 가진 모델을 사용해야 한다는 점입니다. 모델의 기억용량이 부족해서는 안됩니다. 너무 많은 용량과 충부하지 않은 용량 사이의 정충점을 찾아야 합니다. 안타깝지만 알맞은 층의 수나 각 층의 유닛 수를 결정할 수 있는 마법 같은 공식은 없습니다. 데이터 알맞는 모델 크기를 찾으려면 각기 다른 구조를 (당연히 데티스 세트가 아니고 검증 세트에서)평가해 보아야 합니다. 적절한 모델 크기를 찾는 일반적인 작업 흐름은 비교적 적은 수의층과 파라미터로 시작합니다. 그 다음 검증 손실이 감소되기 사작할 때까지 층이나 유닛의 수를 늘리는 것입니다.

영화 리뷰 분류 모델에 적용해 보죠, 원래 네트워크는 다음과 같습니다.

    from keras import models

    from keras import layers


    model = models.Sequenctial()

    model.add(layers.Dense(16, activation='relu', input_Shape=(10000,)))

    model.add(layers.Dense(16, activation='relu'))

    model.add(layers.Dense(1, activation-'sigmoid'))

더 작은 네트워크로 바꾸어 보죠.

    model = models.Sequential()

    model.add(layers.Dense(6, activation='relu', input_shape=(10000,)))

    model.add(layers.Dense(6, activation='relu'))

    model.add(layers.Dense(1, activation='sigmoid'))

그림 4-4는 원본 네트워크와 축소된 네트워크의 검증 손실을 비교한 것입니다. 점으로 표현된 것이 작은 네트워크고, 덧셈기호가 원래 네트워크입니다(검증 손실이 작은것이 좋은 모델입니다). 여기서 볼 수 있듯이 작은 네트워크가 기본 네트워크보다 더 나중에 과대적합되기 시작했습니다(네번째에 에포크가 아니라 여섯 번째 에포크에서). 과대 적합이 시작되었을 때 성능이 더 천천이감소되었습니다.

이번에는 문제에 필요한 것보다 훨씬 더 많은 용량을 가진 테트워크를 비교해 보겠습니다.

    model = models.Sequential()

    model.add(layers.Dense(1024, activation='relu', input_shape=(10000,)))

    model.add(layers.Dense(1024, activation='relu'))

    model.add(layers.Dense(1, activation='sigmoid'))

그림 4-5는 더 큰 네트워크가 기본 네트워크에 비;해 얼마나 차이 나는지 보여 줍니다. 그림에서 점은 용량이 큰 네트워크의 검증 손실이고, 덧셈 기호는 원본 네트워크의 검증 손실입니다.

용량이 큰 네트워크는 첫 번째 에포크 이후 거의 바로 과대적합이 시작되어 갈수록 더 심해집니다. 검증 손실도 매우 불안정합니다.

한편 그림 4-6은 두 네트워크의 훈련 손실을 보여 줍니다. 여기서 볼 수 있듯이 용량이 큰 네트워크는 훈련 손실이 매우 빠르게 0에 가까워집니다. 용량이 많은 네트워크일수록 더 빠르게 훈련 데이터를 모델링할 수 있습니다(결국 훈련 손실이 낮아집니다). 하지만 더욱 과대적합에 민감해집니다(결국 훈련과 검증 손실 사이에 큰 차이가 발생합니다).


2022년 7월 29일 금요일

4.4 과대적합과 과서적합

 이전 장에 있던 세가지 예제인 영화 리뷰 예측, 토픽 분류, 주택 가격 회귀 모두 홀드아웃 데이터에서 모델의 성능이 몇 번의 에포크 후에 최고치에 다다랐다가 감소되기 시작했습니다. 즉 모델이 금방 훈련 데이터에 과대적합(overfitting)되기 시작합니다. 과대적합은 모든 머신 러닝 문제에서 발생합니다. 머신 러닝을 마스터하려면 과대적합을 다루는 방법ㅂ을 꼬 배워야 합니다.

머신러닝의 근본적인 이슈는 최적화 일반화 사이의 줄다리기입니다. 최적화(optimization)는 가능한 훈련 데이터에서 최고의 성능을 얻으려고 모델을 조정하는 과정입니다(머시 러닝의 학습). 반면에 일반화(generalization)는 훈련된 모델이 이전에 본 적 없는 데이터에서 얼마나 잘 수행되는지 의미합니다. 물론 모델을 만드는 목적은 좋은 일반화 성능을 얻는 것입니다. 하지만 일반화 성능을 제어할 방법이 없습니다. 단지 훈련 데이터를 기반으로 모델을 조정할 수만 있습니다.

훈련 초기에 최적화와 일반화는 상호 연관되어 있습니다. 훈련 데이터의 손실이 낮아질수록 테스트 데이터의 손실도 낮아집니다. 이런 상황이 발생할 때 모델이 과소적합(underfitting)되었다고 말합니다. 모델의 성능이 계속 발전될 여지가 있습니다. 즉 네트워크가 훈련 데이터에 있는 관련 특성을 모두 학습하지 모했습니다. 하지만 훈련 데이터에 여러 번 반복학습하고 나면 어느 시점부터 일반화 성능이 더 이상 높아지지 않습니다. 검증 세트의 성능이 멈추고 감소되기 시작합니다. 즉 모델이 과대적합되기 시작합니다. 이는 훈련 데이터에 특화된 패턴을 학습하기 시작했다는 의미입니다. 이패턴은 새로운 데이터와 관련성이 적어 잘못된 판단을 하게 만듭니다.

모델이 관련성이 없고 좋지 못한 패턴을 훈련 데이터에서 학습하지 못하도록 하려면 가장 좋은 방법은 더 많은 훈려 데이터를 모으는 것입니다. 더 많은 데이터에서 훈련된 모델은 자연히 일반화 성능이 더욱 뛰어납니다. 데이터를 더 모으는 것이 불가능할때 차선책은 모델의 수용할 수 있는 정보의 양을 조절하거나 저장할 수 있는 정보에 제약을 가하는 것입니다. 네트워크가 적은 수의 패턴만 기억할 수 있다면 최적화 과정에서 가장 중요한 패턴에 집중하게 될 것입니다. 이런 패턴은 더 나은 일반화 성능을 제공할 수 있습니다.

이런 식으로 과대적합을 피하는 처리 과정을 규제(regularization)라고 합니다. 가장 널리 사용되는 규제 기법을 알아보고 3.4절에서 본 영화 리뷰 분류 모델에 실제로 적용하여 선능을 향상시켜보겠습니다.

4.3.2 특성 공학

 특성 공학은 데이터와 머신 러닝 알고리즘(여기에서는 신경망)에 관한 지식을 사용하는 단계입니다. 모델에 데이터를 주입하기 전에(학습이 아닌) 하드코딩된 변환을 적용하여 알고리즘이 더 잘 수행되도록 만들어 줍니다. 많은 경우에 머신러닝 모델이 임의의 데이터로부터 완벽한 학습을 한다고 기대하기 어렵습니다. 모델이 수월하게 작업할 수 있는 어떤 방식으로 데이터가 표현될 필요가 있습니다.

이해하기 쉬운 예를 하나 살펴보죠. 시계 이미지를 입력으로 받고 하루의 시간을 출력하는 모델을 개발한다고 가정합시다. 이미지의 원본 픽셀을 입력으로 사용한다면 어려운 머신 러닝 문제가 될 것입니다. 이를 해결하려면 합성곱 신경망이 필요할 것이고 이 네트워크를 훈련하기 위해 꽤 많은 컴퓨팅 장원도 필요합니다. 고 수준에서 이 문제를 이해하고 있다면(우리는 시계에서 시간을 읽는 방법을 알고 있습니다) 머신러닝 알고리즘을 위해 훨씬 더 좋은 입력 특성을 만들 수 있습니다. 예를 들어 시계바늘의 검은색 픽셀을 따라 각 바늘 끝의 (x, y) 좌표를 출력하는 간단한 파이썬 스크립트를 만듭니다. 그 다음 머신 러닝 알고리즘이 이 좌표와 적절한 시간을 연결하도록 쉽게 학습될 수 있습니다.

이보다 저 좋은 특성을 만들 수 있습니다. 좌표를 바꾸어 (x, y)포인트를 이미지 중심에 대한 극좌표로 나타낼 수 있습니다. 이제 각 시계 바늘의 각도가 입력됩니다. 이렇게 특성을 준비하면 문제가 너무 쉬워져서 머신 러닝이 전혀 필요하지 않습니다. 간단한 반올림 연산과 딕셔너리 참고만으로 하로ㅜ의 시간을 추정하기 충분합니다.

이것이 특성 공학의 핵심입니다. 특성을 더 간단한 방식으로 표현하여 문제를 쉽게 만듭니다. 일반적으로 해당 문제를 아주 잘 이해하고 있어야 합니다.

딥러닝 이전에는 특성 공학이 아주 중요했습니다. 전통적인 얕은 학습 방법의 알고리즘들은 스스로 유용한 특성을 학습할 만큼 충분히 넓은 가설 공간을 가지고 있지 않습니다. 알고리즘에 데이터를 표현하는 방식에 성공 여부가 달려 있습니다. 옐르 들어 합성곱 신경망이 MNIST 숫자이미지 분류 문제를 해결하기 전까지 전형적인 해결책은 하드코딩된 특성을 사용하는 것이었습니다. 숫자 이미지에 있는 동심원의 수, 이미지에 있는 숫자와 높이, 팩셀 값의 히스토그램(histogram)등 입니다.

다행이 최근 딥러닝은 대부분 특성 공학이 필요하지 않습니다. 신경망이 자동으로 원본 데이터에서 유용한 특성을 추출할 수 있기 때문입니다. 그렇다면 심층 신경망을 상요할 때는 특성 공학에 대해 신경쓰지 않아도 될까요? 두 가지 이유로 그렇지 않습니다.

1) 좋은 특성은 적은 장원을 상요하여 문제를 더 멋지게 풀어낼 수 있습니다. 옐르 들어 시계 늘을 읽는 문제에 합성곱 신경망을 사용하는 것은 어울리지 않습니다.

2) 좋은 특성은 더 적은 데이터로 문제를 풀 수 있습니다. 딥러닝 모델이 스스로 특성을 학습하는 능력은 가용한 훈련 데이터가 많을 때 발휘됩니다. 샘플의 개수가 적다면 특성에 있는 정보가 매우 중요해집니다.

4.3.1 신경망을 위한 데이터 전처리

 데이터 전처리 목적은 주어진 원본 데이터를 신경망에 적용하기 쉽도록 만드는 것입니다. 벡터화(vectorization), 정규화(normalization), 누락된 값 다루기, 특성 추출 등이 포함됩니다.


- 벡터화

신경망에서 모든 입력과 타깃은 부동 소수 데이터로 이루어진 텐서여야 합니다(또는 특정 경우에 정수로 이루어진 테서입니다). 사운드, 이미지, 텍스트 등 처리햐야 할 것이 무엇이든지 먼저 텐서로 벼ㅑㄴ환해야 합니다. 이 단계를 데이터 벡터화(data vectorization)라고 합니다. 예를 들어 이전에 나온 2개의 텍스트 분류 예에서 텍스트를 (단어 시퀀스를 의미하는) 정수 리스트로 변환했습니다. 그다음 원-핫 인코딩을 사용하여 float32타입 데이터로 이루어진 텐서로 바꾸었습니다. 숫자 이미지 분류와 주택 가격 예측의 예에서는 이미 데이터가 벡터 형태로 주어졌으므로 이 단계를 건너 뛰었씁니다.


- 값 정규화

숫자 이미지 분류 예에서 이미지 데이터를 그레이스케일 인코딩 0-255 사이의 정수로 인코딩했습니다. 이 데이터를 네트워크에 주입하기 전에 float32 타입으로 변경하고 255로 나누어서 최종적으로 0-1 사이의 부동 소수 값으로 만들었습니다. 주택 가격을 예측할 때는 특성들의 범위가 제각각이었습니다. 어떤 특성은 작은 부동 소수 값이고 다른 특성은 매우 큰 정수 값을 가졌습니다. 이 데이터를 네트워크에 주입하기 전에 각 특성을 독립적으로 정규화하여 평균이 0이고 표준편차가 1이 되도록 만들었습니다.

일반적으로 비교적 큰 값(예를 들어 네트워크의 가중치 초깃값보다 훨씬 큰 여러 자릿수를 가진 정수)이나 균일하지 않은 데이터(옐르 들어 한 특성의 범위는 0-1이고 다른 특성은 100-200인 데이터)를 신경망에 주입한느 것은 위험합니다. 이렇게 하면 업데이트할 그래디언트가 컼져 네트워크가 수렴하는 것을 방해합니다. 네트워크를 쉽게 학습시키려면 데이터가 다음 특징을 따라야 합니다.

1) 작은 값을 취합니다. 일반적으로 대부분의 값이 0-1 사이여야 합니다.

2) 균일해야 합니다. 즉 모든 특성이 대체로 비슷한 범위를 가져야 합니다.

추가적으로 다음에 나오는 엄격한 정규화 방법은 꼭 필수적이지는 않지만(예를 들어 숫자 이미지 분류 예에서는 사용하지 않습니다) 자주 사용되고 도움이 될수 있습니다.

3) 각 특성별로 평균 0이 되도록 정규화합니다.

4) 각 특성별로 표준 편차가 1이 되도록 정규화합니다.

넘파이 배열에서 방법은 간단합니다.

x -= x.mean(ㅁ탼=0) .................. x가 (샘플, 특성)크기인 2D 행렬이라고 가정합니다.

x /= x.std(axis=0)


- 누락된 값 다루기

이따름 데이터에 값이 누락된 경우가 있습니다. 예를 들어 주택 가격 예측 문제에서 첫 번째 특성(데이터에서 인텍스가 0인컬럼)은 1인당 범쥐율입니다.

이 특성이 모든 샘플에 들어 있지 않으면 어떻게 될까요? 훈련 데이터나 테스트 데이터에 누락된 값이 포함됩니다. 일반적으로 신경망에서 0이 사건에 정의된ㄴ 의미 있는 값이 아니라면 누락된 값을 0으로 입력해도 괜찮습니다. 네트워크가 0이 누락된 데이터를 의미한다는 것을 학습하면 이 값을 무시하기 시작할 것입니다.

테스트 데이터에 누라된 값이 포함될 가능성이 있다고 가정합시다. 하지만 네트워크가 누락된 값이 없는 데이터에서 훈련되었다면 이 네트워크는 누라된 값을 무시하는 법을 알지 못합니다. 이런 경우에는 누락된 값이 있는 훈련 샘플을 고의적으로 만들어야 합니다. 훈련 샘플의 일부를 여러별 복사해서 테스트 데이터에서 빠질 것 같은 즉성을 제거합니다.

4.3 데이터 전처리, 특성 고학, 특성 학습

 모델 평가 외에도 모델 개발로 들어가기 전에 우리가 넘어야 할 중요한 질문이 있습니다. 신경만에 입력 데이터와 타깃 데이터를 주입하기 전에 어떻게 준비해야 할까요? 많은 데이터 전처리와 특성 공학 기법은 특정 도메인에 종속적입니다(예를 들어 텍스트 데이터나 이미지 데이터에 특화되어 있습니다). 이 후 장에서 실전 문제를 보면서 이에 대해 다루겠습니다. 여기서는 모든 종류의 데이터에 공통되는 기본 사항을 살펴보겠습니다.

4.2.2 기억해야 할것

 평가 방식을 선택할 때 다음 사항을 유념해야 합니다.

1) 대표성 있는 데이터: 훈련 세트와 데이터 세트가 주어진 데이터에 대한 대표성이 있어야 합니다. 옐르 들어 숫자 이미지를 분류하는 문제에서 샘플 배열이 클래스 순서대로 나영되어 있다고 가정합시다. 이 배열의 처음 80%를 훈련 세트로 나머지 20%를 테스트 세트로 만들면 훈련 세트에는 0-7 숫자만 담겨 있고 테스트 세트에는 8-9 숫자만 담기게 됩니다. 어처구니없는 실수처럼 보이지만 놀랍게도 자주 일어나는 일입니다. 이런 이유 때문에 훈련 세트와 테스트 세트로 나누기 전에 데이터를 무작위로 섞는 것이 일반적입니다.

2) 시간의 방향: 과거로부터 미래를 예측하려고 한다면(예를 들어 내일의 날씨, 주식 시세등) 데이터를 분할하기 전에 무작위로 섞어서는 절대 안 됩니다. 이렇게 하면 미래의 정보가 누설되기 때문입니다. 이런 문제에서는 훈련 세트에 있는 데이터보다 테스트 세트에 있는 모든 데이터가 미래의 것이어야 합니다.

3) 데이터 중복: 한 데이터셋에 어떤 데이터 포인트가 두 번 등장하면(실제 데이터셋에서 아주 흔한 일입니다.) 데이터를 섞고 훈련 세트와 검증 세트로 나누었을 때 훈련 세트와 검증 세트에 데이터 포인트가 중복될 수 있습니다. 이로 인해 훈련 데이터의 일부로 테스트하는 최악의 경우가 됩니다. 훈련 세트와 검증 세트가 중복되지 않는지 확인하세요.

4.2.1 훈련, 검증, 데스트 세트

 모델 평가의 핵심은 가용한 데이터를 항상 훈련,  검증, 테스트 3개의 세트로 나누는 것입니다. 훈련 세트에서 모델을 훈련하고 검증 세트에서 모델을 평가합니다. 모델을 출시할 준비가 되면 테스트 세트에서 최종적으로 따 한 번 모델을 테스트 합니다.

훈련 세트와 테스트 세트 2개를 사용하면 어떨까요? 훈련 세트에서 훈련하고 테스트 세트에서 평가한느 것이죠. 훨씬 간단하네요!

이렇게 하지 않는 이유는 모델을 개발할 때 항상 모델의 설정을 튜닝하기 때문입니다. 예를 들어 층의 수나 층의 유닛 수를 선택합니다(이런 파라미터를 네트워크의 가중치와 구분하기 위해 하이퍼파라미터(hyperparameter)라고 부릅니다). 검증 세트에서 모델의 성능을 평가하여 이런 튜닝을 수행합니다. 본질적으로 이런 튜닝도 어떤 파라미터 공간에서 좋은 설정을 찾는 학습입니다. 결국 검증 세트의 성능을 기반으로 모델의 설정을 튜닝하면 검증 세트로 모델을 직접 훈련하지 않더라도 빠르게 검증 세트에 과대적합될 수 있습니다.

이 현상의 핵슴은 정보 누설(Information leak)개념에 있습니다. 검증 세트의 모델 성능에 기반하여 모델의 하이퍼파라미터를 조정할 때마다 검증 데이터에 관한 정보가 모델로 새는 것입니다. 하나의 파라미터에 대해서 단 한 번만 튜닝한다면 아주 적은 정보가 누설됩니다. 이런 검증 세트로는 모델을 평가할 만합니다. 하지만 한 번 튜닝하고 나서 검증 세트에 평가한 결과를 가지고 다시 모델을 조정하는 과정을 여러 번 반복하면, 검증 세트에 관한 정보를 모델에 아주 많이 노출시키게 됩니다.

결국 검증 데이터에 맞추어 최적화했기 때문에 검증 데이터에 의도적으로 잘 수행되는 모델이 만들어집니다. 검증 데이터가 아니고 완전히 새로운 데이터에 대한 성능이 관심 대상이라면 모델을 평가하기 위해 이전에 본 적 없는 완전히 다른 데이터셋을 사용해야 합니다. 바로 테스트 세트입니다. 모델을 간접적으로다도 테스트 세트에 대한 어떤 정보도 얻어서는 안됩니다. 테스트 세트 성능에 기초하여 튜닝한 모델의 모든 설정은 일반화 성능을 왜곡시킬 것입니다.

데이터를 훈련, 검증, 테스트 세트로 나누는 것은 간단해 보일 수 있지만 데이터가 적을 때는 몇 가지 고급 기법을 사용하면 도움이 됩니다. 대표적인 세 가지 평가 방법인 단순 홀드아웃 검증(hold-out validation), K-겹 교차 검증(K-fold cross-validation), 셔플링(shuffling)을 사용한 반복 K-겹 교차 검증(iterated K-fold cross-validation)을 살펴보겠습니다.


- 단순 홀드아웃 검증

데이터의 일정량을 테스트 세트로 떼어 놓습니다. 남은 데이터에서 훈련하고 테스트 세트로 평가합니다. 앞 절에서 설명했듯이 정보 누설을 막기 위해 테스트 세트를 사용하여 모델을 튜닝해서는 안 됩니다. 이런 이유로 검증 세트로 따로 떼어 놓아야 합니다.

    num_validation_sample = 10000

    np.random.shuffle(data)  ....................... 데이터를 섞는 것(셔플링)이 일반적으로 좋습니다.

    data = data[num_validation_samples:]


    training_data = add[:] ...................... 훈련 세트를 만듭니다.

    

    model = get_model()                                                훈련 세트에서 모델을              

    model.train(training_data)                                           훈련하고 검증 세트로

    validation_score = model.evaluate(validation_data)  ........      평가합니다.


    # 여기에서 모델을 튜닝하고,

    # 다시 훈련하고, 평가하고, 또 다시 튜닝하고 ........

    model = get_model()                                                 하이퍼파라미터 튜닝이 끝나면

    model.train(np.concatenate([training_data, validation_data]))  테스트 데이터를 제외한 모든

    test_score = model.evaluate(test_data)         데이터를 사용하여 모델을 다시 훈련시킵니다.


이 평가 방법은 단순해서 한 가지 단점이 있습니다. 데이터가 적을 때는 검증 세트와 테스트 세트의 샘플이 너무 적어 주어진 전체 데이터를 통계적으로 대표하지 못할 수 있습니다. 쉽게 일르 확인할 수 있습니다. 다른 난수 초깃값으로 셔플링해서 데이터를 나누었을 때 모델의 성능이 매우 달라지면 바로 이 문제입니다. 다음에 이야기할 K-겹 교차 검증과 반복 K-겹 교차 검증이 이 문제를 해결할 수 있습니다.


K-겹 교차 검증

이 방식에서는 데이터를 동일한 크기를 가진 K개 분할로 나눕니다. 각 분할 i에 대해 남은 K-1 개의 분할로 모델을 훈련하고 분할 i에서 모델을 평가합니다. 최종 점수는 이렇게 얻은 K개의 점술르 평균합니다. 이 방법은 모델의 성능이 데이터 분할에 따라 편차가 클 때 도움이 됩니다. 홀드 아웃 검증처럼 이 방법은 모델의 튜닝에 별개의 검증 세트를 사용하게 됩니다.

    k = 4

    num_validation_samples = len(data) // k


    np.random.shuffle(data)

    validation_scores = []

    for fold in range(k):

        validation_data = data[num_validation_samples * fold:

            num_validation_samples * ( fold +1)]            ......... 검증 데이터 부분을 선택합니다.

        training_data = data[:num_validation_samples * fold] + ..... 남은 데이터를 훈련 데이터로

            data[num_validationi_samples * (fold + 1):]    ..... 사용합니다. 리스트에서 + 연산자

                                                     ...... 는 두 리스트를 더하는 것이 아니고 연결합니다.

        model = get_mode() .......훈련이 되지 않은 새로운 모델을 만듭니다.

        model.train(training_data)

        validation_score = model.evaluate(validation_data) 

        validation_scores.append(validation_score)

    validation_score = np.average(validation_scores) ...검증 점수: K개 폴드의 검증 점수 평균

    

    model = get_model()                  ........ 테스트 데이터를 제외한 전체 데이터로

    model.train(data)                       ........ 최종 모델을 훈련합니다.

    test_score = model.evaluate(test_data)


- 셔플링을 사용한 반복 K-겹 교차 검증

이 방법은 비교적 가용 데이터가 작고 가능한 정확하게 모델을 평가하고 할 때 사용합니다. 캐글 경연에서는 이 방법이 아주 크게 도움이 됩니다. 이 방법은 K-겹 교차 검증을 여러 번 적용하기되 K개의 분할로 나누기 전에 매번 데이터를 무작위로 섞습니다. 최종 점수는 모든 K-겹 교차 검증을 실행해서 얻은 점수의 평균이 됩니다. 결국 P * K개 (P는 반복횟수)의 모델을 훈련하고 평가하므로 비용이 매우 많이 듭니다.


4.2 머신 러님 모델 평가

 3장에서 본 3개의 예제에서 데이터를 훈련 세트, 검증 세트, 테스트 세트로 나누었습니다. 훈련에 사용된 동일한 데이터로 모델을 평가하지 않는 이유는 금방 드러났습니다. 몇 번의 에포크 후에 3개의 모델이 모두 과대적합되기 시작했습니다. 즉 훈련 데이터의 성능에 비해 처음 본 데이터에 대한 성능이 좋아지지 않습니다(또는 더 나빠집니다). 반면에 훈련 데이터의 성능은 훈련이 진행될 수록 항상 증가됩니다.

머신 러닝의 목표는 처음 분 데이터에서 잘 작동하는 일반화된 모델을 얻는 것입니다. 여기에서 과대적합은 주요 장애물입니다. 관측할 수 있는 것만 제어할 수 있으므로 모델의 일반화 성능에 대한 신뢰할 수 있는 측정 밥업이 아주 중요합니다. 다음 절에 과대적합을 완화하고 일반화를 최대화하기 위한 전략을 살펴보겠습니다. 이 절에서는 일반화, 즉 머신 러닝 모델의 성능을 어떻게 측정하는지에 집중합니다.

2022년 7월 28일 목요일

4.1.4 강화 학습

 오랫동안 간과되었던 강화 학습(reainforcement learning)은 구글 딥마인드(DeepMind)가 아타리(Atri)게임 플레이를 학습하는 데 성공적으로 적용하면서 최근에 많은 관심을 받기 시작했습니다(그 이후 최고 수준의 바둑 실력을 학습했습니다). 강화 학습에서 에이전트(agent)는 환경에 대한 정보를 받아 보상을 최대화하는 행동을 선택하도록 학습됩니다. 옐르 들어 강화 학습으로 훈련된 신경망은 비디오 게임 화면을 입력으로 받고 게임 점수를 최대화하기 위한 게임 내의 행동을 축력할 수 있습니다.

현재 강화 학습은 대부분 연구 영역에 속해 있고 게임 이외에 실제적인 성공 사례는 아직 없습니다. 하지만 때가 되면 강화 학습이 실제 세상의 많은 애플리케이션을 대체할 것으로 기대하고 있습니다. 이런 애플리케이션에서 자율 주행 자동차, 자원 과리, 교육 등이 있습니다. 아마 그때가 왔거나 이제 곧 올 것입니다.

분류와 회귀에서 사용하는 용어

분류와 회귀에는 븍별한 용어가 많습니다. 이전 예제에서 일부 용어를 보았고 앞으로 어어지는 장들에서 더 많이 등장합니다. 이런 용어들은 머신 러닝에 특별화된 구체적인 정의를 가지므로 친숙해야 합니다.

1) 샘플 또는 입력: 모델에 주입될 하나의 데이터 포인트

2) 예측 또는 출력: 모델로부터 나오는 값

3) 타깃: 정답, 외부 데이터 소스에 근거하여 모델이 완벽하게 예측해야 하는 값

4) 예측 오차 또는 손실 값: 모델의 예측과 타깃 사이의 거리를 측정한 값

5) 클래스: 분류 문제에서 선택할 수 있는 가능한 레이블의 집합. 예를 들어 고양이와 강아지 사진을 분류할 때 클래스는 '고양이', '강아지' 2개 입니다.

6) 레이블: 분류 문제에서 클래스 할당의 구체적인 사례. 예를 들어 사진 #123에 '강아지'클래스가 들어 있다고 표시 한다면 '강아지'는 사진 #1234의 레이블이 됩니다.

7) 참 값(ground-truth)또는 꼬리표(annotation): 데이터셋에 대한 모든 타깃. 일반적으로 사람에 의해 수집됩니다.

8) 이진 분류: 각 입력 샘플이 2개의 배타적인 범주로 구분되는 분류 작업

9) 다중 분류: 각 입력 샘플이 2개 이상의 범주로 구분되는 분류 작업. 예를 들어 손글씨 숫자 분류를 말합니다.

10)다중 레이블 분류: 각 입력 샘플이 여러 개의 레이블에 할당될 수 있는 분류 작업. 예를 들어 하나의 이미지에 고양이와 강아지가 모두 들어 있을 때는 '고양이'레이블과 '강아지'레이블을 모두 할당해야 합니다. 보통 이미지마다 레이블의 개수는 다릅니다.

11) 스칼라 회귀: 타깃이 연속적인 스칼라 값인 작업. 주택 가격 예측이 좋은 예입니다. 각기 다른 타깃 가격이 연속적인 공간을 형성합니다.

12) 벡터 회귀: 타깃이 연속적인 값의 집합인 작업, 예를 들어 연속적인 값으로 이루어진 벡터입니다.(이미지에 있는 경계 상자의 좌표 같은) 여러 개의 값에 대한 회귀를 한다면 벡터 회귀입니다.

13) 미니 배치 또는 배치: 모델에 의해 동시에 처리되는 소량의 샘플 묶음(일반적으로 8개에서 128개 사이). 샘플 개수는 GPU의 메모리 할당이 용이하도록 2의 거듭제곱으로 하는 경우가 많습니다. 훈련할 때 미니 배치마다 한 번씩 모델의 가중치에 적용할 경사 하강법 업데이트 값을 계산합니다.

4.1.3 자기 지도 학습

 자기 지도 학습(self-supervised learning)은 지도 학습의 특별한 경우지만 별도의 범주로 할 만큼 충분히 다릅니다. 자기 지도 학습은 지도 학습이지만 사람이 만든 레이블을 사용하지 않습니다. 즉 학습 과정에 사람이 개입하지 않는 지도 학습이라고 생각할 수 있습니다.(학습이 무언가에 지도되어야 하므로) 레이블이 여전히 필요하지만 보통 경험적인 알고리즘(heuristic algorithm)을 사용해서 입력 데이터로 부터 생성합니다. 예를 들어 오토인코더(autoencoder)가 잘 알려진 자기 지도 학습의 예입니다. 여기에서 생성된 타깃은 수정하지 않은 원본 입력입니다. 같은 방식으로 지난 프레임이 주어졌을 때 비디오의 다음 프레임을 예측하는 것이나, 이전 단어가 주어졌을 때 다음 단어를 예측하는 것이 자기 지도학습의 예입니다( 이 경우에는 미래의 입력 데이터로 부터 지도되기 때문에 시간에 따른 지도학습(temporally superviesed learnintg)입니다). 지도 학습, 자기 지도  학습, 비지도학습의 구분은 가끔 모호할 수 있습니다. 이 범주들은 명확한 경계가 없고 연속적입니다. 자기 지도 학습 메커니즘과 애플리케이션 측면 중 어디에 중점을 두는지에 따라 지도 학습 또는 비지도 학습으로 재해석 될 수 있습니다.

지도 학습이 광범위한 산업계의 애플리케이션에 적용되어 오늘날 딥러닝의 대부분을 차지하고 있기 때문에 이 책에서는 특히 지도학습에 집중하겠ㅅ브니다. 책의 후반부에서 자기 지도 학습에 대해 잠깐 살펴봅니다.

4.1.2 비 지도 학습

 이 부류의 머신 러닝은 어떤 타깃도 사용하지 않고 입력 데이터에 대한 흥미로운 변환을 찾습니다. 데이터 시각화, 데이터 압축, 데이터의 노이즈 제거 또는 데이터에 있는 상관관계를 더 잘 이해하기 위해 사용합니다. 비지도 학습(unsupervised learning)은 데이터 분석에서 빼놓을 수 없는 요소이며, 종종 지도 학습 문제를 풀기 전에 데이터셋을 잘 이해하기 위해 필수적으로 거치는 단계입니다. 차원 축소(dimensionality reduction)와 군집(clustering)이 비지도 학습에서 잘 알려진 범주입니다.

2022년 7월 26일 화요일

4.1.1 지도학습

 지도 학습이 가장 흔한 경우입니다.(종종 사람이 레이블링한) 샘플 데이터가 주어지면 알고 있는 타깃(꼬리표(annotaion)라고도 합니다)에 입력 데이터를 매핑하는 방법을 학습합니다. 지금까지 이 책에서 본 4개의 예제는 모두 지도 학습의 고전적인 예입니다. 요즘 스포트라이트를 받는 광학문자 판독, 음성 인식, 이미지 분류, 언어 번역 같은 입러닝의 거의 모든 애플리케이션이 일반적으로 이 범주에 속합니다.

지도 학습은 대부분 분류와 회귀로 구성되지만 다음과 같은 특이한 변종도 많습니다.

1) 시퀀스 생성(sequence generation): 사진이 주어지면 이를 설명하는 캡션을 생성합니다. 시퀸스 생성은 아따금(시퀀스에 있는 단어나 토큰(token)을 반복적으로 예측하는 것처럼) 일련의 분류 문제로 재구성할 수 있습니다.

2) 구문 트리(syntax tree)예측: 문장이 주어지면 분해된 구문 트리를 예측 합니다.

3) 물체 감지(object deteaction): 사진이 주어지면 사진 안의 특정 물체 주위에 경계 상자(bounding box)를 그립니다. 이는 (많은 곙계 상자 후보가 주어졌을 때 각 상자의 내용을 분류하는) 분류 문제로 표현되거나, 경계 상자의 좌표를 백터 회귀로 예측하는 회귀와 분류가 결합된 문제로 표현할 수 있습니다.

4) 이미지 분할(image segmentation):사진이 주어졌을 때 픽셀 단위로 특정 물체에 마스킹(masking)을 합니다.

4.1 머신 러닝의 네 가지 분류

 이전 예제에서 세 가지 종류의 머신 러닝 문제를 다루었습니다. 이진 분류, 다중 분류, 스칼라 회귀입니다. 이 셋은 모두 지도학습(supervised learning) 의 예입니다. 지도 학습의 목표는 훈련 데이터의 입력과 타깃 사이에 있는 관계를 학습하는 것입니다.

지도 학습은 빙산의 일각일 뿐입니다. 전체 머신 러닝은 복잡한 하위 분류를 가진 방대합 누야입니다. 일반적으로 머신 러닝 알고리즘은 다음 절에서 소개하는 4개의 커다란 범주 안에 속합니다.

4. 머신러닝의 기본 요소

3장에서 3개의 실용적인 옐르 다루었습니다. 이제 신경망을 사용하여 분류와 회귀 문제에 어떻게 접근하는지 익숙해지기 시3작했을 것입니다. 머신 러닝에서 아주 중요한 문제인 과대적합도 직접 보았습니다. 이 장에서는 딥러닝 문제에 도전하고 해결하기 위해 새롭게 얻은 직관을 확고한 개념으로 정립하겠습니다. 모델 평가, 데이터 전처리, 특성 공학, 과대적합 문제 같은 이런 모든 개념을 머신 러닝 문제를 해결하기 위한 7단계 작업 흐름으로 자세하게 정리하겠습니다.

3.7 요약

 1) 이제 벡터 데이터를 사용하여 가장 일반적인 머신 러닝인 이진 분류, 다중 분류, 스칼라 회귀 작업을 다룰 수 있습니다. 이 장의 '정리'절에서 이런 종류의 작업을 통해 배울 중요한 사항들을 정리해 놓았습니다.

2) 보통 원본 데이터를 신경망에 주입하기 전에 전처리해야 합니다.

3) 데ㅐ이터에 범위가 다른 특성이 있다면 전처리 단계에서 각 특성을 독립적으로 스케일 조정해야 합니다.

4) 훈련이 진행됨에 따라 신경마의 과대 적합이 시작되고 새로운 데이터에 대해 나쁜 결과를 얻게 됩니다.

5) 훈련 데이터가 많지 않으면 과대적합을 피하기 위해 1개 또는 2개의 은닉 층을 가진 신경망을 사용합니다.

6) 데이터가 많은 범주로 나뉘어 있을 때 중간층이 너무 작으면 정보의 병목이 생길 수 있습니다.

7) 회귀는 분류와 다른 손실 함수와 평가 지표를 사용합니다.

8) 작은 데이터를 사용할 때는 K-검증이 신뢰할 수 있는 모델 평가를 도와줍니다.

3.6.5 정리

 다음은 이 예제에서 배운 것들입니다.

1) 회귀는 분류에서 사용했던 것과는 다른 손실 함수를 사용합니다. 평균 제곱 오차(MSE)는 회귀에서 자주 사용되는 손실 함수입니다.

2) 비슷하게 회귀에서 사용되는 평가 지표는 분류와 다릅니다. 당연히 정화도 개념은 회귀에 적용되지 않습니다. 일반적인 회귀 지표는 평균 절대 오차(MAE)입니다.

3) 입력 데이터의 특성이 서로 다른 범위를 가지면 전처리 단계에서 각 특성을 개별적으로 스케일 조정해야 합니다.

4) 가용한 데이터가 적다면 K-겹 검증을 사용하는 것이 신뢰할 수 있는 모델 평가 방법입니다.

5) 가용한 훈련 데이터가 적다면 과대적합을 피하기 위해 은닉 층의 수를 줄인 모델이 좋습니다(일반적으로 1개 또는 2개)(

3.6.4 K-겹 검증을 사용한 훈련 검증

 (훈련에 사용할 에프크의 수 같은) 매개변수들을 조정하면서 모델을 평가하기 위해 이전 예제에서 했던 것처럼 데이터를 훈련 세트와 검증 세트로 나눕니다. 데이터 포인트가 많지 않기 때문에 검증 세트도 매우 작아집니다(약 100개의 샘플). 결국 검증 세트와 훈련 세트로 어떤 데이터 포인트가 선택되었는지에 따라 검증 점수가 크게 달라집니다. 검증 세트의 분할에 대한 검증 점수의 분산이 높습니다. 이렇게 되면 신뢰 있는 모델 평가를 할 수 없습니다.

이런 상황에서 가장 좋은 방법은 K-겹 교차 검증(K-flod cross-validation)을 사용하는 것입니다. 데이터를 K개의 분할(즉 폴드(fold))로 나누고(일반적으로 K = 4 또는 5), K개의 모델을 각각 만들어 K - 1개의 분할에서 훈련하고 나머지 분할에서 평가하는 방법입니다. 모델의 검증 점수는 K개의 검증 점수 평균이 됩니다. 코드로 보면 이해하기 쉽습니다.    

    import numpy as np

    num_val_samples = len(train_data) // k

    num_epochs = 100

    all_scores = []

    for i in range(k):

        print('처리중인 폴드 #', ㅑ)

        val_data = train_Data[i * num_val_samples: (i +1) * num_val_samples]

        ..... 검증 데이터 준비 k번째 분할

        val_targets = train_targets[ i * num_val_samples: (i + 1) * num_val_samples]

    

        partial_train_data = np.concatenate( .....  훈련 데이터 준비:다른 분할 전체

            [train_data[:i * num_val_samples],

                train_data[(i +1) * num_val_samples:]],

            axis = 0)

        partial_train_targets = np.concatenate(

            [train_targets[:i * num_val_samples],

                train_targets[(i +1) * num_val_samples:]],

            axis = 0)

        model = build_model() ..........케라스 모델 구성(컴파일 포함)

        model.fit(partial_Train_Data, partial_train_target, 

                        .....모델 훈련(verbose=0이므로 훈련 과정이 출력되지 않습니다.)

                    epochs=num_epochs, batch_size=1, verbose=0)

        val_mse, val_mae = model.evaluate(val_data, val_targets, verbose=0)

            .......  검증 세트로 모델 평가

        all_scores.append(val_mae)

num_epochs = 100으로 실행하면 다음 결과를 얻습니다.

>>> all_scores

[2.0956787838794217m 2,220593797098292, 2.859968412040484, 2.4053704039111]

>>> np.mean(all_scores)

2.3953995083523267

검증 세트가 다르므로 확실히 검증 점수가 2.1에서 2.9까지 변화가 큽니다. 평균값(2.4)이 각각의 점수보다 훨씬 신뢰할 만합니다. 이것이 K-겹 교차 검증의 핵심입니다. 이 예에서는 평균적으로 2,400달러 정도가 납니다. 주택 가격의 범위가 1만 달러에서 5만 달러 사이인 것을 감안하면 비교적 큰 값입니다.

신경망을 조금 더 오래 500 에포크 동안 훈련해 보죠. 각 에포크마다 모델이 얼마나 개선되는지기록하기 위해 훈련 루프를 조금 수정해서 에포크의 검증 점수를 로그에 저장하겠습니다.

    num_epochs = 500

    all_mae_histories = []

    for i in range(k):

        print('처리중인 폴드 #', i)

        val_data = train_data[i * num_val_samples: (i +1) * num_val_samples] 

            ......... 검증 데이터 준비: k번째 분할

        val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]

        partial_train_Data = np.concatenate( ............ 훈련 데이터 준비: 다른 분할 전체

            [train_Data[:i * num_val_samples],

                train_data[(i +1) * num_val_samples:]],

            axis=0)

        partial_train_targets = np.concatenate(    

            [train_targets[:i * num_val_samples],

                train_targets[(i +1) * num_val_samples:]],

            axis=0)

        model = build_model() ............케라스 모델 구성(컴파일 푸함)

        history = model.fit(partial_train_Data, partial_train_targets, 

            ...... 모델 훈련(verbose=0이므로 훈련 과정이 출력되지 않습니다)

                        validation_data=(val_data, val_targets),

                        epochs=num_epochs, batch_size=1, verbose=0)

        mae_hostory = history.history['val_mean_absolute_error']

        all_mae_histories.append(mae_history)

그다음 모든 폴드에 대해 에포크의 MAE 점수 평균을 계산합니다.

    average_mae_history = [

        np.mean([x[i] for x in all_mae_histories]) for i in range(num_epochs)]

그래프로 나타내면 그림 3-12와 같습니다.

    import matplotlib.pyplot as plt

    plt.plot(range(1, len(average_mae_history) + 1), average_mae_hjistory)

    plt.xlabel('Epochs')

    plt.ylabel('Validation MAE')

    plt.show()

이 그래프는 범위가 크고 변동이 심하기 때뮤ㅜㄴ에 보기가 좀 어렵습니다. 다음과 같이 해 보죠.

1) 곡선의 다른 부분과 스케일이 많이 다른 첫 10개의 데이터 포인트를 제외시킵니다.

2) 부르더운 곡선을 얻기 위해 각 포인트를 이전 포인트의 지수 이동 평균(exponential moving average)으로 대체합니다.


    def smooth_curve(points, factor=0.9):

        smoothed_point=[]

        for point in points:

            if smoothed_points:

                previous = smoothed_points[-1]

                smoothed_points.append(previous *  factor + point * (1 - factor))

            else:

                smoothed_points.append(point)

        return smoothed_points

    smooth_mae_history = smooth_curve(average_mae_history[1-:])


    plt.plot(range(1, len(smooth_mae)history) + 1), smooth_mae_history)

    plt.xlabel('Epochs')

    plt.ylabel('Validation MAE')

    plt.show()

이 그래프를 보면 검증 MAE가 80번째 에포크 이후에 줄어드는 것이 멈추었습니다. 이 지점 이후로는 과대적합이 시작됩니다.

모델의 여러 매개변수에 대한 튜닝이 끝나면(에포크 수뿐만 아니라 은닉 층의 크기도 조절할 수 있습니다) 모든 훈련 데이터를 사용하고 최상의 매개변수로 최종 실전에 투입될 모델을 훈련시킵니다. 그다음 테스트 데이터로 성능을 확인합니다.

    model = build_model() ..............새롭게 컴파일된 모델을 얻습니다.

    model.fit(train_data, train_targets, ................. 전체 데이터로 훈련시킵니다.

                epochs=80, batch_size=16, verbose=0)

    test_mse_score, test_mae_score = model.evaluate(test_data, test_Targets)

최종 결과는 다음과 같습니다.

>>> test_mae_score

2.675027286305147

아직 2.675달러 정도 차이가 나네요