ML

ML/ 로지스틱 회귀, 이진 분류 vs 다중 분류

석새우 2026. 1. 15. 01:26

로지스틱 회귀는 이름은 회귀이지만 분류 모델이다. 이번에 다뤄볼 자료는 여러 특성들을 활용해서 물고기의 종을 예측해보겠다.

Weight, Length, Diagonal, Weight, Width 를 활용해서 Species를 예측해보겠다.

import pandas as pd

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

fish_input = fish[['Weight', 'Length', 'Diagonal', 'Height', 'Width']]

fish_target= fish['Species']

fish_input은 괄호 두개, fish_target은 괄호 한개로 감쌌다.

fish_input 은 훈련 데이터를 위해서 2차원 배열로 놓은거고, fish_target은 타겟값들 정리를 위해 일차원으로 정의한다.

 

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)

로지스틱 함수는 시그모이드 함수를 사용한다.

시그모이드 함수란?

z값이 x축에 해당하는 값인데, z가 무한하게 큰 음수인 경우에는 값이 0에 가까워지고, z가 무한하게 큰 양수가 될 때는 1에 가까워진다. z가 어떤 값이 되더라도 0~1 사이의 범위를 벗어날 수 없다. 이를 0~100% 확률로 해석하자!

 

로지스틱 회귀로 도미와 빙어 이진 분류 수행하기

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)

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

도미와 빙어의 인덱스를 train_target에서 추출해준다. 그 후, 도미와 빙어의 인덱스만 사용하여 훈련 데이터와 타겟 데이터를 재정의 해준다.

 

예측결과는 다음과 같이 나온다.

['Bream' 'Smelt' 'Bream' 'Bream' 'Bream']

[[0.99760007 0.00239993]
 [0.02737325 0.97262675]
 [0.99486386 0.00513614]
 [0.98585047 0.01414953]
 [0.99767419 0.00232581]]

predict_proba() 메서드 출력 결과를 보면 샘플마다 2개의 확률이 출력됐다. 첫번째 열이 0에 대한 확률, 두번째 열이 1에 대한 확률이다.

이제

 

1. 로지스틱 회귀가 학습한 계수 확인, 2. 계수를 이용해서 샘플마다의 z값 구하기, 3. z값을 시그모이드 함수에 넣기 과정

 

을 통해 직접 확률을 구해보겠다.

 

print(lr.coef_, lr.intercept_)

decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions)
[[-0.40451732 -0.57582787 -0.66248158 -1.01329614 -0.73123131]] [-2.16172774]

[-6.02991358  3.57043428 -5.26630496 -4.24382314 -6.06135688]

decision_function() 메서드를 사용하면 양성 클래스들에 대한 z값들을 구할 수 있다. 첫번째 출력 결과물이 회귀모델이 학습한 방정식의 계수, 두번째 출력 결과물이 샘플 별 z값들이다.

 

from scipy.special import expit
print(expit(decisions))
[0.00239993 0.97262675 0.00513614 0.01414953 0.00232581]

싸이파이에 있는 expit() 메서드를 활용하면 시그모이드 함수를 편리하게 계산할 수 있다.

출력된 값을 보면 predict_proba() 메서드로 구한 출력값의 두번째 열 즉, 1(양성 클래스)의 값과 똑같다.

 

로지스틱 회귀로 다중 분류 수행하기

그럼 이제 7개의 생선을 분류해 보면서 이진 분류와의 차이점을 알아보겠다.

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

 

LogisticRegression은 기본적으로 릿지 회귀와 같이 계수의 제곱을 규제한다. 이런 규제를 L2 규제라고도 하는데, 릿지에서는 alpha 매개변수로 규제의 양을 조절했다. 이때 alpha가 커지면 규제도 강해졌다. LogisticRegression에서 규제를 제어하는 매개변수는 C이다. 하지만 alpha와는 반대로 작을수록 규제가 커진다. 이 모델에서는 규제를 완화하기 위해 C를20으로했다.

또한 LogisticRegression은 기본적으로 반복적인 알고리즘을 사용한다. max_iter은 반복횟수를 지정해주는데 기본값은 100이다.

 

출력된 점수들을 보면 잘 훈련이 된 것 같다.

 

import numpy as np

print(np.round(lr.predict_proba(test_scaled[:5]), decimals=3))
[[0.    0.014 0.842 0.    0.135 0.007 0.003]
 [0.    0.003 0.044 0.    0.007 0.946 0.   ]
 [0.    0.    0.034 0.934 0.015 0.016 0.   ]
 [0.011 0.034 0.305 0.006 0.567 0.    0.076]
 [0.    0.    0.904 0.002 0.089 0.002 0.001]]

훈련된 모델로 테스트 세트의 처음 5개에 대한 예측 확률을 출력해봤다. 이진분류와는 다르게 7종류의 물고기에 대한 확률이 구해진다.

또한 다중 분류는 클래스(특성)마다 z값을 하나씩 계산한다. 그리고 샘플에서 가장 높은 z값을 출력하는 클래스가 예측 클래스가 된다.

 

소프트맥스 함수

7개의 z값이 구해지면 z1~z7 까지 값을 사용해 e^z1~e^z7까지를 모두 더해준다. 이 값을 e_sum 이라고 하면

e^z1~e^z7 각각의 값을 e_sum으로 나눠주면 된다. 그 값들을 s1~s7이라고 하면 s값들을 모두 더하면 1이 될 것이다.

 

이진 분류에서처럼 decision_function() 함수로 z1~z7을 구하고 소프트맥스 함수를 사용해서 확률로 바꿔보자.

 

# 각 행 별로 z1~z7 까지의 값 확인
decision = lr.decision_function(test_scaled[:5])
print(np.round(decision, decimals=2))
[[ -6.51   1.04   5.17  -2.76   3.34   0.35  -0.63]
 [-10.88   1.94   4.78  -2.42   2.99   7.84  -4.25]
 [ -4.34  -6.24   3.17   6.48   2.36   2.43  -3.87]
 [ -0.69   0.45   2.64  -1.21   3.26  -5.7    1.26]
 [ -6.4   -1.99   5.82  -0.13   3.5   -0.09  -0.7 ]]

싸이파이는 소프트맥스 함수도 제공한다.

# 구한 z값들을 가지고 소프트맥스 함수에 적용시키기
from scipy.special import softmax
print(np.round(softmax(decision, axis=1), decimals=3))
[[0.    0.014 0.842 0.    0.135 0.007 0.003]
 [0.    0.003 0.044 0.    0.007 0.946 0.   ]
 [0.    0.    0.034 0.934 0.015 0.016 0.   ]
 [0.011 0.034 0.305 0.006 0.567 0.    0.076]
 [0.    0.    0.904 0.002 0.089 0.002 0.001]]

axis=1은 소프트맥스를 계산할 축을 설정하는데, 1은 각 행별로 즉, 샘플별로 소프트맥스를 계산한다.

이렇게 되면 앞에서 구한 proba 배열과 완전히 일치하는 것을 볼 수 있고, 샘플에서 특성 별 제일 높은 값을 가지고 있는놈으로 예측하면 된다.

 

핵심정리를 하자면,

 

이진 분류 vs 다중 분류

클래스 수 2 3 이상
활성화 함수 Sigmoid Softmax
coef_ (1, 특성 수) (클래스 수, 특성 수)
intercept_ 1개 클래스별 1개
decision_function z 값 1개 클래스별 z 값
predict_proba 음성, 양성 확률 클래스별 확률
최종 예측 임계값 argmax

여기서 클래스란? 물고기의 종류 7개를 말함. 타겟 데이터의 unique 값 개수