합성곱 신경망(CNN)

Jiyoon·2023년 2월 2일
1

Deep Learning

목록 보기
6/6

전체 구조

이미지 인식 분야에서 딥러닝을 활용한 기법은 CNN을 기초로 한다

완전연결 계층(Affine 계층) & CNN

완전연결: 인접하는 계층의 모든 뉴런과 결합되어 있는 신경망
완전연결 계층: ‘Affine - ReLU’

CNN 계층: ‘Conv - ReLU - (Pooling)’
→ 출력에 가까운 층에서는 ‘Affine - ReLU’ 구성을 사용한다

완전연결 계층의 문제점

데이터의 형상이 무시된다
ex) 입력 데이터가 이미지인 경우(세로, 가로, 채널) 완전연결 계층에 입력할 때는 평평한 1차원 데이터로 평탄화해줘야 한다
→ 3차원 형상에는 공간적 정보가 담겨있는데 이를 무시하고 모든 입력데이터를 동등한 뉴런으로 취급하는 것

합성곱 계층은 형상을 유지한다(이미지처럼 형상을 가진 데이터를 제대로 이해할 가능성이 있는 것)



합성곱 계층

각 계층 사이에 3차원 데이터와 같은 입체적인 데이터가 흐른다

합성곱 연산

필터의 윈도우를 일정 간격으로 이동해가며 입력데이터에 적용한다(단일 곱셈-누산)
편향(1 X 1)은 필터를 적용한 모든 원소에 더한다

  • 패딩
    - 출력 크기를 조정할 목적(합성곱 연산은 거칠 때마다 작아지게 됨)
    - 입력 데이터 주변을 특정 값(0)으로 채운다
    - 입력 데이터의 공간적 크기를 고정한 채로 다음 계층에 전달 가능
  • 스트라이드
    - 필터를 적용하는 위치의 간격
    - 스트라이드를 키우면 출력 크기는 작아진다

출력 크기

딱 나눠떨어지지 않을 때는 가장 가까운 정수로 반올림한다

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

채널 쪽으로 특징 맵이 여러개 있다면 입력 데이터와 필터의 합성곱 연산을 채널마다 수행한 후 더한다

  • 입력 데이터의 채널 수 = 필터의 채널 수
  • 모든 채널의 필터 크기가 같아야 한다

필터를 다수 사용하게 되면 출력 맵도 FN개가 생성된다 출력 데이터(FN, OH, OW)
→ 필터의 가중치 데이터는 4차원 데이터(출력 채널 수, 입력 채널 수, 높이, 너비)



배치 처리

신경망 처리에서 배치 처리 했던 것과 같이, 합성곱 연산에서도 배치 처리를 지원한다
각 계층을 흐르는 데이터의 차원을 하나 늘려 4차원 데이터로 저장(데이터 수, 채널 수, 높이, 너비)





풀링 계층

가로, 세로 방향의 공간을 줄이는 연산이다
풀링의 윈도우 크기 = 스트라이드 크기

최대 풀링: 특정 영역 내에서 가장 큰 원소 하나를 꺼내기
이미지 인식 분야에서는 최대 풀링을 주로 사용

평균 풀링: 특정 영역 내에서의 평균 계산

💡 풀링 계층의 특징

  • 학습해야 할 매개변수가 없다
  • 채널 수가 변하지 않는다
  • 입력의 변화에 영향을 적게 받는다(강건하다)
    → 입력 데이터가 조금 변해도 풀링의 결과는 잘 변하지 않는다



합성곱/풀링 계층 구현하기

CNN은 4차원 데이터를 다뤄서 for 문을 겹겹이 쌓아서 연산해야될 것 같지만, im2col이라는 트릭을 사용하면 단순해진다

im2col

입력 데이터를 필터링 하기 좋게 전개하는 함수이다
3차원 입력 데이터(배치 안의 데이터 수까지 포함하면 4차원) → 2차원 행렬

입력 데이터를 필터 크기만큼 3부분으로 나누어(3 X 3) 2차원 행렬로 만든다
→ 나눠진 영역이 겹치는 경우가 대부분
→ im2col로 전개한 후의 원소 수가 원래 블록의 원소 수보다 많아짐(메모리 더 많이 소비)

입력 데이터를 im2col로 전개하고, 필터를 세로로 1열로 전개한 후에는 Affine 계층에서 한 것과 같이 두 행렬을 계산하면 된다

출력한 결과는 2차원 행렬이지만 CNN은 데이터를 4차원 배열로 저장하므로 2차원 출력 데이터를 4차원으로 변형한다

합성곱 계층 구현하기

im2col 함수의 인터페이스:

im2col(input_data, filter_h, filter_w, stride = 1, pad = 0)
# input_data: (데이터 수, 채널 수, 높이, 너비) 입력 데이터
# filter_h: 필터의 높이
# filter_w: 필터의 너비
# stride, pad: 스트라이드, 패딩

im2col을 사용한 합성곱 계층 구현:

class Convolution:
    def __init__(self, W, b, stride=1, pad=0):
        self.W = W
        self.b = b
        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)
        
				# 입력 데이터를 (FH, FW) 필터 사이즈만큼 9개로 나눈다
        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
        
				# 축 순서 변경하기 (0, 1, 2, 3) -> (0, 3, 1, 2)
        out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)
        
        return out

reshape에 -1을 지정하면 다차원 배열의 원소 수가 변환 후에도 똑같이 유지되도록 적절히 묶어준다
ex) (10, 3, 5, 5) → (10, 75)

마지막으로 transpose 함수를 사용해 출력 데이터를 적절한 형상으로 바꿔준다

