머신러닝(AI학습 32)

이유진·2024년 7월 8일

--14.SGDClassifier.ipynb--

"""
실무에서는 항상 train 데이터가 '한번'에 모두 준비되어 제공되는 것이 아니다.
한번에 준비되는 것이 아니라 일정량씩(조금씩) 전달 된다.

언제 다 준비될지도 모르는 일이고, 무작정 기다릴수도 없다.
어떻게 할까?

↓ 몇가지 방법을 생각해 볼수 있다.
"""
None

"""
방법 아이디어 1]

매일매일 기존의 훈련 데이터에 새로이 추가된 데이터를 '더하여' 매일매일 다시 훈련시키기.

단점: 시간이 지날수록 훈련 데이터의 크기가 늘어나는데... 한번 훈련시키기 위해 시간도 HW 도 더 많이 필요함..

↑ 지속 가능한 방법은 아니다..

"""
None

"""
방법 아이디어 2]
새로운 데이터가 추가할때, 이전 데이터를 버리고 새로운 데이터로 훈련을 시킴으로
훈련 데이터의 크기를 일정하게 유지하는 방법.

이렇게 하면 데이터셋의 크기가 과도하게 커지지는 않을겁니다.

단점: 그러나... 버리는 데이터에 '중요한' 생선 데이터가 포함되어 있다면 큰일이겠죠.
그렇게 되면 앞으로 그 모델이 과연 생선을 제대로 예측 못하는 방향으로 학습될수도 있다.

"""
None

점진적 학습

Incremental Learning

기존에 훈련한 모델을 '버리지 않고' 새로운 데이터에 대해서만 '조금씩 더 훈련' 하는 방법은 없을까?

이러한 훈련방식을 '점진적 학습' (aka 온라인 학습) 이라고도 합니다.

'점진적 학습' 알고리즘의 대표적인 모델이 '확률적 경사 하강법(Stochastic Gradient Desent, SGD)

확률적 경사 하강법

Stochastic Gradient Desent, SGD

"""
'확률적' : 무작위하게, 랜덤하게, ...
'경사' : 기울기
'하강법' : 경사를 따라 내려가는 방법
"""
None

"""
랜덤하게 하나 선택하여 시작하여 경사를 조금 내려오고
다음으로 랜덤하게 훈련데이터에서 샘플 하나 선택하여 또 조금씩 내려 갑니다.
그렇게 전체 샘플을 모두 사용할때까지 계속 합니다.
모든 샘플을 다 사용했는데도 원하는 지점까지 내려오지 못했다면?

다시 처음부터 시작합니다.
훈련세트에 샘플을 다시 채워놓고, 또 다시 랜덤하게 하나의 샘플을 선택해 이어서 경사를 내려갑니다.
이렇게 만족할 만한 위치에 도달할 때까지 계속 내려가면 됩니다.

확률적 경사 하강법에서 훈련 세트를 한번 모두 사용하는 과정을 '에포크(epoch)' 라고 부릅니다.
일반적으로 확률적 경사 하강법은 수십, 수백번 이상의 에포크를 수행합니다.
"""
None

"""
▶한번에 무작위로 '한개의 샘플'을 학습하여
한걸음씩 움직여 내겨가는 것을 확률적 경사 하강법 (Stochastic Gradient Descent) 라 하고
중간에 튀는 데이터가 있으면 학습의 방향(기울기)이 엉뚱한 곳으로 내려갈수도 있는 위험성이 있습니다.

▶무작위로 몇 개의 샘플을 선택하여 학습한뒤
한걸음씩 경사를 내려가는 방식을 미니배치 경사 하강법 (minibatch gradient descent) 라 합니다
↑ 실전에서 가장 많이 쓰이는 방식

▶극닥적으로 전체 샘플을 다 학습한뒤
한걸음씩 경사를 내려가는 방식을 배치 경사 하강법 (Batch Gradient Descent) 라 합니다.
전체 데이터를 학습한뒤 움직이는 것이기에 가장 안정적인 방법이다.
그러나 한번 움직이는데 전체 데이터를 사용하면 그만큼 HW 자원을 많이 사용하게 되고, HW 용량에 따라
한번에 전체 데이터를 모두 읽어오기 힘들수도 있다.

"""
None

