학습할 데이터가 점진적으로 제공될 때 사용할 수 있는 알고리즘이다.
만약 매일 제공되는 데이터를 모델이 학습한다면 시간이 지날수록 데이터가 늘어나, 서버를 늘려야한다.
이를 해결하기 위해 새로운 데이터가 제공될 때, 기존의 데이터를 버리는 방식을 사용할 수 있다.
기존의 데이터를 버린다는 것은 다른 알고리즘에겐 훈련한 모델을 버린다는 것과 마찬가지이다.(분류, 회귀 모델)
하지만 경사 하강법은 업데이트 형식의 알고리즘이기 때문에 데이터를 버려도 모델은 그대로이다.
이렇게 점진적으로 학습하는 방법을 점진적 학습
또는 온라인 학습이라고 부른다.
대표적인 점진적 학습 알고리즘은 확률적 경사 하강법(Stochastic Gradient Descent)
이다.
물론 사이킷런에서 확률적 경사 하강법을 위한 클래스를 제공한다.
확률적 경사 하강법에서 확률적이란 말은 무작위하게의 기술적인 표현이다.
그 다음 경사는 우리가 다 아는 경사이다. 하강법은 내려가는 방법이다.
즉 확률적 경사 하강법은 무작위하게 경사를 하강한다는 말이다. 이게 무슨 소리지?
손실함수
는 어떤 문제에서 머신러닝 알고리즘이 얼마나 엉터러인지 측정하는 기준이다.
그렇다면 손실 함수 값이 작을수록 좋다. 하지만 어떤 값이 최솟값인지 알지 못한다.
가능한 많이 찾아보고 만족할만한 수준이라면 멈추면 된다.
그렇다면 확률적 경사 하강법
이란 이 손실 함수(경사)를 점진적으로 움직(하강)이며, 손실 함수값
의 최솟값을 찾는 것이다.
위에서 보았던 확률적
이란 말은 학습할 sample을 랜덤으로 뽑는 것을 의미한다.
전체 샘플을 사용했다면, 다시 처음부터 시작한다. 학습했던 샘플을 재사용하는 것이다.
(여기서의 시작점은 다른 모델과 다르게 한 번 학습을 마친 상태이다.)
이렇게 훈련 셋의 한 번 모두 사용하는 과정을 에포크(epoch)
라고 부른다.
(일반적으로 경사하강법은 수십, 수백 번 이상 에포크를 수행한다.)
여러 개의 샘플을 사용해 한 번의 경사 하강법을 수행하는 방식을 미니배치 경사 하강법(minibatch gradient desecent)
라고 한다.
극단적으로 전체 샘플을 사용해 한 번의 경사 하강법을 하는 방법을 배치 경사 하강법(batch gradient descent)
라고 부른다.(가장 안정적인 방법일 수 있지만, 컴퓨터 자원을 많이 사용하게 된다. 어떤 경우는 데이터가 너무 많아 컴퓨터가 한 번에 전체 데이터를 못 읽을 수 있다.)
(참고)
분류에서 손실은 아주 확실하다. 예측에 성공, 실패밖에 없다. 만약 4개의 sample 중 2개만 정확하게 맞추었다면 0.5의 정확도를 가지게 된다.
그런데 만약에 정확도가 0., 0.25, 0.5, 0.75, 1. 이렇게 존재한다면(sample이 4개) 경사 하강법을 사용할 수 없다. 왜냐하면 그래프가 연속적이지 않아, 즉 미분 불가능하여 점진적으로 움직일 수 없다는 것이다.
따라서 경사 하강법을 사용할 손실함수는 미분 가능해야만 한다.
그럼 어떻게 미분 가능한 손실함수를 만들 수 있을까? 로지스틱 회귀에서의 함수값의 범위는 [0, 1]이였다.
이 범위 내에서 어떠한 값도 될 수 있기에, 연속적이다. 위의 sample 4개의 예측값을 0.9, 0.3, 0.2, 0.8이라고 가정해보자. 첫 번째 샘플부터 어떻게 손실 함수를 만들 수 있는지 살펴보자.
손실 함수를 --1 x 예측 확률 x 타깃값로 정한다. 각각 샘플의 타깃값은 1 1 0 0 이다.
첫 번째 샘플의 예측 확률은 0.9이다. 이에 타깃값인 1을 곱하고 음수를 취해주면 -0.9이다.
예측 확률이 1에 가까울수록 좋은 모델이다. 손실함수가 작아지기 때문이다
두 번째 샘플은 -0.3이 나온다. 손실이 첫 번째 함수보다 크다.
세 번째 샘플은 타깃값이 0이기 때문에 0을 곱할 수 없다.
그렇기 때문에 양성 클래스에 대한 손실값으로 바꿔준다. 0의 확률이 0.2라면 1의 확률은 0.8이니, -1 x (1 x 0.8)을 하여 -0.8로 사용한다. -0.8은 꽤 낮은 손실이다.
네 번째 샘플은 손실이 높다. -0.2로 손실이 높다.
손실 함수를 만들 예측확률에 로그를 적용해보자.
타깃이 1일 때 -log(예측확률): 예측확률이 1에서 멀어질수록 손실은 아주 큰 양수가 된다.
타깃이 2일 때 -log(1-예측확률): 예측확률이 0에서 멀어질수록 손실은 아주 큰 양수가 된다.
손실함수를 위와 같이 정의한다면 손실값이 양수로 표현되어 이해하기가 쉬울 것이다.
그리고 모든 손실값을 타깃이 1일 때로 보기 때문에 로그를 적용한 식이 손실함수가 된다.
이를 로지스틱 손실 함수(logistic loss function)
또는 이진 크로스엔트로피 손실 함수(binary cross-entropy loss function)
라고 부른다.
로지스틱 손실함수를 사용한다면 로지스틱 회귀 모델이 만들어진다.
예시는 이진 분류였지만 이젠 다중 분류를 구현한다. 이를 위한 손실 함수는 크로스엔트로피 손실 함수(cross-entropy loss function)
라고 부른다.
참고로 회귀에서 사용하는 손실 함수는 이전에 본 평균 절댓값 오차 또는
평균 제곱 오차(mean square error)
를 많이 사용한다.
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()
feature, target 전체 데이터 셋
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) # 훈련 셋 학습 10번 sc.fit(train_scaled, train_target) sc.score(train_scaled, train_target) # 0.773109243697479 sc.score(test_scaled, train_scaled) # 0.775
훈련 셋과 테스트 셋의 정확도가 낮다. 게다가 과소적합도 나타난 것 같다. 10번으론 부족해보인다.
ConvergenceWarning이 뜬다면 모델이 최저값으로 충분히 수렴되지 않았다는 경고가 뜬다.
이 때 max_iter 매개변수 값을 늘려주는게 좋다.
이어서 모델을 훈련할 때는 partial_fit()
메서드를 사용한다.
이 메서드는 fit() 메서드와 사용법이 같지만 호출할 때마다 1 에포크씩 이어서 훈련할 수 있다.
sc.partial_fit(train_scaled, train_target) sc.score(train_scaled, train_target) # 0.8151260504201681 sc.score(test_scaled, test_target) # 0.85
정확도가 향상되었다. 더 훈련할 필요가 있어보인다.
참고로 SGDClassifier는 미니배치 경사 하강법이나 배치 경사 하강법을 제공하지 않는다. random_state에 따라 샘플을 하나씩 빼서 수행한다.
확률적 경사 하강법을 사용한 모델은 에포크 횟쉥 따라 과소적합이나 과대적합 될 수 있다. 왜 이런 현상이 일어날까?
- 에포크 횟수가 적으면 모델이 훈련 셋을 덜 학습한다.
- 에포크 횟수가 충분히 많으면 훈련 셋을 완전히 학습한다.
당연히 에포크 횟수가 적으면 훈련 셋과 테스트 셋에 잘 맞지 않는 과소적합된 모델이 될 가능성이 높다.
반대로 에포크 횟수가 많으면 훈련 셋에 너무 잘 맞아 테스트 셋에 오히려 나쁜 과대적합된 모델이 될 가능성이 높다.
에포크가 진행될수록 훈련 셋의 점수는 에포크가 진행될수록 꾸준히 증가하지만 테스트 셋의 점수는 어느 순간 감소하기 시작한다.
바로 이 지점이 모델이 과대적합되기 시작하는 지점이다. 이 전에 멈추는 것을 조기 종료(early stopping)
라고 합니다.
이 예제에서 fit()말고 partial_fit() 메서드만 사용한다. 이 메서드만 사용하려면 훈련 셋에 있는 전체 클래스의 레이블을 partial_fit() 메서드에 전달해주어야 합니다. np.unique() 함수로 train_target에 있는 7개 생선의 목록을 만들어준다.
에포크마다 정확도를 저장할 리스트도 준비한다,
import numpy as np sc = SGDClassifier(loss='log', random_state=42) train_score = [] test_score = [] classes = np.unique(train_target)
300번의 에포크 동안 훈련을 반복하여 진행해보자.
for _ in range(0, 300) sc.partial_fit(train_scaled, train_target, classes=classes) train_score.append(sc.score(train_scaled, train_target)) test_score.append(sc.score(test_scaled, test_target))
에포크마다의 정확도를 그래프로 확인해보자.
import matplotlib.pyplot as plt plt.plot(train_score) plt.plot(test_score) plt.xlabel('epoch') plt.ylabel('accuracy') plt.show()
데이터가 작기 때문에 아주 잘 드러나지는 않지만, 백 번째 에포크 이후에는 훈련 셋과 테스트 셋의 점수가 조금씩 벌어지고 있다. 또 확실히 에포크 초기에는 과소적합되어 훈련 셋과 테스트 셋의 점수가 낮다. 이 모델의 경우 백 번째 에포크가 적절한 반복 횟수로 보인다.
에포크 횟수를 100으로 설정하고 모델을 다시 훈련해보자.
sc = SGDClassifier(loss='log', max_iter=100, tol=None, random_state=42) sc.fit(train_scaled, train_target) sc.score(train_scaled, train_target) # 0.957983193277311 sc.score(test_scaled, test_target) # 0.925
이 객체는 일정 에포트 동안 성능이 향상되지 않으면 더 훈련하지 않고 자동으로 멈춘다.
tol
매개변수에서 향상될 최솟값을 지정한다. None으로 지정하여 자동으로 멈추지 않고 max_iter=100 만큼 무조건 반복하도록 하겠다.
최종 점수가 좋다. 훈련 셋과 테스트 셋의 정확도 점수가 비교적 높게 나왔다.
SGDClassifier의 loss 매개변수의 기본값은 hinge
이다. 힌지 손실(hinge loss)는 서포트 벡터 머신(support vector machine)
이라 불리는 또 다른 머신러닝 알고리즘을 위한 손실 함수이다.
더 자세히 다루진 않겠지만 이게 있다는 것만 알아둬라.