합성곱 신경망의 구성 요소
합성곱 신경망을 구성하는 기본 개념과 동작 원리를 배우고 간단한 합성곱,풀링 계산 방법을 익힌다.
합성곱
합성곱은 마치 입력 데이터에 마법의 도장을 찍어서 유용한 특성만 드러나게 하는 것으로 비유할 수 있다.
7장에서 사용한 밀집층에는 뉴런마다 입력 개수만큼의 가중치가 있다. 즉 모든 입력에 가중치를 곱한다. 이 과정을 그림으로 표현하면 다음과 같다

인공 신경망은 처음에 가중치 w1~w10과 절편 b를 랜덤하게 초기화한 다음 에포크를 반복하면서 경사 하강법 알고리즘을 사용하여 손실이 낮아지도록 최적의 가중치와 절편을 찾아간다. 이것이 바로 모델 훈련이다. 결국 뉴런의 개수만큼 출력하게 된다.
합성곱은 밀집층의 계산과 조금 다르다. 입력 데이터 전체에 가중치를 적용하는 것이 아니라 일부에 가중치를 곱한다. 다음 그림과 이전의 밀집층 그림을 비교해 보자.

여기에서는 이 뉴런이 3개의 가중치를 가진다고 가정하겠다.
가중치 w1~w3이 입력의 처음 3개 특성과 곱해져 1개의 출력을 만든다. 그다음이 중요하다. 이 뉴런이 한 칸 아래로 이동해 두 번째부터 네 번째 특성과 곱해져 새로운 출력을 만든다.

여기에서 중요한 것은 첫 번째 합성곱에 사용된 가중치 w1~w3과 절편 b가 두 번째 합성곱에도 동일하게 사용된다.
이렇게 한 칸씩 아래로 이동하면서 출력을 만드는 것이 합성곱이다. 여기에서는 이 뉴런의 가중치가 3개이기 때문에 모두 8개의 출력이 만들어진다.

밀집층의 뉴런은 입력 개수만큼 10개의 가중치를 가지고 1개의 출력을 만든다.
합성곱 층의 뉴런은 3개의 가중치를 가지고 8개의 출력을 만든다. 또한 합성곱 층의 뉴런에 있는 가중치 개수는 정하기 나름이다. 즉 또 다른 하이퍼파라미터이다.
이전에 그렸던 신경망 층의 그림은 뉴런이 길게 늘어서 있고 서로 조밀하게 연결되어 있다. 그런데 합성곱에서는 뉴런이 입력 위를 이동하면서 출력을 만들기 떄문에 이런 식으로 표현하기가 어렵다.
또 뉴런이라고 부르기도 어색하다. 합성곱 신경망에서는 완전 연결 신경망과 달리 뉴런을 필터라고 부른다. 혹은 커널이라고도 부른다.
여기서는 케라스 API와 이름을 맞추어 뉴런 개수를 이야기할 때는 필터라 부르고, 입력에 곱해지는 가중치를 의미할 때는 커널이라고 부르겠다.
합성곱의 장점은 1차원이 아니라 2차원 입력에도 적용할 수 있다는 것이다.

입력이 2차원 배열이면 필터도 2차원이어야 한다. 이 그림에서 이 필터의 커널 크기는 (3,3)으로 가정한다. (커널 크기는 하이퍼파라미터이다.) 그다음 왼쪽 위 모서리에서부터 합성곱을 시작한다. 입력의 9개 원소와 커널의 9개 가중치를 곱한 후 (절편을 더하고) 1개의 출력을 만들어 낸다.
그다음에는 필터가 오른쪽으로 한 칸 이동하여 합성곱을 또 수행한다. 입력의 너비가 4이므로 더 이상 오른쪽으로 한 칸 이동할 수 없다. 이럴 때는 아래로 한 칸 이동한 다음 다시 왼쪽에서부터 합성곱을 수행한다. 그리고 다시 오른쪽으로 한 칸 이동한다.