"""
확률적 경사 하강법에 대한 이해가 되었나요?
그렇다면, 이제는 조금씩 신규 데이터가 추가되어도 학습을 계속 이어나갈수 있을겁니다.
즉, 산꼭대기에서 다시 시작할 필요는 없는 겁니다.

★신경망 알고리즘 은 확률적 경사 하강법을 사용합니다.★
신경망은 일반적으로 '많은 데이터' 를 사용하기 때문에 한번에 모든 데이터를 사용하기 어렵습니다.
또한 모델이 매우 복잡하기 때문에 일반적인 수학적 방법으로 해답을 얻기 어렵습니다.

"""
None

손실함수

loss function

학습한 모델이 예측한 값과 정답간의 '오차'를 측정하는 함수

얼마나 잘 맞추느냐 가 아니라 얼마나 잘 못 맞추는지에 대한 값.

따라서 손실함수 값이 작으면 작을수록 좋은 것이다.

'분류' 에서의 손실(loss)

"""
'분류' 에서의 손실(loss) 는 아주 확실합니다. => 정답을 못맞히는 겁니다.

도미는 양성클래스(1), 빙어는 음성클래스(0) 라고 가정해 보고 아래와 같은 예측과 정답이 있다고 봅시다.

예측 정답(target)
1 = 1
0 ≠ 1
0 = 0
1 ≠ 0

4개의 예측중에서 2개만 맞았으므로 '정확도'는 0.5 입니다.
"""
None

"""
정확도 를 손실함수로 사용할수 있을까요?
예를 들어 정확도에 음수를 취하면 -1.0 ~ -0.0 의 범위가 될겁니다. (괜찮지 않나요?)

그러나 정확도에는 치명적 단점이 있습니다.
예를 들어 앞에서와 가령 4개의 샘플만 있다면 가능한 정확도는 0, 0.25, 0.5, 0.75, 1 <= 다섯가지뿐입니다.
앞에서 경사하강법을 사용할때 '아주 조금씩' 내려온다고 했죠.

정확도가 이렇게 듬성듬성 있다면 경사 하강법을 이용해 조금씩 내려오기 힘듭니다.
산의 경사면은 '연속적' 이어야 합니다! ↓ 아래 그림

↓ ※기술적으로 말하면 손실함수는 '미분 가능'해야 합니다. 따라서 연속적이어야 한다는 겁니다.
"""
None

로지스틱 손실 함수

logistic loss function

(aka. bianary cross_entropy loss function)

"""
이진 분류 문제(ex: 도미, 빙어)

위의 샘플 4개의 예측 확률을 각각, 0.9, 0.3, 0.2, 0.8 이라고 가정

"""
None

"""
어떻게 손실함수를 만들 수 있는지 보자.

▷ 첫번째 샘플
예측이 0.9
target
0.9 x 1 = -0.9

▷ 두번째 샘플

  0.9   x    1      =      -0.9
  0.3   x    1      =      -0.3   <= 훨씬 높은 손실!

▷ 세번째 샘플
target은 음성클래스 0 이다. 이대로 곱하면 안됨.
target 을 마치 양성 클래스처럼 1로 바꿈.
대신!! 예측값도 양성클래스에 대한 예측값.
0.9 x 1 = -0.9
0.3 x 1 = -0.3
0.2→0.8 x 1 = -0.8 <= 꽤 낮은 손실값! (맞추었으니까!)

▷ 네번째 샘플
0.9 x 1 = -0.9 낮은손실
0.3 x 1 = -0.3 높은손실
0.2→0.8 x 1 = -0.8 낮은손실
0.8→0.2 x 1 = -0.2 높은손실
"""
None

