본문 바로가기
딥러닝 & 머신러닝/혼자 공부하는 머신러닝 딥러닝

4-1. 로지스틱 회귀

by sim0609 2023. 2. 14.

로지스틱 회귀

이번에는 럭키백에 담긴 생선의 확률을 알려주는 분류 모델을 만들고자 한다. 여기서 분류 모델은 예측뿐만 아니라 예측의 근거가 되는 확률을 출력할 수 있다. 

 

분류 모델로 k-최근접 이웃 모델을 이용할 수 있지만, 이웃한 샘플의 클래스 비율이기 때문에 정해진 비율로만 출력한다는 단점이 있다.

 

그래서 대표적인 분류 알고리즘인 로지스틱 회귀를 사용하고자 한다. (로지스틱 회귀는 회귀 모델이 아니라 분류 모델임에 유의하자) 로지스틱 회귀는 선형 방정식을 이용하고 0~1 사이의 값을 도출하기 때문에 0~100% 사이의 확률로 이해할 수 있다. 

 

로지스틱 회귀는 이진 분류일 경우, 하나의 선형 방정식을 이용함 → 시그모이드 함수 이용

 

로지스틱 회귀는 다중 분류일 경우, 클래스 개수만큼 방정식을 훈련시키고 각 방정식의 출력값을 소프트맥스 함수를 통과시켜 전체 클래스에 대해 합이 항상 1이 되도록 함  →  소프트맥스 함수 이용

 

소스 코드

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
import numpy as np
from scipy.special import softmax
import matplotlib.pyplot as plt
from scipy.special import expit
from sklearn.linear_model import LogisticRegression

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

# 어떤  종류의 생선이 있는지 species 열에서 고유한 값 추출
print(pd.unique(fish['Species']))

# fish['Species'] -> label(fish_target), fish[['Weight', 'Length', 'Diagonal', 'Height', 'Width']]-> input data(fish_input)
fish_input = fish[['Weight', 'Length', 'Diagonal', 'Height', 'Width']].to_numpy()
fish_target = fish['Species'].to_numpy()

# 5개의 특성과 타겟이 잘 저장됨
print(fish_input[:5])
print(fish_target[:5])

# 훈련 세트와 테스트 세트로 나누기
train_input, test_input, train_target, test_target = train_test_split(fish_input, fish_target, random_state = 42)

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

# k-최근접 이웃 회귀 분류기로 테스트 세트에 들어있는 확률 예측
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))

# 다중분류로 문자열로 된 타깃값을 그대로 이용 가능
# pd.unique(fish['Species'])로 출력한 생선 순서와 kn.classes_에서의 생선 순서가 다름(사이킷런에서는 알파벳 순으로 순서를 매기기 때문)
print(kn.classes_)

# 테스트 세트에 있는 처음 5개 샘플 타깃값 예측 
print(kn.predict(test_scaled[:5]))

# 테스트 세트에 있는 처음 5개 샘플에 대한 확률(decimals = 4: 소수 네 번째 자리까지 표기)
proba = kn.predict_proba(test_scaled[:5])
print(np.round(proba, decimals = 4))

# 네 번째 샘플로 모델이 계산한 확률이 가장 가까운 이웃의 비율이 맞는지 확인하기
distances, indexes = kn.kneighbors(test_scaled[3:4])
print(train_target[indexes])

# 위의 방식은 최근접 이웃으로 3개의 샘플들만 이용하기 때문에 0, 1/3, 2/3, 1이 전부라 다른 방식을 사용해야 함
# 로지스틱 회귀(시그모이드 함수)라는 분류 모델을 이용하자 -> 항상 0~1 사이의 값이 나오기 때문에 0~100%의 확률로 표현 가능
# 로지스틱 회귀 그래프
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() 

# 로지스틱 회귀로 이진분류 수행하기
# 불리언 인덱싱 예시
char_arr = np.array(['A', 'B', 'C', 'D', 'E'])
print(char_arr[[True, False, True, False, False]])

# 훈련 세트에서 도미와 빙어 행만 골라내기 
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] 

# 도미와 빙어 데이터만 추출해 로지스틱 회귀 모델 학습시키기
lr = LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)

# train_bream_smelt에 있는 처음 5개 샘플 예측해보기
print(lr.predict(train_bream_smelt[:5]))

# train_bream_smelt에 있는 처음 5개 샘플 예측 확률 출력하기
# 첫 번째 열이 음성 클래스에 대한 확률이고, 두 번째 클래스가 양성 클래스에 대한 확률(알파벳 순서)
print(lr.predict_proba(train_bream_smelt[:5]))

# 빙어가 양성 클래스
print(lr.classes_)

# 로지스틱 회귀가 학습한 계수
# z = -0.404 * (Weight) - 0.576 * (Length) - 0.663 * (Diagonal) -1.013 * (Height) - 0.732 * (Width) - 2.161
print(lr.coef_, lr.intercept_)

# 로지스틱 회귀가 학습한 방정식인 z값을 출력해보기
decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions)

# z값을 시그모이드 함수에 통과시켜 확률 얻기
print(expit(decisions))

# 로지스틱 회귀 다중 분류 모델 훈련
# 규제 제어 변수 C 존재  
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))

# 테스트 세트의 처음 5개 샘플 예측 출력
print(lr.predict(test_scaled[:5]))

# 테스트 세트의 처음 5개 샘플 예측 확률 출력
test_proba = lr.predict_proba(test_scaled[:5])
print(np.round(test_proba, decimals = 3))

# 클래스 정보 확인
print(lr.classes_)

# 다중 분류의 계수 확인 -> 다중 분류는 클래스마다 z값을 하나씩 계산(즉, 선형 방정식이 7개임) 
print(lr.coef_.shape, lr.intercept_.shape) 

# 다중 분류는 소프트맥스를 이용해 7개의 z값을 확률로 변환
# 시그모이드: 하나의 z값을 0~1로 압축, 소프트맥스: 여러개의 z값을 0~1 사이로 압축하고 전체 합이 1이 되도록 함
decision = lr.decision_function(test_scaled[:])
print(np.round(decision, decimals = 2))

# axis = 1: 각 행에 대해 softmax() 계산
proba = softmax(decision, axis = 1)
print(np.round(proba, decimals = 3))

'딥러닝 & 머신러닝 > 혼자 공부하는 머신러닝 딥러닝' 카테고리의 다른 글

5-1. 결정 트리  (0) 2023.02.15
4-2. 확률적 경사 하강법  (0) 2023.02.14
3-3. 특성 공학과 규제  (0) 2023.02.13
3-2. 선형 회귀  (0) 2023.02.13
3-1. k-최근접 이웃 회귀  (0) 2023.02.12