이전 글에서 어떤 생선이 7개의 생선들 중에서 어떤 생선 일지에 대한 확률을 구하는 방식으로 로지스틱 회귀를 이용하여 모델을 설계해봤다. 이번에는 데이터가 한 번에 준비되는 것이 아닌 점진적으로 준비되는 데이터로 모델을 훈련하는 법을 공부해보자.
점진적인 학습
7개의 생선에 대해 데이터가 한번에 주어지는 것이 아니라 조금씩 전달된다면 어떤 방식으로 훈련을 해야 할까?
기존의 훈련 데이터에 새로운 데이터를 추가하여 모델을 매일 훈련시키는 방법을 떠올릴 수 있을 것이다. 하지만 이 방법은 시간이 지날수록 데이터가 늘어난다는 것이다. 미래를 생각하면 좋지 않은 방법이다.
그렇다면 새로운 데이터를 추가할 때 이전 데이터를 버리면서 훈련 데이터 크기를 일정하게 유지하는 방법은 어떨까?
이 방법에도 문제가 있다. 데이터셋의 크기가 너무 커지지 않는 것, 이전 데이터를 버릴 때 중요한 데이터가 포함되어있을 수도 있다는 것이다.
그렇다면 어떻게 해야 할까? 이전에 훈련한 모델을 버리지 않고 새로운 데이터에 대해서만 조금씩 훈련할 수는 없을까?
이런 훈련 방식을 점진적 학습 또는 온라인 학습이라고 한다. 대표적인 점진적 학습 알고리즘이 이번 글에서 공부할 확률적 경사 하강법(Stochastic Gradient Descent)이다.
확률적 경사 하강법
확률적 경사 하강법에서 확률적이란 말은 '무작위 하게', '랜덤 하게'의 기술적인 표현이다. 경사란 말은 기울기를 의미하고 하강법은 그 기울기로 내려간다는 의미이다.
랜덤하게 경사를 내려간다라는 뜻인데, 의미가 잘 와닿지 않는다. 일반적으로 경사를 내려올 때 가장 빨리 내려오는 법은 가장 가파른 길을 찾는 것이다. 그렇다면 가장 가파른 길을 어떻게 찾을까?
훈련 세트를 사용해서 모델을 훈련하기 때문에 이 경사 하강법 또한 훈련 세트를 사용하여 가장 가파른 길을 찾을 것이다.
하지만 전체 샘플을 사용해서 찾는 것이 아니라 랜덤으로 딱 하나의 샘플을 훈련 세트에서 골라 찾는다. 즉 확률적 경사 하강법이라는 말은 훈련 세트에서 랜덤하게 하나의 샘플을 고르는 것이라고 할 수 있다.
하나의 샘플을 사용하는 것이라기보다는 일단 훈련 세트에서 랜덤 하게 하나의 샘플을 골라서 경사를 조금 내려가고, 그다음에 또 하나를 골라 조금씩 내려간다. 이렇게 전체 샘플을 모두 사용할 때까지 계속한다.
모든 샘플을 사용했는데도 내려오지 못했다면 어떻게 해야 할까?
다시 처음부터 샘플을 채워 넣고 똑같이 경사를 내려간다. 이렇게 만족할만한 위치에 도달할 때까지 계속 내려가면 된다.
확률적 경사 하강법에서 훈련 세트를 한 번 모두 사용하는 과정을 에포크(Epoch)라고 한다. 일반적으로 경사 하강법은 수십, 수백 번 에포크를 수행한다.
꼭 하나의 샘플을 사용할 필요는 없다. 무작위로 몇 개의 샘플을 골라 경사 하강법을 수행하는 방식을미니 배치 경사 하강 (Minibatch Gradient Descent)이라고 한다.
하나의 샘플을 사용하는 경우, 여러 개의 샘플을 사용하는 경우가 있다면, 모든 샘플을 사용하는 경우도 있는 것이 당연하다.
한 번 경사로를 따라 이동하기 위해 전체 샘플을 사용하는 방식을 배치 경사 하강법(Batch Gradient Descent)이라고 한다.
전체 데이터를 사용하기 때문에 가장 안전한 방법이 될 수도 있지만, 전체 데이터를 사용하는 만큼 컴퓨터 자원을 많이 사용한다.
* 확률적 경사 하강법을 꼭 사용하는 알고리즘이 있는데 바로 신경망 알고리즘이다. 신경망은 일반적으로 많은 데이터를 사용하기 때문에 한 번에 모든 데이터를 사용하기 어렵고 모델이 매우 복잡해서 수학적인 방법으로 해답을 얻기가 힘들다. 그래서 신경망 모델은 확률적 경사 하강법이나 미니 배치 경사 하강법을 사용한다.
경사라 하면 너무 추상적인 것 같다. 도대체 우리가 내려갈 경사는 무엇일까? 이 경사를 바로 손실 함수라고 한다.
손실 함수
손실 함수(Loss Function)는 어떤 문제에서 머신 러닝 알고리즘이 얼마나 엉터리인지를 측정하는 기준이다. 그러므로 손실 함수는 작을수록 좋다. 하지만 어떤 값이 최솟값인지는 알 수 없다. 가능한 많이 찾아보고 만족할만하면 인정해야 한다. 다행히 많은 문제에 필요한 손실 함수는 이미 정의되어 있다.
손실 함수는 미분 가능해야 한다. 미분 가능하려면 일단 연속 이어야 하는데, 어떻게 연속적인 손실 함수를 만들 수 있을까? 이전 글에서 공부했던 로지스틱 회귀 모델의 확률을 생각해보면, 예측은 0 또는 1이지만 확률은 0~1 사이의 어떤 값도 될 수 있다.
손실의 크기를 설정하기 위해 확률에 log값을 취해주면 양성 클래스일 때 손실은 -log(예측 확률)이고, 확률이 1에서 멀어질수록 손실은 아주 큰 양수가 된다. 음성 클래스일 때 손실은 -log(1-예측 확률)이고 확률이 0에서 멀어질수록 손실은 아주 커진다. 이 손실 함수를 로지스틱 손실 함수(Logistic Loss Function) 혹은 이진 크로스 엔트로피 손실 함수(binary cross-entropy loss function)라고 한다.
다중 분류도 이진 분류와 비슷한 손실 함수를 사용하는데 이를 크로스엔트로피 손실 함수(cross-entropy loss function)라고 한다.
손실 함수를 직접 만드는 일은 거의 없기 때문에, 이진 분류는 로지스틱 손실 함수, 다중 분류는 크로스엔트로피 손실 함수를 사용한다는 것만 알아두자.
SGDClassifier
fish_csv_data 파일에서 판다스 데이터 프레임을 만들고 Species 열은 타깃 데이터, 나머지 5개를 입력 데이터로 사용하자.
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')
fish_target = fish['Species'].to_numpy()
fish_input = fish[['Weight', 'Length', 'Diagonal', 'Height', 'Width']]
train_test_split() 함수를 사용해서 훈련 세트와 테스트 세트로 나누자.
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 = StandartScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)
사이킷런에서 확률적 경사 하강법을 제공하는 대표적인 분류용 클래스는 SDGClassifier이다. sklearn.liner_model 아래에 있다.
from sklearn.linear_model import SGDClassifier
SDGClassifier의 객체를 만들 때는 loss와 max_iter 매개변수를 지정해야 한다. loss는 손실 함수의 종류를 지정해주고, max_iter는 이전에 사용했던 것처럼 반복 횟수(수행할 에포크 횟수)를 지정한다. loss = 'log'로, max_iter = 10으로 지정하여 전체 훈련 세트를 10회 반복하고, 정확도를 측정해보자.
sc = SGDClassifier(loss='log', max_iter=10, random_state=42)
sc.fit(train_scaled, train_target)
10회만 반복해서 그런지 점수가 낮다. 확률적 경사 하강법은 점진적인 학습이 가능하다고 했다. 추가로 더 훈련을 시켜보자. 모델을 이어서 훈련시킬 때는 partial_fit() 메서드를 사용한다. 이 메서드는 fit() 메서드와 사용법이 같지만 호출할 때마다 1 에포크씩 이어서 훈련할 수 있다.
에포크를 한 번 더 하니 정확도가 올라갔다. 계속 훈련하면 계속해서 값이 올라갈까? 그렇다면 얼마나 더 훈련해야 할까?
그에 대한 기준이 필요할 것 같다.
* train_scaled와 train_target을 한꺼번에 사용했으니 배치 경사 하강법이 아닐까?
-> SGDClassifier 객체에 한 번에 훈련 세트 전체를 전달하지만 전달한 세트에서 1개씩 꺼내어 사용한다.
에포크와 과대/과소 적합
에포크 횟수에 따라 과소 적합이나 과대 적합이 나타날 수 있다. 에포크 횟수가 적으면 훈련 세트에 대해 학습을 충분히 하지 못하여 점수가 낮게 나올 수 있고, 에포크 횟수가 많으면 훈련 세트에 지나치게 최적화되어 점수가 높게 나올 수 있기 때문이다.
위의 그래프에서 왼쪽으로 갈수록 과소 적합, 오른쪽으로 갈수록 과대 적합이 발생하는 것을 알 수 있다.
또한 훈련 세트는 에포크가 진행될수록 증가하지만 테스트 세트는 어느 순간부터 점수가 낮아지기 시작한다.
과대 적합이 시작하기 전, 즉 테스트 세트의 점수가 낮아지는 순간에 훈련을 멈추는 것을 조기 종료(Early Stopping)라고 한다. 위와 같은 그래프를 만들어보자.
이번엔 fit() 메서드를 사용하지 않고 partial_fit() 메서드만 사용해보겠다. parial_fit() 메서드만 사용하려면 훈련 세트에 있는 전체 클래스의 레이블을 partial_fit() 메서드에 전달해주어야 하는데 이를 위해서 np.unique() 함수로 train_target에 있는 7개의 생선의 목록을 만들고 에포크마다 훈련 세트와 테스트 세트에 대한 점수를 기록하기 위해 2개의 리스트를 준비해야 한다.
import numpy as np
sc = SGDClassifier(loss='log', random_state=42)
train_score = []
test_score = []
classes = np.unique(train_target)
classes에는 7개의 생선 이름이 들어있을 것이다. 300번의 에포 동안 훈련을 반복하여 진행하고 반복마다 훈련 세트와 테스트 세트의 점수를 train_score와 test_score에 append 하자.
for _ in range(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))
train_score와 test_score로 그래프를 그려보자.
import matplotlib.pyplot as plt
plt.plot(train_score)
plt.plot(test_score)
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.show()
위의 그래프를 봤을 때 100번째 에포크 이후부터 훈련 세트와 테스트 세트의 점수가 조금씩 벌어진다. 에포크 초기에는 훈련 세트와 테스트 세트의 점수가 모두 낮은 과소 적합 현상이 나타나는 것을 확인할 수 있다. 에포크는 100번 정도가 적절해 보인다.
max_iter를 100으로 맞추고 다시 훈련하여 점수를 확인해보자. SGDClassifier는 일정 에포크 동안 성능이 향상되지 않으면 더 훈련하지 않고 자동으로 멈춘다. tol 매개변수를 None으로 지정하면 자동으로 멈추지 않고 max_iter=100 만큼 무조건 반복한다.
sc = SGDClassifier(loss='log', max_iter=100, tol=None, random_state=42)
sc.fit(train_scaled, train_target)
점수가 높게 나온 것을 확인할 수 있다.
SGDClassifier의 loss 매개변수에 대해 더 알아보자.
loss 매개변수의 기본값은 hinge이다. 힌지 손실(Hinge Loss)은 서포트 벡터 머신(Support Vector Machine)이라 불리는 또 다른 머신러닝 알고리즘을 위한 손실 함수이다. 서포트 벡터 머신이 널리 사용하는 머신러닝 알고리즘 중에 하나이고, SGDClassifier가 여러 종류의 손실 함수를 loss 매개변수에 지정하여 다양한 머신러닝 알고리즘을 지원한다는 것만 기억하고 있자.
'AI > 머신 러닝(ML)' 카테고리의 다른 글
교차 검증과 그리드 서치(Cross Validation & Grid Search) (0) | 2021.08.18 |
---|---|
결정 트리 (Decision Tree) (0) | 2021.08.16 |
로지스틱 회귀 (Logistic Regression) (0) | 2021.08.11 |
특성 공학과 규제 (0) | 2021.08.09 |
선형 회귀 (Linear Regression) (0) | 2021.08.09 |