--13.LogisticRegression.ipynb--
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.Species.unique()
fish_input = fish_df[['Weight', 'Length', 'Diagonal', 'Height', 'Width']].to_numpy()
fish_input[:5]
fish_target = fish_df['Species'].to_numpy()
fish_target
fish_input.shape, fish_target.shape
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)
train_input.shape, test_input.shape
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input) # 반드시 train 세트의 통계값으로 test 세트를 변환해야한다.
"""
사이킷런의 KNeighborsClassifier 클래스 객체를 만들고 train 세트로 모델을 훈련한 다음
train 세트와 test 세트의 점수를 확인해 보겠습니다.
"""
None
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier(n_neighbors=3) # 이웃 개수
kn.fit(train_scaled, train_target)
kn.score(train_scaled, train_target)
kn.score(test_scaled, test_target)
target 데이터에 2개 이상의 클래스가 포함된 분류 문제
※ vs. '이진분류 (binary classification)'
"""
기본적으로 다중분류 문제도 이진분류때와 '모델을 만들고' '훈련하는 방식' 은 동일합니다.
이진분류때에는 각 클래스를 1, 0 으로 지정하여 target 데이터를 만들었었습니다.
다중분류에서도 target 값을 숫자로 바꾸어 입력할수 있지만,
★사이킷런에서는 편리하게도 '문자열로 된 target 값'을 그대로 사용할수 있답니다.
이때 주의할 점 하나!
target 값을 문자열 그대로 사이킷런 모델에 전달하면 순서가 자동으로 알파벳순으로 매겨짐.
↓ 아래와 같이 확인해볼수 있다
"""
None
pd.unique(fish_df["Species"])
kn.classes_ # KNN에서 정렬된 target 값 확인
kn.predict(test_scaled[:5])
test_target[:5]
proba = kn.predict_proba(test_scaled[:5])
proba
proba.shape
print(kn.classes_)
print(np.round(proba, decimals = 4)) # 소수점 4자리 밑에서 반올림
"""
[0. 0. 0.6667 0. 0.3333 0. 0. ] <-- 네번째 샘플에 대한 확률
"""
None
test_scaled[3:4] # 네번째 샘플
distance, indexes = kn.kneighbors(test_scaled[3:4])
train_target[indexes]
"""
[['Roach' 'Perch' 'Perch']]
↑ 이 샘플의 이웃은 다섯번째 클래스인 'Roach' 가 1개. 세번째 클래스인 'Perch' 가 2개
따라서!
다섯번째 클래스일 확률은 1/3 = 0.3333
세번째 클래스일 확률은 2/3 = 0.6667
따라서 앞서 출력했던 아래 결과와 같다
[0. 0. 0.6667 0. 0.3333 0. 0. ]
"""
None
"""
딱 3개의 최근접 이웃?
0/3, 1/3, 2/3, 3/3 <-- 가능한 확률은 딱 4가지 밖에 없다??
이건 굉장히 불안정한것임.
KNN이 다중분류에서의 약점!!
"""
None
"""
z = a x (Weight) + b x (Length) + c x (Diagonal) + d x (Height) + e x (Width) + f
a, b, c, d, e는 가중치 혹은 계수
z 는 어떤 값도 가능. 하지만! 확률이 되려면 0.0 ~ 1.0(0% ~ 100%) 사이 값이 되어주어야 한다.
z 가 아주 큰 음수일때는 0 이 되고 (매핑?), 아주 큰 양수일때는 1 로 바꾸어 주는 방법이 필요하다
=> 바로 시그모이드 함수 (sigmoid function) 또는 로지스틱 함수 (logistic function) 을 사용하면 가능하다!
"""
None
선형방정식의 출력 z 의 음수를 사용해 자연상수 e 를 거듭제곱 하고 1을 더한 값의 역수
바로 위와 같은 그래프를 만들기 위한 함수다!
z 가 무한하게 큰 음수일 경우 이 함수는 0에 가까워지고
z 가 무한하게 큰 양수가 될때 이 함수는 1에 가까워진다
z 가 0 이 될때는 0.5 가 된다.
z 가 어떤 값이 되더라도 Φ(phi) 는 절대로 0 ~ 1 사이의 범위를 벗어날수 없습니다.
z = np.arange(-5, 5, 0.1)
phi = 1 / (1 + np.exp(-z))
plt.plot(z, phi)
plt.xlabel('z')
plt.ylabel('phi')
plt.show()
bream_smelt_indexes = (train_target == 'Bream') | (train_target == 'Smelt')
bream_smelt_indexes
np.sum(bream_smelt_indexes)
train_bream_smelt = train_scaled[bream_smelt_indexes]
train_bream_smelt.shape
target_bream_smelt = train_target[bream_smelt_indexes]
target_bream_smelt.shape
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)
lr.predict(train_bream_smelt[:5])
target_bream_smelt[:5]
print(lr.classes_)
print(lr.predict_proba(train_bream_smelt[:5]))
print(lr.coef, lr.intercept)
"""
로지스틱회귀가 학습한 방정식은.
z = -0.404 x (Weight) - 0.576 x (Length)
- 0.663 x (Diagonal) - 1.013 x (Height) - 0.732 x (Width) - 2.161
이 모델로 z 값을 계산해보자! --> decision_function()
"""
None
decisions = lr.decision_function(train_bream_smelt[:5])
decisions
"""
위 z 값을 시그모이드 함수에 통과시키면 '확률'을 얻을 수 있다.
scipy 라이브러리에 있는 expit() 시그모이드 함수 사용
"""
None
from scipy.special import expit
expit(decisions)
"""
↑ 출력된 값을 보면 아까의 predict_proba() 출력의 두번째 열의 값과 동일하다! (왜지?)
즉, decision_function() 메소드는 양성 클래스에 대한 z 값을 리턴하는 겁니다
[[0.99759855 0.00240145] <- 도미의 확률이 높다.
[0.02735183 0.97264817] <- 두번째 샘플만 빙어의 확률이 높다.
[0.99486072 0.00513928][0.98584202 0.01415798]
[0.99767269 0.00232731]]
"""
None
max_lter, L2 규제, C
"""
LogisticReression 은 기본적으로 '반복' 알고리즘 사용.
max_iter 매개변수에서 '최대 반복횟수' 지정. (default = 100)
LogisticRegression 은 기본적으로 릿지회귀와 같이 계수의 제곱을 규제합니다.
이런 규제를 'L2 규제' 라고 함
규제를 제어하는 매개변수는 C. (릿지회귀 에서는 alpha였다)
릿지회귀의 alpha와는 달리 C는 작을수록 규제의 강도가 커진다. C의 default는 1.
"""
None
lr = LogisticRegression(C=20, max_iter = 1000)
lr.fit(train_scaled, train_target) # 7가지 생선 데이터 사용
lr.score(train_scaled, train_target)
lr.score(test_scaled, test_target)
lr.predict(test_scaled[:5])
test_target[:5]
proba = lr.predict_proba(test_scaled[:5])
np.round(proba, decimals=3) # 소숫점 네번째 자리에서 반올림
"""
5개 샘플 x 7개 생선에 대한 예측확률
[0. , 0.014, 0.841, 0. , 0.136, 0.007, 0.003]
↑ 세번째 생선일 확률이 가장 높다!
"""
None
lr.classes_
lr.coef_
lr.intercept_
"""
그럼 확률은 어떻게 계산한 걸까요?
이진 분류에서는 시그모이드 함수를 사용해 z 를 0 과 1 사이의 값으로 변환했습니다.
다중 분류는 이와 달리 소프트맥스 (softmax) 함수를 사용하여 7개의 z 값을 확률로 변환합니다
소프트맥스 함수란?
시그모이드 함수는 하나의 선형방정식의 출력값을 0 ~ 1 사이로 압축합니다.
이와 달리 소프트맥스 함수는 여러개의 선형방정식의 출력값을 0 ~ 1 사이로 압축하고,
전체 합이 1이 되도록 만듭니다. 이를 위해 지수함수를 사용하기 때문에
'정규화된 지수함수' 라고도 부릅니다.
"""
None
"""
소프트맥스의 계산 방식도 어렵지 않습니다
7개의 z 값의 이름을 z1 ~ z7 이라고 하자.
z1 ~ z7 까지 값을 사용해 지수함수 e^z1 ~ e^z7 을 계산해 모두 더합니다
이를 e_sum 이라 하겠습니다
e_sum = e^z1 + e^z2 + e^z3 + e^z4 + e^z5 + e^z6 + e^z7
그 다음 e^z1 ~ e^z7 을 각각 e_sum 으로 나누어 주면 됩니다
e^z1 e^z2 e^z7
s1 = ────── , s2 = ────── , ... , s7 = ──────
e_sum e_sum e_sum
s1 에서 s7 까지 모두 더하면 1이 됩니다. 7개 생선에 대한 확률의 합은 1이 되는 겁니다. =
"""
None
decision = lr.decision_function(test_scaled[:5])
np.round(decision, decimals = 2)
from scipy.special import softmax # scipy에서 소프트맥스 함수 제공
proba = softmax(decision, axis = 1)
np.round(proba, decimals = 3)
"""
↑ 이 출력결과와 앞서 구한 proba 배열과 비교해보자 => 정확히 일치!
"""
None