본문 바로가기

AI: ML,DL

[인공지능] 로지스틱 회귀

개요: 럭키백의 확률

럭키백을 구입하면 랜덤한 확률로 다양한 생선 중 하나가 등장한다. 소비자는 이를 구입한 뒤 럭키백을 개봉해야 어떤 생선이 들어있는지 확인할 수 있다. 그리고, 판매자는 생선 별로 럭키백에 존재할 확률을 고지해준다. 이 확률을 알기 위해서는 어떤 모델을 활용해야 할까? k-최근접 이웃을 활용하여 럭키백의 생선 근처에 생선 별로 비율을 확인하면 될 듯 싶다.

 

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

fish_csv

위 데이터프레임에서 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 사이의 값으로 바꾼다.

어떤 변수 x를 대입하더라고 0~1 사이의 y로 치환

 

이제 로지스틱 회귀를 이용하여 이진 분류를 수행해보자. 로지스틱 회귀의 이진 분류의 경우 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 사이의 확률로 치환하기 위해 각 방정식의 출력값을 소프트맥스 함수에 통과시킨다.

 

 

참고

혼자 공부하는 머신러닝+딥러닝(박해선 저)