머신러닝/혼공머신

혼공머신 | Chap 04-1. 로지스틱 회귀

하카데미 2022. 9. 1. 09:28

1. 럭키백의 확률

럭키백에 들어갈 수 있는 생선은 7.

럭키백에 들어간 생선의 크기, 무게 등이 주어졌을 때,

7개 생선에 대한 확률을 출력해줘야함.

 

“k-최근접 이웃은 주변 이웃을 찾아주니까 이웃의 클래스 비율을 확률이라고 출력하면 되지 않을까?”

 

 

데이터 준비하기

import pandas as pd

fish = pd.read_csv('https://bit.ly/fish_csv')
fish.head()

 

판다스의 unique()

열에서 고유한 값을 추출.

예시) Species 열에서 어떤 종류의 생선이 있는지 확인할 수 있음.

 

print(pd.unique(fish['Species']))
# 출력 결과 ['Bream' 'Roach' 'Whitefish' 'Parkki' 'Perch' 'Pike' 'Smelt']

 

Species 열을 타깃으로 만들고 나머지 5개 열은 입력 데이터로 사용.

# 입력 데이터
# 책 예제
fish_input = fish[['Weight', 'Length', 'Diagonal', 'Height', 'Width']].to_numpy()

# 예제 변형
# fish_input = fish[fish.columns[1:]].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()

# 예제 변형
# fish_target = fish[fish.columns[0]].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

 

다중 분류 multi-class classification

타깃 데이터에 2개 이상의 클래스가 포함된 문제

 

다중 분류에서 타깃값을 숫자로 바꿔 입력할 수 있지만,

사이킷런에서는 문자열로 된 타깃값을 그대로 사용할 수 있음.

타깃값을 그대로 사이킷런 모델에 전달하면 순서가 자동으로 알파벳 순으로 매겨짐.

pd.unique(fish[‘Species’])의 출력 순서와 다름.

 

classes_ 속성 

KNeighborsClassifier에서 정렬된 타깃값이 저장되어 있음.

 

print(kn.classes_)
# 출력 결과 ['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']

 

사이킷런 predict_proba()

클래스별 확률값을 반환.

출력 순서는 classes_ 속성과 같음.

 

넘파이 round()

기본으로 소수점 첫째 자리에서 반올림.

