Pooling 계층에서의 im2col

 

입력 데이터에 풀링 적용 영역을 전개 (2x2 풀링)

 

풀링 계층도 앞서 설명한 합성곱 계층과 마찬가지로 im2col을 사용해 입력 데이터를 전개한다.

이번에는 코드와 함께 설명하도록 하겠다.

 

우선, pooling 계층을 구현하기 위해서는 im2col 함수가 필요하다.

import numpy as np

def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
    """
    다수의 이미지를 입력받아 2차원 배열로 변환한다(평탄화).

    Parameters
    ----------
    input_data : 4차원 배열 형태의 입력 데이터(이미지 수, 채널 수, 높이, 너비)
    filter_h : 필터의 높이
    filter_w : 필터의 너비
    stride : 스트라이드
    pad : 패딩

    Returns
    -------
    col : 2차원 배열
    """
    N, C, H, W = input_data.shape
    out_h = (H + 2 * pad - filter_h) // stride + 1
    out_w = (W + 2 * pad - filter_w) // stride + 1

    img = np.pad(input_data, [(0, 0), (0, 0), (pad, pad), (pad, pad)], 'constant')
    col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))

    for y in range(filter_h):
        y_max = y + stride * out_h
        for x in range(filter_w):
            x_max = x + stride * out_w
            col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]

    col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N * out_h * out_w, -1)
    return col

 

 

이후 풀링 계층의 진행 순서는 다음과 같다.

(선택사항). 입력 데이터가 만약 4차원이 아니라면 im2col의 입력값으로 바꾸기 위해서 4차원으로 변경한다.

1. im2col로 4차원 데이터를 행렬로 (2차원) 변경한다.

2. np.max를 사용하여 각 행에서의 최대값을 취한다.

3. 최대값만 취한 데이터를 다시 입력 데이터의 shape으로 변경한다.

 

 


 

 

 

그럼 다음의 그림과 같은 예제를 코드로 진행하도록 하겠다.

 

2x2 크기의 max-pooling 적용 예제

 

1. im2col로 4차원 데이터를 행렬로 (2차원) 변경한다.

# 예제 데이터 생성
x = np.array([[[[1,2,3,0],[0,1,2,4],[1,0,4,2],[3,2,0,1]],
              [[3,0,6,5],[4,2,4,3],[3,0,1,0],[2,3,3,1]],
              [[4,2,1,2],[0,1,0,4],[3,0,6,2],[4,2,4,5]]]])

x.ndim # 4차원
x.shape # (1,3,4,4)

# 1번 : im2col로 4차원 데이터를 행렬로 (2차원) 변경한다.
step_1 = im2col(x,2,2,2,0) # 입력 데이터(x)를 필터크기(2x2)와 stride(2), padding(0)으로 평탄화함
step_1

'''
array([[1., 2., 0., 1., 3., 0., 4., 2., 4., 2., 0., 1.],
       [3., 0., 2., 4., 6., 5., 4., 3., 1., 2., 0., 4.],
       [1., 0., 3., 2., 3., 0., 2., 3., 3., 0., 4., 2.],
       [4., 2., 0., 1., 1., 0., 3., 1., 6., 2., 4., 5.]])
'''

step_1.shape # (4,12)

# 1번_2 : 평탄화 된(im2col 사용) 행렬을 그림과 같은 2차원으로 변경한다.
step1 = step_1.reshape(-1,4)

'''
array([[1., 2., 0., 1.],
       [3., 0., 4., 2.],
       [4., 2., 0., 1.],
       [3., 0., 2., 4.],
       [6., 5., 4., 3.],
       [1., 2., 0., 4.],
       [1., 0., 3., 2.],
       [3., 0., 2., 3.],
       [3., 0., 4., 2.],
       [4., 2., 0., 1.],
       [1., 0., 3., 1.],
       [6., 2., 4., 5.]])
'''

step1.shape # (12,4)

 

그림의 전개에 해당하는 부분으로 보고 싶으면 이렇게 묶어 봐야함

