Chapter 4. 다양한 분류 알고리즘

김영빈·2022년 9월 1일
0

혼공머신

목록 보기
4/7
post-thumbnail

4-1 로지스틱 회귀

학습 목표

  • 로지스틱 회귀 알고리즘을 배우고 이진 분류 문제에서 클래스 확률을 예측한다.

1) k - 최근접 이웃 분류기의 확률 예측

📕 데이터 준비

  • 데이터 불러오기
import pandas as pd
fish = pd.read_csv('https://raw.githubusercontent.com/rickiepark/hg-mldl/master/fish.csv')
display(fish.head())
▶
Species	Weight	Length	Diagonal	Height	Width
0	Bream	242.0	25.4	30.0	11.5200	4.0200
1	Bream	290.0	26.3	31.2	12.4800	4.3056
2	Bream	340.0	26.5	31.1	12.3778	4.6961
3	Bream	363.0	29.0	33.5	12.7300	4.4555
4	Bream	430.0	29.0	34.0	12.4440	5.1340

print(pd.unique(fish['Species']))
▶
['Bream' 'Roach' 'Whitefish' 'Parkki' 'Perch' 'Pike' 'Smelt']
  • Species 열을 target으로, 나머지를 input으로
fish_input = fish[['Weight', 'Length', 'Diagonal', 'Height', 'Width']].to_numpy()
print(fish_input[:5])
▶
[[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()
print(fish_target[:5])
▶
['Bream' 'Bream' 'Bream' 'Bream' 'Bream']
  • train, test set 나누기
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

print(kn.predict(test_scaled[:5]))
▶
['Perch' 'Smelt' 'Pike' 'Perch' 'Perch']
  • k - 최근접 이웃 모델에 학습된 분류 클래스 확인(정렬해서 보여줌)
print(kn.classes_)
▶
['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']
  • 클래스별 예측 확률값
import numpy as np
proba = kn.predict_proba(test_scaled[:5])

print(np.round(proba, decimals=4))
▶
[[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.    ]]

❗ discussion
predict_proba() 메서드는 k - 최근접 이웃 분류에서 k_neighbors=k일때,
인근 k개의 클래스가 어떻게 분류(몇개나 존재)되어있는지를 보여준다.

  • 가장 가까운 이웃 확인(네번째 샘플의 경우)
distances, indexes= kn.kneighbors(test_scaled[3:4])
print(train_target[indexes])
▶
[['Roach' 'Perch' 'Perch']]
  • 결과
    클래스별 예측확률 값에 나온 분포와 동일하다.

2) 로지스틱 회귀 이진 분류

📕 sigmoid 함수

  • sigmoid 함수 그래프 그리기
import numpy as np
import matplotlib.pyplot as plt
z = np.arange(-5,5,0.1)
phi = 1 / (1 + np.exp(-z))
plt.plot(z,phi)
plt.xlabel('z')
plt.ylabel('phi')
plt.title('Sigmoid')
plt.show();
  • 결과 이미지

❗ discussion
sigmoid 함수를 이용하여 0에서 1사이의 확률을 나타낼 수 있으며,
극소값은 0에 가깝게, 극대값은 1에 가깝게 나타난다.

📕 로지스틱 회귀 활용 모델링

  • test set에서 원하는 target의 데이터만 추출
# bream, smelt 데이터만 추출 
bream_smelt_indexes = (train_target == 'Bream')|(train_target=='Smelt')
train_bream_smelt = train_scaled[bream_smelt_indexes]
target_bream_smelt = train_target[bream_smelt_indexes]
  • 해당 데이터로 로지스틱 회귀 모델 훈련 및 예측
from sklearn.linear_model import LogisticRegression # 로지스틱 회귀는 선형 모델이므로 linear 패키지에 있음
lr = LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)

print(lr.classes_)                                  # 훈련한 모델의 분류 클래스 확인(0(음성), 1(양성) 순서로 출력한다.) * 알파벳 순 자동 정렬
▶
['Bream' 'Smelt']

