ch.7 합성곱 신경망(CNN)(2)

pasongtak·2024년 5월 27일

딥러닝

목록 보기
10/11

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

4차원 배열

>>> x = np.random.rand(10, 1, 28, 28) # 무작위로 데이터 생성
>>> x.shape
(10, 1, 28, 28)

>>> x[0].shape # 첫 번째 데이터에 접근
(1, 28, 28)

>>> x[0, 0] # 첫 번째 데이터의 첫 채널의 공간 데이터에 접근

im2col로 데이터 전개하기

  • 넘파이에 for문을 사용하면 성능이 떨어지기 때문에 im2col이라는 편의 함수를 사용한다.
  • im2col: 입력 데이터를 필터링(가중치 계산) 하기 좋게 전개하는(펼치는) 함수
    • 필터 적용 영역이 겹치게 되면 im2col로 전개한 후의 원소 수가 원래 블록의 원소 수보다 많아져 메모리를 더 많이 소비하는 단점이 있다.
  • 합성곱 연산의 필터 처리 상세 과정
    • 합성곱 계층의 필터(가중치)를 세로로 1열로 전개하고 im2col로 전개한 데이터와 행렬 곱을 계산한다.
    • 출력한 2차원 행렬을 4차원 배열로 변형한다.

합성곱 계층 구현하기

  • im2col 함수의 인터페이스
im2col(input_data, filter_h, filter_w, stride=1, pad=0)
  • input_data: (데이터 수, 채널 수, 높이, 너비)의 4차원 배열로 이뤄진 입력 데이터
  • filter_h: 필터의 높이
  • filter_w: 필터의 너비
  • stride: 스트라이드
  • pad: 패딩
  • 합성곱 계층의 forward 처리
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)
        
        # 입력 데이터를 im2col로 전개, 필터를 reshape을 사용해 2차원 배열로 전개
        # 두 행렬의 곱 구하기
        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)
        
        return out
  • 필터(가중치), 편향, 스트라이드, 패딩을 인수로 받아 초기화
  • 필터: (FN-필터 개수, C-채널, FH-필터 높이, FW-필터 너비)
  • reshape에 -1을 지정하면 다차원 배열의 원소 수가 변환 후에도 똑같이 유지되도록 묶어준다.
  • transpose 함수: 인덱스(번호)로 다차원 배열의 축 순서를 바꿔주는 함수
  • 합성곱 계층의 backward 처리
    • 합성곱 계층의 역전파에서는 im2col을 역으로 처리해야 하므로, col2im 함수를 사용한다.

풀링 계층 구현하기

  • 채널 쪽이 독립적이라는 점이 합성곱 계층과 다르다.
  • 풀링 계층 구현
    1. 입력 데이터를 전개한다.
    2. 행별 최댓값을 구한다.
    3. 적절한 모양으로 성형한다.
  • 풀링 계층의 forward 처리
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, w):
    	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)
        col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
        col = col.reshape(-1, self.pool_h*self.pool_w)
        
        # 최댓값(2)
        out = np.max(col, axis=1)
        
        # 성형(3)
        out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)
        
        return out
  • 풀링 계층의 backward 처리: ReLU 계층을 구현할 때 사용한 max의 역전파 참고

CNN 구현하기

  • 초기화 때 받는 인수

    • input_dim: 입력 데이터(채널 수, 높이, 너비)의 차원
    • conv_param: 합성곱 계층의 하이퍼파라미터(딕셔너리)
      • filter_num: 필터 수
      • filter_size: 필터 크기
      • stride: 스트라이드
      • pad: 패딩
      • hidden_size: 은닉층(완전연결)의 뉴런 수
      • output_size: 출력층(완전연결)의 뉴런 수
      • weight_init_std: 초기화 때의 가중치 표준편차
  • 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_size = 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(pool_output_size,
                                            hidden_size)
        self.params['b2'] = np.zeros(hidden_size)
        self.params['W3'] = weight_init_std * \
                            np.random.randn(hidden_size, output_size)
        self.params['b3'] = np.zeros(output_size)
  • SimpleConvNet의 초기화(3): CNN을 구성하는 계층들을 생성

        self.layers = OrderedDict()
        self.layers['Conv1'] = Convolution(self.params['W1'],
                                          self.params['b1'],
                                          conv_param['stride'],
                                          conv_param['pad'])
        self.layers['Relu1'] = Relu()
        self.layers['Pool1'] = Pooling(pool_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()
  • predict 메서드: 추론 수행, loss 메서드: 손실 함수의 값을 구함

    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)
    • x: 입력 데이터, t: 정답 레이블
    • 첫 계층부터 마지막 계층까지 forward를 처리
  • 오차역전파법으로 기울기를 구하는 구현: 순전파와 역전파 반복

    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'].db
        grads['W3'] = self.layers['Affine2'].dW
        grads['b3'] = self.layers['Affine2'].db
    
        return grads
  • 합성곱 계층과 풀링 계층은 이미지 인식에 필수적인 모듈

CNN 시각화하기

1번째 층의 가중치 시각화하기

  • 1번째 층의 합성곱 계층에서는 에지나 블롭 등의 저수준 정보가 추출된다.
  • 가로 에지와 세로 에지에 반응하는 필터
    • 출력 이미지 1은 세로 에지에 흰 픽셀이 나타나고, 출력 이미지 2는 가로 에지에 흰 픽셀이 많이 나온다.

층 깊이에 따른 추출 정보 변화

  • 합성곱 계층을 여러 겹 쌓으면, 층이 깊어지면서 더 복잡하고 추상화된 정보가 추출된다(뉴런이 반응하는 대상이 단순한 모양에서 고급 정보로 변화해간다 = 사물의 의미를 이해하도록 변화한다).

대표적인 CNN

LeNet

  • LeNet: 손글씨 숫자를 인식하는 네트워크
  • LeNet과 현재의 CNN의 차이점
    1. LeNet은 활성화함수로 시그모이드 함수를 사용하지만, 현재 CNN은 ReLU를 사용한다.
    2. LeNet은 서브샘플링을 하여 중간 데이터의 크기를 줄이지만, 현재 CNN은 최대 풀링이 대부분이다.

AlexNet

  1. 활성화 함수로 ReLU를 이용한다.
  2. LRN(Local Response Normalization)이라는 국소적 정규화를 실시하는 계층을 이용한다.
  3. 드롭아웃을 사용한다.

딥러닝의 발전에 GPU와 빅데이터가 크게 기여했다.

0개의 댓글