Date:     Updated:

카테고리:

태그: , ,


💡 ‘혼자 공부하는 머신러닝 딥러닝(박해선 저)’ 책을 읽고 공부한 내용을 요약한 페이지입니다.
책에 나오는 코드를 그대로 쓰지 않고, 코드와 파라미터를 변형하고 조정해가며 다른 결과를 출력하며 공부했습니다.


04 다양한 분류 알고리즘


04-1 로지스틱 회귀

  • k-최근접 이웃 분류기: k-최근접 이웃을 이용하여 인접 샘플의 클래스 비율을 확률로 나타내는 방식
#데이터 준비
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')
fish.head() #처음 5개 행 출력
print(pd.unique(fish['Species']))
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.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))

import numpy as np
proba = kn.predict_proba(test_scaled[:5]) #테스트세트 첫 5개 샘플의 확률
print(np.round(proba, decimals=4)) #소수점 네번째 자리까지 표기, 다섯번째 자리에서 반올림

distances, indexes = kn.kneighbors(test_scaled[3:4])
print(train_target[indexes])

그러나 k-최근접 이웃 방식은 3개의 최근접 이웃만을 활용하기에 가능한 확률은 0/3, 1/3, 2/3, 3/3이 전부이다. 더 디테일한 확률이 필요하다. 로지스틱 회귀는 이것을 가능하게 해준다.

  • 로지스틱 회귀(logistic regression): 선형 방정식을 활용한 분류 모델로서, 시그모이드 함수소프트맥스 함수를 활용하여 클래스의 확률을 출력한다. z값이 아주 큰 음수이면 확률이 0이 되고, 아주 큰 양수이면 1이 되도록 한다.

왼쪽은 시그모이드 그래프, 오른쪽은 시그모이드 함수. x값이 로지스틱 방정식이다.

#시그모이드 함수 그리기
import numpy as np
import matplotlib.pyplot as plt
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()

로지스틱 회귀로 간단한 이진 분류를 수행해보자. 이진분류의 경우 시그모이드의 출력이 0.5보다 크면 양성, 작으면 음성으로 판단한다.

#불리언 인덱싱을 활용하여 도미(Bream)와 빙어(Smelt)만 뽑아냄
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])) #처음 5개 샘플 예측
print(lr.predict_proba(train_bream_smelt[:5])) #처음 5개 샘플의 확률 출력
#결과값
['Bream' 'Smelt' 'Bream' 'Bream' 'Bream']
[[0.99759855 0.00240145]
 [0.02735183 0.97264817]
 [0.99486072 0.00513928]
 [0.98584202 0.01415798]
 [0.99767269 0.00232731]]

#decision_function(): z값을 계산하는 메서드
decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions)
#결과값
[-6.02927744  3.57123907 -5.26568906 -4.24321775 -6.0607117 ]
#이 z값을 시그모이드 함수에 넣으면 확률을 얻을 수 있음

from scipy.special import expit
print(expit(decisions))
#결과값
[0.00240145 0.97264817 0.00513928 0.01415798 0.00232731]
#predict_proba()메서드 출력의 두번째 열과 동일!

이제 로지스틱 회귀로 다중 분류를 수행해보자.

lr = LogisticRegression(C=20, max_iter=1000)
#max_iter매개변수는 반복 횟수를 지정
#C는 릿지의 alpha처럼 규제의 정도를 위한 매개변수. 작을수록 규제가 커짐.
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target)) #0.9327
print(lr.score(test_scaled, test_target)) #0.925
print(lr.predict(test_scaled[:5]))
proba = lr.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=3))

다중분류는 클래스마다 z값을 하나씩 계산한다. 이중 분류의 경우 z값을 시그모이드 함수를 사용하여 확률로 변환했지만, 다중 분류의 경우 소프트맥스 함수를 사용하여 z값을 확률로 변환한다.

decision = lr.decision_function(test_scaled[:5])
print(np.round(decision, decimals=2)) #z값 출력.

from scipy.special import softmax
proba = softmax(decision, axis=1) #axis매개변수는 계산할 축을 지정
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 배열과 결과가 일치함!

04-1 핵심 키워드

  • 로지스틱 회귀: 선형 방정식을 사용한 분류 알고리즘
  • 다중 분류: 타깃 클래스가 2개 이상인 분류 문제
  • 시그모이드 함수: 이중 분류에서 쓰이며, 선형 방정식의 출력(z)을 0과 1 사이의 확률값으로 출력
  • 소프트맥스 함수: 다중 분류에서 쓰이며, 여러 선형 방정식의 출력 결과를 정규화하여 합이 1이 되도록 만듦.