im2col 함수를 사용한 변수의 결과는 위의 이미지로, 그림의 전개에 해당하는 부분처럼 이해하고 싶으면 사각형으로 봐야 한다. 실제 코드로 구현할 때는 채널1, 채널2, 채널3에 해당하는 수들을 전개하는 것이 아니라 채널1과 채널2, 채널3에 같은 위치(행)에 존재하는 것들을 일렬로 전개한다. 이때문에 2번째로 reshape을 해 준 array는 그림에서의 전개 후 이미지와는 약간 다르다.

 

 

2. np.max를 사용하여 각 행에서의 최대값을 취한다.

# 2. np.max를 사용하여 각 행에서의 최대값을 취한다.
step2 = np.max(step1,axis=1)
step2 # array([2., 4., 4., 4., 6., 4., 3., 3., 4., 4., 3., 6.])

 

여기서 np.max(,axis=1)을 사용하는 이유는 여기를 참고하길 바란다.

step2의 결과물도 그림의 최댓값 이후의 전개물과 동일하게 이해하려면 다음의 그림을 참고하면 된다.

 

주황색 : 채널1 / 초록색 : 채널2 / 파란색 : 채널3

 

 

3. 최대값만 취한 데이터를 다시 입력 데이터의 shape으로 변경한다.

# 3. 최대값만 취한 데이터를 다시 입력 데이터의 shape으로 변경한다.
step3 = step2.reshape(1,2,2,-1) # step2의 결과를 배치사이즈(1), 출력데이터크기(2x2), 채널(-1)로 reshape한다.
step3.transpose(0,3,1,2) #(1,2,2,3) : step3의 결과(N,H,W,C)를 (N,C,H,W)로 순서를 바꿔준다.

'''
array([[[[2., 4.],
         [3., 4.]],

        [[4., 6.],
         [3., 3.]],

        [[4., 4.],
         [4., 6.]]]])
'''

 

그럼 그림과 일치하게, 우리가 원하는 풀링 계층의 출력 데이터를 만들 수 있다.

위의 흐름을 따라서 Pooling class를 만들면 다음과 같다.

 

class Pooling :
    def __init__(self,pool_h,pool_w,stride=1,pad=0) :
        self.pool_h = pool_h
        self.pool_w = pool_w
        self.stride = stride
        self.pad = pad
        
    def forward(self,x) :
        N, C, H, W = x.shape
        out_h = int(1+(H - self.pool_h)/self.stride) # 공식 사용, padding은 Pooling에서 사용하지 않으므로 애초에 수식에서 뺌
        out_w = int(1+(W - self.pool_w)/self.stride) # 공식 사용, padding은 Pooling에서 사용하지 않으므로 애초에 수식에서 뺌
        
        col = im2col(x,self.pool_h,self.pool_w,self.stride,self.pad) # im2col로 평탄화해줌
        col = col.reshape(-1,self.pool_h*self.pool_w) # np.max를 수행하기 위해 reshape 해줌
        out = np.max(col,axis=1) # np.max 수행
        out = out.reshape(N,out_h,out_w,C).transpose(0,3,1,2) # (N,H,W,C)의 모양을 (N,C,H,W)로 바꿔줌
        return out
    
pool = Pooling(2,2,2) # pooling size(2x2), stride(2)의 풀링 계층 구현

pool.forward(x)

 

 

 

 

 

 


 

 

 

 

 

 

 

im2col을 사용하는 이유는 행렬 연산에 최적화 된 컴퓨터가 연산을 더욱 빠르게 하도록,

또 딥러닝에 많이 사용되는 GPU의 병렬 연산을 효율적으로 사용하기 위해서였다.

 

 

그런데 im2col과 완전연결신경망인 MLNN(Multi-Layer Neural Network)에서의 flatten은 정확히 무슨 차이가 있는걸까?

MLNN은 가중치를 갱신하면서 최적의 가중치를 찾아가며 신경망을 학습하고 이 때 입력 데이터의 모든 데이터가 사용된다. 따라서 초반에 2차원, 혹은 3차원의 입력 데이터를 flatten함으로써 모든 데이터가 신경망 전체를 아우르는 가중치의 학습 및 갱신에 사용된다고 볼 수 있다.

