혼공 ML+DL

3주차 Chapter 04 (다양한 분류 알고리즘)

채영sw 2024. 2. 13. 04:41

 

Chapter 04

 

4-1. 로지스틱 회귀

  • 다중 분류(multi-class classification) : 타깃 데이터에 2개 이상의 클래스가 포함된 문제
  • 로지스틱 회귀(logistic regression) : 선형 방정식을 사용한 분류 알고리즘으로 선형 회귀와 달리 시그모이드 함수나 소프트맥스 함수를 사용하여 클래스 확률을 출력
  • 시그모이드 함수/로지스틱 함수(sigmoid function/logistic regression) : 선형 방정식의 출력을 0과 1 사이의 값으로 압축하여 이진 분류를 위해 사용. 이진 분류일 경우 시그모이드 함수의 출력이 0.5보다 크면 양성 클래스, 0.5보다 작으면 음성 클래스로 판단
  • 불리언 인덱싱(boolean indexing) : 넘파이 배열은 True, False 값을 전달하여 행을 선택할 수 있으며 이를 불리언 인덱싱이라고 함
  • 소프트맥스 함수(softmax function) : 여러 개의 선형 방정식의 출력값을 0~1 사이로 압축하고 전체 합이 1이 되도록 만들며 이를 위해 지수 함수를 사용하기 때문에 정규화된 지수 함수라고도 함
  • 데이터프레임(dataframe) : 판다스에서 제공하는 2차원 표 형식의 주요 데이터 구조

 

#데이터 준비하기
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')
fish.head()

# Species 열애서 고유한 값 추출(unique() 함수 사용)
print(pd.unique(fish['Species']))
['Bream' 'Roach' 'Whitefish' 'Parkki' 'Perch' 'Pike' 'Smelt']

 

# 데이터프레임에서 입력 데이터로 사용할 열 선택
fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()


print(fish_input[:5])

데이터프레임에서 여러 열을 선택하면 새로운 데이터프레임이 반환됨. 이를 to_numpy() 메서드로 넘파이 배열로 바꾸어 fish_input에 저장.

# 타깃 데이터 만들기
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.8907563025210085
0.85

 

타깃 값을 그대로 사이킷런 모델에 전달하면 순서가 자동으로 알파벳 순으로 매겨짐.

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

 

 

# 처음 5개 샘플의 타깃값 예측
print(kn.predict(test_scaled[:5]))
['Perch' 'Smelt' 'Pike' 'Perch' 'Perch']

 

predict_proba() 메서드로 클래스별 확률값 반환

import numpy as np

