본문 바로가기
Python/ML

[머신러닝] 확률적 경사 하강법

by JooRi 2023. 2. 18.
728x90
SMALL

 

* 교재: 혼자 공부하는 머신러닝+딥러닝 (hanbit.co.kr)

 

* 용어 정리

1. 확률적 경사 하강법: 훈련 세트에서 샘플을 하나씩 꺼내 손실 함수의 경사를 따라 최적의 모델을 찾는 알고리즘

2. 미니배치 경사 하강법: 여러 개의 샘플을 사용하는 경사 하강법

3. 배치 경사 하강법: 전체 샘플을 사용하는 경사 하강법

4. 에포크: 확률적 경사 하강법에서 전체 샘플을 모두 사용한 한 번의 반복

 

경사 하강법

 

5. 손실 함수: 확률적 경사 하강법이 최적화할 대상, 머신러닝 알고리즘이 얼마나 나쁜지 측정하는 기준, 값이 작을수록 좋음

6. 로지스틱 손실 함수: 로지스틱 회귀 모델을 만드는 손실 함수로, 이진 크로스엔트로피 손실 함수라고도 함

7. SGDClassifier: 사이킷런에서 확률적 경사 하강법을 제공하는 대표적인 분류용 클래스로, 확률적 경사 하강법을 사용한 분류 모델을 만듦

8. SGDRegressor: 확률적 경사 하강법을 사용한 회귀 모델을 만드는 클래스

 

 

* 문제: 확률적 경사 하강법으로 로지스틱 회귀 모델을 훈련한다.

확률적 경사 하강법을 사용해 생선을 실시간으로 학습하는 머신러닝 모델을 만든다.

 

 

1. 데이터 준비하기(pandas)

먼저 fish_csv_data 파일에서 판다스 데이터프레임을 만들어보자.

 

import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')

 

판다스로 인터넷에서 데이터를 바로 다운로드하였다.

read_csv()는 csv 파일(판다스 데이터프레임을 만들기 위해 사용하는 파일)을 인터넷에서 읽어 판다스 데이터프레임으로 변환하는 함수이다. read_csv() 함수에 주소를 넣으면 판다스에서 파일을 읽을 수 있다.

 

 

 Species열을 타깃 데이터, 나머지 5개 열을 입력 데이터로 사용해 보자.

 

fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()
fish_target = fish['Species'].to_numpy()

 

데이터프레임에서 열을 선택할 때는 원하는 열을 리스트로 나열하면 된다.

Species열을 뺀 나머지 5개 열을 선택한 후 to_numpy() 메서드로 넘파이 배열로 변환하여 fish_input에 저장하였다.

 

 

훈련 세트와 테스트 세트를 만들어보자.

 

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)

 

사이킷런의 train_test_split() 함수로 fish_input과 fish_target을 훈련 세트와 테스트 세트로 나누었고, 랜덤 시드(radom_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)  # 훈련 세트 train_input으로 훈련한 값으로 반드시 테스트 세트도 변환

 

평균과 표준편차를 직접 구해 특성을 표준점수로 바꾸지 않고 사이킷런 변환기 중 하나인  StandardScaler 클래스를 사용하였다.

특성값의 스케일을 맞춘 train_scaled와 test_scaled 두 넘파이 배열을 준비하여 데이터 준비를 끝냈다.

 

 

2. 확률적 경사 하강법(SGDClassifier)

SGDClassifier은 사이킷런에서 확률적 경사 하강법을 제공하는 대표적인 분류용 클래스로, 확률적 경사 하강법을 사용한 분류 모델을 만든다.

 

from sklearn.linear_model import SGDClassifier

 

# 사이킷런 1.1.0 버전 이하일 경우 loss='log_loss'를 loss='log'로 바꾸어야 함
sc = SGDClassifier(loss='log', max_iter=10, random_state=42)
sc.fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target))  # 훈련 세트 점수 출력: 0.773109243697479
print(sc.score(test_scaled, test_target))  # 테스트 세트 점수 출력: 0.775

 

SGDClassifier의 객체를 만들 때 2개의 매개변수를 지정해야 한다.

loss는 손실 함수의 종류를 지정한다. loss='log'로 지정하여 로지스틱 손실 함수로 지정하였다.