합성곱은 마치 도장을 찍듯이 왼쪽 위에서 오른쪽 맨 아래까지 이동하면서 출력을 만든다. 계산식은 밀집층과 크게 다르지 않다. 입력과 가중치의 행과 열을 맞추어 곱셈하고 모두 더하는 게 전부이다. 필터는 그림에서 총 4번 이동할 수 있기 때문에 4개의 출력을 만든다.
이때 4개의 출력을 필터가 입력에 놓인 위치에 맞게 2차원으로 배치한다. 즉 왼쪽 위,오른쪽 위,왼쪽 아래, 오른쪽 아래 모두 4개의 위치에 해당 값을 놓는다. 이렇게 출력을 2차원으로 표현하면 (4,4) 크기의 입력을 (2,2) 크기로 압축한 느낌이 난다.

합성곱 계산을 통해 얻은 출력을 특별히 *특성 맵*이라고 부른다.
밀집층에서 여러 개의 뉴런을 사용하듯이 합성곱 층에서도 여러 개의 필터를 사용한다. 하나만 사용할 이유는 없다.
다음 그림에서처럼 여러 개의 필터를 사용하면 만들어진 특성 맵은 순서대로 차곡차곡 쌓인다.(2,2) 크기의 특성 맵을 쌓으면 3차원 배열이 된다. 다음 그림에서는 3개의 필터를 사용했기 때문에 (2,2,3) 크기의 3차원 배열이 된다.
밀집층에 있는 뉴런의 가중치가 모두 다르듯이 합성곱 층에 있는 필터의 가중치(커널)도 모두 다르다.
2차원 구조를 그대로 유지하면서 사용하기 때문에 합성곱 신경망이 이미지 처리 분야에서 뛰어난 성능을 발휘한다.

케라스 합성곱 층
케라스의 층은 모두 keras.layers 패키지 아래 클래스로 구현되어 있다. 합성곱 층도 마찬가지이다. 특별히 입력 위를 이동하는 합성곱은 Conv2D 클래스로 제공한다.
from tensorflow import keras
keras.layers.Conv2D(10,kernel_size=(3,3),activation='relu')
<keras.layers.convolutional.conv2d.Conv2D at 0x7f9848ad2980>
Conv2D 클래스의 첫 번째 매개변수는 필터(도장)의 개수이다. kernel_size 매개변수는 필터에 사용할 커널의 크기를 지정한다. 필터의 개수와 커널의 크기는 반드시 지정해야 하는 매개변수이다.
마지막으로 밀집층에서처럼 활성화 함수를 지정한다. 여기서는 렐루 함수이다.
특성 맵은 활성화 함수를 적용하기 전인가 후인가? : 후이다. 완전 연결 신경망에서처럼 합성곱 신경망에서도 종종 활성화 함수를 언급하지 않는다. 일반적으로 특성 맵은 활성화 함수를 통과한 값을 나타낸다. 합성곱에서는 활성화 출력이란 표현을 잘 쓰지 않는다.
커널의 크기는 어떻게 정하지? : 커널의 크기는 하이퍼파라미터이다. 따라서 여러 가지 값을 시도해 봐야 한다. 하지만 보통 (3,3)이나 (5,5) 크기가 권장된다.
케라스 API를 사용하면 합성곱 층을 사용하는 것이 어렵지 않다. 이전에 Dense 층을 사용했던 자리에 대신 Conv2D 층을 넣으면 된다. 다만 kernel_size와 같이 추가적인 매개변수들을 고려해야 한다.
그렇다면 합성곱 신경망의 정의는 무엇인가? 일반적으로 1개 이상의 합성곱 층을 쓴 인공 신경망을 합성곱 신경망이라고 부른다. 즉 꼭 합성곱 층만 사용한 신경망을 합성곱 신경망이라고 부르는 것은 아니다. 이전 장에서 보았듯이 클래스에 대한 확률을 계산하려면 마지막 층에 클래스 개수만큼의 뉴런을 가진 밀집층을 두는 것이 일반적이다.
패딩과 스트라이드
앞에 예에서 (4,4) 크기의 입력에 (3,3) 크기의 커널을 적용하여 (2,2) 크기의 특성 맵을 만들었다. 그런데 만약 커널 크기는 (3,3)을 그대로 사용하면서 (4,4) 크기의 특성맵을 만들고 싶으면 어떻게 해야할까
(4,4) 입력과 동일한 크기의 출력을 만들어내고 싶으며 마치 더 큰 크기의 입력에 합성곱하는 척해야 한다. 마치 (6,6)처럼 다룬다고 가정해 보겠다. 아래 그림처럼 (6,6) 크기이면 (3,3) 크기의 커널로 합성을 했을 때 출력의 크기는 (4,4)가 되는 것을 알 수 있다.