im2col로 전개한 덕분에 완전연결 계층의 Affine 계층과 거의 비슷하게 구현 가능하다

합성곱 계층의 역전파에서는 col2im 함수를 사용하면 된다

풀링 계층 구현하기

im2col을 사용해 입력 데이터를 전개하지만, 채널 쪽이 독립적이라는 점에서 합성곱 계층 때와 다르다

입력 데이터 전개, 행별 최댓값 구하기, 적절한 모양으로 성형

풀링 계층 구현:

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)
        out_w = int(1 + (W - self.pool_w) / self.stride)
        
        # 전개 (1) ex)풀링 크기 2x2일 때:
        col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad) # 2x2로 나누고
        col = col.reshape(-1, self.pool_h * self.pool_w) # 1x4로 변환
        
        # 최댓값 (2)
        out = np.max(col, axis=1) # np.max의 축 인수로 지정한 축마다 최댓값을 구할 수 있다
        
        # 성형 (3)
        out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)
        
        return out


CNN 구현

CNN 네트워크 순서: ‘Convolution - ReLU - Pooling - Affine - ReLU - Affine - Softmax’

SimpleConvNet 1번째:

class SimpleConvNet:
    def __init__(self, input_dim = (1, 28, 28)
    			conv_param={'filter_num':30, 'filter_size':5,
                			'pad':0, 'stride':1},
                hidden_size=100, output_size=10, weight_init_std=0.01):
        filter_num = conv_param['filter_num']
        filter_size = conv_param['filter_size']
        filter_pad = conv_param['pad']
        filter_stride = conv_param['stride']
        input_ize = input_dim[1]
        conv_output_size = (input_size - filter_size + 2 * filter_pad) \
        					filter_stride + 1
        pool_output_size = int(filter_num * (conv_output_size/2) * (conv_output_size/2))
  • 합성곱 계층의 하이퍼파라미터 설정 및 변수 할당
  • 합성곱 계층의 출력 크기를 계산한다

SimpleConvNet 2번째:

self.params = {}
self.params['W1'] = weight_init_std * \
					np.random.randn(filter_num, input_dim[0], filter_size, filter_size)
self.params['b1'] = np.zeros(filter_num)
self.params['W2'] = weight_init_std * \
					np.random.randn(filter_num, input_dim[0], filter_size, filter_size)
self.params['b2'] = np.zeros(filter_num)
self.params['W3'] = weight_init_std * \
					np.random.randn(filter_num, input_dim[0], filter_size, filter_size)
self.params['b3'] = np.zeros(filter_num)
  • 합성곱 계층, 완전연결 계층의 가중치 매개변수 초기화

SimpleConvNet 3번째:

self.layers = OrderedDict()
self.layers['Conv1'] = Convolution(self.params['W1'],
									self.params['b1']
                                    conv_params['stride']
                                    conv_parmas['pad']
self.layers['Relu1'] = Relu()
self.layers['pool1'] = Pooling([ppl_h=2, pool_w=2, stride=2)
self.layers['Affine1'] = Affine(self.params['W2']
								self.params['b2'])
self.layers['Relu2'] = Relu()
self.layers['Affine2'] = Affine(self.params['W3']
								self.params['b3']
                                
self.last_layer = SoftmaxWithLoss()
  • layers에 계층들을 차례대로 추가하고 SoftmaxWithLoss는 last_layer라는 별도 변수에

SimpleConvNet 4번째:

def predict(self, x):
        for layer in self.layers.values():
            x = layer.forward(x)
        return x
        
def loss(self, x, t):
    y = self.predict(x)
    return self.last_layer.forward(y, t)

def gradient(self, x, t):
    # 순전파
    self.loss(x, t)
    
    # 역전파
    dout = 1
    dout = self.last_layer.backward(dout)
    
    layers = list(self.layers.values())
    layers.reverse()
    
    for layer in layers:
        dout = layer.backward(dout)
        
    # 결과 저장
    grads = {}
    grads['W1'] = self.layers['Conv1'].dW
    grads['b1'] = self.layers['Conv1'].db
    grads['W2'] = self.layers['Affine1'].dW
    grads['b2'] = self.layers['Affine1'].dW
    grads['W3'] = self.layers['Affine2'].dW
    grads['b3'] = self.layers['Affine2'].dW
    
    return grads
  • predict, loss 함수 구현
  • 오차역전파법으로 매개변수의 기울기 구현



CNN 시각화

MNIST 데이터셋으로 간단한 CNN 학습을 해보았는데, 그때 1번째 층의 합성곱 계층의 가중치는 그 형상이 (30, 1, 5, 5)였다.(필터 30개, 채널 1개, 5 X 5 크기)

학습을 마친 필터는 규칙성 있는 이미지가 되었다
규칙성 있는 필터는 ‘에지’(색상이 바뀐 경계선)와 ‘블롭’(국소적으로 덩어리진 영역)을 보고 있다

1번째 층의 합성곱 계층에서는 에지나 블롭 등의 저수준 정보가 추출되지만 CNN의 각 계층이 깊어질수록 추출되는 정보는 더 추상화된다

→ 1번째 층은 에지와 블롭, 3번째 층은 텍스처, 5번째 층은 사물의 일부 등에 뉴런이 반응한다.

대표적인 CNN

LeNet

  • 손글씨 숫자를 인식하는 네트워크
  • 활성화 함수로 시그모이드 함수를 사용하는데 반해, 현재의 CNN에서는 ReLU를 사용한다

AlexNet

  • 활성화 함수로 ReLU를 이용
  • LRN이라는 국소적 정규화를 실시하는 계층 이용
  • 드롭아웃 사용

0개의 댓글

관련 채용 정보