- 공유 링크 만들기
- 이메일
- 기타 앱
내용
이진분류(Binary Classification)
MNIST 자료 호출
MNIST 데이터셋트에 대해 분류 문제를 적용합니다.
import numpy as np import pandas as pd from sklearn.datasets import fetch_openml import matplotlib as mpl import matplotlib.pyplot as plt import pickle, gzip
다음의 mnist 자료는 kaggle로 부터 다운받은 자료를 pickle.load()함수로 압축을 해제하여 호출한 것입니다. 이 자료는 이미 훈련, 검정, 테스트 셋트로 분리된 상태입니다. 각 그룹에는 특징들과 라벨이 튜플 형식으로 포함되어 있습니다.
with gzip.open('mnist.pkl.gz', 'rb')as f: tr, val, te=pickle.load(f, encoding="latin1")
tr[0].shape, val[0].shape, te[0].shape
((50000, 784), (10000, 784), (10000, 784))
Xtr, ytr=tr[0], tr[1] Xval, yval=val[0], val[1] Xte, yte=te[0], te[1]
plt.imshow(Xtr[0,:].reshape(28, 28)) plt.axis('off') plt.show()
ytr[0], ytr.dtype
(5, dtype('int64'))
라벨의 자료형은 목록형이므로 숫자형으로 전환
이진분류기, SGD classifier
데이터 mnist에서 숫자 하나를 분류해 봅니다. 예를 들어 숫자 '5'와 나머지를 분류하는 분류기를 생성합니다. 이러한 분류는 이진분류기(binary classifier)가 됩니다. 이 목적에 따라 라벨을 다음과 같이 생성합니다. 다음 코드는 라벨이 5이면 True 아니면 False로 반환됩니다.
ytr5=(ytr==5) yte5=(yte==5)
ytr5[:10]
array([ True, False, False, False, False, False, False, False, False, False])
이진분류를 위해 SGD classifier(Stochastic Gradient Descent, 분류기)를 적용합니다.
- 확률적 경사하강법(Stochastic Gradient Descent)을 적용한 정규화된 성형 분류모델
- 계산값 < 0 &rightarr; -1
- 계산값 > 0 &rightarr; 1
from sklearn.linear_model import SGDClassifier
sgdClf=SGDClassifier(random_state=1) sgdClf.fit(Xtr, ytr5)
SGDClassifier(random_state=1)
Xtr[0].shape
(784,)
sgdClf.predict(Xtr[0].reshape(1, -1))
array([ True])
교차검증(Cross-Validation)
위에서 구축한 모델의 성능을 평가하기 위해 층화 kfold 교차평가를 실시합니다. 이 과정에서 생성된 모델의 실제 변수의 복사없이 모델의 매개변수만을 복사(deep copy)하는 sklearn.base.clone()
함수를 적용합니다.
from sklearn.model_selection import StratifiedKFold from sklearn.base import clone
skfold=StratifiedKFold(n_splits=3, shuffle=True, random_state=1) acc={} pred={} n=1 for trIdx, teIdx in skfold.split(Xtr, ytr5): sgdClf_clone=clone(sgdClf) XtrF, ytr5F=Xtr[trIdx], ytr5[trIdx] XteF, yte5F=Xtr[teIdx], ytr5[teIdx] sgdClf_clone.fit(XtrF, ytr5F) pre=sgdClf_clone.predict(XteF) acc[n]=sum(yte5F==pre)/len(pre) pred[n]=pre n +=1
acc
{1: 0.9722805543889123, 2: 0.9743805123897522, 3: 0.9747389895595824}
predT=np.concatenate([pred[1], pred[2], pred[3]]) predT.shape
(50000,)
위 교차평가는 다음sklearn.model_selection.cross_val_score(estimator, X, y, cv, scoring, …)
함수를 사용하여 나타낼 수 있습니다.
sklearn.model_selection.cross_val_score(estimator, X, y, cv, scoring, …)
- cv: 는 교차평가의 분류 mechanism을 지정
- 정수: starified KFold 교차평가를 사용하여 분류하는 그룹수
- None: 5-Fold cross vaalidation을 지정한 것과 같음
- scoring: 평가방식으로 지정하는 것으로 score(estimator, X, y) 또는 "accuracy"와 같이 단일한 metric이 될 수도 있습니다.
from sklearn.model_selection import cross_val_score, cross_val_predict
cross_val_score(sgdClf, Xtr, ytr5, cv=3, scoring="accuracy")
array([0.9749805 , 0.97246055, 0.97119885])
혼동행렬
분류기의 성능을 평가하는 다른 방법으로 혼동행렬(confusion matrix)를 사용할 수 있습니다. 위의 "5"에 대한 실제값과 예측값 사이의 관계는 다음과 같이 나타낼 수 있습니다.
5(Positive) | not 5(Negative) | ||
---|---|---|---|
관측 | 5 | True 5(TP) | False not 5(FN) |
not 5 | False 5(FP) | True not 5(TN) |
위와 같은 표는 라벨 클래스 5에 대한 실측값과 예측값을 비교하여 나타낸 것으로 이 결과를 행렬로 표시하면 혼동행렬됩니다. 다음과 같이 계산할 수 있습니다.
위에서 층화 k fold 교차평가를 실시하였습니다. 이 평가에 대한 추정은 cross_val_prdict(추정기, X, y, cv)
함수를 사용하여 계산할 수 있습니다. 위에서 계산한 교차평가의 경우 3개의 그룹으로 분리하였으며 각 그룹은 훈련과 테스트 셋트로 분리하여 테스트 세트의 추정치를 계산합니다. 그러므로 모든 추정치의 수는 데이터 수와 같습니다.
ytr5Pre=cross_val_predict(sgdClf, Xtr, ytr5, cv=3)
re={"TP":0, "FT":0, "FN":0, "TN":0 } for i, j in zip(ytr5, ytr5Pre): if (i==True) & (j ==True): re["TP"] += 1 elif (i==False) & (j ==True): re["FT"] += 1 elif (i==True) & (j ==False): re["FN"] += 1 else: re["TN"] +=1 re
{'TP': 3602, 'FT': 452, 'FN': 904, 'TN': 45042}
위 결과는 혼동행렬을 반환하는 sklearn.metrics.confusion_matrix(y, y_predict)
함수를 적용할 수 있습니다.
ytr5Pre.shape, ytr5.shape
((50000,), (50000,))
from sklearn.metrics import confusion_matrix
cm=confusion_matrix(ytr5, ytr5Pre) cm
array([[45042, 452], [ 904, 3602]])
위 혼동행렬(cm)의 각 행은 실측값, 열은 예측값을 나타냅니다. 다음과 같습니다.
not 5 | 5 | ||
---|---|---|---|
관측 | not 5 | 45042(TN) | 452(FP) |
5 | 904(FN) | 3602(TP) |
위에서 평가항목으로 사용한 정확도는 관측값과 추정값이 일치한 경우입니다.
$$\begin{align}\tag{acc}\text{Accuracy}&=\frac{TP+TN}{TP+FP+FN+TN}\\&=\frac{45042+3602}{45042+904+452+3602}\\&=0.97288\end{align}$$accuracy=(cm[0,0]+cm[-1,-1])/np.sum(cm) accuracy
0.97288
위 혼동행렬로 부터 정확도외에 더 많은 정보를 알 수 있습니다. 그러한 정보들 중에 양성예측의 정확도 즉, 정밀도(precision)와 재현율(recall)은 분류메커니즘 평가에 중요하게 사용됩니다. 재현율은 민감도(sensitive) 또는 참양성율(true positive rate)라고 합니다.
$$\begin{align}\tag{prec}\text{precision}&=\frac{TP}{TP+FP}\\&=\frac{452}{452+3602}\end{align}$$ $$\begin{align}\tag{recall}\text{recall}&=\frac{TP}{TP+FN}\\&=\frac{3602}{3602+904}\end{align}$$precision=(cm[-1,-1])/(np.sum(cm[:,-1])) precision
0.8885051800690675
recall=(cm[-1, -1])/(np.sum(cm[-1,:])) recall
0.7993786063027075
precision과 recall은 sklearn.metrics 모듈의 precion_score(y, y_predict)
, reacll_score(y, y_predict)
함수에 의해 계산할 수 있습니다.
from sklearn.metrics import precision_score, recall_score, f1_score
precision_score(ytr5, ytr5Pre)
0.8885051800690675
recall_score(ytr5, ytr5Pre)
0.7993786063027075
위의 두 평가항목을 결합하여 사용할 수 있으며 F1 score라고 합니다. F1 score는 정밀도와 재현율의 조화평균입니다.
$$\begin{align}\tag{F1} F_1&=\frac{2}{\frac{1}{\text{precision}}+\frac{1}{\text{recall}}}\\&=2 \cdot \frac{\text{precision}\cdot\text{recall}}{\text{precision}+\text{recall}}\\&=\frac{TP}{TP+\frac{FN+FP}{2}} \end{align}$$이 평가는 sklearn.metrics.f1_score(y, y_predict)
함수를 사용하여 계산할 수 있습니다.
f1_score(ytr5, ytr5Pre)
0.841588785046729
정밀도/재현율 트레이드오프
어떤 경우에는 주로 정밀도에 관심이 있고 다른 상황에서는 주로 재현율에 관심이 있습니다.
동영상의 다운로드를 조절하기 위한 모델을 생성한다고 할 때 어린이를 위한 폭력적이고 성인용 비디오를 제한하는 제한 모드가 가동됩니다. 따라서 모델은 예측 결과에서 잘못이 최소화되는 것이 더 중요합니다. 즉, False Positive(FP)을 줄여 고정밀 $\frac{TP}{TP+FP}$에 초점을 맞춥니다. 즉, 더 높은 정밀도를 만들 것입니다.
다른 경우로 쇼핑몰에서 물건을 훔치는 사람을 감지하는 모델의 경우 진짜 도둑 중에 도둑의 예측을 높여야 합니다. 즉, 진짜 도둑 중에 도둑이 아니라고 분류하는 False Negative를 감소시켜야 합니다. $\frac{TP}{TP+FN}$, 즉 재현율(recall)이 증가하는 방향으로 모델이 생성되어야 할 것입니다.
위에서 나타낸 정밀도와 재현율에서 각각은 FP와 FN의 변화에 좌우됩니다. 대부분의 경우 이 두 인자가 동시에 감소되는 경우는 매우 희소합니다. 즉, 대부분의 경우 정밀도와 재현율을 모두 높일 수는 없습니다. 정밀도를 높이면 재현율이 감소하고 그 반대의 경우도 마찬가지입니다. 이것을 정밀도/재현율 트레이드오프(precision/recall tradeoff)라고 합니다.
Scikit-Learn을 사용하면 임계값을 직접 설정할 수 없지만 예측에 사용하는 결정 점수(score)에 액세스할 수 있습니다. 분류기의 predict() 메서드를 호출하는 대신 각 인스턴스에 대한 점수를 반환하는 decision_function()
메서드를 호출한 다음 원하는 임계값을 사용하여 해당 점수를 기반으로 예측을 수행할 수 있습니다.
그 점수는 분류기의 종류에 따라 결정에 대한 positive class에 포함할 확률이 될 수도 있습니다.
yScore=sgdClf.decision_function(Xtr) yScore[0]
1.3146554711230483
decision_function() 메서드에 의한 각 샘플의 점수는 교차평가에 의한 추정값을 반환하는 cross_val_predict(추정기, x, y, cv, method)
함수에서도 적용할 수 있습니다. 이 함수의 매개변수 method에 decision_function을 인수로 전달하여 샘플의 score를 반환합니다.
yScore=cross_val_predict(sgdClf, Xtr, ytr5, cv=3, method="decision_function") yScore[0]
1.2209962634221634
True/False의 분류기준 점수인 임계점(threshold)을 기준으로 혼동행렬은 작성하여 보면 다음과 같습니다.
threshold = 0 yPre2=(yScore>threshold) yPre2[:10]
array([ True, False, False, False, False, False, False, False, False, False])
ytr5_2=[True if i==1 else False for i in ytr5 ] ytr5_2[:10]
[True, False, False, False, False, False, False, False, False, False]
confusion_matrix(ytr5_2, yPre2)
array([[45042, 452], [ 904, 3602]])
cm
array([[45042, 452], [ 904, 3602]])
임계점=0인 경우는 임계점을 정하지 않은 상태의 예측결과와 같습니다. 임계점을 상승하거나 감소시키면 결과는 다음과 같습니다.
thd=3 yPreUp=(yScore>thd) confusion_matrix(ytr2, yPreUp)
array([[45473, 21], [ 3541, 965]])
thdDn=-3 yPreDn=(yScore>thdDn) confusion_matrix(ytr2, yPreDn)
array([[34714, 10780], [ 166, 4340]])
위의 결과는 임계점의 변화에 혼동행렬의 변화를 보입니다. 이 차이는 정밀도와 재현율의 변화로 명확해 집니다.
thd=[-3, 0, 3] Score=[yPreDn,yPre2, yPreUp] re={} for i in range(3): re[thd[i]]=[precision_score(ytr2, Score[i]), recall_score(ytr2, Score[i])] re1=pd.DataFrame(re.values(), index=thd, columns=['precision','recall']) re1
precision | recall | |
---|---|---|
-3 | 0.287037 | 0.963160 |
0 | 0.888505 | 0.799379 |
3 | 0.978702 | 0.214159 |
임계값을 낮게할 경우 threshold=0에 비해 정밀도는 감소하고 재현율은 증가합니다. 이 결과는 precision_recall_curve(y, yScore 또는 확률)
함수를 적용할 수 있습니다. 이 함수는 0, 1 사이에서 가능한 임계점(FP와 FN은 음이 아닌 상태)에서의 정밀도, 재현율, 임계점등을 반환합니다.
from sklearn.metrics import precision_recall_curve
precision, recall, threshold=precision_recall_curve(ytr5, yScore) threshold.shape, recall.shape, precision.shape
((49794,), (49795,), (49795,))
plt.figure(dpi=100) plt.plot(threshold, precision[:-1], label="precision") plt.plot(threshold, recall[:-1], label="recall") plt.legend(loc="best") plt.xlabel("threshold", weight="bold") plt.show()
plt.figure(dpi=100) plt.plot(recall, precision) plt.xlabel("recall", weight="bold") plt.ylabel("precision", weight="bold") plt.grid(True) plt.show()