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, x):
        N, C, H, W = x.shape
        out_h = int(1 + (H-self.pool_h) / self.stride)
        out_w = int(1 + (H-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

 

7.4.1  4차원 배열

 

CNN에서 데이터는 4차원이다. 만약 (10, 1, 28, 28) 이라면 10개의 데이터, 1개의 채널(색상), 28 height와 28 width이다. 

x = np.random.rand(10,1,28,28)

x.shape
Out[3]: (10, 1, 28, 28)

x[0].shape
Out[4]: (1, 28, 28)

x[1].shape
Out[5]: (1, 28, 28)

다음과 같이 형태를 확인할 수 있다. 그리고 만약 첫 번째 데이터의 첫 채널 공간 데이터에 접근하는 방법은 다음과 같다.

x[0, 0]

합성곱의 연산은 im2col을 이용하여 간단?하게 이루어진다.

 

7.4.2  im2col로 데이터 전개하기

 

일반적으로 n차원 데이터를 그대로 계산하려면 nested loop 즉 중첩 반복문을 사용해야 할 것이다. 하지만 이는 성능에 굉장한 하락을 가져온다. 이를 해결하기 위해서 im2col 이라는 함수를 사용한다.

 

im2col은 입력 데이터를 가중치 계산(filtering)하기 좋게 전개하는 함수이다. 

즉 3차원 입력 데이터에 im2col을 적용하면 2차원 행렬로 바꿀 수 있으며, 4차원 역시 2차원으로 만들 수 있다.

3차원 -> 2차원

im2col은 필터링하기 좋게 입력 데이터를 전개한다. 3차원 블록을 한 줄로 늘어놓는다.

##im2col은 image to column의 약자이다. 

 

im2col로 입력 데이터를 전개한 다음, 합성곱 계층의 필터를 1열로 전개하고, 두 행렬의 내적을 계산한다.

출력을 2차원으로 저장하고, 이를 다시 4차원으로 reshape한다.

 

7.4.3  합성곱 계층 구현하기

 

im2col의 인터페이스

im2col(input_data, filter_h, filter_w, stride=1, pad=0)

사용예

import sys, os
sys.path.append(os.pardir)
from common.util import im2col

x1 = np.random.rand(1,3,7,7)
col1 = im2col(x1, 5, 5, stride=1, pad=0)
print(col1.shape) #(9, 75)

x2 = np.random.rand(10,3,7,7)
col2 = im2col(x2,5,5,stride=1,pad=0)
print(col2.shape) #(90, 75)

   채널 3개, 높이 너비가 7x7인 데이터를 im2col을 이용해서 바꾸어주었다. 3x5x5 짜리인 필터와 내적하기 위해서 다음과 같이 변하였다. 필터 역시 3차원에서 2차원 행렬로 변하기 때문에, col1의 shape는 (9, 75)가 되었다. ( 필터가 2차원이 되는데, 한 블럭이 한 줄이 되는 꼴이기 때문에 3x5x5=75개의 원소가 된다.)   

   x2는 단순히 데이터 갯수가 10배이기 때문에, 행렬 역시 (90, 75)의 꼴을 가지게 된다.

 

 

Convolution class

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 #filter
        N,C,H,W = x.shape #input data
        out_h = int(1 + (H + 2*self.pad - FH) / self.stride)
        out_w = int(1 + (H + 2*self.pad - FW) / self.stride)
        
        col = im2col(x,FH,FW,self.stride,self.pad)
        col_W = self.W.reshape(FH,-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

W는 filter 이고, x 는 input_data이다. 입력 데이터와 필터를 적절하게 2차원으로 바꾸어주고, 내적한다. 코드가 순서대로의 과정이기 때문에, 크게 어려울 것은 없으나 transpose 부분은 한 번 볼 필요가 있다.

 

transpose와 함께 나오는 정수는 인덱스를 의미한다. 그리고 그 인덱스 순서로 변환해주는 transposing을 해준다.

   합성곱 신경망 Convolutional neural network, CNN은 이미지 인식과 음성 인식 등 다양한 곳에서 사용된다. 특히 이미지 인식 분야에서 딥러닝을 활용한 기법은 거의 다 CNN을 기초로 한다. 

 

7.1 전체 구조

 

CNN도 앞서 봤던 신경망들과 동일하게, 레고마냥 계층들을 조합해서 만들 수 있다. 다만 합성곱 계층 Convolutional layer풀링 계층 Pooling layer가 새롭게 등장한다. 

 

지금까지의 신경망은 인접하는 모든 계층의 모든 뉴런과 결합되어 있었다. 이를 완전연결(fully-connected)라 하며, 완전히 연결된 계층을 Affine 계층이라는 이름으로 구현했다.

 

Affine 계층을 사용하면, 5개층 완전연결 신경망은 다음과 같이 구현 할 수 있다.

 

완전연결 신경망은 Affine 계층 뒤에 활성화 함수를 갖는 ReLU 계층 or Sigmoid 계층이 이어진다. 위 예시에서는 Affine-ReLU가 4개 쌓이고, 5번째 층에서 Softmax에 최종 확률을 출력한다. 

 

 

CNN의 경우는 다음과 같이 구현한다.

CNN에서는 새롭게 합성곱 계층 (conv)과 풀링 계층(pooling)이 추가된다. 

주목할 점은 출력에 가까운 층에서는 Affine-ReLU구성을 사용할 수 있고, 마지막 출력 계층에서는 Affine-Softmax 조합을 그대로 사용한다는 것이다. 

 

 

7.2 합성곱 계층

CNN에서는 패딩(padding), 스트라이드(stride) 등 CNN 고유의 용어가 등장한다. 또, 각 계층 사이에 3차원 데이터같이 입체적인 데이터가 흐른다. 

 

7.2.1 완전연결 계층의 문제점

'데이터의 형상이 무시'

ex) 이미지는 가로, 세로, 색상으로 구성된 3차원 데이터, MNIST에서는 (1, 28, 28)의 데이터를 평탄화해서 사용했음.

하지만 공간적으로 가까운 픽셀의 값이 비슷, RGB의 각 채널이 밀접하게 관련, 거리가 먼 픽셀끼리는 연관 없음 등의 3차원 속의 본질적인 패턴을 무시할 수 있음

 

  합성곱 계층은 형상을 유지하기 때문에 3차원으로 받아서, 3차원 데이터로 전달한다. 그렇기에 CNN은 이미지처럼 형상을 가진 데이터를 제대로 이해할 가능성이 있다.

 

특징맵(feature map): 합성곱 계층의 입출력 데이터

입력 특징 맵(input feature map): 입력 데이터

출력 특징 맵(output feature map): 출력 데이터 

 

 

7.2.2 합성곱 연산

합성곱 연산은 이미지 처리에서 말하는 필터 연산에 해당한다.

형상을 (height, width)로 표현하고, 필터커널이라고도 함

 

연산은 필터의 윈도우를 일정한 간격으로 이동해가며 입력 데이터에 적용하는데, 입력데이터와 필터에서 대응하는 원소끼리 곱한 후 총합을 구한다. (단일 곱셈-누산 fused multiply-add, FMA)

 

ex) 1*2+2*0+3*1+0*0+1*1+2*2+3*1+0*0+1*2=15

완전연결 신경망 CNN
가중치 매개변수 필터 매개변수

합성곱 연산의 편향: 필터를 적용한 원소에 편향을 더함

 

 

7.2.3 패딩

패딩: 합성곱 연산을 수행하기 전에 입력 데이터 주변을 특정 값(ex 0)으로 채우는 기법

패딩: 1, 패딩 값: 0

  패딩은 주로 출력 크기를 조정할 목적으로 사용한다. 합성곱 연산을 몇 번이나 되풀이하게 되는데, 연산을 거칠 때마다 크기가 작아지면 어느 시점에는 크기가 1이 된다. 즉 합성곱 연산을 적용할 수가 없어지는 것이다. 이러한 사태를 막기 위해 패딩을 사용한다. 결론적으로 패딩은 입력 데이터의 공간적 크기를 고정한 채로 다음 계층에 전달하고자 사용한다.

 

 

7.2.4 스트라이드

스트라이드: 필터를 적용하는 위치의 간격

 

앞선 예시들은 스트라이드가 1이었다. 밑 그림은 스트라이드가 2인 예시이다.

한 칸이 아닌 두 칸을 이동한 것을 볼 수 있다.

스트라이드를 키우면 출력 크기가 작아진다. 한편, 패딩을 크게 하면 출력 크기가 커졌다. 이를 수식으로 옮기면 다음과 같다.

 

먼저 입력 크기를 (H,W), 필터 크기를 (FH, FW), 출력 크기를 (OH, OW), 패딩을 P, 스트라이드를 S 라고 한다.

 

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

입력 데이터와 필터의 합성곱 연산을 '채널마다' 수행하고, 모두 합산한다.

**단 입력 데이터의 채널 수와 필터의 채널 수가 같아야 한다!

 

 

7.2.6  블록으로 생각하기

 

(channel, height, width) 순으로 쓰며, 약자로 C, H, W를 쓴다. 필터의 경우 F를 붙여, FH, FW로 사용한다(채널은 입력데이터와 동일해야 하므로 그냥 C). 

 

자 그럼 하나의 데이터에 여러개의 필터를 적용한다면 어떤 출력을 얻게 될까? 다음과 같다.

이렇게 완성된 블록을 다음 계층으로 넘기겠다는 것이 CNN의 처리의 흐름이다. 

 

합성곱 연산의 처리 흐름

##편향은 블록의 형상이 다른데, 이는 브로드캐스트 기능으로 쉽게 구현할 수 있다.

 

7.2.7 배치 처리

 

합성곱 연산에서도 입력 데이터를 한 덩어리로 묶어 배치로 처리할 수 있다. 그래서 각 계층을 흐르는 데이터의 차원을 하나 늘려 4차원 데이터로 저장한다. (데이터 수, 채널 수, 높이, 너비) 순

신경망에 4차원 데이터가 하나 흐를 때마다 데이터 N개에 대한 합성곱 연산이 이뤄진다. 즉 N회 분의 처리를 한 번에 수행한다.

  • 하이퍼파라미터

- 각층의 뉴런 수, 배치 크기, 매개변수 갱신 시 학습률, 가중치 감소

- 적절한 설정이 이루어지지 않으면 모델의 성능이 크게 떨어짐

 

 

6.5.1 검증 데이터

 

하이퍼파라미터를 조정할 때는 하이퍼파라미터 전용 확인 데이터가 필요, 이를 검증 데이터 validation data 라고 부름

##시험 데이터를 사용하여 조정하면 하이퍼파라미터 값이 시험 데이터에 오버피팅되어, 범용 성능이 떨어질 수 있다.

 

  • 훈련 데이터: 매개변수 학습
  • 검증 데이터: 하이퍼파라미터 성능 평가
  • 시험 데이터: 신경망의 범용 성능 평가

 

6.5.2 하이퍼파라미터 최적화

 

최적화 시 핵심은 '최적 값'이 존재하는 범위를 조금씩 줄여나간다는 것이다. 

대략적인 범위를 설정하고 무작위로 값을 샘플링 후, 그 값으로 정확도를 평가한다. 이후 이 작업을 반복하여 '최적 값'의 범위를 좁힌다.

 

  • 하이퍼파라미터의 범위는 '대략적으로' 지정하는 것이 효과적이다.
  • 하이퍼파라미터를 최적화할 때는 오랜 시간이 걸리기 때문에, 나쁠 듯한 값을 일찍 포기하자. 따라서 학습을 위한 에폭을 작게 하여, 1회 평가에 걸리는 시간을 단축하는 것이 효과적이다.

 

0단계:

  하이퍼파라미터 값의 범위를 설정

1단계:

  설정된 범위에서 하이퍼파라미터값을 무작위로 추출

2단계:

  1단계에서 샘플링한 하이퍼파라미터값을 사용하여 학습하고, 검증 데이터로 정확도를 평가 (단 에폭은 작게 설정)

3단계:

  1단계와 2단계를 특정 횟수 반복하며, 그 정확도의 결과를 보고 하이퍼파라미터의 범위를 좁힘

 

##위에서 설명한 방법은 실용적인 방법이다. 하지만 굉장히 직관에 의존하는 느낌이 든다. 더 세련된 기법을 원한다면 Bayesian optimization을 소개할 수 있다.

 

 

6.5.3 하이퍼파라미터 최적화 구현

weight_decay = 10 ** np.random.uniform(-8,-4)
lr = 10 ** np.random.uniform(-6,-2)

가중치 감소 계수, 학습률의 예시

 

실선: 검증 데이터에 대한 정확도, 점선: 훈련 데이터에 대한 정확도

위 그림은 검증 데이터의 학습 추이를 정확도가 높은 순서대로 나열했다. 이 결과를 바탕으로 잘된 것 같은 값의 계수들을 확인하고, 하이퍼파라미터들의 계수의 범위를 다시 설정해주면서 좁혀나가면 된다.

신경망 모델이 복잡해지면 가중치 감소만으로 대응하기 어려워진다. 이때 드롭아웃 Dropout 이라는 기법을 이용한다.

 

드롭아웃은 뉴런을 임의로 삭제하면서 학습하는 방법이다. 훈련 때 은닉층의 뉴런을 무작위로 골라 삭제한다. 삭제된 뉴런은 신호를 전달하지 않게 된다. 훈련 때는 데이터를 흘릴 때마다 삭제할 뉴런을 무작위로 선택하고, 시험 때는 모든 뉴런에 신호를 전달한다. 단, 시험 때는 각 뉴런의 출력에 훈련 때 삭제한 비율을 곱하여 출력한다.

 

class Dropout:
    def __init__(self, dropout_ratio=0.5):
        self.dropout_ratio = dropout_ratio
        self.mask = None
        
    def forward(self, x, train_flg=True):
        if train_flg:
            self.mask = np.random.rand(*x.shape) > self.dropout_ratio
            return x * self.mask
        else:
            return x * (1.0 - self.dropout_ratio)
        
    def backward(self, dout):
        return dout * self.mask

순전파:

  train 중이라면, x와 동일한 모양의 bool type 행렬을 만들고, 곱해서 dropout_ratio 보다 큰 원소만 True로 설정한다.

 

역전파:

  ReLU와 동일, 순전파 때 신호를 통과시키는 뉴런은 역전파 때도 신호를 그대로 통과시키고, 그렇지 않은 뉴런은 역전파때도 신호를 차단함

 

드롭아웃을 적용한 결과, 훈련 데이터와 시험 데이터에 대한 정확도 차이가 줄어들었으며, 훈련 데이터에 대한 정확도가 100%에 도달하지 않게 되었다. (train은 100%에 도달하였으나, test가 100%가 아니라면 사실 상 정확하지 않은 오버피팅 상태) 

 

이처럼 드롭아웃을 이용하면 표현력을 높이면서도 오버피팅을 억제할 수 있다.

오버피팅

- 신경망이 훈련 데이터에만 지나치게 적응되어 그 외의 데이터에는 제대로 대응하지 못하는 상태

- 기계학습은 범용 성능을 지향하기 때문에, 아직 보지 못한 데이터가 주어져도 바르게 식별해내는 모델이 바람직함

 

 

6.4.1 오버피팅

 

오버피팅은 주로 두 경우에서 일어남

  • 매개변수가 많고 표현력이 많은 모델
  • 훈련데이터가 적음

다음은 일부러 두 요건을 충족하여 오버피팅을 일으켰을 때이다. 본래 60,000개인 MNIST 데이터넷의 훈련 데이터 중 100개 ,300개, 1000개만 사용하고, 7층 네트워크를 사용해 네트워크의 복잡성을 높였다. 각 층의 뉴런은 100개, 활성화 함수는 ReLU이다. 

각각 앞에서부터 100개, 300개, 1000개이다. 데이터수가 적을수록 훈련 데이터와 시험 데이터의 간극이 커지는 것을 확인할 수 있다.

 

 

6.4.2 가중치 감소

 

오버피팅 억제용으로 가중치 감소(weight decay)라는 것이 있다. 

학습 과정에서 큰 가중치에 대해서는 그에 상응하는 큰 페널티를 부과하여 오버피팅을 억제하는 방법이다. 원래 오버피팅은 가중치 매개변수의 값이 커서 발생하는 경우가 많기 때문이다.

 

신경망 학습은 손실 함수의 값을 줄이도록 되어있다. 그렇기 때문에 역으로 손실 함수가 커진다면 가중치를 줄일 수 있다.

x는 가중치, f는 손실함수

예를 들어 가중치의 제곱 norm (L2 norm)을 손실함수에 더한다. 가중치를 W라고 하면 L2 norm에 따른 가중치는

이 되고, 이를 손실 함수에 더한다. (λ는 정규화의 세기를 조절하는 하이퍼파라미터, λ를 크게 설정할 수록 큰 가중치에 대한 페널티가 커진다.) 

 

def loss(self, x, t):
"""손실 함수를 구한다.

Parameters
----------
x : 입력 데이터
t : 정답 레이블 

Returns
-------
손실 함수의 값
"""
	y = self.predict(x)
	weight_decay = 0
    
	for idx in range(1, self.hidden_layer_num + 2):
		W = self.params['W' + str(idx)]
		weight_decay += 0.5 * self.weight_decay_lambda * np.sum(W ** 2)

	return self.last_layer.forward(y, t) + weight_decay #손실 함수에 더함

가중치 감소는 모든 가중치 각각의 손실 함수에 위 값을 더한다. 따라서 가중치의 기울기를 구하는 계산에서는 그동안의 오차역전파법에 따른 결과의 정규화 항을 미분한 λW를 더한다.

    def gradient(self, x, t):
        """기울기를 구한다(오차역전파법).

        Parameters
        ----------
        x : 입력 데이터
        t : 정답 레이블
        
        Returns
        -------
        각 층의 기울기를 담은 딕셔너리(dictionary) 변수
            grads['W1']、grads['W2']、... 각 층의 가중치
            grads['b1']、grads['b2']、... 각 층의 편향
        """
        # forward
        self.loss(x, t)

        # backward
        dout = 1
        dout = self.last_layer.backward(dout)

        layers = list(self.layers.values())
        layers.reverse()
        for layer in layers:
            dout = layer.backward(dout)

        # 결과 저장
        grads = {}
        for idx in range(1, self.hidden_layer_num+2):
        #미분한 값을 더하는 부분
            grads['W' + str(idx)] = self.layers['Affine' + str(idx)].dW + self.weight_decay_lambda * self.layers['Affine' + str(idx)].W
            grads['b' + str(idx)] = self.layers['Affine' + str(idx)].db

        return grads

배치 정규화 Batch Normalization

- 각 층이 활성화를 적당히 퍼뜨리도록 '강제'하는

아이디어에서 출발한 방법

 

 

6.3.1 배치 정규화 알고리즘

  • 학습을 빨리 진행할 수 있다.
  • 초기값에 크게 의존하지 않음
  • 오버피팅을 억제함 (dropout 등의 필요성 감소

배치 정규화를 사용한 신경망의 예

배치 정규화는 학습 시 미니배치를 단위로 정규화한다. 데이터 분포가 평균이 0, 분산이 1이 되도록 정규화한다.

(정규분포, Normal Distribution)

순서대로 평균, 분산, 변환 데이터

##검증은 다음과 같다.

 

ε은 아주 작은 값으로 divide by zero를 막기 위함이다. 

 

배치 정규화 계층마다 이 정규화된 데이터에 고유한 확대(scale)와 이동(shift)변환을 수행한다.

γ가 확대, β가 이동, 초기값은 1, 0부터 시작
배치 정규화의 Computational Graph

 

6.3.2 배치 정규화의 효과

 

 

배치 정규화시 더 빠른 것을 알 수 있다.

6.2.1 초기값을 0으로 하면?

 

가중치 감소

- 가중치 매겨변수의 값이 작아지도록 학습

- 가중치 값을 작게 하여 오버피팅이 일어나지 않게 함

- 최대한 작은 값에서 시작

 

초기값을 모두 0으로 설정한다면?

- 학습이 올바르게 이뤄지지 않음

- 오차역전파법에서 모든 가중치의 값이 똑같이 갱신되기 때문

- 역전파 과정에서 update가 동일하게 이루어지며, 반복적으로 일어남

- 가중치를 여러 개 갖는 의미를 사라지게 함

 

 

6.2.2 은닉층의 활성화값 분포

 

Case 1: 정규분포*1 / 시그모이드

데이터가 0과 1에 치우쳐 분포하게 되면 역전파의 기울기 값이 점점 작아지다가 사라진다. 이를 기울기 소실 (gradient vanishing) 이라고 한다.

 

 

Case 2: 정규분포*0.01 / 시그모이드

0.5 부근에 집중되어있다. 0과 1에 치우치진 않았기 때문에 기울기 소실 문제는 없지만, 활성화값이 치우쳐져 있기 때문에 표현력을 제한한다는 관점에서 문제가 된다. 즉 다수의 뉴런이 거의 같은 값을 출력하게 되어, 여러 개의 뉴런을 둔 의미가 없어진다.

 

**각 층의 활성화값은 고루 분포되어어야 한다. 층과 층 사이에 적당하게 다양한 데이터가 흐르게 해야 신경망 학습이 효율적으로 이뤄진다.

 

Case 3: Xavier 초기값 / 시그모이드

Xavier 초기값은 일반적인 딥러닝 프레임워크들이 표준적으로 이용하고 있다. 

이 초기값은 각 층의 활성화값들을 광범위하게 분포시킬 목적으로 가중치의 적절한 분포를 찾았다. 

 

앞 계층의 노드가 n개라면 표준편차가 1/sqrt(n) 인 분포를 사용하면 된다.

node_num = 100 # 앞 층의 노드 수
w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num)

 

 

6.2.3 ReLU를 사용할 때의 가중치 초기값

 

Xavier 초기값은 활성화 함수가 선형ㅇ니 것을 전제로 이끈 결과이다. sigmoid 함수와 tanh함수는 좌우 대칭이라 중앙 부근이 선형인 함수로 볼 수 있다. 하지만 ReLU를 이용할 때는 이에 특화된 초기값을 이용하라 권장한다.

이 값을 He 초기값이라고 한다. 

 

He 초기값은 앞 계층의 노드가 n개일 때, 표준편차가 sqrt(2/n)인 정규분포를 사용한다. 

활성화 함수로 ReLU를 사용한 경우의 가중치 초기값에 따른 활성화값 분포 변화

표준편차 0.01:  거의 학습이 이루어지지 않음

Xavier 초기값:  층이 깊어지면서 치우짐이 조금씩 커짐, '기울기 소실'문제를 일으킴

He 초기값:  모든 층에서 균일하게 분포, 층이 깊어져도 분포가 균일

 

결론

ReLU:  He 초기값

sigmoid나 tanh등 S자 모양 곡선:  Xavier 초기값

 

 

6.2.4 MNIST 데이터넷으로 본 가중치 초기값 비교

층별 뉴런이 100개인 5층 신경망에서 활성화 함수로 ReLU를 사용했다.

+ Recent posts