mas_iter는 수행할 에포크 횟수를 지정한다. max_iter=10으로 지정하여 전체 훈련 세트를 10회 반복하였다.

훈련 세트와 테스트 세트의 정확도 출력 결과, 낮은 점수가 나왔다. 지정한 에포크 반복 횟수가 부족한 것 같다.

확률적 경사 하강법은 점진적 학습이 가능하기 때문에 SGDClassifier 객체를 다시 만들지 않고 추가로 더 훈련이 가능하다.

 

 

훈련한 모델 sc를 추가로 더 훈련해 보자.

 

sc.partial_fit(train_scaled, train_target)  # partial_fit() 메서드 호출
print(sc.score(train_scaled, train_target))  # 훈련 세트 점수 출력: 0.8403361344537815
print(sc.score(test_scaled, test_target))  # 테스트 세트 점수 출력: 0.8

 

모델을 이어서 훈련할 때는 partial_fit() 메서드를 사용한다.

partial_fit() 메서드는 호출할 때마다 1 에포크씩 이어서 훈련한다.

점수 출력 결과, 에포크를 한번 더 실행하니 정확도가 향상되었지만 아직 낮은 점수이다.

 

확률적 경사 하강법을 사용한 모델은 에포크 횟수에 따라 과소적합이나 과대적합이 될 수 있다.

에포크 횟수가 적을 경우 과소적합, 에포크 횟수가 많은 경우 훈련 세트에 너무 잘 맞아 테스트 세트에는 점수가 나쁜 과대적합될 가능성이 있다.

 

에포크 진행에 따른 모델의 정확도

 

위 그림은 에포크 진행에 따른 모델의 정확도를 나타낸 그래프이다.

훈련 세트 점수는 에포크가 진행됨에 따라 꾸준히 증가하지만, 테스트 세트는 어느 순간 감소하기 시작한다.

테스트 세트가 감소하기 시작하는 지점이 바로 과대적합되기 시작하는 곳이다.

따라서 과대적합이 시작되기 전에 훈련을 멈추는 조기 종료가 필요하다.

 

 

준비한 데이터로 위 그림과 같은 그래프를 만들어보자.

 

import numpy as np
sc = SGDClassifier(loss='log', random_state=42)
train_score = []
test_score = []
classes = np.unique(train_target)

 

fit() 메서드를 사용하지 않고 partial_fit() 메서드만 사용하려면 훈련 세트에 있는 전체 클래스의 레이블을 partial_fit() 메서드에 전달해야 한다.

이를 위해 np.unique() 함수로 train_target에 있는 생선 7개의 목록을 만들었다.

또한 에포크마다 훈련 세트와 테스트 세트의 점수를 기록하기 위해 2개의 리스트를 준비했다.

 

 

에포크 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))

 

반복마다 훈련 세트와 테스트 세트의 점수를 계산하여 바로 위에서 만든 train_score와 test_score 리스트에 추가하였다.

 

 

300번의 에포크 동안 기록한 훈련 세트와 테스트 세트의 점수를 그래프로 그려보자.

 

import matplotlib.pyplot as plt
plt.plot(train_score)
plt.plot(test_score)
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.show()

 

 

파란색 선이 훈련 세트, 주황색 선이 테스트 세트이다.

에포크 초기에는 과소적합되어 훈련 세트와 테스트 세트의 점수가 낮다.

100번째 에포크 이후에 훈련 세트와 테스트 세트의 점수가 차이 나기 시작하므로 이 모델의 적절한 에포크 반복 횟수는 100이다.

 

 

에포크 반복 횟수를 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))  # 훈련 세트 점수 출력: 0.957983193277311
print(sc.score(test_scaled, test_target))  # 테스트 세트 점수 출력: 0.925

 

SGDClassifier는 일정 에포크 동안 성능이 향상되지 않으면 더 훈련하지 않고 자동으로 멈춘다.

tol 매개변수는 향상될 최솟값을 지정한다. tol=None으로 지정하여 자동으로 멈추지 않고 max_iter=100만큼 무조건 반복하도록 하였다.

점수 출력 결과 정확도가 아주 높게 나왔다.

확률적 경사 하강법을 사용해 로지스틱 회귀 모델을 성공적으로 훈련하였다!

 

 

728x90
LIST

댓글