04-1 핵심 패키지와 함수

  • scikit-learn
    • LogisticRegression: 로지스틱 회귀를 위한 클래스. C매개변수에서 규제의 강도를 제어한다. 작을수록 규제가 강해진다.
    • predict_proba(): 예측 확률을 반환하는 메서드
    • decision_function(): 모델이 학습한 선형 방정식의 값(z)을 반환하는 메서드.


04-2 확률적 경사 하강법

앞서 훈련한 모델을 버리지 않고 새로운 데이터에 대해서만 조금씩 훈련할 수 없을까? ⇒이러한 방식이 점진적 학습이다. 대표적인 점진적 학습 알고리즘은 확률적 경사 하강법(Stochastic Gradient Descent) 이다.

훈련세트에서 랜덤하게 하나의 샘플을 선택하여 가파른 경사를 조금 내려간다. 그 다음 훈련 세트에서 랜덤하게 또 다른 샘플을 하나 선택하여 경사를 조금 내려간다. 이런 식으로 전체 샘플을 모두 사용할 때까지 계속한다. 만약 모든 샘플을 사용했다면, 훈련세트에 모든 샘플을 다시 채워넣고 앞선 과정을 반복한다. 이 때 훈련세트를 한 번 모두 사용하는 텀을 에포크(epoch) 라고 부른다.

샘플을 1개씩 꺼내는 것이 확률적 경사 하강법, 샘플을 여러개씩 꺼내는 것이 미니배치 경사 하강법, 샘플을 전부 꺼내는 것이 배치 경사 하강법이다.

  • 손실함수(loss function): 머신러닝 알고리즘이 얼마나 엉터리인지 측정하는 함수. 로지스틱 손실 함수, 크로스엔트로피 손실 함수 등이 있다.
#데이터 준비 및 전처리
import pandas as pd
fish = pd.read_csv('http://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)
# 확률적 경사 하강법을 제공하는 분류용 클래스 SGDClassifier
from sklearn.linear_model import SGDClassifier
sc = SGDClassifier(loss='log', max_iter=10, random_state=42)
#loss는 손실함수를 지정하는 매개변수. log는 로지스틱 손실 함수
#max_iter는 수행할 에포트 횟수
sc.fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target)) #0.7731
print(sc.score(test_scaled, test_target)) #0.775
#출력된 훈련세트와 테스트세트의 정확도가 낮은 걸 보아하니 반복 횟수 10번이 부족한 것으로 보임.

sc.partial_fit(train_scaled, train_target)
#partial_fit메서드는 호출할 때마다 1 에포크씩 이어서 훈련한다.
print(sc.score(train_scaled, train_target)) #0.8151
print(sc.score(test_scaled, test_target)) #0.825
#아직 점수가 낮지만 그래도 나아졌다.

그렇다면 에포크를 어느 정도로 두어야 가장 뛰어난 점수가 나올까.

import numpy as np
sc = SGDClassifier(loss='log', random_state=42)
train_score = []
test_score = []
classes = np.unique(train_target)

for _ in range(300): #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()

주황색은 테스트 세트, 파란색은 훈련 세트이다.

50번째 에포크부터는 점수가 거의 향상하지 않고, 100번째 에포크부터는 두 세트 간의 격차가 벌어지고 있다. 따라서 백 번째 에포크가 적절한 반복 횟수로 보인다.

sc = SGDClassifier(loss='log', max_iter=100, tol=None, random_state=42)
#SGDClassifier는 일정 에포크동안 성능이 향상되지 않으면 자동으로 멈춘다
#그러나 tol매개변수를 None으로 지정하면 자동으로 멈추지 않고 끝까지 간다.
sc.fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target)) #0.9579
print(sc.score(test_scaled, test_target)) #0.925
#최종 결과가 좋게 나옴!

04-2 핵심 키워드

  • 확률적 경사 하강법(stochastic gradient descent): 훈련 세트에서 샘플 하나씩 꺼내 손실 함수의 경사를 따라 최적의 모델을 찾는 알고리즘.
  • 손실함수: 확률적 경사 하강법이 최적화할 대상으로, 이중 분류에는 로지스틱 회귀 손실함수를 사용하고, 다중 분류에는 크로스엔트로피 손실함수를 사용한다.
  • 에포크(epoch): 확률적 경사 하강법에서 전체 샘플을 모두 사용하는 한 번 반복을 의미한다.

04-2 핵심 패키지와 함수

  • scikit-learn
    • SGDClassifier: 확률적 경사 하강법을 사용한 분류 모델을 만드는 클래스.
      • loss 매개변수는 최적화할 손실 함수를 택한다.
      • penalty 매개변수는 규제의 종류를 지정한다.
      • max_iter 매개변수는 에포크 횟수를 지정한다.
      • tol 매개변수는 반복을 멈출 조건이다.
    • SGDRegressor: 확률적 경사 하강법을 사용한 회귀 모델을 만드는 클래스