5 minute read

로지스틱 회귀

랜덤하게 담긴 생선의 확률을 알아보려고 합니다.
우선 앞서 배웠던 k-최근접 이웃 분류기를 사용하여 구한 이웃 클래스를 토대로 타깃 생선의 확률을 계산해보겠습니다.
먼저 데이터를 준비합니다.

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

HG4-1-1

그리고 target 데이터가 될 Species 열에 어떤 종류가 있는지 판다스의 unique 함수를 사용해서 확인합니다.

print(pd.unique(fish['Species']))

[‘Bream’ ‘Roach’ ‘Whitefish’ ‘Parkki’ ‘Perch’ ‘Pike’ ‘Smelt’]

Species 에 들어있는 종들을 확인했으니 fish 데이터에서 Species 열을 제외하고 나머지 5개의 열을 입력 데이터로 선택합니다.

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

입력 데이터가 잘 생성된 것을 확인했습니다.
동일한 방식으로 target 데이터도 만듭니다.

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)

train 세트와 test를 준비했으니 데이터를 표준화 전처리를 해줍니다.
사이킷런의 StandardScaler을 이용합니다.

#훈련 테스트 세트 표준화 전처리
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)

사이킷런의 KNeighborsClassifier 클래스 객체를 만들고 훈련세트와 테스트세트의 각 점수를 확인해보겠습니다.
최근접 이웃의 개수인 k는 3으로 지정합니다.

#최근접 이웃 분류기의 확률 예측
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.8739495798319328
0.75

print(kn.classes_)

[‘Bream’ ‘Parkki’ ‘Perch’ ‘Pike’ ‘Roach’ ‘Smelt’ ‘Whitefish’]

알파벳 순서로 매겨진 것을 확인했습니다.
이제 predict 메서드를 사용하여 test set에 있는 처음 5개의 샘플의 타깃값을 예측합니다.

print(kn.predict(test_scaled[:5]))

[‘Parkki’ ‘Perch’ ‘Roach’ ‘Perch’ ‘Perch’]

predict 예측 값이 어떻게 나왔는지 predict_proba() 메서드를 사용하면 클래스별 확률 값을 확인할 수 있습니다.

import numpy as np
proba = kn.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=4))  #소숫점 넷째자리까지 표기(다섯번째에서 반올림)

[[0. 0.6667 0.3333 0. 0. 0. 0. ]
[0. 0. 1. 0. 0. 0. 0. ]
[0. 0. 0.3333 0. 0.6667 0. 0. ]
[0. 0. 1. 0. 0. 0. 0. ]
[0. 0. 0.3333 0. 0.3333 0.3333 0. ]]

첫번째 열이 Bream, 두번째 열이 Parkki, 세번째 열이 Perch, … 에 대한 확률을 나타내고 있습니다.
계산한 비율이 올바른지 확인하기 위해 첫 번째 샘플의 최근접 이웃들을 출력해보겠습니다.

distances, indexes = kn.kneighbors(test_scaled[0:1])
print(train_target[indexes])

[[‘Parkki’ ‘Parkki’ ‘Perch’]]

첫 번째 샘플의 이웃은 Perch가 1개, Parkki가 2개임을 확인합니다.
여기까지 k-최근접 이웃 분류기를 사용한 확률 예측을 해보았습니다.

이제 본격적으로 로지스틱 회귀를 이용한 예측을 해보도록 하겠습니다.

로지스틱 회귀는 선형회귀와 동일하게 선형 방정식을 학습합니다.
HG4-1-2

여기서 각 변수 앞에 곱해진 값들은 가중치나 계수의 역할을 합니다.
z는 어떤 값도 가능하지만, 확률이 되기 위해서는 0과 1 사이의 값을 가져야 합니다.
z가 큰 음수일때 0이 되고 큰 양수일 때 1이 되도록 하려면 시그모이드 함수를 사용하면 됩니다.

HG4-1-32
HG4-1-3

시그모이드 함수는 다음과 같은 형태를 띕니다.
넘파이를 사용해서 그래프를 그려보겠습니다.

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.show()

HG4-1-4

이제 로지스틱 회귀를 사용하여 이진분류를 해보겠습니다.
train 데이터에서 불리언 인덱싱을 사용하여 도미와 빙어만 따로 출력해줍니다.

#도미(Bream)와 빙어(Smelt)만 출력하기
bream_smelt_indexes = (train_target == 'Bream')|(train_target == 'Smelt')
print(bream_smelt_indexes)

[False True False False False False True False False True False True True False False False True True False True False False True False False False True False False False False False False False True False True True False True False False True False False True False True False False True True False False False True True False False False True True False False False False False False False True False True False False True False False False False True False False False False True False True False False False False False False False True True False False False False True True False False False False False False False False False False True True True False False True False]

train_bream_smelt = train_scaled[bream_smelt_indexes]
target_bream_smelt = train_target[bream_smelt_indexes]

이 데이터를 이용하여 로지스틱 회귀 모델을 훈련하고 그 모델을 사용하여 train_bream_smelt에 있는 처음 5개의 샘플을 출력해보겠습니다.

from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)

