KoreanFoodie's Study

딥러닝 튜토리얼 4강 2부, 수치 미분과 학습 알고리즘 - 밑바닥부터 시작하는 딥러닝 본문

Deep Learning/밑바닥부터 시작하는 딥러닝 1

딥러닝 튜토리얼 4강 2부, 수치 미분과 학습 알고리즘 - 밑바닥부터 시작하는 딥러닝

GoldGiver 2019. 11. 7. 21:58

해당 포스팅은 한빛 미디어에서 출판한 '밑바닥부터 시작하는 딥러닝'이라는 교재의 내용을 따라가며 딥러닝 튜토리얼을 진행하고 있습니다.관련 자료는여기에서 찾거나 다운로드 받으실 수 있습니다.


수치 미분

이전 포스트에서, 매개 변수의 미분값(기울기)값으로 최적의 매개변수를 찾아나가는 것이 신경망을 학습시키는 원리라고 설명한 바 있다. 그렇다면, 이 기울기 값을 구하기 위해 먼저 미분 함수부터 구현해 보도록 하겠다.

  • 미분

미분의 정의식은 다음과 같다.

나이브하게 구현한다면, 다음과 같이 구현해볼 수 있을 것이다.

def numerical_diff(f, x):
    h = 10e-50
    return (f(x+h) - f(x)) / h

함수의 이름은 수치 미분(numerical differentiation)에서 따온 numerical_diff(f, x)로 했다. 얼핏 보면 문제가 없어 보이지만, 실제로는 개선해야 할 점이 2개 있다.

앞의 구현에서 h에 가급적 작은 값을 대입하고 싶었기에 10e-50이라는 값을 이용했지만, 이 방식은 반올림 오차(rounding error) 문제를 일으킨다. 반올림 오차는 작은 값(가령 소수점 8자리 이하)이 생략되어 최종 계산 결과에 오차가 생기게 한다. 예시를 보자.

>>> np.float32(1e-50)
0.0

너무 작은 값을 이용하면 컴퓨터로 계산하는 데 문제가 생기니, h를 10^-4정도로 놓고 사용해 보자.

두 번째 개선은 함수 f의 차분(임의 두 점에서의 함수 값들의 차이)과 관련된 것이다. 위의 미분 정의를 보면, h를 0으로 무한히 가깝게 보내야 하지만, 실제로는 그 수준까지 작게 만들 수 없기 때문에 오차가 발생한다.

이 오차를 줄이기 위해, (x+h)(x-h)일 때의 함수 f의 차분을 계산하는 방법을 쓰기도 한다. 이런 식으로 차분을 계산하는 것을 중심 차분 혹은 중앙 차분이라고 한다(한편, (x+h)x의 차분은 전방 차분이라고 한다). 이제 수치 미분 함수를 다시 구현해 보자.

def numerical_diff(f, x):
    h = 1e-4    # 0.0001
    return (f(x+h) - f(x-h)) / (2*h)
  • 편미분

다음 식을 구현해 보자.

def function_2(x):
    return x[0]**2 + x[1]**2

x가 넘파이 배열이라고 했을 때, np.sum(x**2)처럼 간단히 구현할 수도 있다. 이 그래프를 그리면, 다음과 같은 그림이 나온다.

위 식은 변수가 두 개이므로, 각각의 변수에 대해 미분을 진행할 수 있고, 이것을 편미분이라고 한다. 실습을 통해 값을 확인해 보자.

  1. x0 = 3, x1 = 4일때, x0에 대한 편미분 값을 구해보자.
def function_tmp1(x0):
    return x0*x0 + 4.0**2.0
numerical_diff(function_tmp1, 3.0)    # 6.0000000378
  1. x0 = 3, x1 = 4일때, x1에 대한 편미분 값을 구해보자.
def function_tmp2(x1):
    return 3.0**2.0 + x1**x1
numerical_diff(function_tmp2, 340)    # 7.999999999119

