Chapter 04.다양한 회귀 알고리즘

고대빵·2023년 5월 6일
0

혼공머신

목록 보기
4/4
post-thumbnail

혹시 머신러닝으로 럭키백의 생선이 어떤 타깃에 속하는지 확률을 구할 수 있는가?

데이터 준비하기

데이터프레임(dataframe)이란 판다스에서 제공하는 2차원 표 형식의 주요 데이터 구조임.

  • 데이터프레임은 넘파이 배열과 비슷하게 열과 행으로 구성되어 있음.
  • 데이터프레임은 통계와 그래프를 위한 메서드를 풍부하게 제공하며, 넘파이로 상호 변환이 쉽고 사이킷런과도 잘 호환됨.
# 데이터 불러오기
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')

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

# Species 열을 빼고 나머지 5개 열을 선택하기(입력 데이터)
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)

# StandardScaler를 사용해 훈련 세트와 테스트 세트를 표준화 전처리하기
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))
### 결과 : 0.8907563025210085
print(kn.score(test_scaled, test_target))
### 결과 : 0.85

다중 분류(multi-class classification)이란 타깃 데이터에 2개 이상의 클래스가 포함된 문제를 말함.

# KNeighborsClassifier에서 정렬된 타깃값은 classes_ 속성에 저장되어 있음.
print(kn.classes_)
### 결과 : ['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']

# predict() 메서드는 친절하게도 타깃값으로 예측을 출력함.
# 테스트 세트에 있는 처음 5개 샘플의 타깃값을 예측해보자.
print(kn.predict(test_scaled[:5]))
### 결과 : ['Perch' 'Smelt' 'Pike' 'Perch' 'Perch']
  • 사이킷런의 분류 모델은 predict_proba( ) 메서드로 클래스별 확률값을 반환함.
  • 넘파이 round( ) 함수는 기본으로 소수점 첫째 자리에서 반올림을 하는데, decimals 매개변수로 유지할 소수점 아래 자릿수를 지정할 수 있음.
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.     0.6667 0.     0.3333 0.     0.    ]
 [0.     0.     0.6667 0.     0.3333 0.     0.    ]]
  • predict_proba( ) 메서드의 출력 순서는 classes_ 속성과 같음.
  • (네 번째 샘플) 첫 번째 열이 'Bream'에 대한 확률, 두 번째 열이 'Parkki'에 대한 확률임.
  • 네 번째 샘플의 최근접 이웃의 클래스를 확인해보자.
distances, indexes = kn.kneighbors(test_scaled[3:4])
print(train_target[indexes])
### 결과 : [['Roach' 'Perch' 'Perch']]

로지스틱 회귀

로지스틱 회귀(logistic regression)는 분류 모델임. 이 알고리즘은 선형 회귀와 동일하게 선형 방정식을 학습함.

예를 들어,

z = a x (Weight) + b x (Length) + c x (Diagonal) + d x (Height) + e x (Width) + f

  • 여기에서 a, b, c, d, e는 가중치 혹은 계수임. z는 어떤 값도 가능함.
  • z는 어떤 값도 가능하나, 확률이 되려면 0~1 사이의 값이 되어야 함.

시그모이드 함수(sigmoid function) 혹은 로지스틱 함수(logistic function)을 사용하면 z가 아주 큰 음수일 때 0이 되고, z가 아주 큰 양수일 때 1이 되도록 바꿀 수 있음.

  • 넘파이를 사용하면 시그모이드 함수 그래프를 간단히 그릴 수 있음.
  • -5~+5 사이에 0.1 간격으로 배열 z를 만든 다음 z 위치마다 시그모이드 함수를 계산함.
  • 지수 함수 계산은 np.exp( ) 함수를 사용함.
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()

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

불리언 인덱싱(boolean indexing)이란 넘파이 배열을 True, False 값을 전달하여 행을 선택하는 것을 말함.

char_arr = np.array(['A','B','C','D','E'])
print(char_arr[[True, False, True, False, False]])
### 결과 : ['A' 'C']
  • 훈련 세트에서 도미(Bream)와 빙어(Smelt)의 행만 골라내보자.
# 비교 연산자를 사용하여 도미와 빙어의 행을 모두 True로 변환 가능함.
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]
  • bream_smelt_indexes 배열은 도미와 빙어일 경우 True이고 그 외에는 모두 False 값이 들어가 있음.
  • 이제 이 데이터로 로지스틱 회귀 모델을 훈련해보자.
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)

# 훈련한 모델을 사용해 train_bream_smelt에 있는 처음 5개 샘플을 예측해보자.
print(lr.predict(train_bream_smelt[:5]))
### 결과 : ['Bream' 'Smelt' 'Bream' 'Bream' 'Bream']
### 두 번째 샘플을 제외하고는 모두 도미로 예측함.