하지만 im2col은 결국 Conv층과 Pooling층의 연산 속도를 빠르게 하기 위해 사용하는 함수이고, 실질적으로 im2col을 사용한 후의 데이터는 Conv층의 합성곱을 수행하고 Pooling층의 기능을 한다. 또한 Conv층과 Pooling층은 각각의 filter를 갖고 있기 때문에 입력 데이터는 각각의 filter에 대해서만 영향을 받는다. 따라서 im2col과 flatten은 데이터를 압축시킨다는 역할은 같으나, im2col은 각 계층의 연산을 빠르게 해준다는 장점 이외의 데이터 변형, 또는 연산 변형은 전혀 없다. (정확하다고 볼 수는 없겠지만 느낌적으로 이 둘의 차이를 이해하길 바란다. 개인적인 해석으로 틀렸다면 꼭 알려주시길 바랍니다 크흡,,)

 

 

결론적으로 CNN은 MLNN과 달리 데이터를 flatten시키지 않아 데이터 형상을 유지하면서 Conv계층, Pooling계층의 연산(내부적으로는 im2col을 사용하여 연산속도를 빠르게 함)을 사용하며 이미지 자체를 학습할 수 있다.

 

 

 

 

 

MLNN(Multi-Layer Neural Network) : Affine(완전 연결) 계층으로 이뤄진 네트워크

여태 설명했던 신경망은 인접하는 모든 계층의 뉴런과 결합되어 있었다.

이를 완전연결(Fully-Connected)계층(Dense Layer라고도 함)이라고 하며,

완전히 연결된 계층을 Affine 계층이라는 이름으로 구현했다.

 

Affine 계층으로 이뤄진 신경망

 

 

 

CNN (Convolutional Neural Network)

CNN은 여태 배웠던 신경망이 Affine-ReLU 연결을 한 것과는 달리 Conv-ReLU-Pooling의 흐름으로 신경망이 구성된다.

 

CNN으로 이뤄진 신경망 (합성곱 계층, 풀링 계층 추가)

 

그런데 왜 CNN을 사용하는걸까?

 

 

 

Affine 계층의 문제점

Affine 계층은 학습을 진행할 때 데이터의 형상을 무시한다. 이미지 데이터는 가로, 세로, 색(RGB) 총 3차원의 데이터인데, Affine 계층으로 학습 시 이 3차원의 데이터를 1차원으로 평평하게 평탄화(Flatten)해야 학습을 할 수 있다. 이렇게 차원을 줄이다보면 유의미한 데이터를 정확하게 살릴 수 없다.

 

MLNN의 문제점

 

또한 위의 MLNN의 문제점 이미지를 살펴보면 글자의 형상을 고려하지 않고 학습을하기 때문에 데이터가 살짝만 모양이 바뀌어도 새롭게 학습을 시켜야 한다는 문제점이 있다. 따라서 데이터의 모든 정보를 최대한 손실 없이 학습 시키면서 데이터의 모양 변화에 큰 영향을 받지 않는 새로운 방법이 필요했고, 그에 대한 해답이 CNN이었다.

 

 

 

 

 


 

 

 

 

Conv 계층

Conv(합성곱) 계층의 입출력 데이터를 CNN에서는 특징 맵(Feature Map)이라고도 해서 Conv 계층의 입력 데이터를 입력 특징 맵(Input Feature Map), 출력 데이터를 출력 특징 맵(Output Feature Map)이라고 한다.

 

Conv 계층은 이름과 일치하게 합성곱 연산을 처리하며, 이는 이미지 처리에서 말하는 필터(커널(Kernel)이라고 주로 표현) 연산에 해당한다. 합성곱 연산은 Kernel의 Window를 일정 간격으로 이동하며 입력 데이터에 적용한다. 이러한 과정은 기존의 Affine 계층으로만 이루어진 신경망과 달리 데이터의 형상을 잃지 않으면서 학습을 진행할 수 있도록(이미지의 특징을 파악할 수 있도록) 도와준다. CNN도 기존 신경망과 동일하게 가중치와 편향이 존재하는데 필터를 구성하는 매개변수들이 가중치에 해당하고 편향은 항상 1x1로 하나만 존재한다.

 

합성곱 연산

 

 

 

 

