ML 세션 - Week4.다양한 분류 알고리즘
Aug 12, 2024
»
AI
데이터 준비
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')
print(pd.unique(fish['Species'])) #species 열에서 교유한 값 추출
# ['Bream' 'Roach' 'Whitefish' 'Parkki' 'Perch' 'Pike' 'Smelt']
fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy() #5개 열 입력데이터로 저장
fish_target = fish['Species'].to_numpy() #species 열을 타깃으로
# 훈련 세트와 테스트 세트로 나누기
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)
k-최근접 이웃 분류기로 확률 예측
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier(n_neighbors=3) # k=3
kn.fit(train_scaled, train_target)
- 타깃 데이터에 2개 이상의 클래스가 포함된 문제를 다중분류라고 함
- 사이킷런에서는 0과 1이 아닌 문자열로 된 타깃값 그대로 사용 가능(순서 알파벳 순으로 매겨짐) -> 정렬된 타깃값 확인해야함
-
print(kn.classes_) # ['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish'] - 테스트 세트 처음 5개 샘플 예측
-
print(kn.predict(test_scaled[:5])) # ['Perch' 'Smelt' 'Pike' 'Perch' 'Perch'] import numpy as np #예측 확률 자세히 살펴보기 proba = kn.predict_proba(test_scaled[:5]) # predict_proba() 메서드로 클래스별 확률값 반환 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. ]] """ - 첫번째 열이 bream 일 확률, 두번째 열이 parkki일 확률….

- 실제 네 번째 샘플 최근접 이웃 확인해보기
-
distances, indexes = kn.kneighbors(test_scaled[3:4]) print(train_target[indexes]) # [['Roach' 'Perch' 'Perch']] # 세 번째 클래스인 perch가 2개, 다섯 번째 클래스인 roach가 1개 -> 세 번째 클래스에 대한 확률 0.6667, 다섯 번째 클래스에 대한 확률 0.3333
로지스틱 회귀
- 이름은 회귀이지만 분류모델
- 선형 회귀와 동일하게 선형 방정식 학습
- 결과값이 확률이 되려면 0~1 또는 0~100% 사이 값이 돼야 함 -> 시그모이드 함수(Sigmoid functioon) 로지스틱 함수(Logistic function) 사용

- 시그모이드 사용 시 결과값 z가 무한하게 큰 음수일 경우 0에 가까워지고 무한하게 큰 양수일 경우 1에 가까워짐
- 도미와 빙어 이진 분류 수행해보기
- 이진 분류의 경우 시그모이드 함수의 출력이 0.5보다 크면 양성 클래스, 작으면 음성 클래스로 판단
-
bream_smelt_indexes = (train_target == 'Bream') | (train_target == 'Smelt') #도미 또는 빙어면 true 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_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.coef_, lr.intercept_) # [[-0.4037798 -0.57620209 -0.66280298 -1.01290277 -0.73168947]] [-2.16155132] # 식: z= -0.404 * (weight) -0.576 * (length) -0.663 * (diagonal) -1.013 * (height) -0.732 * (widtb) -2.161 decisions = lr.decision_function(train_bream_smelt[:5]) # decision_function() 메서드로 z값 계산 print(decisions) # [-6.02927744 3.57123907 -5.26568906 -4.24321775 -6.0607117 ] from scipy.special import expit #시그모이드 함수 print(expit(decisions)) #시그모이드 함수에 z값 통과해서 확률값 변환 # [0.00240145 0.97264817 0.00513928 0.01415798 0.00232731] # predict_proba() 메서드 출력의 두 번째 열과 동일 -> decision_function() 메서드는 양성 클래스에 대한 z값 반환
- 다중 분류 수행
- LogisticRegression 클래스는 기본적으로 반복적 알고리즘 사용
- max_iter 매개변수로 반복 횟수 설정(기본값 100)
- LogisticRegression 클래스는 기본적으로 릿지 회귀와 같이 계수의 제곱 규제
- 이 규제를 L2 규제라고 부름
- 규제를 제어하는 매개변수 C, C는 alpha와 반대로 작을수록 규제 커짐(기본값 1)
-
lr = LogisticRegression(C=20, max_iter=1000) #C=20으로 설정해 규제 완화, 반복횟수 1000으로 늘리기 lr.fit(train_scaled, train_target) print(lr.predict(test_scaled[:5])) # 예측값 출력 # ['Perch' 'Smelt' 'Pike' 'Roach' 'Perch'] print(lr.classes_) # ['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish'] 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]] ''' # 첫번째 행: 세번째 열일 확률 높음 -> perch # 두번째 행: 여섯번째 열일 확률 높음 -> smelt -
print(lr.coef_.shape, lr.intercept_.shape) # (7, 5) (7,) # 5개의 특성 사용하므로 coef_ 배열의 열 5개, 근데 행과 intercept_가 7개 -> z 7개 계산한다는 뜻 - 다중분류는 클래스마다 z값을 하나씩 계산 -> 가장 높은 z 값을 출력하는 클래스가 예측 클래스
- 다중분류는 이진 분류와 달리 시그모이드 함수가 아닌 소프트맥스 함수 사용
- 소프트맥스 함수는 여러 개의 선형방정식 출력값을 0~1 사이로 압축하고 전체 합이 1이 되도록 함
- 정규화된 지수 함수라고도 부름
- 7개의 z 값을 z1 ~ z7이라고 하면
- 7개 생선에 대한 확률의 합은 1
-
decision = lr.decision_function(test_scaled[:5]) # decision_function()메서드로 z1~z7의 값 구하기 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) # 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와 결과 동일
- LogisticRegression 클래스는 기본적으로 반복적 알고리즘 사용
점진적 학습
- 앞서 훈련한 모델을 버리지 않고 새로운 데이터에 대해서만 조금씩 더 훈련하는 방식
- 온라인 학습이라고도 함
- 대표적 알고리즘 확률적 경사 하강법
- 확률적 = 무작위 = 랜덤 -> 훈련세트에서 하나의 샘플 랜덤하게 고르기
- 경사하강법 = 경사를 따라 내려가는 방법 -> 가장 빠른 길 선택 -> 가장 가파른 경사를 따라 원하는 지점에 도달 but 조금씩 내려와야 함
- 훈련세트에서 하나의 샘플 랜덤하게 골라 가장 가파른 길 찾기, 그다음 훈련세트에서 랜덤하게 샘플 선택해 경사 내려오기 이걸 전체 샘플 다 쓸 때까지
- 모든 샘플 다 사용해도 산을 다 못 내려왔다면 다시 처음부터 시작 에포크 = 훈련세트를 한 번 모두 사용하는 과정
- 여러 개의 샘플 골라서 경사 하강하기 -> 미니배치 경사 하강법
- 전체 샘플 사용해서 경사 하강하기 -> 배치 경사 하강법

