개요: 럭키백의 확률
럭키백을 구입하면 랜덤한 확률로 다양한 생선 중 하나가 등장한다. 소비자는 이를 구입한 뒤 럭키백을 개봉해야 어떤 생선이 들어있는지 확인할 수 있다. 그리고, 판매자는 생선 별로 럭키백에 존재할 확률을 고지해준다. 이 확률을 알기 위해서는 어떤 모델을 활용해야 할까? k-최근접 이웃을 활용하여 럭키백의 생선 근처에 생선 별로 비율을 확인하면 될 듯 싶다.
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv')
fish.head()
위 데이터프레임에서 Species는 타깃, 즉 물고기의 종류가 되고 나머지 weight, length, ... 등은 특성이 된다. 데이터를 뽑아 전처리하는 작업을 다음과 같이 거친다.
#데이터 프레임에서 여러 열을 선택하여 numpy 배열로 반환
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)
이제 k-최근접 이웃 분류 클래스를 활용하여 실제 클래스(생선 종류)별 확률을 구해보자.
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier(n_neighbors=3)
kn.fit(train_scaled,train_target)
print(kn.score(train_scaled, train_target))
print(kn.score(test_scaled, test_target))
# 훈련 세트: 0.8907563025210085
# 테스트 세트: 0.85
print(kn.classes_) # 클래스 종류 출력(생선 종류)
# ['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']
print(kn.predict(test_scaled[:5])) # 처음 5개 테스트 샘플에 대한 클래스 예측 출력
# ['Perch' 'Smelt' 'Pike' 'Perch' 'Perch']
import numpy as np
probability = kn.predict_proba(test_scaled[:5])
print(np.round(probability, decimals=4))
# 샘플 별 각 클래스 별 확률
['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']
[[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. ]]
# 시범삼아 네번째 샘플의 최근접 이웃 확인
distances, indexes = kn.kneighbors(test_scaled[3:4])
print(train_target[indexes])
# [['Roach' 'Perch' 'Perch']]
#Perch=2/3=0.6667, Roach=1/3=0.3333
위는 그럴듯 하나 3개의 최근접 이웃만 활용하여 확률이 0, 1/3, 2/3 , 1로 매우 제한적이다.
로지스틱 회귀
로지스틱 회귀는 이름은 회귀이지만 분류 모델이다. 이 모델을 선형 회귀와 비슷하게 선형 방정식을 학습한다.
z=a*특성1+b*특성2+c*특성3+d
여기서 z는 0~1 사이의 확률이어야 한다. 따라서, 이를 적절히 치환하기 위해 시그모이드 함수(로지스틱 함수)를 활용하여 0~1 사이의 값으로 바꾼다.
이제 로지스틱 회귀를 이용하여 이진 분류를 수행해보자. 로지스틱 회귀의 이진 분류의 경우 0.5를 넘으면 A 클래스, 0.5 보다 작으면 B 클래스로 판단한다.
# 도미와 빙어만 추출한 훈련 세트 추출
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
lr = LogisticRegression()
lr.fit(train_bream_smelt,target_bream_smelt)
# 처음 5개 샘플의 분류
print(lr.predict(train_bream_smelt[:5]))
결과:['Bream' 'Smelt' 'Bream' 'Bream' 'Bream']
# 처음 5개 샘플의 클래스별 확률
print(lr.classes_)
print(lr.predict_proba(train_bream_smelt[:5]))
# 결과
['Bream' 'Smelt']
[[0.99759855 0.00240145]
[0.02735183 0.97264817]
[0.99486072 0.00513928]
[0.98584202 0.01415798]
[0.99767269 0.00232731]]
로지스틱 회귀를 이용한 다중 분류
다음으로 최종 목표로 했던 다중 분류를 로지스틱 회귀로 수행해보자. 로지스틱 회귀 또한 릿지 회귀와 비슷하게 계수의 제곱을 규제하여 과대적합/과소적합을 조절한다. 로지스틱 회귀에서는 alpha 대신 C 값으로 이를 규제한다. 다만, 로지스틱 회귀에서는 C가 커질수록 큐제가 적어진다는 점이 반대이다. 또한, Logistic Regression 클래스는 반복적인 알고리즘을 활용하는데 이 때 max_iter 매개 변수를 통해 반복 횟수를 지정할 수 있다. 반복 횟수가 늘어날 수록 모델을 충분히 훈련시킬 수 있게 된다.
lr = LogisticRegression(C=20, max_iter=1000)
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled,train_target))
print(lr.score(test_scaled,test_target))
훈련 세트:0.9327731092436975
테스트 세트:0.925
print(lr.predict(test_scaled[:5]))
['Perch' 'Smelt' 'Pike' 'Roach' 'Perch']
print(lr.classes_)
print(np.round(lr.predict_proba(test_scaled[:5]),decimals=3))
['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']
[[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.coef_.shape,lr.intercept_.shape
결과: (7, 5) (7,)
위 데이터에서는 'Weight','Length','Diagonal','Height','Width' 5개의 특성을 활용하여 coef_ 배열의 열은 5개이다. 그런데 coef_, intercept_의 행이 모두 7개이다. 이는 클래스마다 z값을 하나씩 계산한다는 점을 의미한다. 생선의 종류가 7개이므로 7개 각각에 대하여 계산한 뒤 가장 높은 z값을 가진 클래스가 예측 클래스가 된다. 이진 분류에서는 시그모이드 함수를 활용하여 0~1 사이의 값으로 치환했으나, 다중 분류의 경우 소프트맥스 함수를 활용하여 7개의 z값을 확률로 변환시켜 총 합이 1이 되게 한다.
소프트맥스 함수는 e의 Z 제곱값을 모든 z에 대해 e의 Zn제곱값의 합으로 나누어 각 클래스 별 확률을 구한다.
decision = lr.decision_function(test_scaled[:5])
print(np.round(decision,decimals=2))
from scipy.special import softmax
print(np.round(softmax(decision,axis=1),decimals=3))
# 5개의 샘플 데이터에 대한 클래스 별 z값
[[ -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]]
# 위 값들에 대해 소프트맥스 함수를 거쳐 나타낸 5개 샘플의 각 클래스별 확률
[[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]]
요약
- 여러 특성을 가진 클래스를 분류하는 알고리즘으로 로지스틱 회귀 알고리즘을 자주 사용한다.
- 로지스틱 회귀는 특성 별로 가중치를 둔 선형 방정식을 반복적으로 학습한다.
- 이 때 0~1 사이의 확률로 치환하기 위해 각 방정식의 출력값을 소프트맥스 함수에 통과시킨다.
참고
혼자 공부하는 머신러닝+딥러닝(박해선 저)
'AI: ML,DL' 카테고리의 다른 글
[인공지능] 인공 신경망(딥러닝, DNN) (0) | 2021.03.20 |
---|---|
[인공지능] 확률적 경사 하강법 (0) | 2021.03.15 |
[인공지능] 특성 공학과 규제(다중 회귀, 릿지, 라쏘) (0) | 2021.03.13 |
[인공지능] 회귀: k-최근접 이웃 회귀 (0) | 2021.03.13 |
[인공지능] 데이터: 훈련 세트와 테스트 세트, 데이터 전처리 (0) | 2021.03.12 |