페이지

2022년 7월 31일 일요일

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회사가 아니라면 대부분 작은 데이터셋을 다루므로 여기에서도 작은 훈련 데이터셋을 사용한 이미지 분류 문제에 컨브넷을 적용하는 법을 배우겠습니다.