길이(Length)와 높이(Height) 뿐만 아니라 대각선(Diagonal)과 두께(Width)도 샘플 분류에 사용될 수 있다.
데이터를 불러와준다.
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')
fish.head()
결과는 다음과 같다.
Pandas 라이브러리의 read_csv()를 활용하여 해당 경로의 파일을 불러온다.
head()메서드로 처음 5개의 행을 출력한다.
Species 열에서 고유값 추출
print(pd.unique(fish['Species']))
#결과 ['Bream' 'Roach' 'Whitefish' 'Parkki' 'Perch' 'Pike' 'Smelt']
Input 데이터 만들기
fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()
print(fish_input[:5])
#결과 [[242. 25.4 30. 11.52 4.02 ]
# [290. 26.3 31.2 12.48 4.3056]
# [340. 26.5 31.1 12.3778 4.6961]
# [363. 29. 33.5 12.73 4.4555]
# [430. 29. 34. 12.444 5.134 ]]
Species 열을 제외하고 나머지 열들을 입력데이터로 해줍니다.
Target 데이터 만들기
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)
훈련 세트와 데이터 세트를 나눠준다.
StandardScaler클래스를 사용해 훈련 세트와 테스트 세트를 표준화 전처리 해준다.
k-최근접 이웃 분류기 확률 예측
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
최근접 이웃 개수인 K를 3으로 지정하여 사용한다.
타깃 데이터에 2개 이상의 클래스가 포함된 문제를 다중분류라고 부른다.
print(kn.classes_)
#결과 ['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']
print(kn.predict(test_scaled[:5]))
#결과 ['Perch' 'Smelt' 'Pike' 'Perch' 'Perch']
print(kn.predict(test_scaled[:5]))
#결과 ['Perch' 'Smelt' 'Pike' 'Perch' 'Perch']
KNeighborsClassifier에서 정렬된 타깃값은 classes_속성에 저장 되어 있다.
Predict() 메서드를 활용하여 예측한 타깃값을 출력해준다.
predict_proba() 매서드
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']]
사이킷런의 분류 모델은 predict_proba() 메서드로 클래스별 확률값을 반환한다.
이 메서드는 음성 클래스와 양성 클래스에 대한 확률을 출력해준다.
Numpy의 round() 함수는 소숫점 첫째 자리에서 반올림하는 것이 디폴트이다.
하지만 decimals 매개변수로 유지할 소수점 아래 자릿수를 지정할 수도 있다.
로지스틱 회귀(Logistic regression)
선형 회귀와 동일하게 선형 방정식을 학습한다.
로지스틱 회귀(Logistic Regression)는 통계학과 머신러닝에서 이진 분류(binary classification) 문제를 해결하기 위해 자주 사용되는 기법이다.
이 모델은 입력 변수(feature)와 출력 변수(target) 사이의 관계를 모델링하여, 주어진 입력에 대해 두 가지 가능한 출력 중 하나를 예측하는데 사용된다.
z는 어떠한 값도 가능하나 확률이 되려면 0~1(또는 0~100%)사이 값이 되어야한다.
$$
P(Y=1|X) = \sigma(\beta_0 + \beta_1 x_1 + \beta_2 x_2 + \cdots + \beta_n x_n)
$$
시그모이드 함수(Sigmoid function)
로지스틱 함수(logistic function)이라고도 한다.
밑의 코드는 시그모이드 함수를 그리는 코드이다.
$$
\sigma(z) = \frac{1}{1 + e^{-z}}
$$
Numpy 배열은 True, False 값을 전달하여 행을 선택할 수 있다. 이를 불리언 인덱싱이라고 한다.
다음은 간단히 예시이다.
char_arr = np.array(['A', 'B', 'C', 'D', 'E'])
print(char_arr[[True, False, True, False, False]])
#결과 ['A' 'C']
로지스틱 회귀로 이진 분류 수행하기
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)
도미와 빙어에 대한 비교 결과를 비트 OR 연산자( | )를 사용해 합치면 도미와 빙어에 대한 행만 골라낼 수 있다.
배열을 사용해 train_scaled와 train_target 배열에 불리언 인덱싱을 적용하면 손쉽게 도미와 빙어 데이터만 골라낼 수 있다.
첫 번째 열이 음성 클래스(0)에 대한 확률이고 두 번째 열이 양성 클래스(1)에 대한 확률이다.
사이킷런은 타깃값을 알파벳순으로 정렬하여 사용한다.
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_)
#결과 ['Bream' 'Smelt']
print(lr.coef_, lr.intercept_)
#결과 [[-0.4037798 -0.57620209 -0.66280298 -1.01290277 -0.73168947]] [-2.16155132]
decisions = lr.decision_function(train_bream_smelt[:5])
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]
lr = LogisticRegression(C=20, max_iter=1000)
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target))
print(lr.score(test_scaled, test_target))
#결과 0.9327731092436975
# 0.925
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,)
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)
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]]
decision_function()메서드를 통해 양성 클래스에 대한 z값을 출력한다.
scipy라이브러리의 expit()을 활용하여 시그모이드 함수를 나타낼 수 있다.
다중 분류 수행시 max_iter 매개변수에서 반복횟수를 지정할 수 있다.
소프트맥스 함수란?
하나의 선형 방정식의 출력값을 0~1 사이로 압축하는 시그모이드 함수와 달리 여러 개의 선형 방정식의 출력값을 0~1 사이로 압축하고 전사합이 1이 되도록 만드는 함수이다. 책과 조금 다르기는 하나 식은 다음과 같다.
$$
\sigma(\mathbf{z})_i = \frac{e^{z_i}}{\sum_{j=1}^{K} e^{z_j}}
$$
4-1 2번 문제 풀이
로지스틱 회귀가 이진 분류에서 확률을 출력하기 위해 사용하는 함수는 1번 '시그모이드 함수' 이다.
확률적 경사 하강법이란?
훈련 세트에서 랜덤하게 하나의 샘플을 고르는 것을 말한다. 이를 훈련 세트의 전체 샘플을 사용할 때까지 반복하게 된다.
여기서 '확률적' 이라는 말은 '랜덤하게'의 기술적 표현이라고 볼 수 있다.
그외에 배치 경사 하강법, 미니배치 경사 하강법 등등등...이 있다.
책에 이해하기 쉬운 그림이 있다.
에포크와 과대/과소 적합
훈련 세트를 한번 모두 사용하는 과정을 에포크(epoch)라고 부른다. 기본적으로 에포크 값이 클수록 점수는 높아지기 마련이다. 그러나 과대적합이 시작되면 감소하게 된다. 따라서 우리는 과대 적합이 시작하기 전에 훈련을 멈추는 것을 조기 종료라고 부른다.
손실함수란?
비용함수(cost function)이라고도 하는데 어떤 문제에서 ML알고리즘이 얼마나 개판 오분 전인지 측정하는 기준이다.
당연히 작을 수록 좋다. 하지만 우리는 어떤 값이 최솟값인지 알지 못하기에 확률적 경사 하강법을 사용한다.
Lucky하게도 우리가 다루는 많은 문제에 필요한 손실 함수는 이미 정의되어있다.
분류에서의 손실은 정답을 맞추지 못하는 것이다.
또한 정확도가 연속적으로 잘 나와줘야한다.
로지스틱 손실함수란?
로지스틱 회귀 함수의 손실함수이다. 다른 말로 이진 크로스엔트로피 손실함수(Binary cross-entropy loss function)이라고 한단다.
다중 분류에서 사용하는 손실함수는 크로스엔트로피 손실함수라고 한댄다. 아따~ 이름 참 길다.
카메라 액정이 박살난 관계로 사진이 좀 구리기는 한데 양성 클래스(타깃 = 1)일 때 손실은 -log(예측 확률)로 계산하고 확률이 1에서 멀어져 0에 가까워질수록 손실은 아주 큰 양수가 된다.
반대로 음성 클래스(타깃 = 0)일 때 손실은 -log(1-예측확률)로 계산한다. 이때 확률은 0에서 멀어져 1에 가까워질수록 손실은 아주 큰 양수가 된다.
SGDClassifier란?
사이킷런에서 확률적 경사 하강법을 제공하는 대표 분류용 클래스이다.
요 녀석은 일정 에포크 동안 성능이 향상되지 않으면 더 훈련하지 않고 자동으로 스탑한다.
또한 확률적 경사 하강법을 활용한 회귀 알고리즘은 SGDRegressor도 있다고 한다.
이를 이용하여 실습을 해보겠다. 코드는 크게 변화가 없으므로 따로 설명은 안하겠다.
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
sc = SGDClassifier(loss='log_loss', 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(train_scaled, train_target)
print(sc.score(train_scaled, train_target)) #결과 0.8151260504201681
print(sc.score(test_scaled, test_target)) #결과 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()
결과는 다음과 같다.
SGDClassifier의 loss 매개 변수의 기본값은 'hinge'이다.
Hinge Loss은 SVM을 위한 loss function이다. 또한 여러 종류의 손실함수를 loss 매개 변수에 지정하여 다양한 ML Algorithm을 지원한다.
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)) #결과 0.957983193277311
print(sc.score(test_scaled, test_target)) #결과 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)) #결과 0.9495798319327731
print(sc.score(test_scaled, test_target)) #결과 0.925
tol 매개변수는 에포크 횟수를 지정하며 기본 값은 1000이다.
간단한 소감
LaTeX 코드가 글씨체 때문에 자꾸 말썽이었지만 잘 기록을 남겼다.
라텍스는 개편한데 레이텍 얘는 왜이러는지 참
난 나눔고딕이 좋다고..!
'혼공족 ML&DL' 카테고리의 다른 글
[혼공족 12기] 5주차(8/5-8/11) (0) | 2024.08.12 |
---|---|
[혼공족 12기] 4주차(7/22-7/28) (0) | 2024.07.29 |
[혼공족 12기] 2주차(7/8-7/14) (0) | 2024.07.17 |
[혼공족 12기] 1주차(7/1-7/7) (0) | 2024.07.07 |