본문 바로가기
Study/책『밑바닥부터 시작하는 딥러닝』

6-2. 학습 관련 기술들

by 코드포휴먼 2020. 5. 12.

6.4. 바른 학습을 위해

기계학습에서는 오버피팅을 억제하는 기술이 중요해진다.

오버피팅이란 신경망이 훈련 데이터에만 지나치게 적응되어 그 외의 데이터에는 제대로 대응하지 못하는 상태다.

 

6.4.1. 오버피팅

오버피팅은 주로 다음의 두 경우에 일어난다.

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

이 두 요건을 일부로 충족하여 오버피팅을 일으켜보겠다.

60000개인 MNIST 데이터셋의 훈련 데이터 중 300개만 사용하고, 7층 네트워크를 사용해 네트워크의 복잡성을 높이겠다.

각 층의 뉴런은 100개, 활성화 함수는 ReLU를 사용한다.

 

실험에 필요한 코드를 발췌해 설명하겠다.

(깃허브https://github.com/WegraLee/deep-learning-from-scratch 에서 ch06/overfit_weight_decay.py파일의 일부)

(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)
# 오버피팅을 재현하기 위해 학습 데이터 수를 줄임
x_train = x_train[:300]
t_train = t_train[:300]

 

이어서 훈련을 수행하는 코드다.

에폭마다 모든 훈련 데이터와 모든 시험 데이터 각각에서 정확도를 산출한다는 점이 기존 코드와 다르다.

network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100], output_size=10,
                        weight_decay_lambda=weight_decay_lambda)
optimizer = SGD(lr=0.01) # 학습률이 0.01인 SGD로 매개변수 갱신

max_epochs = 201
train_size = x_train.shape[0]
batch_size = 100

train_loss_list = []
train_acc_list = []
test_acc_list = []

iter_per_epoch = max(train_size / batch_size, 1)
epoch_cnt = 0

for i in range(1000000000):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]

    grads = network.gradient(x_batch, t_batch)
    optimizer.update(network.params, grads)

    if i % iter_per_epoch == 0:
        train_acc = network.accuracy(x_train, t_train)
        test_acc = network.accuracy(x_test, t_test)
        train_acc_list.append(train_acc)
        test_acc_list.append(test_acc)

        print("epoch:" + str(epoch_cnt) + ", train acc:" + str(train_acc) + ", test acc:" + str(test_acc))

        epoch_cnt += 1
        if epoch_cnt >= max_epochs:
            break

train_acc_list와 test_acc_list에는 에폭 단위(모든 훈련 데이터를 한 번씩 본 단위)의 정확도를 저장한다.

이 두 리스트를 그래프로 그리면 아래처럼 된다.

훈련 데이터(train)와 시험 데이터(test)의 에폭별 정확도 추이

훈련 데이터를 사용하여 측정한 정확도는 100 에폭을 지나는 무렵부터 거의 100%다.

그러나 시험 데이터에 대해선 큰 차이를 보인다.

훈련 데이터에만 적응(fitting)하여 시험 데이터에는 제대로 대응하지 못한 것이다.

 

 

6.4.2. 가중치 감소

오버피팅 억제용으로 가중치 감소(weight decay)가 예로부터 많이 쓰였다.

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

오버피팅은 가중치 매개변수의 값이 커서 발생하는 경우가 많기 때문이다. 

 

신경망 학습 목적은 손실 함수의 값을 줄이는 것이다.

이때, 예를 들어 가중치의 제곱 노름(norm), 즉 L2 노름을 손실 함수에 더한다.

그러면 가중치가 커지는 것을 억제할 수 있다.

가중치를 W라 하면 L2 노름에 따른 가중치 감소는 1/2*λ*W^2 가 되고, 1/2*λ*W^2 을 손실 함수에 더한다. 

여기에서 λ(람다)는 정규화의 세기를 조절하는 하이퍼파라미터다.

λ를 크게 설정할 수록 큰 가중치에 대한 페널티가 커진다.

1/2*λ*W^2 의 앞쪽 1/2는 1/2*λ*W^2 미분 결과인 λ*W를 조정하는 역할의 상수다.

가중치 감소는 모든 가중치 각각의 손실 함수에 1/2*λ*W^2 을 더한다.

따라서 가중치의 기울기를 구하는 계산에서는 그동안의 오차역전파법에 따른 결과에 정규화 항을 미분한 λ*W를 더한다.

 

그럼 방금 수행한 실험에서 λ=0.1 로 가중치 감소를 적용한 결과는 다음과 같다.

(깃허브https://github.com/WegraLee/deep-learning-from-scratch 에서

가중치 감소를 적용한 네트워크는 common/multi_layer_net.py 파일, 실험용 코드는 ch06/overfit_weight_decay.py 파일)