print(lr.predict(train_bream_smelt[:5]))            
▶
['Bream' 'Smelt' 'Bream' 'Bream' 'Bream']

print(lr.predict_proba(train_bream_smelt[:5]))      # 1열이 음성 클래스(0)의 확률, 2열이 양성 클래스(1)의 확률
▶
[[0.99759855 0.00240145]
 [0.02735183 0.97264817]
 [0.99486072 0.00513928]
 [0.98584202 0.01415798]
 [0.99767269 0.00232731]]

📕 로지스틱 회귀 방정식 확인

  • 계수, y절편 확인
print(lr.coef_, lr.intercept_)
▶
[[-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.162z = -0.404*(Weight) -0.576*(Length) -0.663*(Diagonal) -1.013*(Height) - 0.732*(Width) - 2.162

  • 로지스틱 회귀 모델 z값 출력

# 처음 5개 샘플의 z값 출력
decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions)
▶
[-6.02927744  3.57123907 -5.26568906 -4.24321775 -6.0607117 ]

❗ discussion
위에서 얻은 개별 샘플에 대한 z값을 sigmoid 함수에 넣으면 원하는 예측 확률을 얻을 수 있다.

  • sigmoid 메서드를 통한 확률 변환
from scipy.special import expit
print(expit(decisions))
▶
[0.00240145 0.97264817 0.00513928 0.01415798 0.00232731]
  • 결과
    predict_proba() 메서드 출력의 두번째 열의 값과 동일하다.

❗ discussion
decision_function() 메서드는 양성 클래스에 대한 z값을 반환한다.

3) 로지스틱 회귀 다중 분류

📕 로지스틱 회귀 모델링

  • 로지스틱 회귀 모델링 및 예측
lr = LogisticRegression(C=20, max_iter=1000)    # C는 규제변수(1이 제일 강한 규제)
lr.fit(train_scaled, train_target)

print(lr.score(train_scaled, train_target))
▶
0.9327731092436975

print(lr.score(test_scaled, test_target))
▶
0.925
  • 결과
    train set, test set 점수가 높고 과대적합이나 과소적합이 발생하지 않은 것 같다.

  • test set 처음 5개 샘플에 대한 예측 및 확률

print(lr.predict(test_scaled[:5]))
▶
['Perch' 'Smelt' 'Pike' 'Roach' 'Perch']

proba = lr.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=3))
▶
[[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]]
 
 print(lr.classes_)
 ▶
 ['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']

❗ discussion
클래스 분류 순서와 예측과 걸맞게 클래스 분류 확률이 분포되어 있다.
다중 분류는 샘플마다 클래스의 개수만큼 확률을 출력한다.

📕 다중 분류 선형 방정식 확인

  • 해당 모델의 계수 및 절편 개수 확인
print(lr.coef_.shape, lr.intercept_.shape)
▶
(7, 5) (7,)

❗ discussion
해당 데이터는 5개의 특성을 사용하므로 coef 배열의 열은 5개이다.
그런데 행이 7개, intercept
도 7개이다.
이 말은 이진 분류에서 보았던 z를 7개 계산한다는 의미로,
다중 분류에서는 클래스마다 z값을 하나씩 계산한다는 것이고
그 중 가장 높은 z 값을 출력하는 클래스가 예측 클래스가 된다.
그렇다면 확률은 어떻게 계산한 것일까?
소프트맥스 함수를 사용하여 7개의 z값을 확률로 변환한다.

soft max function
여러개의 선형 방정식의 출력값을 0~1 사이로 압축하고 전체 합이 1이 되도록 만든다.
이를 위해 지수 함수를 사용하기에 정규화된 지수 함수라고도 부른다.

📕 소프트맥스 함수 사용 확률 변환

  • decision_function() 메서드 사용 z값 얻기
