혹시 머신러닝으로 럭키백의 생선이 어떤 타깃에 속하는지 확률을 구할 수 있는가?
데이터프레임(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)
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']
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. ]]
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
시그모이드 함수(sigmoid function) 혹은 로지스틱 함수(logistic function)을 사용하면 z가 아주 큰 음수일 때 0이 되고, z가 아주 큰 양수일 때 1이 되도록 바꿀 수 있음.
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']
# 비교 연산자를 사용하여 도미와 빙어의 행을 모두 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]
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']
로지스틱 회귀가 학습한 계수를 확인해보자.
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 클래스는 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() 메서드 출력의 두 번째 열의 값과 동일함.
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]]
print(lr.classes_)
### 결과 : ['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']
# coef_와 intercept_의 크기를 출력해보자.
print(lr.coef_.shape, lr.intercept_.shape)
### 결과 : (7, 5) (7,)
소프트맥스 함수(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이 됨.
# 테스트 세트의 처음 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]]
점진적인 학습이란 앞서 훈련한 모델을 버리지 않고 새로운 데이터에 대해서만 조금씩 더 훈련하는 방법임. 대표적인 점진적 학습 알고리즘은 확률적 경사 하강법(stochasitc Gradient Descent)임.
확률적 경사 하강법은 무작위로 배치 크기가 1인 단 한 개의 데이터를 추출하여 기울기를 계산하고, 경사 하강 알고리즘을 적용하는 방법을 말함.
손실 함수(loss function)는 어떤 문제에서 머신러닝 알고리즘이 얼마나 측정하는 기준임.
로지스틱 손실 함수(logistic loss function)란 실제값 y와 모델의 예측값 p 사이의 차이를 계산하는 함수로, 실제값 y가 0 또는 1인 이진 분류 문제를 가정하고, 로지스틱 회귀 함수의 출력값 p를 예측값으로 사용한다.
크로스엔크로피 손실 함수(cross-entropy loss function)란 분류 문제를 다루는 모델에서 사용되는 손실 함수 중 하나로, 다중 클래스 분류(multi-class classification) 문제에서도 사용될 수 있으며, 이 경우에는 소프트맥스 함수(softmax function)와 함께 사용된다.
# 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 클래스 불러오기
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