[머신러닝] 확률적 경사 하강법

3 분 소요

점진적인 학습

새로운 생선이 들어오면 훈련 데이터가 한 번에 준비되는 것이 아니다. 기존의 훈련 데이터에 새로운 데이터를 추가해 모델을 매일 훈련할 수 있지만 시간이 지날수록 데이터가 늘어나 지속가능한 방법이 아니다. 또 다른 방법은 새로운 데이터를 추가할 때 이전 데이터르 버림으로써 훈련 데이터 크기를 일정하게 유지하는 것이다. 이렇게 하면 데이터셋의 크기가 너무 커지지 않을 수 있다. 하지만 데이터를 버릴 때 다른 데이터에 없는 중요한 생선 데이터가 포함되어 있다면 큰일이다.

위에서 언급한 방법은 이전에 훈련한 모델을 버리고 다시 새로운 모델을 훈련하는 방식이다. 하지만 앞서 훈련한 모델을 버리지 않고 새로운 데이터에 대해서만 조금씩 훈련할 수도 있다. 이런식이면 훈련에 사용한 데이터를 모두 유지할 필요도 없고 앞서 학습한 생선을 까먹을 일도 없다.

이런식의 훈련방법은 점진적학습 또는 온라인 학습이라고 한다.

대표적인 점진적 학습 알고리즘은 확률적 경사 하강법(Stochastic Gradient Descent)이다.

사이킷런은 확률적 경사 하강법을 위한 클래스를 제공한다.



확률적 경사 하강법

확률적 경사 하강법은 훈련 세트에서 랜덤하게 하나의 샘플을 선택해 가파른 경사를 조금 내려간다. 그다음 훈련세트에서 랜덤하게 또 다른 샘플을 하나 선택해 경사를 조금 내려간다. 이런 식으로 전체 샘플을 모두 사용할 때까지 계속한다. 만약 모든 샘플을 다 사용했는데 산을 다 내려오지 못했다면 다시 처음부터 시작한다. 훈련 세트에 모든 샘플을 다시 채워넣는다. 그다음 다시 랜덤하게 하나의 샘플을 선택해 이어서 경사를 내려간다. 이렇게 만족할만한 위치에 도달할 때까지 계속 내려가면 된다.

확률적 경사 하강법에 훈련 세트를 한 번 모두 사용하는 과정을 에포크(epoch)라고 부른다. 일반적으로 경사 하강법은 수십, 수백번 이상 에포크를 수행한다.

1개씩 말고 무작위로 여러 개의 샘플을 사용해 경사 하강법을 수행하는 방식을 미니배치 경사 하강법(minibatch gradient descent)이라고 한다.

극단적으로 한 번 경사로를 따라 이동하기 위해 전체 샘플을 사용할 수도 있다. 이를 배치 경사 하강법(batch gradient descent)이라고 부른다.



손실함수

손실 함수(loss function)은 어떤 문제에서 머신러닝 알고리즘이 얼마나 엉터리인지를 측정하는 기준이다. 그래서 손실함수 값이 작을수록 좋다.

비용함수(cost function)은 손실 함수의 다른말이다. 손실 함수는 샘플 하나에 대한 손실을 정의하고 비용 함수는 훈련 세트에 있는 모든 샘플에 대한 손실 함수의 합을 말한다. 하지만 보통 이 둘을 엄격히 구분하지 않고 섞어서 사용한다.



로지스틱 손실 함수

로지스틱 손실 함수는 이진 크로스엔트로피 손실 함수라고도 부른다.

다중 분류에서는 크로스엔트로피 손실 함수를 사용한다.

회귀문제에서는 평균 제곱 오차를 사용한다.

손실 함수는 이미 개발되어 있어 직접 구현할 필요는 없다.



SGDClassifier

import pandas as pd

fish = pd.read_csv('https://bit.ly/fish_csv_data')

fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()
fish_target = fish['Species'].to_numpy()

from sklearn.model_selection import train_test_split

train_input, test_input, train_target, test_target = train_test_split(
    fish_input, fish_target, random_state=42)

from sklearn.preprocessing import StandardScaler

ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)

데이터를 훈련 세트와 테스트 세트로 나누고, 특성을 표준화 전처리 한다. 훈련 세트에서 학습한 통계값으로 테스트 세트도 변환해야 한다.

from sklearn.linear_model import SGDClassifier

sc = SGDClassifier(loss='log', max_iter=10, random_state=42)
sc.fit(train_scaled, train_target)

print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target))
0.773109243697479
0.775

SGDClassifier는 객체에 한 번에 훈련 세트를 전달했지만 이 알고리즘은 전달한 훈련세트에서 1개씩 샘플을 꺼내어 경사 하강법을 수행한다. 미니배치 경사하강법, 배치하강법은 제공하지 않는다.

loss는 손실함수 종류, max_iter는 수행할 에포크 횟수이다. log는 로지스틱 손실 함수이다.

sc.partial_fit(train_scaled, train_target)

print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target))
0.8151260504201681
0.825

확률적 경사 하강법은 점진적 학습이 가능하다. 모델을 이어서 훈련할 때는 partial_fit 메소드를 사용한다. 이 메소드는 fit 메소드와 사용법이 같지만 호출할 때마다 1 에포크씩 이어서 훈련할 수 있다.

점수가 아직 낮지만 에포크를 한 번 더 실행하니 정확도가 올라갔다.



에포크와 과대/과소 적합

확률적 경사 하강법을 사용한 모델은 에포크 횟수에 따라 과소적합이나 과대적합이 일어날 수 있다. 에포크 횟수가 적으면 모델이 훈련 세트를 덜 학습한다. 즉 산을 다 내려오지 못하고 훈련을 마치는 것이고, 에포크 횟수가 충분히 많다면 훈련 세트를 완전 학습할 것이다. 그래서 훈련 세트에 너무 잘 맞아 테스트 세트에서 오히려 점수가 나쁜 과대적합이 될 수 있다.

에포크가 진행됨에 따라 훈련 세트의 정확도는 꾸준히 증가하지만 테스트 세트의 정확도는 어느 순간 감소하기 시작한다. 이 감소하는 시점이 과대적합이 시작되는 시점이다. 과대적합이 시작하기 전에 훈련을 멈추는 것을 조기 종료라고 한다.

sc = SGDClassifier(loss='log', max_iter=100, tol=None, random_state=42)
sc.fit(train_scaled, train_target)

print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target))
0.957983193277311
0.925

SGDClassifier는 일정 에포크 동안 성능이 향상되지 않으면 더 훈련하지 않고 자동으로 멈춘다. tol 매개변수에서 향상될 최소값을 지정한다. 위 코드에서 tol을 None으로 지정하여 자동으로 멈추지 않고 에포크 100만큼 무조건 반복하도록 했다.

최종점수는 좋다.

sc = SGDClassifier(loss='hinge', max_iter=100, tol=None, random_state=42)
sc.fit(train_scaled, train_target)

print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target))
0.9495798319327731
0.925

loss 매개변수의 기본값은 hinge이다. 힌지 손실(hinge loss)은 서포트 백터 머신이라 불리는 또 다른 머신러닝 알고리즘을 위한 손실 함수이다. 서포트 백터 머신은 널리 사용하는 머신러닝 알고리즘이다.