필터 (Filter)

필터란 필터에 해당하는 특징이 데이터에 있는지를 검출해주는 함수로, 아래의 그림에서는 곡선이 필터이다.

이 필터를 기존의 데이터와 합성곱 연산을 진행하면 필터에 해당하는 특성을 갖고 있으면 결과 값으로 큰 값이 나오고, 특성을 가지지 않으면 결과값이 0에 가깝게 나오면서 입력데이터가 필터에 해당하는 특징을 갖고 있는지, 아닌지에 대한 여부를 알 수 있다.

 

필터(Filter)에 대한 이해를 돕기 위한 이미지

 

 

 

 

패딩 (Padding)

합성곱 연산을 하기 이전, 입력데이터 주변을 특정값으로 채워 늘리는 것을 패딩(Padding)이라고 한다. 패딩(Padding)은 주로 출력 데이터의 크기를 조정할 목적으로 사용한다. 합성곱 연산을 반복하다보면 어느 시점부터는 출력 데이터의 크기가 1이 되어 더이상 합성곱 연산을 진행할 수 없게 된다. (위의 이미지로만 봐도 4x4 크기의 입력 데이터가 합성곱 연산을 진행하면서 2x2의 데이터로 출력되는 것을 확인할 수 있다.) 따라서 패딩(Padding)을 사용하여 입력데이터의 외각의 크기를 늘려 출력 데이터의 크기가 줄어드는 것을 방지한다.

 

Padding 사용 시 합성곱 연산 : 출력 데이터의 크기가 입력 데이터의 크기와 동일

 

 

 

 

스트라이드 (Stride)

Stride는 입력데이터에 필터를 적용할 때 이동할 간격을 조절하는, 즉 필터가 이동할 간격을 뜻한다. Stride의 값을 키우면 키울수록 출력 데이터의 크기는 작아지며 Padding은 크게하면 할수록 출력 크기가 커진다.

 

Padding, Stride를 적용한 합성곱 연산

 

이렇게 Padding, Stride를 사용하면 출력 크기가 어떻게 되는지에 대한 수식은 다음과 같으며, 출력 크기에 해당하는 OH와 OW는 정수로 나타내져야 한다.

 

더보기

 

위의 수식을 이용한 예제로 한번 생각해보길 바란다.

 

 

 

 

 

3차원 데이터의 합성곱 연산

이미지 데이터는 가로, 세로, 색(RGB) 총 3차원 데이터라고 언급했다. 하지만 위에서 봤던 Conv 계층의 연산은 2차원 데이터로 진행했다. 그렇다면 이미지 데이터와 같은, 혹은 그 이상의 차원의 데이터같은 경우는 어떻게 모든 차원을 살리며 합성곱 연산을 진행할 수 있을까?

 

3차원 데이터 합성곱 연산의 예

 

이에 대한 해답은 위의 그림을 참고하면 된다. 입력 데이터의 3개의 데이터는 색(RGB) 채널 방향으로 특징 맵이 늘어났다. 즉, 각각의 색에 대한 정보를 담은 데이터로 늘어났다고 보면 된다. 그리고 이것을 각의 필터로 합성곱 연산을 진행하여 출력 데이터로 반환하면 된다. 그러면 출력데이터는 2차원의 형태지만 기존 3차원의 데이터를 모두 갖고 있는 데이터로 출력된다. 이러한 3차원 합성곱 연산에서 주의할 점은 입력 데이터의 채널 수와 필터의 채널 수가 같아야 한다는 사실이다. 예를 들어 RGB 3개의 채널을 갖고 있다면 필터의 채널 수도 3개여야하고, RB 2개의 채널만 갖고 있다면 필터의 채널 수도 2개여야 한다는 뜻이다.

 

 

 

 

 

합성곱 연산의 배치 처리

합성곱 연산 시 배치 처리된 데이터를 학습시키는 방법은 기존의 배치와 크게 다르지 않다. 우리가 CNN을 사용하는 이유는 주로 이미지 데이터와 같이 다차원의 데이터를 최대한 기존 데이터의 특징을 모두 학습시키기 위함인데, 배치를 사용하면 3차원이었던 데이터가 4차원으로 늘어나면 된다. 즉, 맨 앞에 배치 사이즈(N)를 추가하여 데이터를 학습시키면 된다.

 