decision = lr.decision_function(test_scaled[:5])
print(np.round(decision, decimals=2))
▶
[[ -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]]
  • 소프트맥스 함수 사용
from scipy.special import softmax
proba = softmax(decision, axis=1)	# 지정하지 않을 시 배열 전체에 대해 계산
print(np.round(proba, decimals=3))
▶
[[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]]
  • 결과
    predict_proba() 메서드의 출력값과
    decision_function() 메서드와 softmax 메서드를 사용한 출력값이 동일하다.

📕 키워드 정리

로지스틱 회귀
선형 방정식을 사용한 분류 알고리즘으로,
선형 회귀와 달리 시그모이드 함수나 소프트맥스함수를 사용하여 클래스를 예측합니다.

다중 분류
target class가 2개 이상인 분류 문제
로지스틱 회귀는 다중 분류를 위해 소프트맥스 함수를 사용하여 클래스를 예측한다.

시그모이드 함수
선형 방정식의 출력을 0과 1 사이의 값으로 압축하며 이진 분류를 위해 사용한다.

소프트맥스 함수는 다중분류에서 여러 선형 방정식의 출력 결과를 정규화하여 합이 1이 되도록 만든다.


4-2 확률적 경사 하강법

학습 목표

  • 경사 하강법 알고리즘을 이해하고 대량의 데이터에서 분류 모델을 훈련하는 방법을 배웁니다.

1) 손실 함수

📕 손실 함수란

손실 함수 : 머신 러닝 알고리즘의 오차 측정도

  • 선형 회귀의 목표는 모델이 해석하는 z 방정식의 가중치(계수)와 절편을 찾는 것이다.

  • 로지스틱 회귀와 같은 분류의 목표는 올바르게 분류된 샘플 데이터의 비율 자체를 높이는 것이 목표이다.

  • 하지만 올바르게 분류된 샘플의 비율(정확도)은 미분 가능한 함수가 아니기 때문에
    경사 하강법의 손실 함수로 사용할 수 없다. 따라서 미분가능한 다른 함수로 로지스틱 손실 함수를 사용한다.

  • 다중 분류를 위한 손실 함수를 크로스 엔트로피 손실 함수 라고 하는데,
    이 중 이진 분류에 대해서 로지스틱 손실 함수 또는 이진 크로스엔트로피 손실 함수라고 부른다.

로지스틱 손실 함수 표현

a = 예측 확률, y = target일때
L=(ylog(a)+(1y)log(1a))L=-(ylog(a)+(1-y)log(1-a))

❗ discussion
비용함수는 train set에 있는 모든 샘플에 대한 손실 함수의 합이고,
손실함수는 샘플 하나에 대한 손실이지만 보통 이 둘을 엄격히 구분하지 않고 섞어서 사용한다.

2) 확률적 경사 하강법

📕 경사 하강법이란

  • 이미 훈련 데이터가 쌓인 모델에 조금씩 새로운 데이터를 추가해서 훈련하는 점진적 학습 방법 중 하나로, 조금씩 손실함수를 줄이는 방향으로 모델을 훈련하는 방법을 경사 하강법이라고 한다.

📕 경사 하강법의 분류

경사 하강법은 크게 세가지로 나눌 수 있다.
- 확률적 경사 하강법 : train set에서 임의로 하나의 샘플을 골라 훈련하는 방법
- 미니배치 경사 하강법 : 임의로 몇개의 샘플을 사용하여 훈련하는 방식
- 배치 경사 하강법 : 전체 데이터를 사용하여 훈련하는 방식
* 에포크 : 확률적 경사 하강법에서 train set을 모두 사용하는 한번의 과정. 일반적으로 경사 하강법은 수십, 수백 번 이상 애포크를 수행한다.

📕 SGDClassifier

  • 데이터 준비
# 모듈 import, 데이터 불러오기
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')

# input, target data 구분
fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()
fish_target = fish['Species'].to_numpy()

# train set, test set 구분
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 = 2)

