반응형


안녕하세요~



오늘은 신경망의 정확도를 높이기위한 여러가지 방법을 소개하겠습니다.


Ⅰ. 매개변수 갱신


Ⅱ. 가중치 초기값 설정


Ⅲ. 배치 정규화


Ⅳ. Dropout / weight decay


Ⅴ. 적절한 하이퍼 파라미터 값 찾기



이제 각각의 방법에 대해 자세히 다뤄보도록 하겠습니다.



Ⅰ. 매개변수 갱신 ( 고급 경사하강법 )


underfitting을 해결하기위한 방법 중 하나입니다.



GD(GradientDescent)에는 큰 단점 2가지가 있습니다.


1) 계산량이 많아서 속도가 느리다.


2) local minima에 빠질 수 있다.



GD는 가장 기본적인 뉴럴넷 학습 방법으로


전체 데이터에 대한 비용함수의 weight 과 미분하고


각 W(가중치) 파라미터에 대한 기울기를 이용하여


W를 업데이트하는 방법입니다.



그런데 이러한 GD는 층이 많아질수록 parameter의 복잡도가 늘어남에 따라


비용함수가 더욱 복잡해져서


local minima에 빠지는 현상이 더욱 잘 발생하게 됩니다.



이러한 문제를 해결하기 위해 나온 것이


SGD(Stochastic Gradient Descent) 확률적 경사하강법입니다.



1. SGD


전체 data 중 랜덤하게 추출한 1/N의 데이터만을 사용해서 빠르게 가중치를 업데이트 하는 방법입니다.


[SGD 식]



GD의 단점을 해결한 SGD에도 문제가 있는데,

단순하고 구현도 쉽지만 문제에 따라 비효율 적일 때가 있다는 것입니다.


또한 아래와 같은 그래프의 경우 global minima 쪽으로 기울기 방향이 있는게 아니기 때문에

local minima에 빠지고 global minima 쪽으로 도달하지 못하는 단점이 있습니다.




2. Momentum


Momentum은 운동량을 뜻하는 단어로 기울어진 방향으로 물체가 가속되는 물리 법칙을 나타냅니다.


[Momentum 식]




(α: 마찰계수, v: 속도, η : 학습률, W: 가중치, ∂L / ∂W : W에 대한 손실함수 미분)



원리:


내리막: 기울기가 음수이면 속도가 증가


오르막: 기울기 양수이면 속도 감소



3. AdaGrad


learning rate 가 학습되면서 자체적으로 조정되는 경사하강법


보통 처음에는 크게 학습하다가 조금씩 작게 학습을 진행하는 방법입니다.


[AdaGrad 식]



(h: 기울기에 값을 제곱하여 더해줌, ◎: 행렬의 원소별 곱셈)



원리:


learning rate가 클 경우: 발산될 위험은 있지만 수렴의 속도가 빨라진다.


learning rate가 작을 경우 : 발산될 위험은 작지만 local minima에 빠지거나 학습이 안될 수 있다.



각각의 매개변수 원소가 갱신되는 값이 다르다는 것이 특징입니다.



4. Adam


Momentum의 장점 (가속도) + RMSProp의 장점 (각 매개변수마다 학습률 조절) == Adam


최근 딥러닝에서 가장 많이 사용하는 최적화 방법이라고 합니다! : )


* 참고: RMSProp는 가장 최근 반복에서 비롯된 그래디언트만 누적함으로

AdaGrad의 단점(local minima에 수렴함)을 보안한 옵티마이저 입니다.


[각각의 optimizer를 파이썬으로 구현하는 코드]


