본문 바로가기

AI/기계학습(Machine Learning)

[기계학습/ML]9. 확률적 경사 하강법 - 손실 함수, 에포크

728x90
반응형

마켓 "물꼬기"는 럭키백 이벤트를 오픈하고 매출이 껑충 뛰었다.  이제 각지에서 수산물을 공급하겠다고 합니다. 

영업팀은 이제 Pupbani에게 7개의 생선 중에서 일부를 무작위로 제공해 머신러닝 모델을 학습할 수 있도록 하고 있다.

하지만 수산물을 제공하는 기관이 많아 샘플을 골라내는 일이 너무 힘들다.

어느 생선이 먼저 올지도, 모든 생선이 도착할 때까지 기다릴 수도 없다. 어떻게 해야할까...?

 

점진적인 학습

  • 기존의 훈련 데이터에 새로운 데이터를 추가하여 모델을 새로 훈련한다면?
    • 시간이 지남에 따라 데이터의 크기가 엄청 늘어나서 모델 훈련을 위한 서버를 늘려야하기 때문에 사용할 수 없다.
  • 이전 데이터를 버리고 훈련 데이터 크기를 일정하게 유지한다면?
    • 데이터 셋의 크기가 늘어나지는 않지만 데이터를 버릴 때 다른 데이터에 없는 중요한 데이터를 버린다면 큰일이다.
    • 새로운 모델은 예측을 잘 못할 가능성이 있다.
  • 앞서 훈련한 모델을 버리지 않고 새로운 데이터에 대해서만 조금씩 훈련하는 방법은 없을까?
    • 이런 방식의 방법을 점진적 학습 또는 온라인 학습이라고 한다.
    • 이 학습 방법의 대표적인 예는 확률적 경사 하강법(Stochastic Gradient Descent) 알고리즘이 있다.

확률적 경사 하강법

  • 확률적 : 무작위하게, 랜덤하게
  • 경사 : 기울기
  • 하강 : 내려가는 방법
  • 경사하강법 모델을 훈련하는 과정
    • 실제 산에서 내려오는 것 처럼 가파른 길을 찾아 천천히 조금씩 내려온다.
  • 확률적?
    • 훈련 세트에서 랜덤하게 하나의 샘플을 고르는 것
  • 결론적으로 확률적 경사 하강법은 "훈련 세트에서 랜덤하게 하나의 샘플을 선택하여 가파른 경사를 조금 내려간다."
    • 이러한 과정을 반복한다.
    • 만약 모든 샘플을 다 사용해도 다 내려오지 못한다면?
      • 처음부터 다시 시작한다.
      • 훈련 세트에 모든 샘플을 다시 채워 넣는다.
      • 다시 랜덤하게 하나의 샘플을 선택하여 이어서 경사를 내려간다.
    • 만족할만한 위치에 도달할 때까지 계속 내려간다.
    • 훈련 세트를 한 번 모두 사용하는 과정을 에포크(epoch)라고 부른다.
      • 일반적인 경사 하강법은 수십, 수백 번 이상 에포크를 수행한다.
  • 이렇게 한다면 너무 무책임하게 내려가는 것이 아닐까?
  • 잘못 들어선다면 돌아갈 수 없기 때문이다.
  • 이러한 문제점을 해소하려고 무작위로 몇 개의 샘플을 선택해서 경사를 내려간다면 어떨까?
    • 미니배치 경사 하강법(Minibatch Gradient Descent)
      • 여러 개의 샘플을 사용해 경사 하강법을 수행하는 방법
  • 극단적으로 한 번 경사로를 따라 이동하기 위해 전체 샘플을 사용할 수 도 있다.
    • 배치 경사 하강법(Batch Gradient Descent)
      • 컴퓨터 자원을 너무 많이 사용한다.
      • 한 번에 전체 데이터를 모두 읽을 수 없을 수 있다.
  • 이러한 확률적 경사 하강법을 반드시 사용하는 알고리즘 - 신경망 알고리즘