편미분을 구할 때는, 여러 변수 중 목표 변수 하나에 초점을 맞추고 다른 변수는 값을 고정하면 된다!


기울기

위의 예시에서는 x0와 x1의 편미분을 변수별로 따로 계산했는데, 이를 동시에 계산할 수도 있을까? 가능하다! 그리고 이 편미분을 벡터로 정리한 것을 기울기(gradient)라고 한다. 코드로는 다음과 같이 구현 가능하다.

def _numerical_gradient_no_batch(f, x):
    h = 1e-4 # 0.0001
    grad = np.zeros_like(x) # x와 형상이 같은 배열을 생성

    for idx in range(x.size):
        tmp_val = x[idx]

        # f(x+h) 계산
        x[idx] = float(tmp_val) + h
        fxh1 = f(x)

        # f(x-h) 계산
        x[idx] = tmp_val - h 
        fxh2 = f(x) 

        grad[idx] = (fxh1 - fxh2) / (2*h)
        x[idx] = tmp_val # 값 복원

    return grad

실제 결과값을 테스트 해보면 다음과 같은 형식으로 나온다(아까 전 정의한 function_2함수를 이용).

>>> gr2.numerical_gradient(function2, np.array([3.0, 4.0]))
array([6., 8.])
>>> gr2.numerical_gradient(function2, np.array([0.0, 2.0]))
array([0., 4.])
>>> gr2.numerical_gradient(function2, np.array([3.0, 0.0]))
array([6., 0.])

이처럼, (x0, x1)의 각 점에서의 기울기를 계산할 수 있음을 알 수 있다. 그림으로 이 값의 의미를 살펴보자.

기울기는 위의 그림처럼, 방향을 가진 벡터(화살표)로 그려진다. 이 그림을 보면 기울기는 '가장 낮은 장소(최솟값)'을 가리키는 것 같다. 위의 그림에서 기울기는 가장 낮은 장소를 가리키지만, 실제는 반드시 그렇다고는 할 수 없다. 사실 기울기는 각 지점에서 낮아지는 방향을 가리킨다. 더 정확히는 기울기가 가리키는 쪽은 각 장소에서 함수의 출력 값을 가장 크게 줄이는 방향이다!

기울기는 각 지점에서 낮아지는 방향을 가리킨다. 더 정확히는 기울기가 가리키는 쪽은 각 장소에서 함수의 출력 값을 가장 크게 줄이는 방향이다!

  • 경사법(경사 하강법)

기계학습 문제의 대부분은 학습 단계에서 최적의 매개변수를 찾아낸다. 신경망 역시 최적의 매개변수(가중치와 편향)를 학습 시에 찾아야 한다. 하지만 이는 매우 복잡하므로, 이런 상황에서 기울기를 잘 이용해 함수의 최솟값을 찾으려는 것이 경사법이다.

여기에서 주의할 점은 각 지점에서 함수의 값을 낮추는 방안을 제시하는 지표가 기우리라는 것이다. 하지만 복잡한 함수에서는 기울기가 가리키는 방향에 최솟값이 없는 경우가 대부분이다.

함수가 극솟값, 최솟값, 또 안장점(saddle point)이 되는 장소에서는 기울기가 0이 된다. 안장점은 어느 방향에서 보면 극댓값이고 다른 방향에서 보면 극솟값이 되는 점이다. 경사법은 기울기가 0인 장소를 찾지만 그것이 반드시 최솟값이라고는 할 수 없다. 또, 복잡하고 찌그러진 모양의 함수라면 평평한 곳으로 파고들면서 고원(plateau)이라 하는, 학습이 진행되지 않는 정체기에 빠질 수 있다.

경사법은 현 위치에서 기울어진 방향으로 일정 거리만큼 이동한다. 그런 다음 이동한 곳에서도 마찬가지로 기울기를 구하고, 또 그 기울어진 방향으로 나아가기를 반복한다. 이것을 경사법(gradient method)이라고 한다.