# gradient descent 종류 class SGD: def __init__(self, lr=0.01): self.lr = lr def update(self, params, grads): for key in params.keys(): params[key] -= self.lr * grads[key] class Momentum: def __init__(self, lr=0.01, momentum=0.9): self.lr = lr self.momentum = momentum self.v = None def update(self, params, grads): if self.v is None: self.v = {} for key, val in params.items(): self.v[key] = np.zeros_like(val) for key in params.keys(): self.v[key] = self.momentum * self.v[key] - self.lr * grads[key] params[key] += self.v[key] class AdaGrad: def __init__(self, lr=0.01): self.lr = lr self.h = None def update(self, parmas, grads): if self.h is None: self.h = {} for key, val in params.items(): self.h[key] = np.zeros_like(val) for key in parmas.keys(): self.h[key] += grads[key] * grads[key] params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7) class Adam: def __init__(self, lr=0.001, beta1=0.9, beta2=0.999): self.lr = lr self.beta1 = beta1 self.beta2 = beta2 self.iter = 0 self.m = None self.v = None def update(self, params, grads): if self.m is None: self.m, self.v = {}, {} for key, val in params.items(): self.m[key] = np.zeros_like(val) self.v[key] = np.zeros_like(val) self.iter += 1 lr_t = self.lr * np.sqrt(1.0 - self.beta2 ** self.iter) / (1.0 - self.beta1 ** self.iter) for key in params.keys(): self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key]) self.v[key] += (1 - self.beta2) * (grads[key] ** 2 - self.v[key]) params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)



[각 optimizer 가 최소값에 수렴하는 방법]





Ⅱ. 가중치 초기값 설정


underfitting을 해결하기위한 방법 중 하나입니다.


가중치 초기값을 적절히 설정하면 각 층의 활성화 값의 분포가 적당히 퍼지는 효과가 발생합니다.


활성화 값이 적당히 퍼지게 되면 학습이 잘되고 정확도가 높아집니다.



1. 초기값을 0으로 설정


초깃값을 모두 0으로 설정하면

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

학습이 올바르게 이뤄지지 않습니다.


2. 표준편차가 1인 정규분포를 사용해 초기값 설정


- 표준편차가 작을수록 데이터가 평균에 가깝게 분포


- 표준편차가 클 수록 데이터가 많이 흩어져 있다.


즉 데이터가 0과 1에 치우쳐 분포하게 되면 역전파의 기울기 값이 점점 작아지다 사라집니다.


층을 깊게 하는 딥러닝에서는 기울기 소실은 더 심각한 문제가 될 수 있습니다.


3. Xavier 초기값 설정


- 표준편차가 √1/n 인 정규분포로 초기화한다.


- sigmoid 와 짝꿍


각 층의 활성화 값들을 광범위하게 분포 시킬 목적으로 적절한 분포를 찾고자 했습니다.


그리고 앞 계층의 노드가 n개라면 표준편차가 √1/n 인 분포를 사용하면 된다는 결론을 이끌었습니다.



4. He 초깃값


- 표준편차가 √2/n 인 정규분포로 초기화한다.


- ReLU 와 짝꿍


ReLU는 음의 영역이 0이라서 더 넓게 분포시키기 위해


Xavier함수보다 2배의 계수가 필요하다고 해석할 수 있습니다.



[초기화 그래프]





Ⅲ. 배치 정규화


마찬가지로 underfitting을 해결하기 위한 방법 중 하나 입니다.


앞에서 가중치 초기값을 적절히 설정하면 각 층의 활성화 값의 분포가 적당히 퍼지는 효과를 보았습니다.


그런데,

배치 정규화는 바로 각 층에서의 활성화 값이 적당히 분포되도록 강제로 조정하는 것을 말합니다.



딥러닝의 경우는 hidden layer가 많아서


가중치의 조금한 변화가 가중되어서 쌓이면


hidden layer가 많아질수록 출력되는 값의 변화가 크기 때문에


학습이 어렵습니다.



배치 정규화는 값이 활성화 함수를 통과하기 전에 가중의 변화를 줄이는 것이 목표


가중의 합이 배치 정규화에 들어오게 되면 기존의 값의 스케일을 줄여버리게 됩니다.



[ 배치초기화 식]



[파이썬으로 구현하기]


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



[구현 예]

# 계층생성 layers = OrderedDict() # 순전파 순서의 반대로 역전파가 된다. layers['Affine1'] = Affine(params['W1'], params['b1']) layers['BatchNorm1'] = BatchNormalization(gamma=1.0, beta=0.) layers['Relu1'] = Relu() layers['Affine2'] = Affine(params['W2'], params['b2']) layers['BatchNorm2'] = BatchNormalization(gamma=1.0, beta=0.) layers['Relu2'] = Relu() layers['Affine3'] = Affine(params['W3'], params['b3']) lastLayer = SoftmaxWithLoss()




Ⅳ. Dropout / weight decay


두 방법 모두 overfitting을 규제하기 위한 방법입니다.