손실함수(Loss Function)

  • 그런데 어디서 내려가야 하는 걸까?
    • 가장 빠른 길을 찾아 내려가려고 하는 이 산은 도대체 무엇일까? 
    • 이 산을 손실 함수라 부른다.
  • 손실함수는 어떤 문제에서 머신러닝 알고리즘이 얼마나 엉터리로 동작하는지 측정하는 기준이다.
    • 손실함수의 값은 작을수록 좋다.
    • 하지만 어떤 값이 최솟값인지는 알지 못한다.
    • 가능한 많이 찾아보고 만족할만한 수준이면 산을 다 내려왔다고 인정해야한다.
  • 다행히 우리가 다루는 많은 문제에 필요한 손실 함수는 이미 정의되어 있다.
  • 예시1
    • 도미와 빙어를 구분하는 문제: 도미는 1, 빙어는 0
    • 예측을 4번 중에 2번 성공했다고 가정 - 정확도 0.5
    • 정확도를 손실 함수로 사용한다.
      • 4개의 샘플만 있다면 5가지의 정확도이기 때문에 그래프를 그렸을 때 듬성듬성 그려진다. 

  • 어떻게 하면 연속적인 함수를 만들 수 있을까?
    • 로지스틱 회귀 모델에서 확률을 출력할 때 예측은 0 or 1이지만 0~1 사이의 어떤 값도 될 수 있다.
      • 연속적이다.

로지스틱 손실 함수

  • 예시2
    • 샘플 4개의 예측 화률 값과 타겟 값 을 가정한다.
      • 예측 확률 값 : 0.9, 0.3, 0.2, 0.8
      • 타겟 값 : 1, 1, 0, 0
    • 손실 함수 = (양성 클래스 예측 확률 ) x (-1)
      • -1 ~ 0 사이의 실수 값이 된다.
      • 음성 클래스들은 "1-음성 클래스 예측"으로 사용한다.

  • 예측 확률에 로그함수를 적용하면 더 좋다.
    • 로그 함수는 이 사이에서 음수가 되므로 최종 손실 값은 양수가 된다.
    • 로그 함수는 0에 가까울 수록 아주 큰 음수가 되기 때문에 손실을 아주 크게 만들어 모델에 큰 영향을 미칠 수 있다.
    • 양성(타깃 = 1)일 때 손실은 -log(예측 확률)
    • 음성(타깃 = 0)일 때 손실은 -log(1-예측 확률)

  • 이 손실함수를 로지스틱 손실 함수(Logistic Loss Function) 또는 이진 크로스엔트로피 손실 함수(Binary Cross-Entropy Loss Function)라고 부른다.
  • 다중 분류에서도 비슷한 손실 함수를 사용한다.
    • 크로스엔트로피 손실 함수(Cross-Entropy Loss Function)를 사용한다.

SGDClassifier

데이터 준비

  • pandas를 라이브러리를 통해 데이터를 불러오고 표준화 전처리를 진행한다.
# 데이터 불러오기
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv')

# 특성, 타겟 데이터 분리 
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)

모델 학습 및 평가

  • 확룰적 경사 하강법을 제공하는 대표적인 분류용 클래스 SGDClassifier을 사용한다.
    • sklearn.linear_model 패키지 아래에 있다.
    • loss 매개변수 : 손실 함수의 종류
    • max_iter 매개변수 : 에포크 횟수 지정
  • log로 로지스틱 손실함수를 지정하고 에포크 횟수는 10로 지정하겠다.
from sklearn.linear_model import SGDClassifier
sc = SGDClassifier(loss='log', max_iter=10, random_state=42)
sc.fit(train_scaled, train_target)

print(f'훈련세트 정확도: {sc.score(train_scaled, train_target)*100:.2f}%')
print(f'테스트 세트 정확도: {sc.score(test_scaled, test_target)*100:.2f}%')

  • ConvergenceWarning은 모델이 충분히 수렴하지 않았다는 경고로 반복 횟수를 올려주게 되면 사라진다.

