8. 확률적 경사 하강법

1. 들어가며

  • 이전에서 진행했던 로지스틱 회귀를 통해 성공적으로 럭키백 이벤트를 오픈하고 나서 매출이 증가한 한빛 마켓은 고객들이 좋아하는 이 상품을 각지에서 한빛 마켓에 수산물을 공급하겠다고 난리가 났다.
  • 수산물을 공급하겠다는 곳이 너무 많아서 샘플을 골라내는 일이 너무 힘든 상황이다. 추가되는 수산물은 아직 샘플을 가지고 있지도 않은 상황이다. 어느생선이 빨리올지 어떤 생선을 데이터에 추가할지 모르는 상황이다. 이를 해결해야 한다.
  • 데이터가 한번에 전부 도착하는 것이 아닌 지속적으로 데이터가 추가된다고 한다. 그렇다면 기존의 훈련 데이터를 이용해 훈련하고 새로운 데이터가 들어오는대로 새로운 데이터를 이용해 추가하는 형식은 어떨까? 데이터의 양이 방대해지면 물리적인 서버의 크기도 증가해야 한다. 즉 지속하기 힘들다는 것이다.
  • 다른 방법으로는 새로운 데이터를 추가할 때 이전 데이터를 버림으로써 훈련 데이터 크기를 일정하게 유지하는 것이다. 하지만 이 방법은 데이터를 버릴 떄 다른 데이터에 없는 중요한 생선 데이터가 포함되어있다면 만들어진 모델이 정확하게 생선을 예측하지 못한다.
  • 확률적 경사 하강법을 이용하면 해결이 가능하다. 확률적 경사 하강법에서 확률적이라는 말은 ‘무작위하게’ 혹은 ‘랜덤하게’의 기술적인 표현이다. 경사는 기울기를 말한다. ‘하강법’은 ‘내려가는 방법’을 말한다. 다시 말해 경사 하강법은 경사를 따라 내려가는 방법을 말한다. 즉 확률적 경사 하강법은 훈련세트에서 랜덤하게 하나의 샘플을 고르는 것을 말한다.
  • 만약 확률적 경사 하강법을 이용해 답을 내리지 못할 경우에는 다시 처음부터 랜덤한 하나의 샘플들을 이용해 시도한다. 확률적 경사 하강법에서 훈련 세트를 한 번 모두 사용하는 과정을 에포크라고 부른다. 일반적으로 경사 하강법은 수십, 수백번 이상의 에포크를 수행한다.
  • 몇개의 샘플을 이용해 경사 하강법을 수행하는 방식인 미니배치 경사 하강법도 존재한다. 그리고 극단적으로 한 경사로만 따라 이동하기위해 전체 샘플을 사용하는 배치 경사 하강법도 존재한다. 자원이 많이 들지만 가장 안정적인 방법으로 볼 수 있다.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import SGDClassifier
fish = pd.read_csv('https://bit.ly/fish_csv_data')

2. 손실함수

  • 손실함수는 어떤 문제에서 머신러닝 알고리즘이 얼마나 엉터리인지를 측정하는 기준이 된다. 즉 손실함수의 값은 작을수록 좋다. 하지만 어떤 값이 최솟값인지는 알지 못한다.
  • 비용함수는 훈련 세트에 있는 모든 샘플에 대한 손실함수의 합을 말한다.
  • 손실함수는 연속적이여야 한다. 이를 만들 수 있는 방법에는 어떤것이 있을까? 첫번째로 살펴볼 수 있는 것은 로지스틱 회귀모델이 확률을 출력한다는 것을 알 수 있다. 이를 이용해보면 다음과 같다.

1. 로지스틱 손실 함수

  • 양성 클래스(타깃 = 1)일 때 손실은 -log(예측확률)로 계산한다. 확률이 1에서 멀어질수록 손실은 아주 큰 양수가 된다. 음성클래스(타깃 = 0)일 때 손실은 -log(1-에측확률)로 계산한다. 이 예측확률이 0에서 멀이질 수록 손실은 아주 큰 양수가 된다.
  • 이러한 손실함수를 로지스틱 손실 함수라고 부른다. 또는 이진 크로스엔트로피 손실 함수로도 부른다.
  • 물론 다중 분류에서도 비슷한 손실 함수를 사용한다. 이는 크로스엔트로피 손실 함수라고 부른다.
  • 실제로는 우리가 손실 함수를 만들일은 거의 없다. 회귀에서는 손실 함수로 평균 절댓값 오차를 사용한다.
  • 이제부터 확률적 경사 하강법을 사용한 분류 모델을 만들어보자.

3. SGDClassifier

fish.columns
Index(['Species', 'Weight', 'Length', 'Diagonal', 'Height', 'Width'], dtype='object')
  • 컬럼을 확인하고 ‘Species’를 제외한 나머지는 입력데이터로 지정해 넘파이 배열형태로 넣어준다.
fish_input = fish[['Weight', 'Length', 'Diagonal', 'Height', 'Width']].to_numpy()
fish_target = fish['Species'].to_numpy()
  • 훈련셋과 데이터 셋으로 나눠준다.