이렇게 입력 배열의 주위를 가상의 원소로 채우는 것을 *패딩*이라고 한다.
실제 입력값이 아니기 때문에 패딩은 0으로 채운다. 즉 (4,4) 크기의 입력에 0을 1개 패딩 하면 다음과 같은 (6,6) 크기의 입력이 된다. 패딩의 역할은 순전히 커널이 도장을 찍을 횟수를 늘려주는 것밖에는 없다. 실제 값은 0으로 채워져 있기 때문에 계산에 영향을 미치지는 않는다.
이렇게 입력과 특성 맵의 크기를 동일하게 만들기 위해 입력 주위에 0으로 패딩 하는 것을 *세임 패딩*이라고 한다. 합성곱 신경망에서는 세임 패딩이 많이 사용된다. 바꿔 말하면 입력과 특성 맵의 크기를 동일하게 만드는 경우가 아주 많다.
패딩 없이 순수한 입력 배열에서만 합성곱을 하여 특성 맵을 만드는 경우를 밸리드 패딩이라고 한다. 밸리드 패딩은 특성 맵의 크기를 줄일 수밖에 없다.
그렇다면 패딩을 하는 이유는?? 만약 패딩이 없다면 위의 예에서 (4,4) 크기의 입력에 패딩 없이 합성곱을 한다면 왼쪽 위 모시리의 3은 커널 도장에서 딱 한 번만 찍힌다. 사실 네모서리에 있는 다른 3개의 값도 마찬가지이다. 결국 모서리에 있는 정보가 특성 맵으로 잘 전달되지 않을 가능성이높다. 반면 가운데 있는 정보는 두드러지게 표현된다.
패딩을 하면 할수록 이 두 공간의 차이는 줄어든다.
케라스 Conv2D 클래스에서는 padding 매개변수로 패딩을 지정할 수 있다. 기본값은 valid로 밸리드 패딩을 나타낸다. 세임 패딩을 사용하려면 same으로 지정한다.
keras.layers.Conv2D(10 , kernel_size=(3,3),activation = 'relu',padding='same')
<keras.layers.convolutional.conv2d.Conv2D at 0x7f983ec5c6a0>
지금까지는 좌우 위아래로 한 칸씩 이동했다. 하지만 두 칸씩 건너뛸 수도 있다. 이렇게 두 칸씩 이동하면 만들어지는 특성 맵의 크기는 더 작아진다.
이런 이동의 크기를 *스트라이드*라고 한다. 기본으로 스트라이드는 1이다. 즉 한 칸씩 이동한다. 이 값이 케라스 Conv2D의 strides 매개변수의 기본값이다.
keras.layers.Conv2D(10,kernel_size=(3,3),activation='relu',padding='same',strides = 1)
<keras.layers.convolutional.conv2d.Conv2D at 0x7f983ec5d240>
strides 매개변수는 오른쪽으로 이동하는 크기와 아래쪽으로 이동하는 크기를 (1,1)과 같이 튜플을 사용해 각각 지정할 수 있다. 하지만 커널의 이동 크기를 가로세로 방향으로 다르게 지정하는 경우는 거의 없다. 또 1보다 큰 스트라이드를 사용하는 경우도 드물다. 대부분 기본값을 그대로 사용하기 때문에 strides 매개변수는 잘 사용하지 않는다.
풀링
풀링은 합성곱 층에서 만든 특성 맵의 가로세로 크기를 줄이는 역할을 수행한다.
하지만 특성 맵의 개수는 줄이지 않는다. 예를 들면 다음 그림처럼 (2,2,3) 크기의 특성 맵에 풀링을 적용하면 마지막 차원의 개수는 그대로 유지하고 너비와 높이만 줄어들어 (1,1,3) 크기의 특성 맵이 된다.

