1. 로지스틱 회귀
1) 럭키백의 확률
- 럭키백에 들어갈 수 있는 생선은 7개
- 럭키백에 들어간 생선의 크기, 무게 등이 주어졌을 때 7개 생선에 대한 확률을 출력
- K-최근점 이웃은 주변 이웃을 찾아주니까 이웃의 클래스 비율을 확률이라고 출력
- X 주위에 가장 가까운 이웃 샘플 10개를 표시
- 사각형이 3개, 삼각형이 5개, 원이 2개
- 이웃한 샘플의 클래스를 확률로 삼는다면 샘플 X가 사각형일 확률은 30%, 삼각형일 확률은 50%, 원일 확률은 20%이다.
데이터 준비
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')
fish.head()
- 어떤 종류의 생선이 있는지 Species 열에서 고유한 값 추출 -> 판다스의 unique() 함수를 사용
print(pd.unique(fish['Species']))
- Species 열을 타깃으로 만들고 나머지 5개의 열은 입력 데이터로 사용
- 데이터프레임에서 원하는 열을 리스트로 나열하여 열을 선택
- 데이터프레임에서 여러 열을 선택하면 새로운 데이터프레임이 반환됨, 이를 to_numpy() 메서드로 넘파이 배열로 바꾸어 fish_input에 저장
fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()
print(fish_input[:5])
- 데이터를 훈련 세트와 테스트 세트로 나누기
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))
print(kn.score(test_scaled, test_target))
- 타깃 데이터에 2개 이상의 클래스가 포함된 문제를 다중 분류(multi-class classification)이라고 함
- 이진 분류를 사용했을 때는 양성 클래스와 음성 클래스를 각각 1과 0으로 지정하여 타깃데이터를 만든다.
- 다중 분류에서도 타깃값을 숫자로 바꾸어 입력할 수 있지만 사이킷런에서는 편리하게도 문자열로 된 타깃값을 그대로 사용할 수 있다.
- KNeighborsClassifier에서 정렬된 타깃값은 classes_속성에 저장되어 있다.
print(kn.classes_)
- 테스트 세트에 있는 처음 5개 샘플의 타깃값을 예측
print(kn.predict(test_scaled[:5]))
- 사이킷런의 분류 모델은 predict_proba() 메서드로 클래스별 확률값을 반환
import numpy as np
proba = kn.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=4))
- 이 모델이 계산한 확률이 가장 가까운 이웃의 비율이 맞는지 확인
- 네 번째 샘플의 최근접 이웃의 클래스를 확인
distances, indexes = kn.kneighbors(test_scaled[3:4])
print(train_target[indexes])
2) 로지스틱 회귀
- 이름은 회귀이지만 분류 모델
- 선형 회귀와 동일하게 선형 방정식을 학습
z = a *(Weight) + b*(Length) + c*(Diagonal) + d*(Height) + e*(Width) +f
- a,b,c,d,e는 가중치 혹은 계수
- z는 어떤 값도 가능하지만 확률이 되려면 0~1 사이의 값이 되어야 한다.
- 시그모이드함수(sigmoid function)를 사용하여 z가 아주 큰 음수일 때 0이 되고, z가 아주 큰 양수일 때 1이 되도록 바꾼다.
- z가 0이 될때는 0.5가 된다.
- 선형 방정식의 출력 z의 음수를 사용해 자연 상수 e를 거듭제곱하고 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()
로지스틱 회귀로 이진 분류 수행하기
- 넘파이 배열은 True, False 값을 전달하여 행을 선택할 수 있다. 이를 불리언 인덱싱(boolean indexing)이라고 한다.
- 훈련 세트에서 도미(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]
- bream_smelt_indexes 배열은 도미와 빙어일 경우 True이고 그 외에는 모두 False 값이 들어있음
- 이 배열을 사용해 train_scaled와 train_target 배열에 불리언 인덱싱을 적욯하면 손쉽게 도미와 빙어 데이터만 골라낼 수 있다.
- LogisticRegression 클래스로 로지스틱 회귀 모델 훈련하고 train_bream_smelt에 있는 처음 5개 샘플을 예측
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)
print(lr.predict(train_bream_smelt[:5]))
- train_bream_smelt에서 처음 5개 샘플의 예측 확률 출력
print(lr.predict_proba(train_bream_smelt[:5]))
- 첫 번째 열이 음성 클래스(0)에 대한 확률이고 두 번째 열이 양성 클래스(1)에 대한 확률이다.
print(lr.classes_)
- 빙어(Smelt)가 양성 클래스
- 로지스틱 회귀가 학습한 계수 확인
- coef_ 속성과 intercept_ 속성에는 로지스틱 모델이 학습한 선형 방정식의 계수가 들어있다.
print(lr.coef_, lr.intercept_)
- 로지스틱 회귀 모델이 학습한 방정식
z = -0.404 * (Weight) - 0.576*(Length) - 0.663*(Diagonal) - 1.013*(Height) - 0.732*(Width) - 2.161
- LogisticRegression 클래스는 decision_function() 메서드로 z값을 출력할 수 있다.
decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions)
- z 값을 시그모이드 함수에 통과시키면 확률을 알 수 있다.
- 파이썬의 사이파이(scipy) 라이브러리의 시그모이드 함수(expit())를 사용
from scipy.special import expit
print(expit(decisions))
- decision_function() 메서드는 양성 클래스에 대한 z값을 반환한다.
로지스틱 회귀로 다중 분류 수행하기
- LogisticRegression 클래스를 사용해 7개의 생선을 분류
- LogisticRegression 클래스는 기본적으로 반복적인 알고리즘을 사용
- max_iter 매개변수에서 반복 횟수를 지정하며 기본값은 100
- LogisticRegression은 릿지 회귀와 같이 계수의 제곱을 규제 -> L2규제
- 규제를 제어하는 매개변수는 C이고 작을수록 규제가 커진다.
- LogisticRegression 클래스로 다중 분류 모델을 훈련하고 처음 5개 샘플에 대한 에측 출력
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))
print(lr.predict(test_scaled[:5]))
- 테스트 세트의 처음 5개 샘플에 대한 예측 확률을 출력
proba = lr.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=3))
- 5개 샘플에 대한 예측이므로 5개의 행이 출력되고, 7개 생선에 대한 확률을 계산했으므로 7개의 열이 출력
- classes_ 속성에서 클래스 정보를 확인
print(lr.classes_)
- 이진 분류는 샘플마다 2개의 확률을 출력하고 다중 분류는 샘플마다 클래스 개수만큼 확률을 출력
- coef_와 intercept_ 의 크기 출력
print(lr.coef_.shape, lr.intercept_.shape)
- 다중 분류는 클래스마다 z값을 하나씩 계산
- 가장 높은 z값을 출력하는 클래스가 예측 클래스가 된다
- 다중 분류는 소프트맥스(softmax)함수를 사용하여 7개의 z값을 확률로 변환
소프트맥스 함수
- 여러 개의 선형 방정식의 출력값을 0~1 사이로 압축하고 전체 합이 1이 되도록 만든다.
- decision_function() 메서드로 z1 ~ z7까지의 값을 구한 다음 소프트맥스 함수를 사용해 확률로 변환
decision = lr.decision_function(test_scaled[:5])
print(np.round(decision, decimals=2))
- scipy.special 아래에 softmax() 함수를 임포트해서 사용
- 앞서 구한 decision 배열을 softmax() 함수에 전달
- softmax()의 axis 매개변수는 소프트맥스를 계산할 축을 지정
- axis=1로 지정하여 각 행, 즉 각 샘플에 대해 소프트맥스를 계산
- axis 매개변수를 지정하지 않으면 배열 전체에 대해 소프트맥스를 계산
from scipy.special import softmax
proba = softmax(decision, axis=1)
print(np.round(proba, decimals=3))
2. 확률적 경사 하강법
1) 점진적인 학습
- 이전에 훈련한 모델을 버리고 다시 새로운 모델을 학습하는 방식
- 앞서 훈련한 모델을 버리지 않고 새로운 데이터에 대해서만 조금씩 더 훈련
확률적 경사 하강법(Stochastic Gradient Descent)
- 훈련세트에서 랜덤하게 하나의 샘플을 구하는 것
- 훈련세트에서 랜덤하게 하나의 샘플을 선택하여 가파른 경사를 조금씩 내려간다. 그 다음 훈련 세트에서 랜덤하게 또 다른 샘플을 하나 선택하여 경사를 조금 내려간다. 이런식으로 전체 샘플을 모두 사용할 때까지 계속한다.
에포크(epoch)
- 확률적 경사 하강법에서 훈련 세트를 한 번 모두 사용하는 과정
미니배치 경사 하강법(minibatch gradient descent)
- 여러 개의 샘플을 사용해 경사 하강법을 수행하는 방식
배치 경사 하강법(batch gradient descent)
- 경사로를 따라 이동하기 위해 전체 샘플을 사용
손실 함수(loss function)
- 어떤 문제에서 머신러닝 알고리즘이 얼마나 엉터리인지를 측정하는 기준
로지스틱 손실 함수
- 양성 클래스(타깃 = 1)일 때 손실은 -log(예측 확률)로 계산
- 확률이 1에서 멀어져 0에 가까워질수록 손실은 아주 큰 양수가 된다.
- 음성 클래스(타깃 = 0)일 때 손실은 -log(1-예측 확률)로 계산
이진 분류 -> binaray cross-entropy loss function
다중 분류 ->cross-entropy loss function
2) SGDClassifier
- fish_csv_data 파일에서 판다스 데이터프레임 만들기
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의 객체를 만들 때 2개의 매개변수를 지정
- loss는 손실 함수의 종류를 지정
- loss의 기본값은 'hinge'
-> 힌지 손실(hinge loss)는 서포트 벡터 머신(support vector machine)을 위한 손실 함수
- loss = 'log_loss'로 지정하여 로지스틱 손실 함수를 지정
- 훈련 세트와 테스트 세트에서 정확도 점수를 출력
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))
- 출력된 훈련 세트와 테스트 세트 정확도가 낮다.
- SGDClassfier 객체를 다시 만들지 않고 훈련한 모델 sc를 추가로 더 훈련
- 모델을 이어서 훈련할 때는 partial_fit() 메서드를 사용
- 호출할 때마다 1 에포크씩 이어서 훈련 가능
sc.partial_fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target))
- 아직 점수가 낮지만 에포크를 한 번 더 실행하니 정확도가 향상됨
3) 에포크와 과대/과소 적합
- 적은 에포크 횟수 동안에 훈련한 모델은 훈련 세트와 테스트 세트에 잘 맞지 않는 과소적합된 모델일 가능성이 높다.
- 많은 에포크 횟수 동안에 훈련한 모델은 훈련 세트에 너무 잘맞아 테스트 세트에는 오히려 점수가 나쁜 과대적합될 모델일 가능성이 높다.
- 훈련 세트 점수는 에포크가 진행될수록 꾸준히 증가하지만 테스트 세트 점수는 어느 순간 감소하기 시작한다. (과대적합이 시작되는 지점)
- 과대적합이 시작하기 전에 훈련을 멈추는 것을 조기 종료(early stopping)라고 한다.
ㄸ
- partical_fit() 메서드만 사용하려면 훈련 세트에 있는 전체 클래스의 레이블을 partial_fit() 메서드에 전달해주어야 한다.
- np.unique() 함수로 train_target에 있는 7개 생선의 목록을 만든다.
- 에포크마다 훈련 세트와 테스트 세트에 대한 점수를 기록하기 위해 2개의 리스트를 준비한다.
import numpy as np
sc = SGDClassifier(loss='log_loss', random_state=42)
train_score = []
test_score = []
classes = np.unique(train_target)
- 300번의 에포크 동안 훈련을 반복하여 진행
- 반복마다 훈련 세트와 테스트 세트의 점수를 계산하여 train_score, test_score 리스트에 추가
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번의 에포크 동안 기록한 훈련 세트와 테스트 점수를 그래프로 그리기
->100번째 에포크 이후에는 훈련 세트와 테스트 세트의 점수가 조금씩 벌어지므로 100번째 에포크가 적절한 반복횟수
import matplotlib.pyplot as plt
plt.plot(train_score)
plt.plot(test_score)
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.show()
- SDGClassfier의 반복 횟수를 100에 맞추고 모델을 다시 훈련
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))
print(sc.score(test_scaled, test_target))
- SGDClassfier는 일정 에포크 동안 성능이 향상되지 않으면 더 훈련하지 않고 자동으로 멈춘다.
- tol 매개변수에서 향상될 최솟값을 지정한다.
- tol 매개변수를 None으로 지정하여 자동으로 멈추지 않고 max_iter=100만큼 무조건 반복하도록 하였다.
'데이터 > 머신러닝' 카테고리의 다른 글
[혼공] ch 6. 비지도 학습 (0) | 2024.06.28 |
---|---|
[혼공] ch 5. 트리 알고리즘 (0) | 2024.06.27 |
[혼공] ch 3. 회귀 알고리즘과 모델 규제 (1) | 2024.06.26 |
[혼공] ch 2. 데이터 다루기 (0) | 2023.12.25 |
[혼공] ch 1. 나의 첫 머신러닝 (1) | 2023.12.23 |