# 예측 확률은 predict_proba() 메서드에서 제공함.
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]]
 
# 첫 번째 열이 음성 클래스(0)에 대한 확률이고 두 번째 열이 양성 클래스(1)에 대한 확률임. 
# 그러면 Bream과 Smelt 중에 어떤 것이 양성 클래스인가?
print(lr.classes_)
### 결과 : ['Bream' 'Smelt']
  • 빙어(Smelt)가 양성 클래스임. predict_proba( ) 메서드가 반환한 배열 값을 보면 두 번째만 양성 클래스인 빙어의 확률이 높음. 나머지는 모두 도미(Bream)으로 예측할 수 있음.

로지스틱 회귀가 학습한 계수를 확인해보자.

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

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

z = -0.404 x (Weight) - 0.576 x (Length) - 0.663 x (Diagonal) - 1.013 x (Height) - 0.732 x (Width) - 2.161

확실히 로지스틱 회귀는 선형 회귀와 매우 비슷함.

  • LogisticRegression 모델로 z값 및 확률을 계산할 수 있는가?
# LogisticRegression 클래스는 decision_function() 메서드로 z 값을 출력할 수 있음.
decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions)
### 결과 : [-6.02927744  3.57123907 -5.26568906 -4.24321775 -6.0607117 ]

# 시그모이드 함수인 expit() 함수를 이용하여 확률을 계산할 수 있음.
from scipy.special import expit
print(expit(decisions))
### 결과 : [0.00240145 0.97264817 0.00513928 0.01415798 0.00232731]
# 출력된 값은 predict_proba() 메서드 출력의 두 번째 열의 값과 동일함.

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

  • LogisticRegression 클래스는 다중 분류 모델을 훈련하는 코드임.
  • LogisticRegression은 기본적으로 계수의 제곱을 규제함.
  • 7개의 생선 데이터가 모두 들어 있는 train_scaled와 train_target을 사용하였음.
lr = LogisticRegression(C=20, max_iter=1000)
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target))
### 결과 : 0.9327731092436975
print(lr.score(test_scaled, test_target))
### 결과 : 0.925

# 훈련 세트와 테스트 세트에 대한 점수가 높고 과대적합이나 과소적합으로 치우치지 않은 것 같음.
# 테스트 세트의 처음 5개 샘플에 대한 예측을 출력해보자.
print(lr.predict(test_scaled[:5]))
### 결과 : ['Perch' 'Smelt' 'Pike' 'Roach' 'Perch']
# 테스트 세트의 처음 5개 샘플에 대한 예측 확률 출력하기
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]]
  • 첫 번째 샘플에서는 세 번째 열의 확률이 가장 높음. (84.1%)
  • classes_ 속성에서 클래스 정보를 확인해보자.
print(lr.classes_)
### 결과 : ['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']
  • 다중 분류일 경우 선형 방정식은 어떤 모습인가?
# coef_와 intercept_의 크기를 출력해보자.
print(lr.coef_.shape, lr.intercept_.shape)
### 결과 : (7, 5) (7,)
  • 이진 분류에서는 시그모이드 함수를 사용해 z를 0과 1 사이의 값으로 변환함.
  • 다중 분류에서는 이와 달리 소프트맥스 함수를 사용하여 7개의 z 값을 확률로 변환함.

소프트맥스 함수(softmax function)란 여러 개의 선형 방정식의 출력값을 0~1 사이로 압축하고 전체 합이 1이 되도록 만드는 함수임. 이를 위해 지수 함수를 사용하기 때문에 "정규화된 지수 함수"라고도 부름.

소프트맥스의 합

e_sum = e^z1+e^z2+e^z3+e^z4 ... + e^zn

그다음 e^z1~e^zn을 각각 e_sum으로 나누어 주면 됨.

s1 = e^z1/e_sum ... , sn = e^zn/e_sum

s1~sn을 모두 더하면 분자와 분모가 같아지므로 1이 됨.

  • 그러면 decision_function( ) 메서드로 z1~z7까지의 값을 구한 다음 소프트맥스 함수를 사용해 확률로 바꾸어 보자.
# 테스트 세트의 처음 5개 샘플에 대한 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]]

# 사이파이는 소프트맥스 함수도 제공함. 
# scipy.special( ) 아래에 softmax( ) 함수를 임포트하여 사용해보자.
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]]
  • 출력 결과를 앞서 구한 proba 배열과 비교해 보면, 결과가 정확히 일치함을 알 수 있음.
  • 로지스틱 회귀를 사용해 7개의 생선에 대한 예측하는 모델을 훈련하였음.

