머신러닝/CustomFramework

Batch Normalization

hwijin97 2022. 4. 3. 21:05

 

Batch Normalization 

batch normalization 은 미니배치 내의 데이터들에 대해 각 위치의 원소들을 평균 0 표준편차 1의 분포로 만들어 주고, scale factor 을 곱하고 shift factor 을 더하는 정규화 방법이다.

깊은 네트워크를 학습시킬때 역전파 층 처리를 진행하다보면 gradient 가 폭주 혹은 소실 될 가능성이 있다. 이는 각 층의 입력을 구성하는 성분별 분포가 심하게 달라서, 특정 입력이 가중치 파라미터의 기울기를 좌지우지할 때 쉽게 일어난다. 그래서 여러 입력 성분간의 분포간에 적절한 균형을 잡아주는 것이 필요하다.

 

이를 해결하기위해 Kernel Initialization 을 사용하더라도, 학습 중간에 발생하는 폭주와 소실을 감소시켜주기는 힘들다.  Batch normalization 은 학습 도중 Layer 사이에서 입력의 미니배치의 데이터를 대상으로 적용한다.

미니배치 데이터들의 각 원소들을 정규화 ( 평균 0, 표준편차 1 ) 하고, 학습되는 파라미터 scale factor 으로 곱하고, shift factor 을 더한다. factor 들은 처음엔 1, 0 으로 초기화한다.

여기서 의문점이 데이터를 정규화할때 이미 평균을 빼고 표준편차로 나눴는데 이를 반대로 해주는 이유이다.

위에서 문제였던 Batch 데이터들의 분포가 심하게 다른 문제는 이미 Batch 데이터들이 학습하는데 도움이 되지 않은 분포를 지니고 있다는 뜻이다. 이는 데이터가 가진 평균과 표준편차가 적합하지 않다는 의미이고 이 정보들을 무시하고, 모델이 문제풀이에 적합한 방향으로 학습되는 scale, shift factor 으로 곱하고 더해주면서 Batch 데이터의 분포를 더 유용하게 표현하도록 해준다. 

여기서 사용되는 scale, shift factor 가 MiniBatch 마다 다르기 때문에 일종의 잡음 주입같은 효과를 가져와 Regulaization 효과도 어느정도는 있지만, minibatch 가 커질수록 효과는 줄어들고 이는 부가적은 효과일 뿐이다.

 

Batch Normalization 을 적용할 때 주의할 점으로, MIniBatch 크기를 어느정도 키울때 학습 결과가 좋아질 수 있다.

훈련시에 scale, shift factor 을 지수적 이동 평균을 통해 저장해서, 추론시에 이를 사용한다. 

 


계산식

 

 

$ x $ : 입력 데이터

$ \mu $ : 미니배치 평균

$ \sigma $ : 미니배치 표준편차

$ \hat{x_i} $ : 정규화한 데이터

$ \gamma $ : 모델이 학습한 새로운 표준편차

$ \beta $ : 모델이 학습한 새로운 평균

$ y_i $ : 모델이 학습한 새로운 분포

 

$ \mu = \frac{1}{m} \sum_{i=1}^{m} x_i $

$ \sigma^2 = \frac{1}{m} \sum_{i=1}^{m} (x_i - \mu)^2 $

$ \hat{x_i} = \frac{x_i - \mu}{\sqrt{\sigma^2 + \epsilon}} $

$ y_i = \gamma \hat{x_i} + \beta $

 

 


편미분식

 

 

 

$ \frac{\partial L}{\partial \gamma } $ , $ \frac{\partial L}{\partial \beta } $

 

$$ \frac{\partial y_i}{\partial \gamma } = \hat{x_i} \\ \\ \frac{\partial y_i}{\partial \beta } = 1  $$

 

$$ \frac{\partial L}{\partial \gamma} = \frac{\partial L}{\partial y_i} \frac{\partial y_i}{\partial \gamma } = \hat{x_i} \sum_{k=1}^{i} \frac{\partial L}{\partial y_k} \\ \\ \frac{\partial L}{\partial \beta} = \frac{\partial L}{\partial y_i} \frac{\partial y_i}{\partial \beta } = \sum_{k=1}^{i} \frac{\partial L}{\partial y_k} $$


$ \frac{\partial L}{\partial x } $

 

$$ \frac{\partial y_i}{\partial x_i} = \frac{\partial y_i}{\partial \hat{x_i}} \frac{\partial \hat{x_i}}{\partial x_i} = \frac{\gamma}{\sqrt{\sigma^2 + \epsilon}} \\ \\ \frac{\partial L}{\partial x_i} = \frac{\partial L}{\partial y_i} \frac{\partial y_i}{\partial x_i} = \frac{\gamma}{\sqrt{\sigma^2 + \epsilon}}\frac{\partial L}{\partial y_i} $$

 


코드 구현

 

코드를 구현하기전에 생각해 보아야할 점이 있다.

먼저 입력 데이터가 [N, m] 형식의 미니배치 데이터라면 1 차원을 기준으로 정규화를 수행하면 되지만, 이미지의 경우 [N, H, W, C] 의 형식인데 어떻게 정규화를 수행해야 할까?

첫번째, H, W, C 각 차원을 독립적으로 정규화를 한다. -> H x W x C x 4 개의 파라미터가 생성

두번째, N, H, W 를 미니배치 데이터로 생각하고, C 차원을 기준으로 정규화한다.  -> C x 4 개의 파라미터가 생성

우리가 사용할 방법은 두번째 방법을 사용한다. 각 필터맵은 하나의 커널이 반복되면서 만들어졌기 때문에, 이러한 방법이 더 타당하다. 텐서플로우에서도 이러한 방식을 사용한다.

 

    def forward(self, inputs):
        x = inputs
        
        if self.training:
            _mean = np.mean(x, axis=self.norm_axis)
            _variance = np.var(x, axis=self.norm_axis)

            self.moving_mean = self.moving_mean * self.momentum + _mean * (1. - self.momentum)
            self.moving_variance = self.moving_variance * self.momentum + _variance * (1. - self.momentum)

        else:
            _mean = self.moving_mean
            _variance = self.moving_variance
        
        _std = np.sqrt(_variance + self.epsilone)
        x_hat = (x - _mean) / _std
        y = self.gamma * x_hat + self.beta

        if self.training:
            self.std = _std
            self.x_hat = x_hat
        return y
    
    def backprop(self, dLdy, optimizer):

        #--
        dLdGamma = np.sum(dLdy * self.x_hat, axis=self.norm_axis)

        gamma_regularize_term = self.gamma_regularizer(self.gamma) if self.gamma_regularizer is not None else 0
        dLdGamma += gamma_regularize_term
        self.gamma -= optimizer.learning_rate * dLdGamma

        #--
        dLdBeta = np.sum(dLdy, axis=self.norm_axis)

        beta_regularize_term = self.beta_regularizer(self.beta) if self.beta_regularizer is not None else 0
        dLdBeta += beta_regularize_term
        self.beta -= optimizer.learning_rate * dLdBeta
        #--
        dLdx = dLdy * self.gamma / self.std

        return dLdx

 

 

'머신러닝 > CustomFramework' 카테고리의 다른 글

Residual Connection  (0) 2022.04.04
Dropout  (0) 2022.04.03
L1 L2 Regularizer  (0) 2022.04.03
Convolution 2D Layer  (0) 2022.03.26
Dense (Fully Connect) Layer  (0) 2022.03.21