"""
양성클래스 (target = 1) 일때 손실은 -log(예측확률) 로 계산.
확률이 1에서 멀어질수록 손실이 아주 큰 양수가 됩니다.

음성클래스 (target = 0) 일때 손실은 -log(1 - 예측확률) 로 계산.
이 예측 확률이 0에서 멀어질수록 손실이 아주 큰 양수가 됩니다
"""
None

크로스 엔트로피 손실 함수

cross-entropy loss function
'다중분류' 문제에서의 손실함수

"""
위에서는 '이진분류' 를 예로 설명했지만, 다중 분류도 매우 비슷한 손실 함수를 사용합니다.
다중 분류에서 사용하는 손실 함수를 '크로스엔트로피 손실 함수' cross-entropy loss function 라고 합니다.

손실함수를 우리가 직접 만드는 일은 거의 없습니다.
이미 손실함수들은 정의되어 제공되기 때문입니다.

이진분류는 --> '로지스틱 손실함수' 사용 하고
다중분류는 --> '크로스엔트로피 손실함수' 사용 합니다

"""
None

참고 : MSE, MAE

회귀 문제에서의 손실함수

"""
회귀 문제의 경우 '평균 절대값 오차(Mean absolute error, MAE)' 를 사용할수 있습니다.
target 에서 예측을 뺀 절대값을 모든 샘플에 평균한 값.

혹은 '평균 제곱 오차 (mean squared error, MSE)' 를 많이 사용함.
target 에서 예측을 뺀 값을 제곱한 다음 모든 샘플에 대한 평균한 값. <- 이 값이 작을수록 좋은 모델!
"""
None

SGDClassifier

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os

base_path = r'/content/drive/MyDrive/dataset'

file_path = os.path.join(base_path, 'fish.csv')
fish_df = pd.read_csv(file_path)
fish_df

fish_df.columns

입력 데이터와 타겟 데이터 분리

fish_input = fish_df[['Weight', 'Length', 'Diagonal', 'Height', 'Width']].to_numpy()

fish_target = fish_df['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

from sklearn.linear_model import SGDClassifier

SGDClassifier 객체를 만들 때 2개의 매개변수를 지정

loss : 손실함수 종류 'log_loss' - 로지스틱 손실함수

max_iter : 수행할 epoch 횟수.

"""
공식참조
https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.SGDClassifier.html

loss{‘hinge’, ‘log_loss’, ‘modified_huber’, ‘squared_hinge’, ‘perceptron’,
‘squared_error’, ‘huber’, ‘epsilon_insensitive’, ‘squared_epsilon_insensitive’},

default=’hinge’ <= 디폴트 손실함수는 hinge 란다.

"""
None

sc = SGDClassifier(
loss = 'log_loss', # 로지스틱 손실함수
max_iter = 10, # 최대 epoch(에포크) 횟수
random_state = 42,
)
sc.fit(train_scaled, train_target)

print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target))

"""
ConvergenceWarning : 사이킷런은 모델이 충분히 수렴하지 않은 경우 발생하는 경고.
이 경우는 max_iter값을 충분히 늘려보자.
"""
None

일단 예제에서는 이대로 진행해보겠습니다.

"""
앞서 이야기 한 것처럼 확률적 경사 하강법은 점진적 학습이 가능합니다.
SGDClassifier 객체를 다시 만들지 않고, 훈련한 모델 sc 를 추가로 더 훈련해 보죠.
모델을 이어서 훈련할 때는 partial_fit() 을 사용합니다.
이 메소드는 fit() 과 사용법이 같지만 호출할때마다 1 에포크씩 이어서 훈련할수 있습니다.
"""
None

partial_fit() 사용

sc.partial_fit(train_scaled, train_target)

print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target))

"""
아직 점수가 낮지만, 에포크를 한번 더 수행하니 정확도가 향상되었다.
도대체 얼마나 더 훈련해야할까?
"""
None

