카테고리 없음

[Machine Learning] chapter 4. 다양한 분류 알고리즘

선경이 2025. 4. 16. 01:12

1. 로지스틱 회귀

데이터 준비

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

 

위 데이터프레임에서 Species 열을 타깃으로 만들고 나머지 5개 열을 입력 데이터로 사용하겠다.

print(pd.unique(fish['Species']))
>>> ['Bream' 'Roach' 'Whitefish' 'Parkki' 'Perch' 'Pike' 'Smelt']

fish_input = fish[['Weight','Length','Diagonal','Width']].to_numpy()
print(fish_target[:5])
>>> [[242.      25.4     30.       4.02  ]
    [290.      26.3     31.2      4.3056]
    [340.      26.5     31.1      4.6961]
    [363.      29.      33.5      4.4555]
    [430.      29.      34.       5.134 ]]
    
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-최근접 이웃 분류기의 확률 예측

KNeighborsClassifier 클래스 객체를 만들고 훈련시킨다.

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.8067226890756303
>>> 0.725

 

지금 우리의 타깃 데이터에는 7개의 생선 종류가 들어가있다.

이렇게 타깃 데이터에 2개 이상의 클래스가 포함된 문제를 다중 분류라고 부른다.

 

앞에서 이진 분류를 할 때는 클래스를 1과 0으로 지정해서 타깃 데이터를 만들었다.

다중 분류에서도 숫자로 바꾸어 입력해도 되지만 사이킷런에는 문자열로 된 타깃값을 그대로 사용해도 된다.

 

주의할 점은 사이킷런 모델로 전달하면 순서가 기존과 다르게 알파벳 순으로 매겨진다.

print(kn.classes_)
>>> ['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']

 

predict() 메서드로 처음 5개 샘플의 타깃값을 예측해보겠다.

print(kn.predict(test_scaled[:5]))
>>> ['Perch' 'Smelt' 'Pike' 'Parkki' 'Perch']

 

이제 predict_proba() 메서드로 클래스별 확률값을 반환하겠다.

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.6667 0.3333 0.     0.     0.     0.    ]
     [0.     0.     1.     0.     0.     0.     0.    ]]

 

round() 함수는 기본적으로 소수점 첫째 자리에서 반올림을 하는데 decimals 매개변수로 소수점 아래 자릿수를 지정할 수 있다.

위 출력 결과는 앞서 보았던 classes_ 속성과 같다.

즉, 첫 번째 열이 'Bream'에 대한 확률이고 두 번째 열이 'Parkki'에 대한 확률이다.

 

이 모델이 계산한 확률이 가장 가까운 이웃의 비율이 맞는지 확인해보자.

distances, indexes = kn.neighbors(test_scaled[3:4])
print(train_scaled[indexes])
>>> [['Parkki' 'Parkki' 'Perch']]

 

로지스틱 회귀

로지스틱 회귀는 이름은 회귀이지만 분류 모델이다.

이 알고리즘은 선형회귀와 동일한 선형 방정식을 학습한다.

 

시그모이드 함수를 사용하면 z가 아주 큰 음수일 때는 0이 되고 아주 큰 양수일 때는 1이 되도록 바뀌고 0일 때는 0.5가 된다.

그럼 z는 어떤 값이 되더라도 0~1 사이의 범위를 벗어날 수 없기 때문에 확률로 계산할 수 있다.

 

로지스틱 회귀로 이진 분류 수행하기

 

char_arr = np.array(['A','B','C','D','E'])
print(char_arr[[True, False, True, False, False]])
>>> ['A','C']

 

위 배열에서 'A'와 'C'만 골라내려면 첫 번째, 세 번째 원소만 True이고 나머지 원소는 False인 배열을 전달하면 된다.

이와 같은 방식을 사용해서 훈련 세트에서 Bream와 Smelt 행을 골라내겠다.

bream_smelt_indexes = (train_target == 'Bream') or (train_target == 'Smelt')
train_bream_smelt = train_scaled[bream_smelt_indexes]
target_bream_smelt = train_target[bream_smelt_indexes]

 

bream_smelt_indexes 배열은 도미와 빙어일 경우 True이고 그 외는 모두 False 값이 들어가 있을 것이다.

따라서 이 배열을 이용해서 train_scaled와 train_target 배열에 불리언 인덱싱을 적용하면 도미와 빙어 데이터만 골라낼 수 있다.

 

from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)
print(lr.score(train_bream_smelt[:5]))
>>> ['Bream' 'Smelt' 'Bream' 'Bream' 'Bream']

 

두 번째 샘플을 제외하고 모두 도미로 예측했다.

 

print(lr.predict_proba(train_bream_smelt[:5])
>>> array([[0.99639444, 0.00360556],
       [0.03746765, 0.96253235],
       [0.9918687 , 0.0081313 ],
       [0.97629643, 0.02370357],
       [0.99635626, 0.00364374]])

 

첫 번째 열이 음성 클래스 (0)에 대한 확률이고 두 번재 열이 양성 클래스 (1)에 대한 확률이다.

bream과 smelt 중에 어떤 것이 양성 클래스인지 확인해보겠다.

print(lr.classes_)
>>> ['Bream', 'Smelt']

 

빙어가 양성 클래스인 것을 알 수 있다.

predict_proba() 메서드가 반환한 두 번째 샘플 값을 보면 양성 클래스인 빙어의 확률이 높으므로 빙어로 예측한다.

나머지는 모두 도미로 예측한다.

print(lr.coef_, lr.intercept_)
>>> [[-0.59865464 -0.85208293 -0.97968478 -1.0791584 ]] [-2.67789827]

 

이 로지스틱 회귀 모델이 학습한 방정식은 위와 같다.

그럼 LogisticRegression 모델로 z 값을 계산해보자.

 

# z값 계산
decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions)
>>> [-5.6216656   3.24608969 -4.80387025 -3.71814047 -5.61109532]

# 확률 계산
from scipy.special import expit
print(expit(dicisions)) # sigmoid
>>> [0.00360556 0.96253235 0.0081313  0.02370357 0.00364374]

 

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