* 모든 코드는 제 깃허브 (cdjs1432/DeepLearningBasic: Deep Learning from scratch)에서 확인할 수 있습니다.

 

 

이번 시간부터는, Convolutional Neural Network를 구현해 보겠습니다.

그리고 이번 차시에서는 우선 간단하게 Convoluton Layer을 구현해 보도록 합시다.

 

 

 

Convolution Layer

Convolution Layer는 일반적으로는 5*5, 3*3, ... 정도의 크기를 가진 filter를, 대략 32개, 64개, ... 만큼 가지게 됩니다.

원래의 Mul Layer이나 Add Layer들이 1차원 크기의 parameter를 가진 것과 다르게, 이 친구는 무려 3차원 크기 (channel도 고려하면 4차원 크기)의 parameter를 가지게 됩니다.

그 점을 반영해서, 일단 ConvLayer의 Init부분부터 짜봅시다.

 

class ConvLayer:
    def __init__(self, filters, kernel_size, stride=1, pad=0, initializer='he', reg=0):
        self.activation = False
        self.reg = reg
        self.x = None
        self.param = None
        self.grad = None
        self.stride = stride
        self.pad = pad
        self.init = initializer
        self.kernel_size = kernel_size
        self.filters = filters
        self.col = None
        self.col_param = None
        if type(kernel_size) == int:
            kernel_size = (kernel_size, kernel_size)
        self.out = (filters, *kernel_size)

원래 다른 Layer이 공통적으로 가지는 reg, x, ... 등을 제외하고, 지금 단계에서 봐야 할 변수들은 (일단은) kernel_size, filters, self.out이 되겠습니다.

 

kernel_size는 Convolution Layer의 크기를 의미하고, filters는 filter의 개수를 의미합니다.

저 아래 if type... 부분은, kernel_size를 int형으로 입력했을 경우 out을 제대로 넣기 위한 부분입니다.

보통 conv layer는 3*3, 4*4... 와 같이 정사각형 모양이기에, 저렇게 input을 받아도 되도록 만들어 두었습니다.

 

self.out은 parameter가 가지는 shape를 의미합니다.

 

 

 

다음은, train 함수에서 이 Layer가 연산되기 위해 parameter size를 변환하는 코드를 보겠습니다.

    def train(self, x_train, y_train, optimizer, epoch, learning_rate, skip_init=False):
        if not skip_init:
            in_size = x_train.shape[1:]
            for name in self.layers:
                if not self.layers[name].activation:
                    out_size = (self.layers[name].out,)
                    if isinstance(self.layers[name], ConvLayer):
                        out_size = out_size[0]
                        fn, fh, fw = out_size
                        c, h, w = in_size
                        size = (fn, c, fh, fw)
                        out_h = int(1 + (h + 2 * self.layers[name].pad - fh) / self.layers[name].stride)
                        out_w = int(1 + (w + 2 * self.layers[name].pad - fw) / self.layers[name].stride)
                        in_size = (fn, out_h, out_w)

코드를 잠깐 설명해 보겠습니다.

 

우선, out_size[0]은 아까 받았던 self.out을 꺼내줍니다.

그러면, filter의 개수, filter의 높이, filter의 너비를 알 수 있습니다.

또한, in_size 안에는 input image의 channel, 높이, 너비를 알 수 있습니다.

 

그리고, 우리는 필터의 크기를 (filter 개수, channel, 높이, 너비)로 만들 것입니다.

(참고: channel을 위처럼 앞에 놓는 것을 channel_first라 하고, (fn, fh, fw, c) 처럼 뒤에 놓는 것을 channel_last라고 합니다. 이번 구현에선 channel_first로 구현합니다.)

 

다음은 out_h, out_w입니다.

위 out_h, out_w는 Convolution 연산 이후에 나오는 output의 크기입니다.

아직 우리는 padding layer와 stride를 구현하진 않았으나, 그냥 있는 셈 치고 out_h와 out_w를 계산해 봅시다.

 

이미지의 가로 크기를 w, filter의 가로 크기를 fw라 해 봅시다.

만약 이미지를 padding한다면 가로 크기는 w + 2*pad가 될 것이고, stride=1인 상태라면 연산 횟수는 1 + w + 2*pad - fw가 될 것입니다.

하지만 여기서 stride까지 생각해 준다면, 연산 횟수는 stride의 크기만큼 나눠준 값으로 바뀔 것이므로,

stride를 포함한 연산 횟수는 1 + (w + 2 * pad - fw) / stride가 됩니다.

그리고, output의 크기는 연산 횟수와 동일하므로, out_w는 위의 식으로 정리가 됩니다.

마찬가지의 방식으로, out_h도 동일하게 계산이 가능합니다.

 

그 뒤, 다음 layer가 받을 in_size의 크기를 (fn, out_h, out_w)로 두면서 size 연산을 끝내 줍니다.

 

 

그런데, 이렇게 연산을 하기 위해서는, Flatten Layer도 필요합니다.

사실 지금까지는 우리가 이미 Flatten된 MNIST dataset을 사용하고 있었기 때문에 Flatten Layer가 필요하지 않았지만, Convolution Layer를 구현하기 위해선 Flatten Layer가 필요합니다.

4차원의 이미지를, Fully-connected Layer 및 Softmax 함수가 받을 수 있게 1차원 벡터화 시키는 것입니다.

 

Flatten 구현은 간단하니 지금 바로 해봅시다.

class Flatten:
    def __init__(self):
        self.shape = None
        self.activation = True

    def forward(self, x):
        return x.reshape(x.shape[0], -1)

    def backward(self, dout):
        return dout.reshape(-1, *self.shape)

 

예, 그렇습니다. forward와 backward는 그저 받았던 input을 1차원으로 바꿔주고, 돌아오는 dout을 원래 형태로 펴주기만 하면 됩니다.

그러기 위해서, self.shape를 만들어서 train 전처리 과정에서 shape를 지정해 주도록 합시다.

 

                elif isinstance(self.layers[name], Flatten):
                    self.layers[name].shape = in_size
                    tmp_size = 1
                    for items in in_size:
                        tmp_size *= items
                    in_size = (tmp_size,)

(Model.py - train함수 - if not activation 뒷부분)

Flatten 함수는 이전 Layer의 input size - in_size를 shape로 가지게 됩니다.

그리고, 해당 shape를 가졌을 때의 1차원 벡터의 크기는 shape에 있는 모든 원소의 곱이므로, shape의 원소를 죄다 곱해 줍니다.

그리고 해당 값을 다음 layer의 in_size로 바꿔주면 끝입니다.

 

 

이제 Convolution Layer의 연산을 다룰 차례입니다만, 이 부분은 다음 포스트에서 더 자세히 다루도록 하겠습니다.

Convolution Layer의 연산이 그렇게까지 단순하게 이뤄지진 않기 때문입니다. (물론, 그냥 곱셈이긴 하지만 말이죠...)

 

 

 

+ Recent posts