점진적 학습

  • 확률적 경사하강법은 점진적 학습이 가능하다.
  • 모델을 이어서 훈련할 때는 partial_fit() 메서드를 이용한다.
sc.partial_fit(train_scaled, train_target)

print(f'훈련세트 정확도: {sc.score(train_scaled, train_target)*100:.2f}%')
print(f'테스트 세트 정확도: {sc.score(test_scaled, test_target)*100:.2f}%')

  • 아직 정확도가 낮지만 한 번더 실행하니 정확도가 올라갔다.
  • 무작정 반복할 수 없으니 어떠한 기준이 필요할 것 같다.

에포크와 과대/과소 적합

  • 확률적 경사 하강법을 이용한 모델은 에포크 횟수에 따라 과대 또는 과소 적합이 될 수 있다.
    • 에포크 횟수가 적은 경우 : 모델이 훈련 세트를 덜 학습한다. - 과소적합 위험
    • 에포크 횟수가 많은 경우 : 모델이 훈련 세트를 많이 학습한다. - 과대적합 위험

  • 에포크가 진행됨에 따라 훈련 세트의 점수는 꾸준히 증가하지만 테스트 세트는 어느 시점에서 감소하기 시작한다.
  • 이 지점이 모델이 과대적합되기 시작하는 곳이다.
  • 괴대적합이 시작되기 전에 훈련을 멈추는 것을 조기 종료(Early Stopping)라고한다.

조기종료 지점 찾기

  • fit()을 사용하지 않고 partial_fit() 만을 사용하려면 훈련 세트의 있는 전체 클래스의 레이블을 classes 매개변수에 전달해줘야 한다.
  • 각 에포크마다 훈련 세트와 테스트 세트의 대한 점수를 기록하기 위해 2개의 리스트를 준비한다.
  • 300번의 에포크를 진행하고 기록한 데이터 세트들을 시각화 해보겠다.
import numpy as np

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

train_score = []
test_score = []

classes = np.unique(train_target)
for _ in range(0, 300):
	# 전체 클래스의 레이블 classses 매개변수에 전달
    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번째 에포크 이후에는 훈련 세트와 테스트 세트의 점수가 조금씩 벌어 지고 있다.
  • 에포크 초기에는 과소적합되어 점수가 낮다.
  • 에포크 횟수를 100으로 맞추고 모델을 다시 훈련해본다.
    • tol 매개변수는 매개변수에서 향상될 최소값을 지정한다.
    • tol = None --> 자동으로 멈추지 않고 100번 에포크하도록함
sc = SGDClassifier(loss='log', max_iter=100, tol=None, random_state=42)
sc.fit(train_scaled, train_target)

print(f'훈련세트 정확도: {sc.score(train_scaled, train_target)*100:.2f}%')
print(f'테스트 세트 정확도: {sc.score(test_scaled, test_target)*100:.2f}%')

  • 훈련 세트와 테스트 세트에서의 정확도도 비교적 높게 나왔다.

SGDClassifier의 loss 매개변수

  • loss 매개변수의 기본값은 'hinge'이다.
  • hinge는 힌지 손실(hinge loss)서포트 벡터 머신(Support Vector Machine)이라 불리는 또 다른 머신러닝 알고리즘을 위한 손실 함수 이다.
  • 힌지 손실을 사용한 SGDClassifier 모델 훈련
sc = SGDClassifier(loss='hinge', max_iter=100, tol=None, random_state=42)
sc.fit(train_scaled, train_target)
print("힌지 손실 함수를 사용한 SGDClassifier 모델의 정확도\n")
print(f'훈련세트 정확도: {sc.score(train_scaled, train_target)*100:.2f}%')
print(f'테스트 세트 정확도: {sc.score(test_scaled, test_target)*100:.2f}%')

728x90
반응형