합성곱 연산 처리

 

위의 이미지는 CNN의 연산 처리 과정으로 지금부터 하나하나 뜯어 설명해보도록 하겠다. 입력 데이터는 배치된 데이터로 N개의 데이터로 구성되어져있어 채널, 가로, 세로로 구성되어져 있는 N개의 데이터 묶음(배치)이다. 그렇다면 필터의 FN은 정확히 무엇을 의미하는걸까? 필터의 FN은 입력 데이터의 배치 사이즈와는 정확히 다르다. 이해를 돕기위해 다시 한번 맨 위의 필터(Filter) 설명에 사용했던 쥐의 이미지를 가져 오겠다.

 

1개의 필터에 대한 합성곱 연산

 

이 그림에 대한 필터는 곡선으로, 오직 1개 뿐이다. 그러면 출력 데이터는 특징 1개에 대한 정보만을 갖고 있는 데이터로 출력된다. 그렇다면 필터의 개수를 FN개로 늘려보면 어떨까?

 

FN개의 필터들

 

FN개의 필터들로 입력 데이터를 학습시키면 각각의 특징에 대한 출력 데이터를 FN개 얻을 수 있다. 이것들의 묶음을 블록 단위로 하여 다음 계층에 넘기는 것이 필터 (FN, C, FH, FW)의 의미이다. 입력 데이터와 FN개의 필터들과의 합성곱 연산으로 출력된 데이터 (FN, OH, OW)는 필터 개수만큼의 편향 (FN, 1, 1) 과의 연산을 통해 (FN, OH, OW)의 데이터로 출력될 수 있다. 여기서 초기 입력 데이터의 C(채널)가 출력 데이터들에 없는 이유는 합성곱을 진행하면서 C에 대한 데이터가 OH, OW에 모두 스며들었기 때문이다.

 

 

 

 

 

 


 

 

 

 

 

풀링(Pooling) 계층

풀링은 세로, 가로 방향의 공간을 줄이는 연산이다.

풀링의 종류로는 크게 max pooling과 average pooling이 있는데,

max pooling은 이미지를 선명하게 만드는 역할을, average pooling은 이미지를 부드럽게 만드는 역할을 한다.

 

Max-pooling을 사용하는 경우

 

Pooling은 주로 Conv 계층에서 출력데이터의 크기는 입력 데이터의 크기 그대로 유지하고, 풀링 계층에서 크기를 조절한다.

그렇다면 풀링 계층의 특징은 무엇일까?

 

  • 학습해야 할 매개변수가 없다.
  • 채널 수가 변하지 않는다.
  • 입력의 변화에 영향을 적게 받는다.

 

여기서 맨 마지막 입력의 변화에 영향을 적게 받는 이유는 아래의 이미지를 참고하면 이해하기 쉽다.

 

 

출력 데이터가 오른쪽으로 하나씩 밀려도 풀링 결과는 달라지지 않는다.

 

또한 Pooling은 parameter를 줄이기 때문에 신경망의 표현력이 줄어들어 Overfitting을 억제하고 연산을 비교적 간단하게 만들어준다.

 

 

 


 

 

 

CNN 구현 흐름 시각화 / 원하는 만큼 Conv, Pooling 계층 진행 후 완전연결계층 진행

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

층 깊이에 따른 특징 추출 변화

 

딥러닝의 흥미로운 점은 합성곱 계층이 여러 개 쌓이면서 층이 깊어질수록 더 복잡하고 추상화된 정보가 추출된다는 것이다. 위의 이미지를 보면 Conv1층은 단순하게 Edge에만 반응하지만 Conv3층은 텍스쳐에 반응하고 Conv5는 사물의 일부에 반응하도록 변화된다. 즉, 층이 깊어지면서 뉴런이 반응하는 대상이 단순한 모양에서 고급 정보로 변화해간다. 따라서 합성곱 계층이 깊어지면 깊어질수록 신경망은 사물의 의미를 이해하게 된다.

 

 

 

+ Recent posts