print(lr.predict(train_bream_smelt[:5]))

[‘Smelt’ ‘Smelt’ ‘Smelt’ ‘Smelt’ ‘Bream’]

다섯 번째를 도미, 나머지를 빙어로 예측했습니다.
5개 샘플의 확률을 확인합니다.

print(lr.predict_proba(train_bream_smelt[:5]))

[[0.03391154 0.96608846]
[0.03603855 0.96396145]
[0.03582089 0.96417911]
[0.02900509 0.97099491]
[0.99410165 0.00589835]]

두 개의 열 중에 어떤 것이 도미이고 빙어인지 헷갈린다면 classes_ 를 사용하여 알 수 있습니다.

print(lr.classes_)

[‘Bream’ ‘Smelt’]

이제 로지스틱 회귀가 학습한 계수를 확인합니다.

print(lr.coef_, lr.intercept_)

[[-0.42646881 -0.60256452 -0.68252074 -0.99456193 -0.78263044]] [-2.28791769]

이를 통해 로지스틱 회귀 모델이 학습한 방정식을 다음과 같이 도출할 수 있습니다. HG4-1-5

이제 이 방정식을 이용하여 z 값을 계산해보겠습니다.

decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions)

[ 3.34950001 3.28646197 3.29274597 3.51084984 -5.12716733]

이 z값을 시그모이드 함수에 통과하면 확률을 얻을 수 있습니다.
scipy에 시그모이드 함수를 제공하는 expit()를 사용해서 확률을 구합니다.

from scipy.special import expit
print(expit(decisions))

[0.96608846 0.96396145 0.96417911 0.97099491 0.00589835]

이제 다중 분류를 수행해보겠습니다.

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.9411764705882353
0.85

test 세트의 처음 5개 샘플에 대한 예측을 출력해봅니다.
그리고 그 예측에 대한 확률을 출력해보겠습니다.

#test set의 처음 5개 샘플에 대한 예측
print(lr.predict(test_scaled[:5]))

[‘Parkki’ ‘Perch’ ‘Roach’ ‘Perch’ ‘Perch’]

#test set의 처음 5개에 대한 예측 확률
proba = lr.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=3)) #소수점 세번째자리까지 표기

[[0.01 0.73 0.006 0. 0.223 0. 0.03 ]
[0.01 0. 0.898 0.002 0. 0. 0.089]
[0.001 0.018 0.28 0.002 0.661 0. 0.038]
[0.002 0. 0.953 0.001 0. 0. 0.044]
[0. 0.012 0.816 0. 0.119 0.051 0.002]]

5개 샘플과 7개의 클래스가 있기 때문에 5개의 행과 7개의 열로 출력이 되었습니다.
각 열이 의미하는 것이 어떤 생선에 대한 확률인지 확인합니다.

#classes_속성으로 클래스 정보 확인
print(lr.classes_)

[‘Bream’ ‘Parkki’ ‘Perch’ ‘Pike’ ‘Roach’ ‘Smelt’ ‘Whitefish’]

print(lr.coef_.shape, lr.intercept_.shape)

(7, 5) (7,)

coef_ 배열이 7행에 5열의 형태를 띄고 있고 intercept_도 7개가 있는 것을 확인할 수 있습니다.
7개의 클래스가 있기 때문에 클래스마다 z값을 계산하여 총 7개의 z값이 나온다는 말과 같습니다.

이와 같이 7개의 z값들을 확률로 바꾸기 위해 소프트맥스 함수를 사용합니다.
HG4-1-6

7개의 z값을 소프트맥스 함수를 이용하여 확률로 바꾸면 0과 1사이의 값을 가진 확률이 출력됩니다.
그럼 7개의 z값을 구한 후에 소프트맥스 함수를 사용하여 확률로 바꾸어보겠습니다.

#z1부터 z7까지의 값 구하고 확률로 바꾸기
decision = lr.decision_function(test_scaled[:5])
print(np.round(decision, decimals=2)) #소수점 둘째자리까지 표기

[[ -0.17 4.13 -0.67 -3.44 2.95 -3.75 0.96]
[ 4.31 -5.68 8.79 2.78 0.69 -17.37 6.48]
[ -2.69 0.28 3.04 -2.01 3.9 -3.56 1.05]
[ 3.78 -6.28 9.85 2.82 0.69 -17.63 6.78]
[ -8.63 1.35 5.59 -4.1 3.66 2.82 -0.7 ]]

from scipy.special import softmax
proba = softmax(decision, axis = 1)
print(np.round(proba, decimals = 3)) #소수점 셋째자리까지 표시

[[0.01 0.73 0.006 0. 0.223 0. 0.03 ]
[0.01 0. 0.898 0.002 0. 0. 0.089]
[0.001 0.018 0.28 0.002 0.661 0. 0.038]
[0.002 0. 0.953 0.001 0. 0. 0.044]
[0. 0.012 0.816 0. 0.119 0.051 0.002]]

이렇게 로지스틱 회귀를 사용하여 7개의 클래스에 대한 확률을 예측하는 모델을 만들었습니다.
다음에는 확률적 경사 하강법에 대해 배워보도록 하겠습니다.

Comments