경사법은 최솟값을 찾느냐, 최댓값을 찾느냐에 따라 이름이 다르다. 전자를 경사 하강법(gradient descent method), 후자를 경사 상승법(gradient ascent method)이라고 한다.

경사법을 수식으로 나타내 보자.

η기호(eta)는 갱신하는 양을 나타낸다. 이를 신경망 학습에서는 학습률(learning rate)이라고 하며, 매개변수 값을 얼마나 갱신할 것인지를 나타낸다.

학습률 값은 0.01이나 0.001 등 미리 특정 값으로 정해두어야 한다. 일반적으로 이 값이 너무 크거나 작으면 '좋은 장소'를 찾아갈 수 없고, 학습 도중에 이 값을 변경하며 올바르게 학습이 진행되는지를 확인하며 진행한다.

이제 경사 하강법을 구현해 보자.

def gradient_descent(f, init_x, lr=0.01, step_num=100):
    x = init_x
    x_history = []

    for i in range(step_num):
        x_history.append( x.copy() )

        grad = numerical_gradient(f, x)
        x -= lr * grad

    return x, np.array(x_history)

실제로 이 코드를 이용해서 결과값을 확인해 보자. (아래 코드를 추가)

def function_2(x):
    return x[0]**2 + x[1]**2

init_x = np.array([-3.0, 4.0])    

lr = 0.1
step_num = 20
x, x_history = gradient_descent(function_2, init_x, lr=lr, step_num=step_num)

plt.plot( [-5, 5], [0,0], '--b')
plt.plot( [0,0], [-5, 5], '--b')
plt.plot(x_history[:,0], x_history[:,1], 'o')

plt.xlim(-3.5, 3.5)
plt.ylim(-4.5, 4.5)
plt.xlabel("X0")
plt.ylabel("X1")
plt.show()

파일을 실행시켜 보면, 다음과 같은 결과가 나온다.

학습률 lr값을 10, 1e-10 같은 값으로 바꾸면, 매개변수 값 갱신이 잘 이루어지지 않는 것을 확인해 볼 수 있을 것이다!

학습률 같은 매개변수를 하이퍼파라미터(hyper parameter)라고 한다. 이 하이퍼파라미터들은 가중치와 매개변수와는 달리 직접 정해야 하는 것이며, 여러 후보 값 중에서 시험을 통해 가장 잘 학습하는 과정을 거쳐야 한다.

  • 신경망에서의 기울기

신경망 학습에서도 기울기를 구해야 한다. 여기서 말하는 기울기는 가중치 매개변수에 대한 손실 함수의 기울기이다. 예를 들어, 형상이 2X3, 가중치가 W, 손실 함수가 L인 신경망을 생각해 보자.

행렬의 각 원소는 각각의 원소에 관한 편미분을 나타낸다.

그럼 이것을 이용해 실제로 기울기를 구하는 코드를 구현해 보자(소스 코드는 ch04/gradient_simplenet.py에 있다).

# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
from common.functions import softmax, cross_entropy_error
from common.gradient import numerical_gradient


class simpleNet:
    def __init__(self):
        self.W = np.random.randn(2,3) # 정규분포로 초기화

    def predict(self, x):
        return np.dot(x, self.W)

    def loss(self, x, t):
        z = self.predict(x)
        y = softmax(z)
        loss = cross_entropy_error(y, t)

        return loss

x = np.array([0.6, 0.9])
t = np.array([0, 0, 1])

net = simpleNet()

f = lambda w: net.loss(x, t)
dW = numerical_gradient(f, net.W)

print(dW)

여기에서는 common/function.py에 정의한 softmaxcross_entropy_error메서드를 이용한다. simpleNet 클래스는 형상이 2X3인 가중치 매개변수 하나를 인스턴스 변수로 갖는다. predict(x)는 예측을 수행하고, loss(x, t)는 손실 함수의 값을 구한다. 인수 x는 입력 데이터, t는 정답 레이블이다. 이제 테스트를 돌려 보자.