가중치 감소를 이용한 훈련 데이터(train)와 시험 데이터(test)에 대한 정확도 추이

훈련 데이터에 대한 정확도와 시험 데이터에 대한 정확도에는 여전히 차이가 있지만, 

가중치 감소를 이용하지 않은 정확도 추이와 비교하면 그 차이가 줄었다.

즉, 오버피팅이 억제됐다는 것이다.

앞서와 달리 훈련 데이터에 대한 정확도가 100%(1.0)에 도달하지 못한 점도 주목한다.

 

 

6.4.3. 드롭아웃

앞 절에서는 오버피팅을 억제하는 방식으로 손실 함수에 가중치의 L2 노름을 더한 가중치 감소 방법을 말했다.

가중치 감소는 간단하게 구현할 수 있고 어느 정도 지나친 학습을 억제할 수 있다.

그러나 신경망 모델이 복잡해지면 드롭아웃(Dropout) 기법을 이용한다.

 

드롭아웃은 뉴런을 임의로 삭제하면서 학습하는 방법이다.

훈련 때 은닉층의 뉴런을 무작위로 골라 삭제한다.

삭제된 뉴런은 아래 그림과 같이 신호를 전달하지 않는다.

훈련 때는 데이터를 흘릴 때마다 삭제할 뉴런을 무작위로 선택하고, 시험 때는 모든 뉴런에 신호를 전달한다.

단, 시험 때는 각 뉴런의 출력에 훈련 때 삭제 안 한 비율을 곱하여 출력한다. 

드롭아웃의 개념 : 왼쪽이 일반적인 신경망, 오른쪽이 드롭아웃을 적용한 신경망. 드롭아웃은 뉴런을 무작위로 선택해 삭제하여 신호 전달을 차단한다.

 

 

다음 코드는 드롭아웃을 쉽게 구현했다.

순전파를 담당하는 forward 메서드에는 훈련 때 (train_flg = True 일 때)만 잘 계산해두면 시험 때는 단순히 데이터를 흘리기만 하면 된다.

삭제 안 한 비율은 곱하지 않아도 좋다. 실제 딥러닝 프레임워크들도 비율을 곱하지 않는다.

(더 효율적인 구현은 체이너(Chainer) 프레임워크의 드롭아웃 구현을 참고하면 좋다.)

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

여기서의 핵심은 훈련 시에는 순전파 때마다 self.mask에 삭제할 뉴런을 False로 표시한다는 것이다.

self.mask는 x와 형상이 같은 배열을 무작위로 생성하고, 그 값이 dropout_ratio보다 큰 원소만 True로 설정한다.

역전파 때의 동작은 ReLU와 같다.

즉, 순전파 때 신호를 통과시키는 뉴런은 역전파 때도 신호를 그대로 통과시키고, 순전파 때 통과시키지 않은 뉴런은 역전파 때도 신호를 차단한다.

 

드롭아웃의 효과를 MNIST 데이터셋으로 확인해보겠다.

소스 코드에서는 Trainer라는 클래스가 네트워크 학습을 대신 해주도록 간소화했다.

드롭아웃 실험은 앞의 실험처럼 7층 네트워크(각 층의 뉴런 수는 100개, 활성화 함수는 ReLU)를 써서 진행했다.

(깃허브https://github.com/WegraLee/deep-learning-from-scratch 에서 ch06/overfit_dropout.py 파일의 실행 결과)

왼쪽은 드롭아웃 없이, 오른쪽은 드롭아웃을 적용한 결과(dropout_ratio=0.15)

그림과 같이 드롭아웃을 적용하니 훈련 데이터와 시험 데이터에 대한 정확도 차이가 줄었다.

또, 훈련 데이터에 대한 정확도가 100%에 도달하지도 않게 되었다.

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

 

 

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

신경망에는 하이퍼파라미터가 다수 등장한다.

하이퍼파라미터는, 예를 들어 각 층의 뉴런 수, 배치 크기, 매개변수 갱신 시의 학습률과 가중치 감소 등이다.

하이퍼파라미터 값을 적절히 설정하지 않으면 모델의 성능이 크게 떨어지지만 값 결정이 쉽지 않다.

하이퍼파라미터 값을 효율적으로 탐색하는 방법을 보겠다.

 

6.5.1. 검증 데이터

(추후 작성)

 

'Study > 책『밑바닥부터 시작하는 딥러닝』' 카테고리의 다른 글

6-1. 학습 관련 기술들  (0) 2020.05.11
5-2. 오차역전파법  (0) 2020.04.09
5-1. 오차역전파법  (0) 2020.03.31
4-2. 신경망 학습  (0) 2020.03.25
4-1. 신경망 학습  (0) 2020.03.16

댓글