1. Dropout(드롭아웃)


오버피팅을 억제하기 위해서 뉴런을 임의로 삭제하면서 학습시키는 방법


사공이 많으면 배가 산으로 간다는 속담이 있습니다.


따라서 몇 명의 전문가만 선별해서 반복적으로 같은 결과가 나오면 그것을 답으로 보는 방법입니다.



일반적으로 입력층에서는 20% ~ 50% 정도,


은닉층에서는 50% 정도의 노드를 생략한다고 합니다.



[파이썬 코드]

class Dropout: """ http://arxiv.org/abs/1207.0580 """ def __init__(self, dropout_ratio=0.15): 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


[구현 예]

# 계층생성 layers = OrderedDict() # 순전파 순서의 반대로 역전파가 된다. layers['Affine1'] = Affine(params['W1'], params['b1']) layers['BatchNorm1'] = BatchNormalization(gamma=1.0, beta=0.) layers['Relu1'] = Relu() layers['Dropout1'] = Dropout() layers['Affine2'] = Affine(params['W2'], params['b2']) layers['BatchNorm2'] = BatchNormalization(gamma=1.0, beta=0.) layers['Relu2'] = Relu() layers['Dropout2'] = Dropout() layers['Affine3'] = Affine(params['W3'], params['b3']) lastLayer = SoftmaxWithLoss()




2. Weight Decay(가중치 감소)


학습과정에서 큰 가중치에 대해서는 그에 상응하는 큰 패널티를 부여하여 오버피팅을 억제하는 방법


가중치 감소는 모든 가중치 각각의 손실함수에 (1/2) * λW **2를 더합니다.



[구현 예]

weight_decay_lambda = 0.001


# 1. 비용함수 수정

weight_decay = 0 for idx in range(1, 4): # 3계층 하드코딩 W = self.params['W' + str(idx)] weight_decay += 0.5 * self.weight_decay_lambda * np.sum(W ** 2)


# 2. 기울기 계산하는 코드 변경

for idx in range(1, 4): 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



Ⅴ. 적절한 하이퍼파라미터 값 찾기


신경망에서 말하는 하이퍼파라미터는 각 층의 뉴런 수, 배치 크기, 매개변수 생신 시의 학습률과 가중치 감소 등입니다.



1. 검증데이터


우선 하이퍼파라미터를 조정할 때는 검증데이터로 하이퍼파라미터의 적절성을 평가해야합니다.


test_data에서 하이퍼파라미터를 조정하면 값이 test_data에 오버피팅 되기 때문에

범용 성능이 떨어지는 모델이 될 수도 있기 때문입니다.



즉, 아래와 같이 데이터를 나눠서 사용할 수 있습니다.


- 훈련데이터: 매개변수 학습


- 검증데이터: 하이퍼파라미터 성능 평가


- test 데이터: 신경망의 범용 성능 평가



※ Tip! 만약 적은 양의 데이터라면 교차검증(cross validation)방법을 사용할 수 있습니다.



2. 하이퍼 파라미터 최적화


하이퍼파라미터를 최적화할 때의 핵심은


무작위로 샘플링 후 정확도를 살피면서 하이퍼파라미터의 '최적 값'이 존재하는 범위를 조금씩 줄여간다는 것입니다.


0단계: 하이퍼파라미터 값의 범위를 설정합니다.


1단계: 설정된 범위에서 하이퍼파라미터의 값을 무작위로 추출합니다.


2단계: 1단계에서 샘플링한 하이퍼 파라미터 값을 사용하여 학습하고, 검증 데이터로 정확도를 평가합니다.


3단계: 1단계와 2단계를 반복하며, 그 정확도의 결과를 보고 하이퍼파라미터의 범위를 좁힙니다.



※ Tip!


위에서 설명한 하이퍼파라미터 최적화 방법은

과학이라기보다는 직관에 의존한다는 느낌이 들지만 많이 사용되는 실용적인 방법입니다.


더 세련된 기법을 원한다면 베이즈 최적화(수학이론을 구사)를 사용할 수 있습니다 : )




오늘은 여기까지!!


유난히 긴 포스팅이었네요!


※ '밑바닥부터 시작하는 딥러닝' 을 참고하였습니다


도움이 되었다면 공감♡ 꾸욱 

반응형

+ Recent posts