>>> import gradient_simplenet as gs
[[ 0.20712809  0.30619585 -0.51332394]
 [ 0.31069213  0.45929378 -0.76998591]]
>>> net = gs.simpleNet()
>>> print(net.W)
[[ 0.77872534  1.68368502  1.25014485]
 [-1.29329159  0.30947396 -0.60716658]]
>>> import numpy as np
>>> x = np.array([0.6, 0.9])
>>> p = net.predict(x)
>>> print(p)
[-0.69672723  1.28873757  0.20363699]
>>> np.argmax(p)
1
>>> t = np.array([0, 1, 0])
>>> net.loss(x, t)
0.38878297065251444

이어서 기울기를 구해보자. 지금까지처럼 numerical_gradient(f, x)를 써서 구하면 된다. 함수 f(W)를 정의할 것인데, 함수의 인수 W는 더미로 만든 것이다(신경쓰지 않아도 됨).

>>> def f(W):
...     return net.loss(x, t)
...
>>> import gradient_2d as g2
>>> dW = g2.numerical_gradient(f, net.W)
>>> print(dW)
[[ 0.05585067 -0.19327121  0.13742053]
 [ 0.08377601 -0.28990681  0.2061308 ]]

nuerical_gradient(f, x)인수 f는 함수, x는 함수 f의 인수이다. 여기에서는 net.W를 인수로 받아 손실 함수를 계산하는 새로운 함수 f를 정의했다. 그리고 이 새로 정의한 함수를 numerical_gradient(f, x)에 넘긴다.

dW는 numerical_gradient(f, x)의 결과로, 그 형상은 2X3의 2차원 배열이다. dW의 내용을 보면, (1, 1)의 원소가 0.05585067이므로, W11을 h만큼 늘리면 손실 함수의 값은 0.05만큼 증가한다는 의미이다. 그래서 손실 함수를 줄인다는 관점에서는 W11은 음의 방향으로 갱신해야 함을 알 수 있다.

참고로 이 구현에서는 새로운 함수를 정의하는 데 def f(x): ...문법을 썼는데, 파이썬에서는 간단한 함수라면 람다(lambda) 기법을 쓰면 더 편하다. 가량 lambda를 쓰면 다음과 같이 구현할 수 있다.

>>> f = lambda w: net.loss(x, t)
>>> dW = numerical_gradient(f, net.W)

신경망의 기울기를 구한 다음에는 경사법에 따라 가중치 매개변수를 갱신하기만 하면 된다. 이제 2층 신경망을 대상으로 학습 과정 전체를 구현해 보자.


학습 알고리즘 구현하기

신경을 구현하기 전에, 신경망 학습 절차를 다시 되짚어 보자.

전제 :

신경망에는 적응 가능한 가중치와 편향이 있고, 이 가중치와 편향을 훈련 데이터에 적응하도록 조정하는 과정을 '학습'이라 한다. 신경망 학습은 다음과 같이 4단계로 수행한다.

1단계 - 미니배치 :

훈련 데이터 중 일부를 무작위로 가져온다. 이렇게 선별한 데이터를 미니배치라 하며, 그 미니배치의 손실 함수 값을 줄이는 것이 목표이다.

2단계 - 기울기 산출 :

미니배치의 손실 함수 값을 줄이기 위해 각 가중치 매개변수의 기울기를 구한다. 기울기는 손실 함수의 값을 가장 작게 하는 방향을 제시한다.

3단계 - 매개변수 갱신 :

가중치 매개변수를 기울기 방향으로 아주 조금 갱신한다.

4단계 - 반복 :

1~3단계를 반복한다.