점진적인 학습이란 앞서 훈련한 모델을 버리지 않고 새로운 데이터에 대해서만 조금씩 더 훈련하는 방법임. 대표적인 점진적 학습 알고리즘은 확률적 경사 하강법(stochasitc Gradient Descent)임.

확률적 경사 하강법

확률적 경사 하강법은 무작위로 배치 크기가 1인 단 한 개의 데이터를 추출하여 기울기를 계산하고, 경사 하강 알고리즘을 적용하는 방법을 말함.

  • 확률적 경사 하강법에서 훈련 세트를 한 번 모두 사용하는 과정을 에포크(epoch)라고 함.
  • 미니배치 경사 하강법(minibatch gradient descent)이란 여러 개의 샘플을 사용해 경사 하강법을 수행하는 방식을 말함.
  • 배치 경사 하강법(batch gradient descent)이란 한 번 경사로를 따라 이동하기 위해 전체 샘플을 사용하는 방법을 말함.

손실 함수

손실 함수(loss function)는 어떤 문제에서 머신러닝 알고리즘이 얼마나 측정하는 기준임.

로지스틱 손실 함수

로지스틱 손실 함수(logistic loss function)란 실제값 y와 모델의 예측값 p 사이의 차이를 계산하는 함수로, 실제값 y가 0 또는 1인 이진 분류 문제를 가정하고, 로지스틱 회귀 함수의 출력값 p를 예측값으로 사용한다.

크로스엔크로피 손실 함수(cross-entropy loss function)란 분류 문제를 다루는 모델에서 사용되는 손실 함수 중 하나로, 다중 클래스 분류(multi-class classification) 문제에서도 사용될 수 있으며, 이 경우에는 소프트맥스 함수(softmax function)와 함께 사용된다.


SGDClassifier

# pandas dataframe 생성
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')

# Species 열을 제외한 나머지 5개는 입력 데이터로 사용
# Species 열은 타깃 데이터
fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()
fish_target = fish['Species'].to_numpy()

# train_test_split() 함수를 사용해 데이터를 훈련 세트와 테스트 세트로 나눔
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임.
# SGDClassifier 클래스 불러오기
from sklearn.linear_model import SGDClassifier

# log='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))
### 결과 : 0.773109243697479
print(sc.score(test_scaled, test_target))
### 결과 : 0.775

# SGDClassifier 객체를 다시 만들지 않고 훈련한 모델 sc를 추가로 더 훈련해보자.
# 모델을 이어서 훈련할 때는 partial_fit() 메서드를 사용
# partial_fit() 메서드를 호출하고 다시 훈련 세트와 테스트 세트의 점수를 확인해보자.

sc.partial_fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target))
### 결과 : 0.8151260504201681
print(sc.score(test_scaled, test_target))
### 결과 : 0.85

에포크와 과대/과소적합

  • 적은 에포크 횟수 동안에 훈련한 모델은 훈련 세트와 테스트 세트에 잘 맞지 않는 과소적합된 모델일 가능성이 높음.
  • 많은 에포크 횟수 동안에 훈련한 모델은 훈련 세트에 너무 잘 맞아 테스트 세트에는 오히려 점수가 나쁜 과대적합된 모델일 가능성이 높음.

과대적합이 시작하기 전에 훈련을 멈추는 것을 조기 종료(early stopping)라고 함.

import numpy as np

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

# 300번의 에포크 동안 훈련을 반복하여 진행
# 반복마다 훈련 세트와 테스트 세트의 점수를 계산
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))
    
# 300번의 에포크 동안 기록한 훈련 세트와 테스트 세트의 점수를 그래프로 그려보기
import matplotlib.pyplot as plt
plt.plot(train_score)
plt.plot(test_score)
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.show()

  • 백 번째 에포크 이후에는 훈련 세트와 테스트 세트의 점수가 조금씩 벌어지고 있음
  • 에포크 초기에는 과소적합되어 훈련 세트와 테스트 세트의 점수가 낮음
# SGDClassifier의 반복 횟수를 100에 맞추고 모델을 다시 훈련해보자.
sc = SGDClassifier(loss='log', max_iter=100, tol=None, random_state=42)
sc.fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target))
### 결과 : 0.957983193277311
print(sc.score(test_scaled, test_target))
### 결과 : 0.925
  • 최종 점수가 좋고, 훈련 세트와 테스트 세트에서의 정확도 점수가 비교적 높게 나왔음.

힌지 손실(hinge loss)은 서포트 벡터 머신(support vector machine)이라 불리는 또 다른 머신러닝 알고리즘을 위한 손실 함수

힌지 손실을 사용한 예시

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))
### 결과 : 0.9495798319327731
print(sc.score(test_scaled, test_target))
### 결과 : 0.925

0개의 댓글