proba = kn.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=4))  #소수점 4번째까지 표시 (5번째에서 반올림)
[[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']]

 

 

4 번째 샘플의 최근접 이웃의 클래스를 확인해보면 앞서 출력한 것과 확률이 일치하는 것을 확인할 수 있음

최근접 이웃으로 가능한 확률이 제한적임

 

- 로지스틱 회귀

시그모이드 함수 그리기

import numpy as np
import matplotlib.pyplot as plt

z = np.arange(-5, 5, 0.1)  # -5 ~ 5 사이에 0.1 간격으로 배열 z 생성
phi = 1 / (1 + np.exp(-z)) # 지수 함수 계산 np.exp() 함수 사용

plt.plot(z, phi)
plt.xlabel('z')
plt.ylabel('phi')
plt.show()

 

 

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

 

불리언 인덱싱으로 도미와 빙어 데이터만 골라내기

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

 

 

예측 확률 출력

print(lr.predict_proba(train_bream_smelt[:5]))
[[0.99759855 0.00240145]
 [0.02735183 0.97264817]
 [0.99486072 0.00513928]
 [0.98584202 0.01415798]
 [0.99767269 0.00232731]]

 

속성 확인

print(lr.classes_)

 

로지스틱 회귀가 학습한 계수 확인

print(lr.coef_, lr.intercept_)
[[-0.4037798  -0.57620209 -0.66280298 -1.01290277 -0.73168947]] [-2.16155132]
 

로지스틱 회귀 모델이 학습한 방정식

 
# z값 출력
decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions)
[-6.02927744  3.57123907 -5.26568906 -4.24321775 -6.0607117 ]

 

파이썬의 사이파이(scipy 라이브러리)의 expit() 함수 사용하면 분수 계산 하는 것보다 편리

from scipy.special import expit

print(expit(decisions))
[0.00240145 0.97264817 0.00513928 0.01415798 0.00232731]

 

 

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

lr = LogisticRegression(C=20, max_iter=1000) # 로지스틱 회귀에서 규제를 제어하는 매개변수 C(작을수록 규제 커짐)
lr.fit(train_scaled, train_target)		# max_iter 반복횟수 지정

print(lr.score(train_scaled, train_target))
print(lr.score(test_scaled, test_target))

 

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

['Perch' 'Smelt' 'Pike' 'Roach' 'Perch']

 

예측 확률 출력

proba = lr.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=3))
[[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.classes_)
['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']

 

크기 출력

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

(7, 5) (7,)

 

소프트맥스 함수

# z1 ~ z7 값 구하기
decision = lr.decision_function(test_scaled[:5])
print(np.round(decision, decimals=2))

 

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

 

from scipy.special import softmax

proba = softmax(decision, axis=1)  # 축 지정 : 1은 행에 대해
print(np.round(proba, decimals=3))
[[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]]

 위 proba 배열과 결과 일치 !

 

 

4-2. 확률적 경사 하강법

  • 확률적 경사 하강법(stochastic gradient descent) : 훈련 세트에서 랜덤하게 하나의 샘플을 선택하여 손실 함수의 경사를 따라 최적의 모델을 찾는 알고리즘
  • 에포크(epoch) : 확률적 경사 하강법에서 훈련 세트를 한 번 모두 사용하는 과정
  • 미니배치 경사 하강법(minibatch gradient descent) : 1개가 아닌 여러 개의 샘플을 사용해 경사 하강법을 수행하는 방법으로 실전에서 많이 사용
  • 배치 경사 하강법(batch gradient descent) : 한 번에 전체 샘플을 사용하는 방법, 컴퓨터 자원을 많이 사용
  • 손실 함수(loss function) : 어떤 문제에서 머신러닝 알고리즘이 얼마나 엉터리인지를 측정하는 기준
  • 로지스틱 손실 함수/이진 크로스엔트로피 손실 함수(logistic loss function) : 양성 클래스(타깃 = 1)일 때 손실은 -log(예측 확률)로 계산하며, 1 확률이 1에서 멀어질수록 손실은 아주 큰 양수가 됨. 음성 클래스(타깃 = 0)일 때 손실은 -log(1-예측 확률)로 계산함. 이 예측 확률이 0에서 멀어질수록 손실은 아주 큰 양수가 됨
  • 크로스엔트로피 손실 함수(cross-entropy loss function) : 다중 분류에서 사용하는 손실 함수
  • 힌지 손실(hinge loss) : 서포트 벡터 머신이라 불리는 또 다른 머신러닝 알고리즘을 위한 손실 함수로 널리 사용하는 머신러닝 알고리즘 중 하나. SGDClassifier가 여러 종류의 손실 함수를 loss 매개변수에 지정하여 다양한 머신러닝 알고리즘을 지원함 

 

점진적 학습 -> 확률적 경사 하강법

SGDClassifer

import pandas as pd

fish = pd.read_csv('https://bit.ly/fish_csv_data')
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)

 

from sklearn.linear_model import SGDClassifier

 

loss :  손실 함수 종류 지정 -> 로지스틱 손실 함수 log

max_iter : 수행할 에포크 횟수 지정

sc = SGDClassifier(loss='log', max_iter=10, random_state=42)
sc.fit(train_scaled, train_target)

print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target))

0.773109243697479

0.775

정확도 낮음

 

# 모델 sc 추가 훈련 partial_fit() 메서드로
sc.partial_fit(train_scaled, train_target)

print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target))

0.8151260504201681

0.85

에포크 훈련 더 할 필요 있음

 

 

에포크와 과대/과소적합

import numpy as np

sc = SGDClassifier(loss='log_loss', random_state=42)

train_score = []
test_score = []

classes = np.unique(train_target)
for _ in range(0, 300):
    sc.partial_fit(train_scaled, train_target, classes=classes)

    train_score.append(sc.score(train_scaled, train_target))
    test_score.append(sc.score(test_scaled, test_target))
import matplotlib.pyplot as plt

plt.plot(train_score)
plt.plot(test_score)
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.show()

반복 횟수 100 적절 !

 

# 반복 횟수 100에 맞추고 모델 다시 훈련
sc = SGDClassifier(loss='log_loss', max_iter=100, tol=None, random_state=42)
sc.fit(train_scaled, train_target)

print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target))

0.957983193277311

0.925

 

힌지 손실을 이용해 모델 훈련

sc = SGDClassifier(loss='hinge', max_iter=100, tol=None, random_state=42)
sc.fit(train_scaled, train_target)

print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target))
0.9495798319327731
0.925