이것이 신경망 학습이 이뤄지는 순서이다. 이는 경사 하강법으로 매개변수를 갱신하는 방법이며, 이때 데이터를 미니배치로 무작위로 선정하기 때문에 확률적 경사 하강법(stochastic gradient descent, SGD)이라고 부른다. '확률적으로 무작위로 골라낸 데이터'에 대해 수행하는 경사 하강법이라는 의미다. 대부분 딥러닝 프레임워크는 확률적 경사 하강법의 영어 머리글자를 딴 SGD라는 함수로 이 기능을 구현하고 있다.

그럼 실제로 손글씨 숫자를 학습하는 신경망을 구현해 보자.

  • 2층 신경망 클레스 구현하기

처음에는 2층 신경망을 하나의 클래스로 구현하는 것부터 시작한다. 해당 클래스의 이름은 TwoLayerNet이다. 소스 코드는 ch04/two_layer_net.py에 있다.

class TwoLayerNet:

    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
        # 가중치 초기화
        self.params = {}
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
        self.params['b2'] = np.zeros(output_size)

    def predict(self, x):
        W1, W2 = self.params['W1'], self.params['W2']
        b1, b2 = self.params['b1'], self.params['b2']

        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)
        a2 = np.dot(z1, W2) + b2
        y = softmax(a2)

        return y

    # x : 입력 데이터, t : 정답 레이블
    def loss(self, x, t):
        y = self.predict(x)

        return cross_entropy_error(y, t)

    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        t = np.argmax(t, axis=1)

        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy

    # x : 입력 데이터, t : 정답 레이블
    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t)

        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])

        return grads

먼저, 하나하나 변수가 가지는 의미를 짚어 보자.

  1. TwoLayerNet 클래스가 사용하는 변수

params : 신경망의 매개변수를 보관하는 딕셔너리 변수(인스턴스 변수)
grads : 기울기를 보관하는 딕셔너리 변수(numerical_gradient() 메서드의 반환 값)

  1. TwoLayerNet 클래스의 메서드

__init__(self, input_size, hidden_size, output_size) : 초기화를 수행한다. 인수는 순서대로 입력층의 뉴런 수, 은닉층의 뉴런 수, 출력층의 뉴런 수
predict(self, x) : 예측(추론)을 수행한다. 인수 x는 이미지 데이터
loss(self, x, t) : 손실 함수의 값을 구한다. x는 이미지 데이터, t는 정답 레이블
accruacy(self, x, t) : 정확도를 구한다.
numerical_gradient(self, x, t) : 가중치 매개변수의 기울기를 구한다
gradient(self, x, t) : 가중치 매개변수의 기울기를 구한다. numerical_gradient()의 성능 개선판.

grads 변수에는 params 변수에 대응하는 각 매개변수의 기울기가 저장된다. 예를 들면 다음과 같다.

x = np.random.rand(100, 784)
t = np.random.rand(100, 10)

grads = net.numerical_gradient(x, t)

grads['W1'].shape    # (784, 100)
grads['b1'].shape    # (100,)
grads['W2'].shape    # (100, 10)
grads['b2'].shape    # (10)
  • 미니배치 학습 구현하기

신경망 학습 구현에는 앞에서 설명한 미니배치 학습을 활용해 보겠다. 미니배치 학습이란 훈련 데이터 중 일부를 무작위로 꺼내고(미니배치), 그 미니배치에 대해 경사법으로 매개변수를 갱신하는 것이다. 그럼 TwoLayerNet 클래스와 MNIST 데이터셋을 사용하여 학습을 수행해 보자.

# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet

# 데이터 읽기
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)

network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

# 하이퍼파라미터
iters_num = 10000  # 반복 횟수를 적절히 설정한다.
train_size = x_train.shape[0]
batch_size = 100   # 미니배치 크기
learning_rate = 0.1

train_loss_list = []

for i in range(iters_num):
    # 미니배치 획득
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]

    # 기울기 계산
    #grad = network.numerical_gradient(x_batch, t_batch)
    grad = network.gradient(x_batch, t_batch)

    # 매개변수 갱신
    for key in ('W1', 'b1', 'W2', 'b2'):
        network.params[key] -= learning_rate * grad[key]

    # 학습 경과 기록
    loss = network.loss(x_batch, t_batch)
    train_loss_list.append(loss)

