기본 콘텐츠로 건너뛰기

[Linear Algebra] 고유벡터(Eigenvector)와 고유값(Eigenvalue) 연습

이진분류(Binary Classification)

내용

이진분류(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)
관측5True 5(TP)False not 5(FN)
not 5False 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 55
관측not 545042(TN)452(FP)
5904(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()

이 블로그의 인기 게시물

유사변환과 대각화

내용 유사변환 유사행렬의 특성 대각화(Diagonalization) 유사변환(Similarity transformation) 유사변환 n×n 차원의 정방 행렬 A, B 그리고 가역 행렬 P 사이에 식 1의 관계가 성립하면 행렬 A와 B는 유사하다고 하며 이 변환을 유사 변환 (similarity transformation)이라고 합니다. $$\begin{equation}\tag{1} A = PBP^{-1} \Leftrightarrow P^{-1}AP = B \end{equation}$$ 식 1의 유사 변환은 다음과 같이 고유값을 적용하여 특성 방정식 형태로 정리할 수 있습니다. $$\begin{align} B - \lambda I &= P^{-1}AP – \lambda P^{-1}P\\ &= P^{-1}(AP – \lambda P)\\ &= P^{-1}(A - \lambda I)P \end{align}$$ 위 식의 행렬식은 다음과 같이 정리됩니다. $$\begin{align} &\begin{aligned}\textsf{det}(B - \lambda I ) & = \textsf{det}(P^{-1}(AP – \lambda P))\\ &= \textsf{det}(P^{-1}) \textsf{det}((A – \lambda I)) \textsf{det}(P)\\ &= \textsf{det}(P^{-1}) \textsf{det}(P) \textsf{det}((A – \lambda I))\\ &= \textsf{det}(A – \lambda I)\end{aligned}\\ &\begin{aligned}\because \; \textsf{det}(P^{-1}) \textsf{det}(P) &= \textsf{det}(P^{-1}P)\\ &= \t

[matplotlib] 히스토그램(Histogram)

히스토그램(Histogram) 히스토그램은 확률분포의 그래픽적인 표현이며 막대그래프의 종류입니다. 이 그래프가 확률분포와 관계가 있으므로 통계적 요소를 나타내기 위해 많이 사용됩니다. plt.hist(X, bins=10)함수를 사용합니다. x=np.random.randn(1000) plt.hist(x, 10) plt.show() 위 그래프의 y축은 각 구간에 해당하는 갯수이다. 빈도수 대신 확률밀도를 나타내기 위해서는 위 함수의 매개변수 normed=True로 조정하여 나타낼 수 있다. 또한 매개변수 bins의 인수를 숫자로 전달할 수 있지만 리스트 객체로 지정할 수 있다. 막대그래프의 경우와 마찬가지로 각 막대의 폭은 매개변수 width에 의해 조정된다. y=np.linspace(min(x)-1, max(x)+1, 10) y array([-4.48810153, -3.54351935, -2.59893717, -1.65435499, -0.70977282, 0.23480936, 1.17939154, 2.12397372, 3.0685559 , 4.01313807]) plt.hist(x, y, normed=True) plt.show()

R 미분과 적분

내용 expression 미분 2차 미분 mosaic를 사용한 미분 적분 미분과 적분 R에서의 미분과 적분 함수는 expression()함수에 의해 생성된 표현식을 대상으로 합니다. expression expression(문자, 또는 식) 이 표현식의 평가는 eval() 함수에 의해 실행됩니다. > ex1<-expression(1+0:9) > ex1 expression(1 + 0:9) > eval(ex1) [1] 1 2 3 4 5 6 7 8 9 10 > ex2<-expression(u, 2, u+0:9) > ex2 expression(u, 2, u + 0:9) > ex2[1] expression(u) > ex2[2] expression(2) > ex2[3] expression(u + 0:9) > u<-0.9 > eval(ex2[3]) [1] 0.9 1.9 2.9 3.9 4.9 5.9 6.9 7.9 8.9 9.9 미분 D(표현식, 미분 변수) 함수로 미분을 실행합니다. 이 함수의 표현식은 expression() 함수로 생성된 객체이며 미분 변수는 다음 식의 분모의 변수를 의미합니다. $$\frac{d}{d \text{변수}}\text{표현식}$$ 이 함수는 어떤 함수의 미분의 결과를 표현식으로 반환합니다. > D(expression(2*x^3), "x") 2 * (3 * x^2) > eq<-expression(log(x)) > eq expression(log(x)) > D(eq, "x") 1/x > eq2<-expression(a/(1+b*exp(-d*x))); eq2 expression(a/(1 + b * exp(-d * x))) > D(eq2, "x") a * (b * (exp(-d * x) * d))/(1 + b