핀아의 저장소 ( •̀ ω •́ )✧

[ML] 혼자 공부하는 머신러닝_4장 본문

Big Data/ML & DL

[ML] 혼자 공부하는 머신러닝_4장

_핀아_ 2023. 5. 30. 18:09
💡 학습목표
1. K-최근접 이웃 알고리즘을 사용해서 다중 분류 수행과 로지스틱 회귀 알고리즘을 사용해서 이진/다중 분류 수행 2. 시그모이드 함수와 소프트맥스 함수의 차이에 대해 학습함

목차

  1. K-최근접 이웃을 사용한 다중 분류
    1. 데이터 준비
    2. K-최근접 이웃 분류기의 확률 예측
  2. 로지스틱 회귀
    1. 로지스틱 회귀 (이진 분류)
    2. 로지스틱 회귀 (다중 분류)
  3. 점진적인 학습
    1. 확률적 경사 하강법

1️⃣ K-최근접 이웃을 사용한 다중 분류

💡 다중 분류란 타깃 데이터에 2개 이상의 클래스가 포함된 문제임
K-최근접 이웃 알고리즘을 사용해서 이웃 클래스의 비율을 확률로 표현해봄

✅ 데이터 준비

import pandas as pd

fish = pd.read_csv('https://bit.ly/fish_csv_data')
fish.head()
'''
	Species	Weight	Length	Diagonal	Height	Width
0 Bream	  242.0	  25.4	  30.0	  11.5200	  4.0200
1	Bream	  290.0	  26.3	  31.2	  12.4800	  4.3056
2	Bream	  340.0	  26.5	  31.1	  12.3778	  4.6961
3	Bream	  363.0	  29.0	  33.5	  12.7300	  4.4555
4	Bream	  430.0	  29.0	  34.0	  12.4440	  5.1340

'''

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

#pandas to numpy
fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()
fish_target = fish['Species'].to_numpy()
print(fish_target[:5])
# ['Bream' 'Bream' 'Bream' 'Bream' 'Bream']

사이킷런 라이브러리는 타깃 데이터가 정수 타입(1, 0)으로 치환하지 않은 문자열이여도 입력 값으로 받을 수 있음

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler


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-최근접 이웃 분류기의 확률 예측

from sklearn.neighbors import KNeighborsClassifier

kn = KNeighborsClassifier(n_neighbors=3)
kn.fit(train_scaled, train_target)

print(kn.score(train_scaled, train_target))
#0.8907563025210085
print(kn.score(test_scaled, test_target))
#0.85

print(kn.classes_)
#['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']
  • 사이킷런의 객체 속성 뒤에 가 붙을 경우(ex. kn.classes) 학습 이후 나온 결과를 출력하는 것을 의미함
  • kn.classes_는 K-최근접 이웃 알고리즘 학습 이후 학습 데이터 세트로부터 추출된 클래스 정보(속성)이며 알파벳 순으로 정렬됨
  • Bream은 첫 번째 클래스, Parkki는 두 번째 클래스 임을 의미함
import numpy as np

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

proba = kn.predict_proba(test_scaled[:5])
print(np.round(proba, 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.    ]]
'''
  • predict_proba 메서드를 통해 해당 결과의 확률을 출력 할 수 있음

K-최근접 이웃 알고리즘의 단점은 이웃의 수에 따라 나올수 있는 확률이 정해져있음

예로 이웃의 수를 3으로 학습 시켰을 때 나올 수 있는 확률은 [0, 1/3, 2/3, 3/3]로 정해져있음

2️⃣ 로지스틱 회귀

💡 대표적인 분류 알고리즘이자 인공신경망에 기본이 되는 알고리즘
이름은 회귀 알고리즘이지만 분류 알고리즘에 속함

  • 로지스틱 회귀는 선형회귀랑 유사한 형태로 선형 함수를 학습하는 알고리즘임
  • 계산되는 z값을 바로 사용하면 회귀의 결과로 도출함으로 0~1의 사이의 확률로 표현하기 위해 시그모이드 함수(로지스틱 함수)를 취함
  • z값을 시그모이드 함수를 취해 나온 출력 결과가 0.5 보다 크면 양성 클래스, 작으면 음성 클래스로 분류되고 만약 값이 정확히 0.5라면 일반적으로 음성 클래스로 판단함
  • 시그모이드 함수 특성상 z 값만 가지고 음성/양성 클래스를 구분 할 수 있음(z값이 0보다 작으면 음성)
  • predict 메서드는 z값만 가지고 결과를 출력하고 predict_proba 메서드는 시그모이드를 취한 결과 값을 기준으로 결과를 출력함
import numpy as np
import matplotlib.pyplot as plt

z = np.arange(-5, 5, 0.1)
#지수 함수 계산을 위해 numpy의 exp 메서드를 사용
phi = 1 / (1 + np.exp(-z))

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

2️⃣ 로지스틱 회귀(이진 분류)

💡 로지스틱 회귀를 사용하여 이진 분류를 수행함
numpy의 boolean indexing을 사용하여 ['True']로 되어있는 원소만 추출
#도미와 빙어 데이터 세트만 True로 변경
bream_smelt_indexes = (train_target == 'Bream') | (train_target == 'Smelt')
print(bream_smelt_indexes)
'''
[
 True False  True False False False ...
]
'''

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.classes_)
# ['Bream' 'Smelt']
# Bream(음성 클래스), Smelt(양성 클래스)


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]]
'''
#계수와 절편을 출력해 z값의 선형 함수식을 구하고 직접 양성 클래스일 확률을 구해봄