여기에서는 미니배치 크기를 100으로 했다. 즉, 60,000개의 훈련 데이터에서 임의로 100개의 데이터(이미지 데이터와 정답 레이블 데이터)를 추려낸다. 그리고 그 100개의 미니배치를 대상으로 확률적 경사 하강법을 수행해 매개변수를 갱신한다. 경사법에 의한 갱신 횟수(반복 횟수)를 10,000번으로 설정하고, 갱신할 때마다 훈련 데이터에 대한 손실 함수를 계산하고, 그 값을 배열에 추가한다. 이 손실 함수의 값이 변화하는 추이를 나타내면 다음과 같다.

이를 보면 학습이 진행될수록, 손실 함수의 값이 줄어들며, 신경망의 가중치 매개변수가 데이터에 적응하고 있음을 할수 있다! 이는 최적 가중치 매개변수를 찾아나가는 방향으로 신경망이 학습하고 있음을 의미한다.

  • 시험 데이터로 평가하기

신경망 학습에서는 훈련 데이터 외의 데이터를 올바르게 인식하는지 확인해야 한다. 이는 '오버피팅'을 일으키지 않는지 확인해야 한다는 뜻이다. 오버피팅되었다는 것은, 즉 훈련된 데이터만 정확하게 구분한다는 뜻이다.

이를 위해 다음 구현에서는 학습 도중 정기적으로 훈련 데이터와 시험 데이터를 대상으로 정확도를 기록한다. 여기에서는 1에폭별로 훈련 데이터와 시험 데이터에 대한 정확도를 기록한다.

에폭(epoch)은 하나의 단위이다. 1에폭은 학습에서 훈련 데이터를 모두 소진했을 때의 횟수에 해당한다. 예컨대 훈련 데이터 10,000개를 100개의 미니배치로 학습할 경우, 확률적 경사 하강법을 100회 반복하면 모든 훈련 데이터를 '소진'한 게 된다. 이 경우 100회가 1에폭이 된다.

# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet

# 데이터 읽기
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)

network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

# 하이퍼파라미터
iters_num = 10000  # 반복 횟수를 적절히 설정한다.
train_size = x_train.shape[0]
batch_size = 100   # 미니배치 크기
learning_rate = 0.1

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

# 1에폭당 반복 수
iter_per_epoch = max(train_size / batch_size, 1)

for i in range(iters_num):
    # 미니배치 획득
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]

    # 기울기 계산
    #grad = network.numerical_gradient(x_batch, t_batch)
    grad = network.gradient(x_batch, t_batch)

    # 매개변수 갱신
    for key in ('W1', 'b1', 'W2', 'b2'):
        network.params[key] -= learning_rate * grad[key]

    # 학습 경과 기록
    loss = network.loss(x_batch, t_batch)
    train_loss_list.append(loss)

    # 1에폭당 정확도 계산
    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("train acc, test acc | " + str(train_acc) + ", " + str(test_acc))

앞의 코드로 얻은 결과는 다음과 같다.

훈련 데이터에 대한 정확도를 실선으로, 시험 데이터에 대한 정확도를 점선으로 표시했다. 에폭이 진행될수록 훈련 데이터와 시험 데이터를 사용하고 평가한 정확도가 모두 좋아지고 있으며, 두 정확도에는 거의 차이가 없음을 알 수 있다.

가끔 훈련 데이터에 지나치게 적응하면, 훈련 데이터에 대한 정확도는 높아지나 시험 데이터에 대한 정확도가 떨어지는 일이 생긴다. 그 순간을 포착해 학습을 중단하면 오버피팅을 효과적으로 예방할 수 있으며, 이 기법을 '조기 종료(early stopping)'이라고 한다. 자세한 내용은 추후에 다루도록 하겠다.

Comments