4 분 소요

배치 정규화

  • 가중치의 초깃값을 적절히 설정하면 각층의 활성화 값 분포가 적당히 퍼지면서 학습이 원활하게 수행된다.
  • 이러한 각 층의 활성화를 적당히 퍼뜨리도록 강제하는걸 배치 정규화라고 한다.

배치 정규화 알고리즘

  • 학습을 빨리 진행할 수 있다(학습 속도 개선)
  • 초깃값에 크게 의존하지 않는다 (골치 아픈 초깃값 선택 장애를 할 필요가 없음)
  • 오버피팅을 억제한다. (드롭아웃 등의 필요성 감소)
  • 배치 정규화의 기본 아이디어는 각 층에서 활성화 값이 적당이 분포되도록 조정하는 것이다.

  • 학습을 할 때 미니배치 단위로 정규화 하며 구체적으로는 데이터 분포가 평균이 0, 분산이 1이 되도록 정규화 한다.
  • 여기에서 미니배치 B = {x1, x2, ...xm} 이라는 m개의 입력 데이터의 집합에 대해 평균 과 분산 을 구한다.
  • 그리고 입력 데이터를 평균이 0, 분산이 1이 되게 (적절한 분포가 되게) 정규화한다.
  • 그리고 위에 식에서 (엘십론)는 작은값 (10e-7) 으로 , 0으로 나누는 사태를 예방하는 역할이다.
  • 위 식은 미니배치 입력 데이터 {x1,x2,…. xm} 을 평균 0, 분산 1인 데이터 {X1,X2, ...Xm } 으로 변환하는 일을 한다.
  • 이 처리를 활성화 함수의 앞 (혹은 뒤) 에 삽입함으로써 데이터 분포가 덜 치우치게 할 수 있다.
  • 또 배치 정규화 계층마다 이 정규화된 데이터에 고유한 확대와 이동 변환을 수행한다.

수식은 아래와 같다.

이 식에서 가 확대를 가 이동을 담당한다. 두 값은 처음에는 = 1 = 0 부터 시작하고 학습하면서 적합한 값으로 조정된다. 이 알고리즘은 신경망에서 순전파 때 적용된다. 계산 그래프로 나타내면 아래와 같다.

역전파 유도

역전파 유도 포스트

배치 정규화의 효과

  • MNIST 데이터셋으로 배치 정규화 계층을 사용할 떄와 사용하지 않을 떄의 학습 진도가 어떻게 달라지는지 알 수 있다.

  • 노란점선 -> 배치정규화 x
  • 파란선 -> 배치정규화 o
  • 배치정규화를 사용하지 않았을 경우 초깃값 분포가 잘 되지 않아 학습이 전혀 진행되지 않는 모습이 많이 있다는걸 확인할 수 있다.
  • 정규화를 사용하면 가중치 초깃값에 크게 의존하지 않아도 된다.

코드 구현

class BatchNormalization:

    """
    http://arxiv.org/abs/1502.03167
    """

    def __init__(self, gamma, beta, momentum=0.9, running_mean=None, running_var=None):
        self.gamma = gamma
        self.beta = beta
        self.momentum = momentum
        self.input_shape = None # 합성곱 계층은 4차원, 완전연결 계층은 2차원  

        # 시험할 때 사용할 평균과 분산
        self.running_mean = running_mean
        self.running_var = running_var  

        # backward 시에 사용할 중간 데이터
        self.batch_size = None
        self.xc = None
        self.std = None
        self.dgamma = None
        self.dbeta = None

    def forward(self, x, train_flg=True):
        self.input_shape = x.shape
        if x.ndim != 2:
            N, C, H, W = x.shape
            x = x.reshape(N, -1)
        out = self.__forward(x, train_flg)
        
        return out.reshape(*self.input_shape)

    def __forward(self, x, train_flg):
        if self.running_mean is None:
            N, D = x.shape
            self.running_mean = np.zeros(D)
            self.running_var = np.zeros(D)

        if train_flg:
            mu = x.mean(axis=0)
            xc = x - mu
            var = np.mean(xc**2, axis=0)
            std = np.sqrt(var + 10e-7)
            xn = xc / std
            self.batch_size = x.shape[0]
            self.xc = xc
            self.xn = xn
            self.std = std
            self.running_mean = self.momentum * self.running_mean + (1-self.momentum) * mu
            self.running_var = self.momentum * self.running_var + (1-self.momentum) * var            

        else:
            xc = x - self.running_mean
            xn = xc / ((np.sqrt(self.running_var + 10e-7)))
        out = self.gamma * xn + self.beta
        return out

    def backward(self, dout):
        if dout.ndim != 2:
            N, C, H, W = dout.shape
            dout = dout.reshape(N, -1)
        dx = self.__backward(dout)
        dx = dx.reshape(*self.input_shape)

        return dx

    def __backward(self, dout):
        dbeta = dout.sum(axis=0)
        dgamma = np.sum(self.xn * dout, axis=0)
        dxn = self.gamma * dout
        dxc = dxn / self.std
        dstd = -np.sum((dxn * self.xc) / (self.std * self.std), axis=0)
        dvar = 0.5 * dstd / self.std
        dxc += (2.0 / self.batch_size) * self.xc * dvar
        dmu = np.sum(dxc, axis=0)
        dx = dxc - dmu / self.batch_size
        self.dgamma = dgamma
        self.dbeta = dbeta

        return dx
        

출처 : 밑바닥부터 시작하는 딥러닝

댓글남기기