3.1 MNIST
sklearn.datasets : Toy datasets를 담은 lib (크기가 작아 학습용이 아닌 샘플용으로 사용)
일반적인 구조 ⇒ 쓸 수 있는 keys :
- 데이터셋을 설명하는 DESCR 키
- 샘플이 하나의 행, 특성이 하나의 열로 구성된 배열을 가진 data 키
- 레이블 배열을 담은 target 키
- 등등
1 | from sklearn.datasets |
위의 결과는 mnist의 key들로, dictionary 구조를 가진 mnist에 대해 쓸 수 있는 명령들을 의미한다.
- data : 학습용(feed) 데이터
- target : label 데이터로, 분류될 class를 의미
따라서,
1 | X, y = mnist["data"], mnist["target"] |
X.shape는 70000개의 data에 대해 각 data 당 28*28에 해당하는 pixel 강도(0~255)을 담고 있는 배열을 나타내고,
y.shape는 70000개의 분류된 class를 나타낸다.
mnist의 data를 담은 X 배열에서 하나의 이미지를 예시로 추출하여 출력해보도록 한다.
1 | some_digit = X[0] # 0번째 data some_digit_image = some_digit.reshape(28, 28) # 28*28 size에 맞게 벡터 reshape |
some_digit.reshape(28, 28)이 의미하는 바는, 현재 [1, 784]로 저장되어 있는 X[0]의 이미지 벡터는 [28,28]의 배열로 reshaping한다는 것이다.
1 | y[0] >>> '5' |
y에는 분류된 class가 담기므로, X[0]은 ‘5’ class에 속하는 data라는 것이다.
위에서 출력된 것은 ‘(작은따옴표)’로부터 알 수 있듯이 string data이므로 이를 정수로 바꾸는 코드가 다음과 같다.
1 | y = y.astype(np.uint8) |
1장에서 공부했듯이, 위의 MNIST은 지도 학습 모델이므로,
더 좋은 training result를 내기 위해서는 하나의 dataset에서 train dataset과 test dataset을 적절히 나누어야 한다. 따라서 해당 MNIST에서는 6:1로(전체 7만개의 dataset) train 및 test dataset을 나누었다.
또한 MNIST dataset은 데이터셋이 적절히 섞여 있어, 교차 검증 폴드(train / test set 간 분포의 균질성)가 비슷하고 훈련 샘플의 순서에 따른 성능 저하를 방지한다.
3.2 이진 분류기 훈련
이진 분류기 : YES or NO로 class 분류
1 | y_train_5 = (y_train == 5) |
위의 2가지 변수는 “감지기” 그 자체이다. 즉, 오른쪽 조건이 True인지 False인지 판단하는 감지기의 역할을 한다.
분류 모델로 사용할 SGD(Stochastic Gradient Descent) 분류기는 손실함수 자체를 최소화하는 것이 아닌, 손실함수의 기댓값을 최소화하는 방법을 이용한다. 즉, gradient가 아닌 gradient의 기댓값의 추정치를 이용한다. 이에 사용되는 수식은 다음과 같다. 𝑤(𝑘+1)=𝑤(𝑘)+E[∇𝐿]. 모든 학습 데이터를 사용하는 것이 아닌 minibatch(일부 데이터, 하나의 훈련 샘플 그룹)를 이용하여 gradient의 추정치를 구하므로, gradient의 기댓값의 추정치는 표본 평균으로 작용한다. 따라서 계산량과 학습 데이터가 많은 deep learning과 온라인 학습(미니배치 이용)에 적합하다.
1 | from sklearn.linear_model |
SGDClassifier(random_state=42)에서 random_state은 무작위 값을 넣으며 최적값을 찾는 SGDClassifier의 특성에 따라, 이전에 실행했던 결과값을 repeatable하게 받기 위해 특정값을 저장하는 것을 의미한다. 값의 의미는 딱히 없으므로, 아무 정수로 설정할 수 있다. 이 후 classifier에 data와 y_train_5를 넣어 classifier을 fitting한다.
classifier은 predict의 결과로 some_digit(=X[0])을 넣었을 때 y_train_5에 따라 5 여부를 감지한 결과를 array에 담는다.
3.3 성능 측정
3.3.1 교차 검증을 사용한 정확도 측정
1 | from sklearn.model_selection |
StartifiedKFold에서 n_splits=3으로 설정하였으므로, 폴드가 3개인 k-겹 교차 검증이 수행된다.
train/test_index으로 skfolds 객체의 split()을 호출하여 학습용/검증용 데이터로 분할할 수 있는 인덱스를 반환받고 실제 분할된 데이터를 해당 index를 대입하여 추출한다. 이 때, split()의 대상은 X_train → y_train_5 집합이다. 이를 토대로 y 값의 prediction인 y_pred를 X_test_fold로부터 predict하고, 올바른 예측의 수를 y_pred와 y_test_fold를 비교하여 sum 값을 통해 계산한다.
위의 과정은 sklearn의 cross_val_score과 거의 같은 기능을 나타내므로 다음과 같은 코드로 정리할 수 있다.
1 | from sklearn.model_selection import cross_val_score |
이는 약 95%로 굉장히 높은 정확도를 나타낸다. 하지만, 이러한 정확도를 성능 측정 지표로 사용하지 않는 이유는 다음과 같다.
위의 예시와 반대로, 5가 아닌 클래스를 분류하는 더미 분류기를 만들어 정확도를 비교해볼 수 있다.
1 | from sklearn.base |
이 때 Never5Classifier의 predict 함수는 배열의 결과를 전부 False로 세팅하여 배열을 반환한다.
해당 classifier을 cross_val_score에 적용할 시 정확도가 90% 가량 나오는 것을 확인할 수 있다. 이는 약 10%의 이미지가 숫자 5이므로 이를 제외한 90%를 정확도로 출력한다. 따라서, 불균형한 데이터셋(5임/5가 아님 = 어떤 클래스가 다른 것보다 월등히 많음)을 다룰 때는 정확도를 분류기의 성능 측정 지표로 선호하지 않는다.
3.3.2 오차 행렬
오차 행렬 : 데이터와 다른 클래스로 잘못 분류한 횟수를 담은 행렬
1 | from sklearn.model_selection |
cross_val_score()
: 테스트 세트의 output의 평균을 이용해 평가 점수 반환
cross_val_predict()
: 테스트 세트의 input의 각 element에 대한 깨끗한 예측 반환 (훈련하는 동안 사용되지 않은 데이터에 대해 예측)
1 | confusion_matrix(y_train_5, y_train_pred) >>> array([[53057, 1522], [1325, 4096]]) |
- 행 : 실제 클래스 (데이터)
- 열 : 예측한 클래스
따라서, 위의 결과에서 첫 번째 행이 “5 아님”(음성 클래스)일 때, 첫 번째 열은 True Negative(예측 N, 실제 N)을 나타내며, 두 번째 열은 False Positive(예측 P, 실제 N)를 나타낸다. 두 번째 행은 “5임”(양성 클래스)이며, 첫 번째 열은 FN(예측 N, 실제 P), 두 번째 열은 TP(예측 P, 실제 P)이다.
이에 완벽한 분류기는 TP와 TN만 가지고 있을 것이므로, 실제 confusion matrix의 출력값은 주대각선 값만 존재한다.
1 | y_train_perfect_predictions = y_train_5 confusion_matrix(y_train_5, y_train_perfect_predictions) >>> array([[54579, 0], [0, 5421]]) |
3.3.3 정밀도와 재현율
- 정밀도 = 정확도 = TP / (TP+FP)
- 재현율 = 민감도 = TP / (TP+FN)
정밀도는 예측이 양성인 결과(TP, FP)를, 재현율은 실제 양성인 결과(TP,FN)를 이용한다.
즉, 정밀도는 확실한 양성 샘플 하나만 예측할 시 1이 나오지만 이는 다른 모든 양성 샘플을 무시한 결과이므로, 재현율(민감도 또는 진짜 양성 비율)과 같은 다른 지표와 함께 사용해야 한다.
1 | precision_score(y_train_5, y_train_pred) // 정밀도 >>> 0.729085... |
0.729는 정밀도로, 5로 판별된 이미지 중 72.9%가 정확하다는 의미이며, 0.756은 재현율으로, 전체 숫자 5에서 75.6%의 5만 감지한 것을 의미한다.
이를 하나의 숫자로 통합해 살펴볼 수 있는 평가 지표가 F1 점수, 즉 정밀도와 재현율의 조화 평균이다.
- F1 = TP / (TP + (FN + FP) / 2)
1 | f1_score(y_train_5, y_train_pred) >>> 0.742096... |
정밀도와 재현율이 비슷할 시 F1의 값이 높게 나오므로 간단한 평가 지표로 이용할 수 있지만,
정밀도/재현율 트레이드오프에 따라 정밀도와 재현율이 반비례하는 경우 상황에 따라 둘의 중요성이 달라지므로 F1을 주요 평가 지표로 사용하는 것이 바람직하지 않다.
3.3.4 정밀도/재현율 트레이드오프
정밀도와 재현율은 결정 함수의 결과로 나온 output dataset에 적용하는 임곗값에 따라 달라진다.
즉, 임곗값을 높게 설정할 경우 정밀도는 높아지나 포함되는 TP가 낮아지므로 재현율은 떨어지며, 임곗값을 낮게 설정할 경우 정밀도는 떨어지나 재현율은 높아진다. some_digit의 예측에 사용한 점수를 확인하고 임곗값을 정해 양성/음성 예측을 만들기 위해 decision_function()을 이용한다.
1 | y_scores = sgd_clf.decision_function([some_digit]) |
따라서 적절한 임곗값을 설정하기 위하여, cross_val_predict()의 decision_function을 이용하여 모든 샘플의 결정 점수를 얻는다. 이후 precision_recall_curve()를 이용해 가능한 임곗값에 대한 정밀도와 재현율을 얻는다.
1 | y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3, method="decision_function") |
좋은 정밀도/재현율 트레이드오프는 재현율에 대한 정밀도 곡선에서 정밀도가 급격하게 줄어드는 지점 직전의 값으로 임곗값을 설정하면 된다.
또한 특정한 목표치의 정밀도를 달성하는 것이 목표(n%라고 가정해보자.)일 때는, 다음과 같이 임곗값을 설정하면 된다.
1 | threshold_90_precision = thresholds[np.argmax(precisions >= n / 100)] |
하지만 재현율이 너무 낮을 경우, 높은 정밀도의 분류기는 유용하지 않다.
3.3.5 ROC 곡선
ROC 곡선 : 거짓 양성 비율(FPR)에 대한 진짜 양성 비율(TPR, 재현율)의 곡선으로, FPR = 1(실제 음성) - TNR이다. 이 때 TNR을 특이도라고 하며, ROC를 재현율에 대한 1-특이도라고 한다.
TPR과 FPR을 계산하기 위해 사용되는 함수는 roc_curve()로, 다음과 같이 사용된다.
1 | fpr, tpr, thresholds = roc_curve(y_train_5, y_scores) |
TPR이 높아질 수록 FPR도 높아지므로, 좋은 분류기는 완전한 랜덤 분류기(TPR : FPR = 1 : 1)에서 최대한 왼쪽 위 모서리(TPR은 높고 FPR은 낮은)로 멀리 떨어져야 한다. 이를 평가하는 지표로는 곡선 아래의 면적(AUC)가 있는데, AUC를 계산하기 위해 RandomForestClassifier을 이용한다.
1 | from sklearn.ensemble import RandomForestClassifier |
RandomForestClassifier에서는 decision_function 대신 predict_proba 기능을 제공한다.
그 결과로서, RandomForestClassifier는 SGDClassifier보다 왼쪽 위 모서리에 더 가까운 ROC 곡선을 생성하므로 AUC 점수로 더 높다.
3.4 다중 분류
다중 분류기로 둘 이상의 클래스를 구별할 수 있으나, 보통 이진 분류기를 이용해 다음과 같은 2가지의 방법으로 다중 클래스를 구별한다. 보통 다중 클래스 분류 작업에 이진 분류 알고리즘을 선택하면 자동적으로 OvR이나 OvO를 실행하지만, import를 통해 클래스 객체를 생성하여 강제적으로 원하는 Classifier을 설정할 수도 있다.
- OvR(OvA) : 특정 클래스만 구분하는 클래스별 이진 분류기 n개를 훈련시켜 클래스가 n개의 클래스 분류 시스템을 만든다. 이후 각 분류기의 결정 점수 중에서 가장 높은 것을 클래스로 선택한다. 이는 큰 훈련 세트에서 적은 분류기를 훈련시킨다.
1 | from sklearn.multiclass import OneVsRestClassifier |
- OvO : 각 클래스의 조합마다 이진 분류기를 훈련시킨다. 이 중 가장 많이 양성으로 분류된 클래스를 선택한다. 이는 각 분류기의 훈련에 전체 훈련 세트 중 구별할 두 클래스의 샘플만 필요하므로, 작은 훈련 세트에서 많은 분류기를 훈련시킬 수 있다.
1 | from sklearn.multiclass import OneVsOneClassifier |
더 높은 정확도를 위해, 입력의 스케일을 조정하면 분류기의 성능을 높일 수 있다.
1 | from sklearn.preprocessing import StandartScaler |
3.5 에러 분석
cross_val_predict()를 이용해 결과값을 예측하고, 이 후 confusion_matrix()를 이용해 오차 행렬을 생성한다. 그 후 matshow()를 실행 시, 각 클래스 별로 예측한 클래스의 확률이 밝기로 나타나는데, 밝을 수록 높은 확률을 나타낸다. 따라서 올바른 오차 행렬은 주대각선의 밝기가 밝고, 나머지는 어두워야 한다.
이러한 오차 행렬의 특성에 따라 잘못 분류된 클래스를 파악하고 분류기의 성능 향상 방안에 대해 생각해볼 수 있다. 특히 이 과정에서 데이터의 전처리가 중요한데, 3과 5의 클래스를 분류하는 예시의 경우 SGDClassifier을 적용할 경우 미세한 픽셀 강도의 차이가 클래스 분류의 오류로 이어지므로 이미지의 회전값을 0으로 조정하는 것이 이에 해당한다.
3.6 다중 레이블 분류
다중 레이블 분류 시스템 : 하나의 샘플에 여러 개의 클래스가 포함될 경우, 레이블을 달아 각 클래스를 구별하는 시스템
다중 레이블 분류 시 적절한 지표를 사용하는 것이 중요한데, 다음과 같이 F1 점수에 다른 가중치 기준을 적용하여 분류기를 평가할 수 있다.
1 | y_train_knn_pred = cross_val_predict(knn_clf, X_train, y_multilabel, cv=3) |
3.7 다중 출력 분류
다중 출력 분류(다중 출력 다중 클래스 분류) 시스템 : 다중 레이블 분류에서 각 레이블이 다중 클래스가 될 수 있도록 일반화한 시스템
이를 잡음이 포함된 이미지를 예시로 설명해보자면, 잡음이 포함된 이미지의 각 픽셀은 하나의 레이블에 해당되며 모든 픽셀은 0~255의 다양한 값을 가질 수 있다. 해당 이미지에서 잡음을 제거 시 각 픽셀의 값은 변화하므로 다중 클래스가 될 수 있다. 따라서 이는 다중 레이블에 다중 클래스가 합쳐진 다중 출력 다중 클래스 분류이다.