#럭키백의 확률
럭키백~!
도미일 확률 70% ,빙어일 확률 30%
머신러닝으로 럭키백의 생선이 어떤 타깃에 속하는지 확률을 구할 수 있을까?
럭키백에 들어갈 수 있는 생선은 7개이다.
길이,높이,두께,대각선 길이와 무게를 사용해 7개 생선에 대한 확률을 출력해야 한다.
KNN 분류기로 클래스(럭키백에 들어간 생선)의 확률을 계산
#데이터 준비하기
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')
fish.head() #처음 5개만 출력
어떤 종류의 생선이 있는지 Species열에서 고유한 값 추출하기 -> unipue() 사용
print(pd.unique(fish['Species']))
#['Bream' 'Roach' 'Whitefish' 'Parkki' 'Perch' 'Pike' 'Smelt']
이 데이터 프레임에서 Species열을 타깃으로 만든다.
나머지 5개 열은 입력데이터로 사용한다.
데이터프레임에서 열을 선택하는 방법은 간단하다.
원하는 열을 리스트로 나열한다.
Species 빼고 나머지를 선택한다.
넘파이배열로 바꾸자
fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()
입력데이터 : Species 뺀 나머지 출력하기
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()
훈련세트 테스트 세트 나누기
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)
#KNN 분류기의 확률 예측
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
앞서 fish 데이터 프레임에서 7개의 생선이 있었다.
타깃데이터를 만들때 fish['Species'] 를 사용해 만들었기 때문에
훈련세트와 테스트세트의 타깃데이터에도 7개의 생선종류가 들어가 있다.
이렇게 타깃데이터에 2개 이상의 클래스가 포함된 문제를 다중분류라고 부른다.
이때 주의할점 타깃값을 그대로 사이킷런 모델에 전달하면 순서가 자동으로 알파벳순으로 매겨진다.
이것과 순서가 다름.
print(pd.unique(fish['Species']))
#['Bream' 'Roach' 'Whitefish' 'Parkki' 'Perch' 'Pike' 'Smelt']
정렬된 타깃값은 classes_ 속성에 있다.
print(kn.classes_)
#['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']
#첫번째 클래스~ 두번째 순서대로
predict는 타깃 값으로 예측을 출력한다.
테스트 세트에 있는 처음 5개의 샘플을 타깃값으로 예측하겠다.
print(kn.predict(test_scaled[:5]))
#['Perch' 'Smelt' 'Pike' 'Perch' 'Perch']
이 예측은 어떤 확률로 만들어졌을까?
사이킷런의 분류 모델은 predict_proba() 메서드로 클래스별 확률값을 반환한다.
테스트 세트에 있는 처음 5개의 샘플에 대한 확률을 출력해보자
메서드의 출력 순서는 classes_ 속성과 같다
import numpy as np
proba = kn.predict_proba(test_scaled[:5]) #클래스별 확률 값 계산
print(np.round(proba, decimals=4)) #소수점 네번째자리까지표시(5번째에서 반올림)
"""
[[0. 0. 1. 0. 0. 0. 0. ]
[0. 0. 0. 0. 0. 1. 0. ]
[0. 0. 0. 1. 0. 0. 0. ] #3번째
[0. 0. 0.6667 0. 0.3333 0. 0. ] #4번째 샘플
[0. 0. 0.6667 0. 0.3333 0. 0. ]]
"""
"""
['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']
[첫번째 클래스에 대한 확률, 2번째~,]
"""
바로 위 모델이 계산한 확률이 가장 가까운 이웃의 비율이 맞는지 확인하자
네번째 샘플의 최근접 이웃 클래스를 확인하겠다.
다섯번째 클래스인 Roach가 1개 -> 확률 : 1/3 =0.3333
세번째 클래스인 Perch가 2개 -> 확률 : 2/3 =0.6667
제대로 나온게 맞다.
distances, indexes = kn.kneighbors(test_scaled[3:4])
print(train_target[indexes])
#[['Roach' 'Perch' 'Perch']]
근데 이상하다.
3개의 KNN을 사용하기 때문에 가능한 확률은 0/3 1/3 2/3 3/3 이 전부다.....
만약 럭키백의 확률을 이렇게만 표시한다면 ...별로다! 확률이라 말하기 어색하다.
다른방법을 찾자
#로지스틱 회귀
이름은 회귀이지만 분류모델이다.
선형회귀와 동일하게 선형방정식을 학습한다.
0~1 사이의 값을 확률로 해석하기 위해
시그모이드함수=로지스틱함수를 사용하자
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위치마다 시그모이드 함수를 계산한다. exp()로 지수함수 계산
plt.plot(z, phi)
plt.xlabel('z')
plt.ylabel('phi')
plt.show()
시그모이드 함수의 출력은 정말 0에서 1까지 변한다.
#로지스틱 회귀로 이진 분류 수행하기
이진분류를 수행하자
시그모이드 함수의 출력이 0.5보다 크면 양성, 작으면 음성 클래스로 판단
도미,빙어를 사용해 이진 분류를 실행해보자
불리언 인덱싱
넘파이 배열은 true false 값을 전달하여 행을 선택할 수있다.
char_arr = np.array(['A', 'B', 'C', 'D', 'E'])
print(char_arr[[True, False, True, False, False]]) #A,C만 골라내기
#['A' 'C']
이런 방식을 사용해 도미(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
lr = LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)
훈련한 모델을 사용해 train_bream_smelt의 처음 5개 샘플 예측
두번째 샘플을 제외하고 모두 도미로 예측했다.
print(lr.predict(train_bream_smelt[:5]))
#['Bream' 'Smelt' 'Bream' 'Bream' 'Bream']
predict_proba()로 클래스별 확률값 반환
train_bream_smelt에서 처음 5개 샘플의 예측확률을 출력
샘플마다 2개의 확률이 출력되었다.
print(lr.predict_proba(train_bream_smelt[:5]))
"""
[[0.99759855 0.00240145] #음성클래스(0) 양성클래스(1)
[0.02735183 0.97264817]
[0.99486072 0.00513928]
[0.98584202 0.01415798]
[0.99767269 0.00232731]]
"""
Bream과 Smelt중 어떤 것이 양성클래스일까?
사이킷런은 타깃값을 알파벳순으로 정렬하여 사용한다.
classes_ 속성에서확인
두번째 클래스인 빙어가 양성이다. 나머지는 모두 도미다.
print(lr.classes_) #['Bream' 'Smelt']
로지스틱 회귀가 학습한 계수를 확인 (메서드로)
print(lr.coef_, lr.intercept_) #[[-0.4037798 -0.57620209 -0.66280298 -1.01290277 -0.73168947]] [-2.16155132]
처음 5개 샘플의 z값 출력
decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions)
#[-6.02927744 3.57123907 -5.26568906 -4.24321775 -6.0607117 ]
이 z 값을 시그모이드 함수에 통과 시켜 확률얻기
from scipy.special import expit
print(expit(decisions))
#[0.00240145 0.97264817 0.00513928 0.01415798 0.00232731]
#로지스틱 회귀로 다중 분류 수행
LogisticRegression 클래스로 다중 분류 모델 훈련
점수도 높고 과대적합, 과소적합도 아니다.
lr = LogisticRegression(C=20, max_iter=1000)
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target)) #7개의 생선 데이터 모두 들어있음
print(lr.score(test_scaled, test_target))
"""
0.9327731092436975
0.925
"""
처음 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]]
"""
다중분류일 경우 선형 방정식은 무슨 모습일까
cofe_ 열
intercept_ 행
z를 7개나 계산한다
크기 출력
print(lr.coef_.shape, lr.intercept_.shape)
#(7, 5) (7,)
다중 분류는 소프트맥스 함수를 사용한다 (시그모이드 말고)
7개의 z값 확률로 반환
decision_function() 메세드로 테스트 세트의 처음 5개에 대한 z값 구한다
decision = lr.decision_function(test_scaled[:5])
print(np.round(decision, decimals=2))
소프트맥스 함수로 확률 구하기
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]]
"""
<결론>
그럴싸한 확률을 출력하고 싶다.
로지스틱 회귀를 사용하자,
선형회귀처럼 선형방정식을 사용한다, 하지만 계산된 값을 그대로 출력하는 것이 아니라 0~1로 압축한다.
0~100% 사이의 확률로 이해하자
로지스틱회귀는 이진분류에서 하나의 선형방정식을 훈련한다,.
이 방정식의 출력값을 시그모이드 함수에 통과시켜 0~1사이 값을 만든다.
이것이 양성클래스에 대한 확률이다.
음성 클래스의 확률 : 1- 양성클래스
다중분류의 경우 클래스 개수만큼 방정식을 훈련한다.
방정식 출력값을 소프트맥스 함수를 통과시켜 전체 클래스에 대한 합이 항상 1이 되도록 만든다.
'PYTHON > 데이터분석' 카테고리의 다른 글
5-1 결정 트리 (0) | 2023.09.15 |
---|---|
4-2 확률적 경사 하강법 (0) | 2023.09.15 |
3-3 특성 공학과 규제 (0) | 2023.09.15 |
3-2 선형회귀 (0) | 2023.09.15 |
3-1 KNN 회귀 (0) | 2023.09.15 |