럭키백에 들어갈 수 있는 생선은 7개임. 럭키백에 들어간 생선의 크기, 무게 등이 주어졌을 때 7개 생선에 대한 확률을 출력해야한다.
아이디어 : K-최근접 이웃은 주변 이웃을 찾아주니까 이웃의 클래스 비율을 확률이라고 출력하면 되지 않을까?
샘플 X 주위에 가장 가까운 이웃 샘플 10개를 표시 (사각형 3개, 삼각형 5개, 원 2개) 이웃한 샘플의 클래스를 확률로 삼으면 샘플 X가 사각형일 확률은 30%, 삼각형일 확률은 50%, 원일 확률은 20%이다 ~~
<데이터 준비하기>
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')
fish.head()
<어떤 종류의 생선이 있는지 Species 열에서 고유한 값 추출하기>
print(pd.unique(fish['Species']))
output
['Bream' 'Roach' 'Whitefish' 'Parkki' 'Perch' 'Pike' 'Smelt']
ㄴ 이 데이터 프레임에서 Species 열을 타깃으로 만들고 나머지 5개 열은 입력 데이터로 사용할 거임 / 데이터프레임에서 열 선택하는 법 : 원하는 열을 리스트로 나열
<Species 열 빼고 나머지 5개 열 선택하기>
fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()
# 여러 열 선택해서 나온 새로운 데이터프레임을 넘파이 배열로 바꿈
print(fish_input[:5]) # 처음 5개 행 출
output
[[242. 25.4 30. 11.52 4.02 ]
[290. 26.3 31.2 12.48 4.3056]
[340. 26.5 31.1 12.3778 4.6961]
[363. 29. 33.5 12.73 4.4555]
[430. 29. 34. 12.444 5.134 ]]
<타깃 데이터 만들기>
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)
이제 k-최근접 이웃 분류기로 테스트 세트에 들어있는 확률을 예측해볼거임
<클래스 객체 만들고 훈련 세트로 모델 훈련해서 훈련세트와 테스트세트의 점수 확인하기>
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier(n_neighbors = 3)
kn.fit(train_scaled, train_target)
print(kn.score(train_scaled, train_target)) # 0.8907563025210085
print(kn.score(test_scaled, test_target)) # 0.85
앞에서 fish 데이터프레임에서 7개의 생선이 있었자나
타깃 데이터를 만들 때 fish['Species']
를 사용해서 만들어서 훈련 세트와 테스트 세트의 타깃 데이터에도 7개의 생선이 들어가있음. 이렇게 타깃 데이터에 2개 이상의 클래스가 포함된 문제를 다중 분류
라고 한다
주의할 점
타깃값을 그대로 사이킷런 모델에 전달하면 순서가 자동으로 알파벳 순서로 매겨져서 pd.unique(fish['Species'])
로 출력한 순서랑 다름 => KNeighborsClassifier
에서 정렬된 타깃값은 classes_
속성에 저장되어 있음
print(kn.classes_)
output
['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']
predict()
는 타깃값으로 예측을 출력하는 메소드임
<테스트 세트에 있는 처음 5개 샘플의 타깃값을 예측해보기>
print(kn.predict(test_scaled[:5]))
output
['Perch' 'Smelt' 'Pike' 'Perch' 'Perch']
ㄴ 이 5개 샘플에 대한 예측은 어떤 확률로 만들어졌을까요 ??
사이킷런의 분류 모델은 predict_proba()
메소드로 클래스별 확률 값을 반환함
<테스트 세트에 있는 처음 5개의 샘플에 대한 확률 출력하기>
import numpy as np
proba = kn.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=4)) # 소수점 네번째 자리까지 표기하고 다섯번째 자리에서 반올림함
output
[[0. 0. 1. 0. 0. 0. 0. ]
[0. 0. 0. 0. 0. 1. 0. ]
[0. 0. 0. 1. 0. 0. 0. ]
[0. 0. 0.6667 0. 0.3333 0. 0. ]
[0. 0. 0.6667 0. 0.3333 0. 0. ]]
출력순서 : 첫 번째 열이 Bream
에 대한 확률, 두 번째 열이 Parkki
에 대한 확률
<네 번째 샘플의 최근접 이웃의 클래스 확인하기>
distances, indexes = kn.kneighbors(test_scaled[3:4])
print(train_target[indexes]) # [['Roach' 'Perch' 'Perch']]
ㄴ 다섯 번째 클래스인 Roach
가 1개이고, 세 번째 클래스인 perch
가 2개임.
다섯 번째 클래스에 대한 확률은 1/3
이고, 세 번째 클래스에 대한 확률은 2/3
이다 = 앞서 출력한 네 번쨰 샘플의 클래스 확률과 같다
근데 이렇게 3개의 최근접 이웃을 사용하면 가능한 확률은 0/3
,1/3
,2/3
,3/3
이 끝일듯
로지스틱 회귀
: 이름은 회귀지만 분류 모델임/ 얘는 선형 회귀와 동일하게 선형 방정식을 학습한다.
z = a * (Weight) + b * (Length) + c * (Diagonal) + d * (Height) + e * (Width) + f
ㄴ a,b,c,d,e는 가중치 or 계수
z는 어떤 값도 가능하긴 한데, 확률이 되려면 0~1 사이의 값이 되어야해서 z가 아주 큰 음수일 때 0이 되게, z가 아주 큰 양수일 때 1이 되도록 바꿔야함
=> 이를 시그모이드 함수( or 로지스틱 함수)
를 사용해서 바꾼다
<시그모이드 함수 식>
<넘파이 사용해서 그래프 그리기>
import numpy as np
import matplotlib.pyplot as plt
z = np.arange(-5,5,0.1) # -5와 5 사이에 0.1 간격으로 배열 z 만듦
phi = 1 / (1 + np.exp(-z)) # z 위치마다 시그모이드 함수 계산, np.exp()는 지수함수 계산하는 함수
plt.plot(z,phi)
plt.xlabel('z')
plt.ylabel('phi')
plt.show()
이번엔 로지스틱 회귀 모델을 훈련시켜보자
훈련 전에 이진 분류 수행을 해야함 (시그모이드 함수의 출력이 0.5보다 크면 양성 클래스, 0.5보다 작으면 음성 클래스로 판단)
넘파이 배열은 True, False 값을 전달하여 행을 선택할 수 있음 = 불리언 인덱싱
<불리언 값으로 값 뽑아내기>
char_arr = np.array(['A','B','C','D','E'])
print(char_arr[[True, False, True, False, False]]) # ['A' 'C']
이 방식으로 도미(Bream) 와 빙어(Smelt)의 행만 골라내기
bream_smelt_indexes = (train_target == 'Bream') | (train_target == 'Smelt') # 도미와 빙어의 행 모두 True로 만들기
train_bream_smelt = train_scaled[bream_smelt_indexes]
target_bream_smelt = train_target[bream_smelt_indexes]
ㄴ bream_smelt_indexes
배열은 도미와 빙어일 경우 True이고 그 외는 모두 False값이 들어가있음 => 이 배열을 사용해서 train_scaled
와 train_target
배열에 불리언 인덱싱을 적용해서 도미와 빙어 데이터만 골라낼 수 있음
<로지스틱 회귀 모델 훈련하기>
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)
<train_bream_smelt에 있는 처음 5개 샘플 예측하기>
print(lr.predict(train_bream_smelt[:5]))
output
['Bream' 'Smelt' 'Bream' 'Bream' 'Bream']
<train_bream_smelt에서 처음 5개 샘플의 예측 확률 출력하기>
print(lr.predict_proba(train_bream_smelt[:5]))
output
[[0.99759855 0.00240145]
[0.02735183 0.97264817]
[0.99486072 0.00513928]
[0.98584202 0.01415798]
[0.99767269 0.00232731]]
ㄴ 첫번째 열이 0에 대한 확률이고 두번째 열이 1에 대한 확률임
그럼 Bream
과 Smelt
중 어떤 것이 양성 클래스인가? -> 사이킷런은 타깃값을 알파벳순으로 정렬하여 사용한다
print(lr.classes_) # ['Bream' 'Smelt']
ㄴ Smelt가 양성이구나 ~~
다시 predict_proba()
메서드가 반환한 배열 값을 보면 두번째 샘플만 양성 클래스인 빙어의 확률이 높다 !! -> 나머지는 다 도미로 예측하겠다
<로지스틱 회귀가 학습한 계수 확인하기>
print(lr.coef_, lr.intercept_)
output
[[-0.4037798 -0.57620209 -0.66280298 -1.01290277 -0.73168947]] [-2.16155132]
-- (방정식화) -->
z = -0.404 (Weight) - 0.576 (Length) - 0.663 (Diagonal) - 1.013 (Height) - 0.732 * (Width) - 2.161
<로지스틱 모델로 z값 계산하기>
# decision_function() 메서드로 z값 출력가능
decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions)
output
[-6.02927744 3.57123907 -5.26568906 -4.24321775 -6.0607117 ]
ㄴ 처음 5개 샘플의 z값 출력
ㄴ 이 z값을 시그모이드 함수에 통과시키면 확률을 얻을 수 있따
<decisions 배열의 값을 확률로 변환하기>
from scipy.special import expit
print(expit(decisions))
output
[0.00240145 0.97264817 0.00513928 0.01415798 0.00232731]
ㄴ 출력값에서 predict_proba()
메서드 출력의 두번째 열의 값과 동일함 !!
=> decision_function()
메서드는 양성 클래스에 대한 z값을 반환하는구나 ~~~
정리
1. predict_proba()
메서드는 음성 클래스와 양성클래스에 대한 확률 출력
2. decision_function()
메서드는 양성클래스에 대한 z값 계산
3. coef_
와 intercept_
는 로지스틱 모델이 학습한 선형 방정식의 계수가 들어있음
<LogisticRegression 클래스로 다중 분류 모델 훈련>
lr = LogisticRegression(C = 20, max_iter = 1000)
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target)) # 0.9327731092436975
print(lr.score(test_scaled, test_target)) # 0.925
ㄴ 설명
: LogisticRegression 클래스는 기본적으로 반복적인 알고리즘을 사용함 max_iter
매개변수에서 반복횟수를 지정하는데 이의 기본값은 100
이다 ! -> 100으로는 반복횟수가 부족해서 1000으로 늘림
: 이 회귀는 계수의 제곱을 규제함 (=L2 규제)
여기서는 C
가 규제를 제어하는 매개변수임. C는 작을수록 규제가 커져서 기본값이 1인데 20으로 늘렸다
<테스트 세트의 처음 5개 샘플에 대한 예측 출력하기>
print(lr.predict(test_scaled[:5]))
output
['Perch' 'Smelt' 'Pike' 'Roach' 'Perch']
<테스트 세트의 처음 5개 샘플에 대한 예측 확률 출력하기>
proba = lr.predict_proba(test_scaled[:5])
print(np.round(proba, decimals = 3))
output
[[0. 0.014 0.841 0. 0.136 0.007 0.003]
[0. 0.003 0.044 0. 0.007 0.946 0. ]
[0. 0. 0.034 0.935 0.015 0.016 0. ]
[0.011 0.034 0.306 0.007 0.567 0. 0.076]
[0. 0. 0.904 0.002 0.089 0.002 0.001]]
ㄴ 5개 샘플에 대한 예측이어서 5개 행이 출력, 7개 생선에 대한 확률을 계산해서 7개의 열이 출력 (이진 분류일 경우 2개의 열만 있었다)
ㄴ 첫번째 샘플을 보면 세번째 열의 확률이 가장 높다 !! -> Perch에 확률일까?
<classes_속성으로 클래스 정보 확인하기>
print(lr.classes_)
output
['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']
첫 번째 샘플은 Perch를 가장 높은 확률로 예측했음. 두 번째 샘플은 여섯 번째 열인 Smelt
를 가장 높은 확률로 예측했음
이진 분류 - 샘플마다 2개의 확률 출력
다중 분류 - 샘플마다 클래스 개수만큼 확률 출력
여기서는 7개여서 이 중에서 가장 높은 확률이 예측 클래스가 된다 ~~
<계수 출력>
print(lr.coef_.shape, lr.intercept_.shape)
output
(7, 5) (7,)
5개의 특성을 사용하므로 coef_
배열의 열으 5개인데 행이 7개!! intercept_
도 7개나 있음 = 이진 분류에서 봤던 z를 7개나 계산한다는 뜻
=> 다중 분류는 클래스마다 z값을 하나씩 계산한다 -> 가장 높은 z값을 출력하는 클래스가 예측 클래스가 됨
이진 분류에서는 시그모이드 함수를 써서 z를 0과 1 값으로 변환했는데, 다중 분류에서는 소프트맥스 함수를 써서 7개의 z값을 확률로 변환함
7개의 z값의 이름 : z1 ~ z7
이 값을 사용해 지수함수 e^z1 ~ e^z7 을 계산해서 모두 더함 이를 e_sum
이라 함.
이제 e^z1 ~ e^z7 를 각각 e_sum으로 나눠주기
s1에서 s7까지 다 더하면 분자와 분모가 같아지므로 1이 됨
그럼 decision_function()
메서드로 z1~z7까지의 값을 구한 다음 소프트맥스 함수를 사용해서 확률로 바꿔볼 것임.
<테스트 세트의 처음 5개 샘플에 대한 z1~z7의 값 구하기>
decision = lr.decision_function(test_scaled[:5])
print(np.round(decision, decimals = 2))
output
[[ -6.5 1.03 5.16 -2.73 3.34 0.33 -0.63]
[-10.86 1.93 4.77 -2.4 2.98 7.84 -4.26]
[ -4.34 -6.23 3.17 6.49 2.36 2.42 -3.87]
[ -0.68 0.45 2.65 -1.19 3.26 -5.75 1.26]
[ -6.4 -1.99 5.82 -0.11 3.5 -0.11 -0.71]]
<softmax()함수 임포트 해서 사용하기>
from scipy.special import softmax
proba = softmax(decision, axis = 1)
print(np.round(proba, decimals = 3))
output
[[0. 0.014 0.841 0. 0.136 0.007 0.003]
[0. 0.003 0.044 0. 0.007 0.946 0. ]
[0. 0. 0.034 0.935 0.015 0.016 0. ]
[0.011 0.034 0.306 0.007 0.567 0. 0.076]
[0. 0. 0.904 0.002 0.089 0.002 0.001]]
ㄴ decision 배열을 softmax() 함수에 전달하였음.
ㄴ proba 배열과 일치한다 !!
로지스틱 회귀를 사용해 7개의 생선에 대한 확률 예측하는 모델 훈련 성공 !
현재 문제 : 훈련 데이터가 한번에 준비되는 것이 아니라 조금씩 전달된다는 것 ! -> 도착하는 대로 생선을 판매해야 하므로 데이터가 쌓일 때까지 무작정 기다릴 수도 없음
해결 방법 1)
기존의 훈련 데이터에 새로운 데이터를 추가해서 모델을 매일매일 다시 훈련한다
ㄴ 장점 : 매일 추가되는 새로운 데이터를 활용해서 모델을 훈련할 수 있음
ㄴ 단점 : 시간이 지날수록 데이터가 늘어남 = 지속가능하지 않음
해결 방법 2)
새로운 데이터를 추가할 때 이전 데이터를 버림으로써 훈련 데이터 크기를 일정하게 유지한다
ㄴ 장점 : 데이터 셋의 크기가 너무 커지지 않을 수 있음
ㄴ 단점 : 데이터를 버릴 때 다른 데이터에 없는 중요한 생선 데이터가 포함되어있다면 큰일이다 = 앞으로 모델이 그 생선을 잘 예측하지 못한다
원하는 해결 방법
: 훈련한 모델을 버리지 않고 새로운 데이터에 대해서만 조금씩 더 훈련하기
= 이 방식을 점진적 학습
또는 온라인 학습
이라고 함
대표적인 점진적 학습 알고리즘 : 확률적 경사 하강법
: 경사를 따라 내려가는 방법 = 가장 가파른 경사를 따라서 원하는 지점에 도달하는 것이 목표
경사 하강법에서는 가장 가파른 길을 찾아 내려오지만 조금씩 내려오는 것이 중요하고 이 과정이 경사하강법 모델을 훈련하는 것이다
확률적
이란 말은 뭘까?
: 경사 하강법으로 내려올 때 가장 가파른 길을 찾는 방법은 훈련 세트를 사용해서 찾는 방법임. 하지만 전체 샘플을 사용하지 않고 딱 하나의 샘플을 훈련 세트에서 랜덤하게 골라 가장 가파른 길을 찾습니다
= 훈련 세트에서 랜덤하게 하나의 샘플을 고르는 것 = 확률적 경사 하강법
근데 훈련 세트에서 랜덤하게 하나의 샘픙르 선택해서 가파른 경사를 조금씩 내려가고 그 다음 또 조금 내려가고 해서 전체 샘플을 모두 사용할 때 까지 계속하는데 산을 다 내려오지 못했다면 ?
=> 훈련 세트에 모든 샘플을 다시 채워넣어서 또 랜덤하게 하나의 샘플을 선택해 이어서 경사를 내려감
이렇게 확률적 경사 하강법에서 훈련 세트를 한 번 모두 사용하는 과정을 에포크
라고 하고 일반적으로 수십, 수백번 이상의 에포크를 수행함
1개씩 골라서 경사를 내려가는게 좀 무섭다면?
무작위로 몇개의 샘플을 선택해서 경사를 따라 내려가면 된다. 이 방식을 미니배치 경사 하강법
이라고 함
전체 샘플을 사용한다면 ? -> 배치 경사 하강법
<경사 하강법 그림으로 표현>
그럼 가장 빠른 길을 찾아 내려가려고 하는데 어케 해야할까?
= 손실함수
: 어떤 문제에서 머신러닝 알고리즘이 얼마나 엉터리인지를 측정하는 기준임
-> 손실함수의 값이 작을 수록 좋은 것 (최솟갑은 알지 못한다)
분류에서 손실은 아주 확실함 (= 정답을 맞추지 못하는 것)
ex) 도미와 빙어를 구분하는 이진 분류 문제
4개의 예측 중에 2개만 맞았으므로 1/2 = 0.5이다.
ㄴ 정확도엔 단점이 있음 !! 예시와 같이 4개의 샘플만 있다면 가능한 정확도는 0
, 0.25
, 0.5
, 0.75
, 1
이 다섯개 뿐이어서 조금씩 내려올 수가 없음 ! 산의 경사면은 확실히 연속적이어야 한다
그럼 어떻게 연속적인 손실 함수를 만들 수 있을까?
로지스틱 손실함수
를 쓰장
ex) 샘플 4개의 예측 확률을 0.9
, 0.3
, 0.2
, 0.8
이라고 하즈아
첫번째 샘플의 예측은 0.9
여서 양성 클래스 타깃인 1과 곱한 다음 음수로 바꿀 수 있음. 이 경우 예측이 1에 가까울 수록 좋은 모델이 된다
이 값을 손실 함수로 사용해도 될 듯
두번째 샘플의 예측은 0.3
임 이 역시 타깃을 곱해 음수로 바꾸면 -0.3
이 되기 때문에
첫 번째 샘플 보다 높은 손실이 된다
세번째 샘플의 타깃은 음성 클래스라 0이다. 이땐 예측 확률인 0.2와 그래도 곱하면 바로 0이 되기 때문에 타깃을 마치 양성 클래스처럼 바꿔서 1로 만들고, 예측값도 양성클래스에 대한 예측으로 바꾼다 = 1 - 0.2 = 0.8
로 사용한 다음 곱하고 음수로 바꾸기
세 번째 샘플은 음성 클래스인 타깃을 맞췄기 때문에 손실이 낮아야 하고 -0.8
은 꽤나 낮은 손실이다
네 번째 샘플도 타깃은 음성 클래스 !! 하지만 정답을 맞추지 못했음;;
타깃을 1로 바꾸고 예측 확률을 1에서 뺀 다음 곱해서 음수로 바꾸자
손실이 높당
여기서 예측 확률에 로그함수를 적용하면 더 좋음
ㄴ 예측 확률의 범위는 0~1 사이인데 로그함수는 이 사이에서 음수가 되므로 최종 손실 값은 양수가 되어서 이해하기가 더 쉽다. 그리고 로그함수는 0에 가까울수록 아주 큰 음수가 되기 때문에 손실을 아주 크게 만들어 모델에 큰 영향을 미칠 수 있음
양성 클래스는 손실은 -log
로 계산해서 확률이 1에서 멀어질수록 손실은 아주 큰 양수가 됨. 음성 클래스일 때 손실은 -log
로 계산해서 확률이 0에서 멀어질 수록 손실은 아주 큰 양수가 됨
확률적 경사 하강법을 사용한 분류 모델 만들어보기 !!
<판다스 데이터프레임 만들기>
import pandas as pd
fish = pd.read_csv("https://bit.ly/fish_csv_data")
<Species 열을 제외한 나머지 5개는 입력 데이터로 사용하고 Speceis 열은 타깃 데이터가 됨>
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 임포트>
from sklearn.linear_model import SGDClassifier
ㄴ 2개의 매개변수 지정함
loss
는 손실 함수의 종류를 지정해주고 max_iter
는 수행할 에포크 횟수를 지정함
여기선 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)) # 0.773109243697479
print(sc.score(test_scaled, test_target)) # 0.775
ㄴ 정확도가 낮다 !! 아마도 10번으로는 부족한듯
확률적 경사 하강법은 점진적 학습이 가능해서 SGDClassifier 객체를 다시 만들지 않고 훈련한 모델 sc를 추가로 더 훈련해 볼 것임. 모델을 이어서 훈련할 때는 partial_fit()
메소드를 사용함
ㄴ 이 메소드는 fit()
메소드랑 사용법이 같지만 호출할 때마다 1 에포크씩 이어서 훈련할 수 있다.
<partial_fit()메서드 호출하고 훈련 세트와 테스트 세트의 점수 확인하기>
sc.partial_fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target)) # 0.8151260504201681
print(sc.score(test_scaled, test_target)) # 0.825
ㄴ 아직 점수가 낮긴 한데 에포크를 한 번 더 하니 정확도가 향상됐음
얼마나 더 훈련해야할까? 무작정 반복할 수는 없는데.. 기준이 필요하겠군
확률적 경사하강법을 사용한 모델은 에포크 횟수에 따라서 과소적합
이나 과대적합
이 될 수 있음
1) 에포크 횟수가 적으면 모델이 훈련 세트를 덜 학습해서 훈련 세트와 테스트 세트에 잘 맞지 않는 과소적합된 모델일 가능성이 높음
2) 에포크 횟수가 많으면 훈련 세트를 완전히 학습할 것임 = 훈련 세트에 아주 잘맞는 모델이 만들어지는데 훈련 세트에 너무 잘 맞아 테스트 세트에는 오히려 점수ㅏㄱ 나쁜 과대적합된 모델일 가능성이 높다
<에포크가 진행됨에 따라 모델의 정확도를 표현한 그래프>
ㄴ 훈련 세트 점수는 에포크가 진행될수록 꾸준히 증가하지만 테스트 점수는 어느 순간 감소하기 시작함 = 이 지점이 과대적합되기 시작하는 곳
ㄴ 우리는 과대적합이 시작되기 전에 훈련을 멈추는 것을 조기 종료
라고 한다
이번에는 fit()
을 쓰지 않고 partial_fit()
만 쓸 것인데 이 메서드만 사용하려면 훈련 세트에 있는 전체 클래스의 레이블을 메소드에 전달해줘야함. 이를 위해 np.unique()
함수로 train_target
에 있는 7개 생선의 목록을 만들어야 함. 또, 에포크마다 훈련 세트와 테스트 세트에 대한 점수를 기록하기 위해 2개의 리스트를 준비해야 함.
import numpy as np
sc = SGDClassifier(loss = 'log', random_state = 42)
train_score = []
test_score = []
classes = np.unique(train_target)
<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)) # 테스트 세트 점수 계산해서 리스트에 추가
<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
만큼 무조건 반복하게 시킴
점수가 좋게 나왔다 ~ 성공 !!
마무리 하기 전에 SGDClassifierdml loss
매개변수에 대해 알아보자면 loss 매개변수의 기본값은 hinge
이다. 힌지 손실
은 서포트 벡터 머신
이라고 불리는 또 다른 머신러닝 알고리즘을 위한 손실 함수이다.
걍 서포트 벡터 머신이 널리 사용하는 머신러닝 알고리즘 중 하나라는 점과 SGDClassifier가 여러 종류의 손실함수를 loss 매개변수에 지정하여 다양한 머신러닝 알고리즘을 지원한다는 것만 알아라 ~~
<힌지 손실을 사용한 모델 훈련 예시>
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)) # 0.9495798319327731
print(sc.score(test_scaled, test_target)) # 0.925