print(lr.coef_, lr.intercept_)
#[[-0.4037798  -0.57620209 -0.66280298 -1.01290277 -0.73168947]] [-2.16155132]
#z = -0.404x무게 - 0.576x길이 - 0.663x대각선 - 0.013x높이 - 0.732x두께 - 2.161

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

from scipy.special import expit
#시그모이드를 씌워 확률 계산
print(expit(decisions))
#[0.00240145 0.97264817 0.00513928 0.01415798 0.00232731]

로지스틱 회귀가 이진분류일 경우 양성 클래스의 확률만 계산함

로지스틱 회귀(다중 분류)

💡 로지스틱 회귀를 사용하여 7개 종류의 생선 다중 분류를 수행함
#L2 규제(릿지), C의 값이 작아질수록 규제 강도가 강해짐
#C = default(1)
#max_iter 만큼 반복하여 학습을 수행함
#max_iter = default(10)

lr = LogisticRegression(C=20, max_iter=1000)
lr.fit(train_scaled, train_target)

print(lr.score(train_scaled, train_target))
# 0.93
print(lr.score(test_scaled, test_target))
# 0.92

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

print(lr.predict(test_scaled[:5]))
# ['Perch' 'Smelt' 'Pike' 'Roach' 'Perch']

#z값에 소프트맥스 함수를 취한 확률 값
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.coef_.shape, lr.intercept_.shape)
# (7, 5) (7,)
  • lr.coef_.shape(7, 5)의 경우 7개의 클래스와 5개의 특성과 곱해지는 계수임을 알 수 있음
  • 각 클래스 마다 선형 함수가 1개씩 만들여짐, 즉 z값이 클래스마다 1개씩 만들어짐을 의미함
  • OvR(One-vs-Rest) 방식을 사용하여 z값을 도출함 각 클래스는 이진 분류의 형태로 z값을 산출하며 예로 클래스1의 z값을 구하기 위해 클래스1과 나머지 클래스2~7로 나누어 이진 분류를 수행함
  • z값이 산출되면 소프트맥스 함수를 취해 확률을 계산함
#z값 출력
decision = lr.decision_function(test_scaled[:5])
print(np.round(decision, decimals=2))
#5개 샘플에 대해 각 7개의 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]]
'''
  • 출력된 선형 함수의 결과인 각 7개의 z를 시그모이드 취하여 합하면 1의 값이 나오지 않음
  • 다중 분류일 경우 시그모이드 함수가 아닌 소프트맥스 함수를 취하여 확률을 출력함

  • z값에 지후 함수를 취한 값을 각 z 값을 지수 함수 취해 나온 모든 결과를 더한 값으로 분모로 나눔
from scipy.special import softmax

proba = softmax(decision, axis=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]]
'''

3️⃣ 점진적인 학습

💡 새로운 데이터가 들어왔을 때 모델을 학습하는 다양한 방법에 대해 알아봄
  • 첫번째 방법은 새로운 데이터가 추가되면 매번 다시 훈련시키는 방법임 이 방법은 시간이 지날수록 쌓이는 데이터의 양이 많아지기 때문에 지속 가능한 방법으론 적절하지 못함
  • 두번째 방법은 새로운 데이터가 추가되면 이전 데이터를 버림으로써 훈련 데이터 크기를 일정하하게 유지시켜 훈련하는 방법임 이 방법은 데이터를 버릴 때 중요한 데이터가 포함될 가능성이 있음
  • 세번째 방법은 앞서 훈련한 모델을 버리지 않고 새로운 데이터가 들어오면 그 데이터에 대해서만 학습을 수행함 이러한 훈련 방법을 점진적 학습(또는 온라인 학습)이라 부르며 대표적인 점진적 학습 알고리즘은 확률적 경사 하강법

확률적 경사 하강법

훈련 세트에서 랜덤하게 하나의 샘플을 선택하여 손실 함수의 경사를 따라 최적의 모델을 찾는 알고리즘을 의미함

Comments