혼자 공부하는 머신러닝 + 딥러닝

[혼공머신] 8. Stochastic Gradient Descent

Seon_ 2021. 12. 18. 18:49
8.fish_SGD

Stochastic Gradient Descent

  • 앞서 훈련한 모델을 버리지 않고 새로운 데이터에 대해서만 조금씩 더 훈련하는 방식을 점진적 학습이라고 한다.

    • 점진적 학습의 대표적인 알고리즘이 확률적 경사 하강법(Stochastic Gradient Descent, SGD)이다.
    • SGD는 전체 샘플을 사용하지 않고 딱 하나의 샘플을 훈련 세트에서 랜덤하게 골라 Loss function을 가장 빠르게 줄이는 방법을 찾는다.
    • 이런 방식으로 훈련 세트를 한 번 모두 사용하는 과정을 epoch라고 한다. 일반적으로 경사 하강법은 수십, 수백 번의 epoch를 사용한다.
    • mini-batch gradient descent는 딱 하나가 아니라 여러 개의 샘플을 사용해 경사 하강법을 수행하는 것을 뜻한다.
    • batch gradient descent는 모든 샘플을 사용하여 경사 하강법을 수행하는 것을 뜻한다. 가장 정확하지만 컴퓨터 자원이 많이 필요하다.
    • 참고로 후에 나올 신경망 모델의 대부분이 SGD나 mini-batch GD를 사용한다.
  • 손실 함수는 어떤 문제에 대해 머신러닝 알고리즘이 얼마나 엉터리인지 나타내는 지표이다.

    • 분류에서 손실은 정답을 맞추지 못하는 것이다.
    • 정확도는 손실 함수가 될 수 없다. 연속적이지 않기 때문이다.
    • 연속적인 손실 함수를 만들기 위해 로지스틱 손실 함수를 채택할 수 있다. 앞서 배운 로지스틱 회귀 모델을 이용한 분류에서 예측은 0 또는 1이지만 확률은 0~1 사이 어떤 값도 될 수 있었다. 즉, 연속적이다.
    • 로지스틱 손실 함수는 이진 크로스엔트로피 손실 함수라고도 하며, 정답(t)과 예측값(y)에 대해 Loss=-(t*log y + (1-t)log(1-y)) 의 관계를 갖는다.
    • 다중 분류에 대해서는 크로스엔프로피 손실 함수를 사용한다.

SGDClassifer

In [1]:
import pandas as pd

fish = pd.read_csv('https://bit.ly/fish_csv_data')
In [2]:
# Species 데이터를 제외한 나머지 데이터를 input 값으로 사용
fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()
fish_target = fish['Species'].to_numpy()
In [3]:
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)
In [4]:
# 훈련 세트와 테스트 세트의 특성을 표준화 전처리
from sklearn.preprocessing import StandardScaler

ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)
In [5]:
from sklearn.linear_model import SGDClassifier
In [6]:
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
/usr/local/lib/python3.7/dist-packages/sklearn/linear_model/_stochastic_gradient.py:700: ConvergenceWarning: Maximum number of iteration reached before convergence. Consider increasing max_iter to improve the fit.
  ConvergenceWarning,

iteration이 낮아서 정확도가 낮게 나왔다.

In [7]:
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() 메서드를 사용한다.
  • 아직 점수가 낮지만 epoch를 한 번 더 실행하니 정확도가 조금 높아졌다.
  • 그럼 얼마나 많은 epoch를 수행해야 할까?
    • epoch가 너무 적으면 과소적합할 수 있고, 너무 많으면 과대적합할 수 있다.
    • 과대적합이 시작하기 전에 훈련을 멈추는 것을 조기 종료(early stopping)이라고 한다.
In [8]:
import numpy as np

sc = SGDClassifier(loss='log', random_state=42)

train_score = []
test_score = []

classes = np.unique(train_target)
In [9]:
for _ in range(0, 300): #300 epoch동안 훈련 진행, _는 나중에 사용하지 않고 버리는 값을 넣어두는 용도로 사용함
    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))
In [10]:
import matplotlib.pyplot as plt

plt.plot(train_score)
plt.plot(test_score)
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.show()
  • 파란색이 훈련 세트, 주황색이 테스트 세트에 대한 결과이다.
    • 초반에는 과소적합의 위험이 있고, 후반에는 두 데이터 셋의 스코어 사이 간격이 커진다
    • 따라서 100 epoch 정도가 적당해보인다.
In [11]:
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
  • tol 매개변수는 반복을 멈출 조건을 뜻한다. n_iter_no_change 매개변수에서 지정한 에포크 동안 손실이 tol만큼 줄어들지 않으면 알고리즘이 중단된다.
  • 참고적으로, SGD의 loss function의 default값은 hinge이다.
    • 힌지 손실은 서포트 벡터 머신(SVM)이라는 알고리즘을 위한 손실 함수이다.
    • 여러 종류의 손실 함수를 loss에 지정하여 사용할 수 있다.
    • 아래는 default값을 적용한 경우이다.
In [12]:
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
In [1]:
from IPython.core.display import display, HTML

display(HTML("<style>.container { width:90% !important; }</style>"))