- 여기서 산이란 손실함수
- 머신러닝 알고리즘이 얼마나 엉터리인지 측정하는 기준
- 비용함수라고도 함
- 작을수록 좋음
- 연속적이어야 함
로지스틱 손실 함수
- 이진분류는 로지스틱 손실 함수(이진 크로스엔트로피 손실함수) 사용
- 양성클래스(타깃=1)일 때 손실은 -log(예측확률)로 계산
- 확률이 1에서 멀어질수록 손실은 커짐
- 음성클래스(타깃=0)일 때 손실은 -log(1-예측확률)로 계산
- 예측확률이 0에서 멀어질수록 손실 커짐
-
다중분류는 크로스엔트로피 손실함수 사용
-
from sklearn.linear_model import SGDClassifier #확률적 경사 하강법 클래스 제공 sc = SGDClassifier(loss='log_loss', max_iter=10, random_state=42) #SGDClassifier의 객체 만들 때 2개의 매개변수 지정 #loss는 손실함수 종류 -> 로그로 지정 # max_iter는 에포크 횟수 -> 10회 반복 sc.fit(train_scaled, train_target) # ConvergenceWarning: Maximum number of iteration reached before convergence. Consider increasing max_iter to improve the fit. 경고가 뜸 max-iter 값 늘려주기 sc.partial_fit(train_scaled, train_target) # 훈련한 모델 sc를 추가로 더 훈련해보기 -> partial_fit() 메서드 사용( fit()과 비슷하지만 1에포크씩 이어서 훈련 ) - 에포크 횟수가 너무 적으면 과소적합 가능성 o
- 반대로 많으면 과대적합 가능성 o

- 과대적합 시작하기 전 훈련을 멈추는 것을 조기종료라고 함
-
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): # 에포크 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() 
- 에포크 초기는 과소적합 형태
-
100번째 에포크 이후로 세트 간 점수차 벌어짐 -> 에포크 100으로 설정
-
sc = SGDClassifier(loss='log_loss', max_iter=100, tol=None, random_state=42) # SGDClassifier는 일정 에포크 동안 성능이 향상되지 않으면 자동으로 멈춤 -> tol=None으로 무조건 100번 반복하게 함 sc.fit(train_scaled, train_target) print(sc.score(train_scaled, train_target)) # 0.957983193277311 print(sc.score(test_scaled, test_target)) # 0.925