풀링도 합성곱처럼 입력 위를 지나가면서 도장을 찍는다. 위 그림에서는 (2,2) 크기로 풀링을 한다. 하지만 풀링에는 가중치가 없다. 도장을 찍은 영역에서 가장 큰 값을 고르거나 평균값을 계산한다.
이를 각각 *최대 폴링과 평균 폴링*이라고 한다. 풀링은 합성곱 층과 뚜렷이 구분되기 때문에 풀링 층이라고 부른다.
예를 들어 (4,4) 크기의 특성 맵이 있다고 가정해 보겠다. 여기에 (2,2) 최대 풀링을 적용하면 절반으로 크기가 줄어든다.

특성 맵이 여러개라면 동일한 작업을 반복한다. 즉 10개의 특성 맵이 있다면 풀링을 거친 특성 맵도 10개가 된다.
눈여겨볼 점은 풀링 영역이 두 칸씩 이동했다는 점이다. 합성곱에서는 커널이 한 칸씩 이동했기 때문에 겹치는 부분이 있었지만 풀링에서는 겹치지 않고 이동한다. 따라서 풀링의 크기가 (2,2)이면 가로세로 두 칸씩 이동한다. 즉 스트라이드가 2이다. (3,3) 풀링이면 가로세로 세 칸씩 이동한다.
케라스에서는 MaxPooling2D 클래스로 풀링을 수행할 수 있다.
keras.layers.MaxPooling2D(2)
<keras.layers.pooling.max_pooling2d.MaxPooling2D at 0x7f983e94eef0>
평균 풀링을 제공하는 클래스는 AveragePooling2D이다. 최대값 대신 평균값을 계산하는 것만 빼면 MaxPooling2D와 동일하며 제공하는 매개변수도 같다.
많은 경우 평균 풀링보다 최대 풀링을 많이 사용한다. 평균 풀링은 특성 맵에 있는 중요한 정보를 희석시킬 수 있기 때문이다.
꼭 기억할 점은 풀링은 가로세로 방향으로만 진행한다. 특성 맵의 개수는 변하지 않고 그대로이다.
합성곱 신경망의 전체 구조
지금까지 배운 합성곱 층, 필터, 패딩, 스트라이드, 풀링 등 중요한 합성곱 신경망들을 합쳐 전체 구조를 그려 보겠다.
먼저 입력층 : 합성곱 층에서 사용할 커널의 크기는 (3,3) 크기이고 세임 패딩이므로 1픽셀이 입력데이터 주변에 추가되었다. 이때 패딩은 텐서플로에서 자동으로 추가하므로 수동으로 입력에 어떤 작업을 추가할 필요가 없다.
그다음 패딩이 추가된 입력에서 합성곱이 수행된다.

이 그림에서 합성곱의 필터는 3개이다. 각각 (3,3) 크기 가중치를 가지고 있으며 필터마다 절편이 하나씩 있다. 따로 언급하지 않는다면 합성곱의 스트라이드는 항상 1이다. 따라서 만들어지는 특성 맵의 크기는 입력과 동일한 (4,4)이다.
따라서 만들어지는 특성 맵의 크기는 입력과 동일한 (4,4)이다. 3개의 필터가 하나씩 합성곱의 출력을 만들고 이 출력이 합쳐져서 (4,4,3)크기의 특성 맵이 만들어진다. 밀집층과 마찬가지로 합성곱 층에서도 활성화 함수를 적용한다. 합성곱층은 활성화 함수로 렐루 함수를 많이 사용한다.
그 다음 풀링 층 : 풀링 층은 합성곱 층에서 만든 특성 맵의 가로세로 크기를 줄인다.보통(2,2) 풀링을 사용해 절반으로 줄인다. 특성 맵의 개수는 변하지 않으므로 (4,4,3) 에서 (2,2,3)으로 특성 맵 개수는 유지된 게 보인다.
풀링을 사용하는 이유는 합성곱에서 스트레이드를 크게 하여 특성 맵을 줄이는 것보다 풀링 층에서 크기를 줄이는 것이 경험적으로 더 나은 성능을 내기 때문이다.
합성곱 신경망은 이렇게 합성곱 층에서 특성 맵을 생성하고 풀링에서 크기를 줄이는 구조가 쌍을 이룬다.
밀집층 : 밀집층인 출력층에 이 3차원 배열 (2,2,3)을 전달하려면 1차원으로 펼쳐야 한다. 이 배열은 12개의 원소를 가진 1차원 배열이고 출력층의 입력이 된다.
출력층에는 3개의 뉴런을 두었다. 즉 3개의 클래스를 분류하는 다중 분류 문제이다.
출력층에서 계산된 값은 소프트맥스 활성화 함수를 거쳐 최종 예측 확률이 된다.
컬러 이미지를 사용한 합성곱
지금까지 입력을 2차원 배열이라고 가정했다. 이 장에서 다룰 패션 MNIST 데이터는 실제로 흑백 이미지이기 때문에 2차원 배열로 표현할 수 있다.
하지만 컬러이미지라면 어떨까? 컬러 이미지는 RGB(빨강,초록,파랑) 채널로 구성되어 있기 때문에 컴퓨터는 이를 3차원 배열로 표시한다.