train_input, test_input, train_target, test_target = train_test_split(fish_input, fish_target, random_state= 42)
train_input.shape
(119, 5)
  • 훈련 세트와 테스트 세트의 특성을 표준화 전처리 해준다.
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)
  • SGDClassifier 를 임포트 해준다. SGDClassifier의 객채를 만들 때 2개의 매개변수를 지정한다. loss는 손실 함수의 종류를 지정한다. 여기에서는 loss=’log’로 지정해 로지스틱 손실함수를 지정했다. max_iter는 수행할 에포크 횟수를 지정한다. 10으로 지정해 전체 훈련 세트를 10회 반복했다. 그 뒤 훈련세트에서와 테스트 세트에서의 정확도 점수를 출력한다.
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


/Users/janghyeseong/Desktop/PR/PR2/lib/python3.9/site-packages/sklearn/linear_model/_stochastic_gradient.py:574: ConvergenceWarning: Maximum number of iteration reached before convergence. Consider increasing max_iter to improve the fit.
  warnings.warn("Maximum number of iteration reached before "
  • 출력된 훈련 세트와 테스트 세트 정확도가 낮다. 10번의 에포크 횟수가 부족한 것으로 보인다.
  • 확률적 경사 하강법은 점진적 학습이 가능하다. 따라서 훈련한 모델 sc를 추가로 더 훈련해보도록 하자.
  • 모델을 이어서 훈련할 때는 partial_fit()메서드를 이용한다.
sc.partial_fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target))
0.8151260504201681
0.85
  • 아직 점수가 낮지만 에포크를 한번 더 실행하니 정확도가 향상되었다. 이 모델을 여러 에포크에서 더 훈련해볼 필요성이 있다. 무작정 반복하지 않을 기준이 필요하다.

4. 에포크와 과대/과소적합

  • 에포크의 횟수에 따라 과소적합이나 과대적합이 될 수 있다. 에포크의 횟수가 적으면 모델이 훈련세트를 덜 학습한다. 반대로 에포크가 충분하면 훈련세트를 완전히 학습한다. 즉 적은 에포크 횟수 동안에 훈련한 모델은 훈련 세트와 테스트 세트에 잘 맞지 않는 과소적합된 모델일 가능성이 높다. 반대로 많은 에포크 횟수 동안에 훈련한 모델은 훈련 세트에 너무 잘 맞아 테스트 세트에는 오히려 나쁜 점수를 반환하는 과대적합된 모델일 가능성이 높다.
  • 에포크가 진행됨에 따라 적절한 지점에 훈련을 멈추는 조기 종료가 필요하다.
  • 먼저 적정 지점을 찾을 수 있는 그래프를 만들어보도록 하자.
sc = SGDClassifier(loss='log', random_state=42)
train_scores = []
test_scores = []
classes = np.unique(train_target)
  • 300번의 에포크 동안 훈련을 반복하여 진행해보자. 반복마다 훈련 세트와 테스트 세트의 점수를 계산하여 train_scores, test_scores 리스트에 추가한다.
for _ in range(300):
    sc.partial_fit(train_scaled, train_target, classes=classes)
    train_scores.append(sc.score(train_scaled, train_target))
    test_scores.append(sc.score(test_scaled, test_target))
plt.plot(train_scores)
plt.plot(test_scores)
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.show()

output_23_0

  • 백 번째 에포크 이후에는 훈련 세트와 테스트 세트의 점수가 조금씩 벌어지고 있습니다. SGDClassifier의 반복 횟수를 100에 맞추고 모델을 다시 훈련해보겠습니다.
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 매개변수에서 향상될 최솟값을 지정한다. None으로 위에서 지정했기 때문에 자동으로 멈추지 않고, 100만큼 무조건 반복하도록 했다.
  • 최종점수는 좋다. 이번에는 훈련 세트, 테스트 세트 모두 높게 나왔다.

5. SGDClassifier의 loss 매개변수

  • SGDClassifier의 loss매개변수의 기본값은 ‘hinge’이다. 힌지 손실서포트 벡터 머신이라 불리는 또 다른 머신러닝 알고리즘을 위한 손실 함수이다.
  • 간단한 예로 힌지 손실을 사용해 같은 반복 횟수 동안 모델을 훈련해 보자.
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

6. 마치며

  • 이번 시간에는 확률적 경사 하강법에 대해 알아보았다. 확률적 경사 하강법은 샘플 하나씩 꺼내 손실 함수의 경사를 따라 최적의 모델을 찾는 알고리즘이다.
  • 손실함수는 확률적 경사 하강법이 최적화해야하는 대상이다. 이진 분류에서는 로지스틱회귀(이진크로스엔트로피)손실 함수를 사용하고, 다중 분류에서는 크로스엔트로피 손실 함수를 사용한다. 회귀문제에는 평균 제곱 오차 손실함수를 사용한다.
  • 에포크는 확률적 경사 하강법에서 전체 샘플을 모두 사용하는 한 번 반복을 의미한다. 일반적으로 경사 하강법 알고리즘은 수십에서 수백 번의 에포크를 반복한다.

Categories:

Updated:

Leave a comment