본문 바로가기
Computer Science/Deep Learning

[비전공자용] [Python] CNN(합성곱 신경망) - 합성곱 계층 구현

by 롱일스 2020. 7. 28.
반응형

 

이전 포스트에서 합성곱 계층과 풀링 계층에 대해 이론적으로 살펴보았습니다. 

2020/07/10 - [Computer Science/Deep Learning] - [비전공자용]합성곱 신경망 (CNN) - 합성곱 계층 & 풀링 계층

 

[비전공자용]합성곱 신경망 (CNN) - 합성곱 계층 & 풀링 계층

합성곱 신경망 Convolutional Neural Network, CNN은 이미지 인식과 음성 인식 등 다양한 곳에서 사용됩니다. 이제부터 CNN에 대해서 낱낱이 살펴보도록 합시다. # CNN 전체 구조 지금까지 공부했던 신경망��

huangdi.tistory.com

지금부터 두 계층을 직접 Python 코드로 구현하고자 합니다. 

 

1. 합성곱 계층 구현

합성곱 계층에서는 image to column (통상적으로 im2col 함수로 표현)을 함수로 만들어서 합성곱 계층을 구현할 때 많이 이용합니다. 

im2col로 입력 데이터를 전개하고 합성곱 계층의 필터를 1열로 전개해서
입력 데이터 X 필터 --> 두 행렬의 곱을 계산하면 됩니다. 완전연결 계층의 Affine 계층과 같은 방식으로 하면 됩니다.

im2col 함수를 먼저 Python 코드로 구현하면 아래와 같습니다. 

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

im2col로 입력 데이터를 2차원 배열로 전개하고 합성곱 계층을 Convolution이라는 클래스로 구현하면 아래와 같다. 

class Convolution:
	def __init__(self, W, b, stride=1, pad=0):
            self.W = W   # 필터(가중치)
            self.b = b   # 편향 bias
            self.stride = stride  #스트라이드
            self.pad = pad   #패딩
        
	def forward(self,x):
            FN, C, FH, FW = self.W.shape   #필터 개수, 채널, 필터 높이, 필터 너비
            N, C, H, W = x.shape
            out_h = int(1 + (H + 2*self.pad - FH) / self.stride)
            out_w = int(1 + (W + 2*self.pad - FW) / self.stride)

            col = im2col(x, FH, FW, self.stride, self.pad)  # 입력 데이터 전개
            col_W = self.W.reshape(FN, -1).T  # 2차원 배열로 필터 전개
            out = np.dot(col, col_W) + self.b  # 전개된 두 행렬의 곱 

            out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)
            # (N, H, W, C)  --->  (N, C, H, W) 
            #  0, 1, 2, 3   --->   0, 3, 1, 2  로 transpose 이용해서 인덱스로 축 순서 변경해줌

            return out

위 코드에서 reshape(FN, -1)에 대해 설명하고 지나가겠습니다. 

원래 self.W인 필터는 (FN, C, FH, FW)의 4차원 형상입니다.
reshape 함수를 이용해서 2차원 배열인 (FN, -1)로 전개하게 한다는 것은 
결국 (FN, FN*C*FH*FW/FN) 의 형상의 배열로 만들어준다는 얘기가 됩니다.

더 이해가 잘 가도록 예를 들어서, A = (10, 5, 4, 3)의 형상을 한 다차원 배열 A가 있다고 합시다.
배열 A의 총 원소의 수는 10*5*4*3=600개죠?

A.reshape(10, -1)는 (10, 60)의 형상을 가진 배열로 만들어주면서
형상이 변환된 후에도 원소 수가 똑같이 유지가 됩니다.

여기까지 reshape 함수에 대한 설명이었고요, 마저 Convolution 합성곱 계층을 구현해봅시다.

backward 역전파 구현도 Convolution 클래스 내에서 해야 하는데 이 부분은 Affine 계층과 동일하고 im2col이 아니라 col2im을 사용해야 한다는 점만 다릅니다. 아래 코드를 확인해 보세요.

class Convolution:
    def __init__(self, W, b, stride=1, pad=0):
        self.W = W
        self.b = b
        self.stride = stride
        self.pad = pad
        
        # 중간 데이터(backward 시 사용)
        self.x = None   
        self.col = None
        self.col_W = None
        
        # 가중치와 편향 매개변수의 기울기
        self.dW = None
        self.db = None

    def forward(self, x):
        FN, C, FH, FW = self.W.shape
        N, C, H, W = x.shape
        out_h = 1 + int((H + 2*self.pad - FH) / self.stride)
        out_w = 1 + int((W + 2*self.pad - FW) / self.stride)

        col = im2col(x, FH, FW, self.stride, self.pad)
        col_W = self.W.reshape(FN, -1).T

        out = np.dot(col, col_W) + self.b
        out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)

        self.x = x
        self.col = col
        self.col_W = col_W

        return out

    def backward(self, dout):
        FN, C, FH, FW = self.W.shape
        dout = dout.transpose(0,2,3,1).reshape(-1, FN)

        self.db = np.sum(dout, axis=0)
        self.dW = np.dot(self.col.T, dout)
        self.dW = self.dW.transpose(1, 0).reshape(FN, C, FH, FW)

        dcol = np.dot(dout, self.col_W.T)
        dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)

        return dx

 참고로 col2im 함수를 파이썬으로 구현하면 아래와 같습니다.

def col2im(col, input_shape, filter_h, filter_w, stride=1, pad=0):
    """(im2col과 반대) 2차원 배열을 입력받아 다수의 이미지 묶음으로 변환한다.
    
    Parameters
    ----------
    col : 2차원 배열(입력 데이터)
    input_shape : 원래 이미지 데이터의 형상(예:(10, 1, 28, 28))
    filter_h : 필터의 높이
    filter_w : 필터의 너비
    stride : 스트라이드
    pad : 패딩
    
    Returns
    -------
    img : 변환된 이미지들
    """
    N, C, H, W = input_shape
    out_h = (H + 2*pad - filter_h)//stride + 1
    out_w = (W + 2*pad - filter_w)//stride + 1
    col = col.reshape(N, out_h, out_w, C, filter_h, filter_w).transpose(0, 3, 4, 5, 1, 2)

    img = np.zeros((N, C, H + 2*pad + stride - 1, W + 2*pad + stride - 1))
    for y in range(filter_h):
        y_max = y + stride*out_h
        for x in range(filter_w):
            x_max = x + stride*out_w
            img[:, :, y:y_max:stride, x:x_max:stride] += col[:, :, y, x, :, :]

    return img[:, :, pad:H + pad, pad:W + pad]

 

지금까지 Python을 이용해서 합성곱 계층을 구현하는 방법을 알아봤습니다. 

다음 포스트에서는 Python을 이용해서 풀링 계층을 구현하는 방법을 알아보도록 하겠습니다~~

2020/07/28 - [Computer Science/Deep Learning] - [비전공자용] [Python] CNN(합성곱 신경망) - 풀링 계층 구현

 

[비전공자용] [Python] CNN(합성곱 신경망) - 풀링 계층 구현

이번 포스트에서는 지난 포스트(CNN-합성곱 계층 구현)에 이어서 CNN에서의 풀링 계층을 Python으로 구현해보려 합니다. 2. 풀링 계층 구현 합성곱 계층과 마찬가지로 풀링 계층에서도 im2col 함수를 �

huangdi.tistory.com

[출처] Deep Learning from Scratch, ゼロ から作る

728x90
반응형