하나의 컬러 이미지는 너비와 높이 차원 외에 깊이 차원(또는 채널 차원)이 있다. 예를 들어 앞의 예제에서 입력이 (4,4)가 아니라 (4,4,3)이 되는 거다. 마지막 3이 깊이가 된다.
깊이가 있는 입력에서 합성곱을 수행하기 위해서는 도장도 깊이가 필요하다. 즉 필터의 크기가 (3,3,3)이 된다.

이 합성곱의 계산은 (3,3,3) 영역에 해당하는 27개의 원소에 27개의 가중치를 곱하고 절편을 더하는 식이 된다. 기본적으로 2차원 합성곱과 같지만 도장이 입력의 깊이만큼 쑥 들어간다고 생각해 보자.

여기서 중요한 점은 입력이나 필터의 차원이 몇 개인지 상관없이 항상 출력은 하나의 값이라는 점이다. 즉 특성 맵에 있는 한 원소가 채워진다.
사실 케라스의 합성곱 층은 항상 이렇게 3차원 입력을 기대한다. 만약 패션 MNIST 데이터처럼 흑백 이미지일 경우에는 깊이 차원이 1인 3차원 배열로 변환하여 전달한다.
예를 들어 (28,28) 크기의 2차원 배열을 (28,28,1) 크기의 3차원 배열로 반환한다. 원소 개수는 동일하면서 차원만 맞춘 거다.
이와 비슷한 경우가 또 있다. 합성곱 층-풀링 층 다음에 다시 또 합성곱 층이 올 때이다. 예를 들어 첫 번째 합성곱의 층의 필터 개수가 5개라고 가정하여 첫 번째 풀링 층을 통과한 특성 맵의 크기가 (4,4,5)라고 해 보자.
두 번째 합성곱 층에서 필터의 너비와 높이가 각각 3이라면 이 필터의 커널 크기는 (3,3,5)가 된다. 왜냐하면 입력의 깊이와 필터의 깊이는 같아야 하기 때문이다. 다음 그림처럼 (3X3X5 = 45개의 가중치를 곱하고 절편을 더한) 이 합성곱의 결과는 1개의 출력을 만든다.

두 번째 합성곱 층의 필터 개수가 10개라면 만들어진 특성 맵의 크기는 (2,2,10)이 될 것이다. 이렇게 합성곱 신경망은 너비와 높이는 점점 줄어들고 깊이는 점점 깊어지는 것이 특징이다.
그리고 마지막에 출력층 전에 특성 맵을 모두 펼쳐서 밀집층의 입력으로 사용한다.
합성곱 신경망에서 필터는 이미지에 있는 어떤 특징을 찾는다고 생각할 수 있다. 처음에는 간단한 기본적인 특징(직선,곡선)을 찾고 층이 깊어질수록 다양하고 구체적인 특징을 감지할 수 있도록 필터의 개수를 늘린다. 또 어떤 특징이 이미지의 어느 위치에 놓이더라도 쉽게 감지할 수 있도록 너비와 높이 차원을 점점 줄여간다.