# 표준화 전처리
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)
  • 확률적 경사 하강법 클래스 분류 모델(SGDClasifier)
from sklearn.linear_model import SGDClassifier
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
  • 결과
    train set과 test set의 정확도가 너무 낮다.
    지정한 반복 횟수 10번이 부족한 것으로 보인다.

❗ discussion
SGDClassifier의 메소드 설명

  • loss : 손실 함수 종류 지정. loss='log'는 로지스틱 손실 함수(이진 분류)
  • max_iter : 수행할 에포크(반복) 횟수
  • 다중 분류, 로지스틱 손실 함수일 경우 클래스마다 이진 분류 모델을 만든다.
    이런 방식을 OvR(One versus Rest)이라고 부른다.

📕 확률적 경사 하강법을 활용한 점진적 학습

  • 앞서 만든 객체에 훈련한 모델sc를 추가로 훈련
sc.partial_fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target))
▶
0.8151260504201681

print(sc.score(test_scaled, test_target))
▶
0.85
  • 결과
    아직 과소적합하며, 점수가 낮지만 에포크를 한번 더 실행하니 정확도가 향상됨.

❗ discussion
위 모델에서 훈련에 사용된 데이터는 train_scaled 전체이다.
하지만 SGDClassifier 객체에 한 번에 train set 전체를 전달하였어도,
알고리즘에서는 1개씩 샘플을 꺼내어 경사 하강법 단계를 수행한다.

3) 에포크와 과대/과소적합

📕 에포크 횟수에 따른 모델 적합도

  • 에포크 횟수가 적을 시 train set을 덜 학습하므로 모델이 과소적합하며,
  • 에포크 횟수가 많을 시 train set에 너무 집착하여 과대적합된다.

에포크가 진행됨에 따라 train set의 정확도는 상승하지만,
test set의 정확도는 과대적합되기 시작하는 순간 감소한다.
과대적합이 시작되기 전에 훈련을 멈추는 것을 조기 종료라고 한다.

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

# epoch = 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))

# 시각화
import matplotlib.pyplot as plt
plt.plot(train_score, label = 'train score')
plt.plot(test_score, label = 'test score')
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.legend()
plt.show();
  • 결과 이미지

❗ discussion
초반에는 과소적합되어 두개의 점수가 모두 낮지만 점점 동반 상승하며,
약 epoch = 100인 지점에서 test score는 그대로이지만, train score는 상승한다.
따라서 이 모델의 경우 백번째 epoch가 적절한 반복 횟수로 보인다.

  • 반복횟수 수정 모델
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
  • 결과
    두 점수가 모두 높고 train set의 점수가 더 높은 것으로 보아 과소적합도 일어나지 않았다.

❗ discussion
SGDClassifier는 일정 epoch동안 성능이 향상되지 않으면 더 이상 훈련하지 않고 멈춘다. 이때 향상될 최솟값을 tol 매개변수에 지정한다.

📕 키워드 정리

확률적 경사 하강법이란
train set에서 샘플 하나씩 꺼내 손실 함수의 경사를 따라 최적의 모델을 찾는 알고리즘이다. 샘플을 꺼내는 개수에 따라 미니배치(여러개), 배치(전체) 경사 하강법으로 나뉜다.

손실 함수란
확률적 경사 하강법이 최적화할 대상으로 대부분의 문제에 잘 맞는 손실 함수가 이미 정의되어있다.
이진 분류에는 로지스틱 회귀(= 이진 크로스엔트로피) 손실 함수,
다중 분류에는 크로스엔트로피 손실 함수를 사용한다.
회귀 문제에는 평균 제곱 오차 손실 함수를 사용한다.

에포크란
확률적 경사 하강법에서 전체 샘플을 모두 사용하는 한 번 반복을 의미한다.
일반적으로 경사 하강법 알고리즘은 수십에서 수백 번의 에포크를 반복한다.

profile
개발도상인 냄비짱

0개의 댓글