decimals 매개변수로 유지할 소수점 아래 자릿수를 지정.

 

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.    ]]
'''

 

계산한 확률이 가장 가까운 이웃의 비율이 맞는지 확인.

distances, indexes = kn.kneighbors(test_scaled[3:4])
print(train_target[indexes])
# 출력 결과 [['Roach' 'Perch' 'Perch']]

 

이 샘플의 이웃은

5번째 클래스인 ‘Roach’1개 → 확률 1/3 = 0.3333

3번째 클래스인 ‘Perch’2개 → 확률 2/3 = 0.6667

 

2. 로지스틱 회귀

로지스틱 회귀 logistic regression

이름은 회귀이지만, 분류 모델임.

선형 회귀와 동일하게 선형 방정식을 학습.

 

 

a, b, c, d, e는 가중치 혹은 계수.

z는 어떤 값도 가능.

하지만 확률이 되려면 0~1 사이 값이어야함.

z가 아주 큰 음수일 때 0이 되고,

z가 아주 큰 양수일 때 1이 되도록 하려면,

시그모이드 함수를 사용한다.

 

시그모이드 함수 sigmoid function

(또는 로지스틱 함수 logistic function)

선형 방정식의 출력 z의 음수를 사용해 자연 상수 e를 거듭제곱하고 1을 더한 값의 역수를 취함.

 

z가 무한하게 큰 음수일 경우

함수는 0에 가까워지고

z가 무한하게 큰 양수일 경우

1에 가까워짐.

z0일 경우 0.5가 됨.

 

시그모이드 그래프 그리기

import matplotlib.pyplot as plt

z = np.arange(-5, 5, 0.1)
phi = 1 / (1 + np.exp(-z))
plt.plot(z, phi)
plt.show()

 

 

불리언 인덱싱 boolean indexing

넘파이 배열은 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')
train_bream_smelt = train_scaled[bream_smelt_indexes]
target_bream_smelt = train_target[bream_smelt_indexes]

 

도미와 빙어일 경우 True,

그 외는 모두 False 값이 들어있음

 

로지스틱 회귀 모델 훈련

from sklearn.linear_model import LogisticRegression

lr = LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)
print(lr.predict(train_bream_smelt[:5]))
# 출력 결과 ['Bream' 'Smelt' 'Bream' 'Bream' 'Bream']


train_bream_smelt에서 처음 5개 샘플의 예측 확률

print(lr.predict_proba(train_bream_smelt[:5]))
'''
출력 결과
[[0.99759855 0.00240145]
 [0.02735183 0.97264817]
 [0.99486072 0.00513928]
 [0.98584202 0.01415798]
 [0.99767269 0.00232731]]
 '''



첫 번째 열이 음성 클래스(0)에 대한 확률

두 번째 열이 양성 클래스(1)에 대한 확률

 

BreamSmelt 중 어떤 것이 양성일까?

print(lr.classes_)
# 빙어(smelt)가 양성 클래스
# 출력 결과 ['Bream' 'Smelt']

 

로지스틱 회귀가 학습한 계수를 확인

print(lr.coef_, lr.intercept_)
# 출력 결과 [[-0.4037798  -0.57620209 -0.66280298 -1.01290277 -0.73168947]] [-2.16155132]

 

이 로지스틱 회귀 모델이 학습한 방정식

 

사이킷런 decision_function()

LogisticRegression 모델로 z값 계산

 

LogisticRegression 모델로 z값 계산

decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions)
# 출력 결과 [-6.02927744  3.57123907 -5.26568906 -4.24321775 -6.0607117 ]

 

파이썬의 사이파이 expit()

z값을 시그모이드 함수에 통화시키면 확률을 얻을 수 있다.

 

from scipy.special import expit

print(expit(decisions))
# 출력 결과 [0.00240145 0.97264817 0.00513928 0.01415798 0.00232731]

 

predict_proba() 메서드 출력의 두 번째 열의 값과 동일

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

 

로지스틱 회귀로 다중 분류

LogisticRegression 클래스

기본적으로 반복적인 알고리즘을 사용

max_iter 매개변수에서 반복 횟수를 지정하며 기본값은 100

반복 횟수가 부족하다는 경고가 발생하면 늘려주면 됨.

계수의 제곱을 규제함(=L2 규제라고 부름)

 

C

LogisticRegression에서 규제를 제어하는 매개변수

작을 수록 규제가 커짐

기본값은 1

 

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

 

테스트 세트의 처음 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]]
 '''

 

첫 번째 샘플을 보면 세 번째 열의 확률이 가장 높음.

Perch일까?

 

클래스 정보 확인

print(lr.classes_)
# 출력 결과 ['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']

 

다중 분류일 경우 선형 방정식은 어떤 모습일까?

print(lr.coef_.shape, lr.intercept_.shape)
# 출력 결과 (7, 5) (7,)

 

다중 분류는 클래스마다 z값을 하나씩 계산함.

가장 높은 z값을 출력하는 클래스가 예측 클래스.

 

확률 계산은 어떻게 할까?

softmax 함수 사용.

 

소프트맥스 함수 softmax

여러 개의 선형 방정식의 출력값을 0~1 사이로 압축하고 전체 합이 1이 되도록 만듬.

정규화된 지수 함수라고도 부름.

 

z1~z7까지 값을 사용해 지수 함수를 계산해 모두 더함.

 

 

그 다음 지수 함수를 각각 구한 값(e_sum)으로 나누어 주면 됨.

 

테스트 세트의 처음 5개 샘플에 대한 z1~z7의 값 구하기.

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]]
 '''