"""
train_scaled 와 train_target 을 한꺼번에 모두 사용했으니 확률적 경사 하강법이 아닌
배치 경사 하강법 아닌가요?

아닙니다!
SGDClassifier 객체에 한 번에 훈련 세트 전체를 전달했지만 이 알고리즘은 전달한 훈련 세트에서 1개씩
샘플을 꺼내어 경사 하강법 단계를 수행합니다.
아쉽지만 SGDClassifier 는 미니배치 경사 하강법이나 배치 하강법을 제공하지 않습니다.

▷ 나중에 배울 신경망에서 미니배치 경사 하강법을 사용해 보겠습니다
"""
None

에포크의 과대/과소 적합

과대적합 (Over fit)
과소적합 (Under fit)

"""
확률적 경사 하강법을 사용한 모델은 epoch 횟수에 따라 과소적합 이나 과대적합이 될수 있습니다.
왜 이런 현상이 일어나는지 잠시 생각해 보자.

에포크 횟수가 적으면 모델이 훈련 세트를 덜 학습합니다. 마치 산을 다 내려오지 못하고 훈련을 마치는 셈이죠
에포크 횟수가 충분히 많으면 훈련 세트를 완전히 학습할 것입니다.
훈련세트에 아주 잘 맞는 모델이 만들어집니다.

바꾸어 말하면 적은 에포크 횟수 동안에 훈련한 모델은 훈련 세트와 테스트 세트에 잘 맞지 않는
과소적합된 모델일 가능성이 높습니다.

반대로 과도한 에포크 횟수로 훈련한 모델은 훈련세트에 너무 잘 맞아 테스트 세트에는
오히려 점수가 나쁜 과대적합된 모델일 가능성이 높습니다
"""
None

"""
이 그래프는 에포크가 진행됨에 따라 모델의 정확도를 나타낸 것입니다.
훈련 세트 점수는 에포크가 진행될수록 꾸준히 증가하지만 테스트 세트 점수는 어느 순간 감소하기 시작합니다.
바로 이 지점이 모델이 과대 적합되기 시작하는 곳입니다.
과대 적합이 시작하기 전에 훈련을 멈추는 것을 조기 종료 (early stopping) 이라 합니다.

"""
None

위 그래프를 시각화 해보자

partial_fit() '만' 사용해볼거다. <- 이 경우 전체 클래스의 레이블도 전달해주어야 한다.

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

train_score = []
test_score = []

classes = np.unique(train_target)
classes

train_score = []
test_score = []

for _ in range(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))

len(train_score)

plt.plot(train_score)
plt.plot(test_score)

plt.xlabel('epoch')
plt.ylabel('accuracy')

plt.show()

"""
데이터가 작기 때문에 아주 잘 드러나지는 않지만.
백번째 에포크 이후에는 훈련 세트와 테스트 세트의 점수가 조금씩 벌어지고 있습니다.
또, 확실히 에포크 초기에는 과소 적합되어 훈련 세트와 테스트 세트의 점수가 낮습니다.
이 모델의 경우 100번째 에포크가 적절한 반복 횟수로 보입니다.
"""
None

SGDClassifier 의 max_iter를 100에 맞추고 훈련, 점수 확인

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

tol 매개변수는 향상될 최솟값을 지정합니다.

None 으로 지정하면 자동으로 멈추지 않고 max_iter 만큼 무조건 반복합니다

sc = SGDClassifier(loss = 'log_loss', 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))

"""
SGDClassifier 의 loss 기본값은 hinge 입니다.

힌지 손실 (hinge loss) 는 서포트 벡터머신 (support vector machine. SVM) 이라는 또 다른 머신러닝 알고리즘을
위한 손실 함수 입니다. SVM 도 널리 사용되는 머신러닝 알고리즘 중 하나입니다.
"""
None

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))

profile
독해지자

0개의 댓글