728x90

매개변수 갱신

신경망을 학습한다는 것은 결국 손실함수의 값을 낮추는 것이고, 손실함수를 낮추려면 각 노드의 매개변수들을 수정했어야 했다.

이 작업 자체를 최적화(optimization)이라고 한다.

최적의 매개변수를 찾기 위해서 기울기를 구하고, 낮아지는 방향으로 조금씩 나아갔었다.

이 자체가 확률적 경사 하강법의 방법인데, 당연히 랜덤값으로 최적의 값을 구하는 것보다는 나은 방법이다.

그렇다고 해서 이게 최고의 방법은 아니니, 다른 방법도 알아보도록 하자.

 

  • 확률적 경사 하강법(SGD)

계속 사용했던 방법이다.

W는 가중치이고, n 비슷하게 생긴 친구는 학습률이다.

SGD는 이 미분값을 학습률에 곱해서 가중치를 계속 수정했었다.

파이썬으로 작성해보면 다음과 같다.

class SGD:

  def __init__(
      self,
      learning_rate: float = 0.01,
  ):
    self.learning_rate = learning_rate

  def update(
      self,
      params,
      grads,
  ):
    for key in params.keys():
      params[key] -= self.learning_rate * grads[key]
    return params

 

하지만 SGD에는 다음과 같은 단점이 있다.

 

이런 함수에서 y축 방향으로는 자리를 빠르게 찾아갈지 몰라도, x축 방향은 기울기가 너무 낮아서 찾아가는 시간이 오래 걸릴 것이다.

이런 문제들을 해결하기 위해 모멘텀, AdaGrad, Adam 이라는 방법을 알아보자.

 

  • 모멘텀

모멘텀은 뭔가 물리학 시간에 무슨무슨 모멘텀 이러면서 힘의 크기를 말했던 것 같다.

진짜 물리처럼 v는 속도이다.

class Momentum:

  def __init__(
      self,
      learning_rate: float = 0.01,
      momentum: float = 0.9,
  ):
    self.learning_rate = learning_rate
    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.learning_rate*grads[key]
      params[key] += self.v[key]

속도에 관성을 주고, 저장하고 있다가 

다음에 또 업데이트가 들어오면 그 속도를 사용하고, 다시 속도를 저장하는 방법이다.

 

  • AdaGrad

학습률(learning_rate) 값이 너무 크면 정확하게 학습이 불가능하고, 값이 너무 작으면 학습이 너무 오래걸렸었다.

이럴 때 유용한 방법이 초반에는 학습률을 높게 잡고, 나중에 점차 줄여가는 것이다.

 

변화율에 루트를 씌운 값을 나눠주어, 매개변수가 많이 움직이면 학습률을 점차 낮추는 방법이다.

 

import numpy as np

class AdaGrad:

  def __init__(
      self,
      learning_rate: float = 0.01
  ):
    self.learning_rate = learning_rate
    self.h = None

  def update(
      self,
      params,
      grads,
  ):
    if self.h is None:
      self.h = {}
      for key, val in params.items():
        self.h[key] = np.zeros_like(val)

    for key in params.keys():
      self.h[key] += grads[key] * grads[key]
      params[key] -= self.learning_rate * grads[key] / (np.sqrt(self.h[key]) + 1e-7)

파이썬 함수로 나타내면 위와 같다.

 

  • Adam

위에 있는 모멘텀, AdaGrad 2개를 합치면 어떨까?

그게 바로 Adam이다.

파이썬 코드로는 다음과 같다.

class Adam:

  def __init__(
      self,
      learning_rate: float = 0.001,
      beta1: float = 0.9,
      beta2: float = 0.999,
  ):
    self.learning_rate = learning_rate
    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

    learning_rate_t = self.learning_rate * 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] -= learning_rate_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)

 

가중치의 초깃값

각 노드에 가중치를 얼마로 시작할지는 굉장히 중요하다.

 

만약 가장 간단하게 모두 0으로 시작하면 어떻게 될까?

이렇게 만들면 역전파 할 때 모든 가중치가 똑같이 업데이트되기에 의미가 없는 신경망이 되어 버린다.

그렇기 때문에 초기값은 랜덤으로 시작해야 한다.

 

그냥 단순하게 이렇게 생성을 하면

x = np.random.randn(1000, 100)

 

그리고 시그모이드 함수를 활성화 함수로 사용하면, 이렇게 끝에 극단적으로 몰린 히스토그램이 나온다.

당연히 치우치면 안되고, 값이 고르게 퍼져야 한다.

 

이 문제를 해결하기 위해서 Xavier 초깃값이라는 방법을 사용한다.

가중치를 앞 층 노드 수의 루트만큼 나누어서 시작하는 것이다.

 

pre_layer_node_num = 400

w = np.random.rand(pre_layer_node_num, pre_layer_node_num) / np.sqrt(pre_layer_node_num)

위보다 훨씬 값이 고르게 나온 것을 볼 수 있다.

 

하지만 이 방법은 Relu에서는 사용 할 수 없다고 한다.

값이 선형적으로 증가하는 경우에만 사용 할 수 있기 때문이다.

 

relu 함수를 그냥 랜덤 초기값으로 사용하면 분포가 다음과 같다.

 

Xvaier을 사용하면 위와 같지만 이것도 좋아보이지는 않는다.

 

이 문제를 해결하기 위해서 He 초기값을 사용한다.

He 초기값을 사용하면 이렇게 값이 고르게 나오는 것을 볼 수 있다.

 

과대적합

과대적합? 과적합?은 테스트 데이터로만 학습을 하기에 너무 거기에만 맞추고, 실제 데이터에는 똑바로 맞추지 못하는 상황을 말한다.

보통 데이터가 적은 경우에 많이 발생한다.

 

  • 가중치 감소

과대적합을 막기 위해 가장 예전부터 사용한 방법이다.

큰 가중치에 큰 페널티를 부과하여 과적합을 막는 방법이다.

 

원점으로부터 거리를 norm이라고 하는데.

이 가중치의 제곱 norm을 손실 함수에 더한다.

 

  • 드롭아웃

드롭아웃은 값을 조절하는게 아니라 뉴런을 임의로 건너 뛰는 것이다.

훈련 때 데이터를 넘길 때마다 건너뛸 뉴런을 랜덤으로 선택하고, 테스트 때에는 모든 뉴런을 사용하는 것이다.

 

한 번 파이썬으로 구현을 해본다면

class Dropout:

  def __init__(
      self,
      drouput_ratio: float = 0.5
  ):
    self.dropout_ratio = drouput_ratio
    self.mask = None
  
  def forward(
      self,
      x,
      train_flag = True,
  ):
    if train_flag:
      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

다음과 같다.

 

'인공지능 > 딥러닝' 카테고리의 다른 글

오차역전파법  (0) 2026.06.20
신경망 학습  (0) 2026.06.15
신경망  (0) 2026.06.10
퍼셉트론  (0) 2026.06.08

+ Recent posts