03_Classification.ipynb

책: O'REILLY의 Hands-On Machine Learning with Scikit-Learn & TensorFlow

URL: http://shop.oreilly.com/product/0636920052289.do

본문에 사용되는 사진들은 모두 위의 책에서 발췌

소스코드: https://github.com/ageron/handson-ml

틀린 내용이 있으면 언제든지 댓글 달아주세요! (오역은 어느정도 이해해주시길)


chapter3. Classification

chap1에서는 가장 일반적은 지도학습은 회귀(regression)와 분류(classification)이라고 언급했다.

chap2에서는 선형 회귀(Linear Regression), 결정 트리(Decision Trees), 랜덤 포레스트(Random Forests) 알고리즘을 이용하여 주택 가격을 예측하는 회귀 문제를 경험했다.

이제 우리는 분류 시스템에 대해 주목하고자 한다.


MNIST

 이번 장에서 우리는 MNIST 데이터를 이용할 것이다. MNIST데이터는 0에서 9까지의 숫자를 70,000장의 수작업으로 작성 된 데이터 셋이다. 각 이미지는 0에서 9까지의 숫자에 대해 라벨링 되어 있다. 이 데이터 셋은 너무나 많이 연구되어 머신러닝의 "hello world"라고도 불리며, 사람들이 새로운 분류 알고리즘을 적용할 때마다 MNIST에서 어떻게 작동되는지를 알고 싶어한다.

Scikit-learn은 인기있는 데이터 셋을 다운로드 할 수 있는 기능을 제공하는데 그 중 하나가 MNIST이다. 


 

 다운로드 받은 Datasets은 일반적으로 사전식(dictionary) 구조와 유사하다.

  • DESCR key: 데이터셋의 설명(description)
  • data key: 인스턴스당 하나의 행과 feature당 하나의 열이 있는 배열
  • target Key: 라벨을 가진 배열

 총 70,000장의 이미지는 784개의 feature를 가지고 있다. 각각의 이미지는 28*28 픽셀을 가지고 있으며, 각각의 feature는 0(white)에서 255(black)까지의 하나의 픽셀로 나타낼 수 있다. 


 설명만으로는 감을 잡기 어려울 것이다. 이를 실제로 28*28 행렬로 변환하여 이미지를 출력해보자.

0에서 69,999까지의 범위 중 랜덤 값으로 하나를 출력하여 이미지를 출력한 결과이다. 실제 타겟의 값이 8이고, 이미지 또한 8이라는 것을 "우리는" 확인 할 수 있다.

 이제 우리는 2장에서 배운 학습용 데이터와 테스트용 데이터를 분리 해야한다. MNIST 데이터 셋은 실제로 학습데이터(첫 번째 데이터에서 60,000까지의 이미지)와 테스트 데이터(나머지 10,000장의 이미지)로 이미 분리되어 있다.

 또한, 학습용 데이터를 섞어보자(shuffle). 몇몇 알고리즘은 인스턴스의 순서에 민감하다. 그렇기 때문에 섞는 작업을 통해 이러한 문제를 해결할 수 있다.


Training a Binary Classifier

 단순한 문제를 풀어보자. 예를 들어 숫자 5를 식별하는 분류기를 만들어보자. 이 분류기는 단순히 5가 맞다, 아니다의 두가지 클래스만을 가지고 있다. 이 분류 작업을 위해 타겟 벡터를 만들어보자.

 이제 학습을 시켜보자. 시작하기 좋은 방법으로 Scikit-learn의 SGDClassifier 클래스를 이용하여, Stochastic Gradient Descent(SGD) 분류기를 이용하여 시작해보자.

 [Stochastic Gradient Descent 방식은 기존의 경사 하강법(Gradient Descent)이 매 번 모든 데이터들에 대해 살펴보고 기울기를 계산하기 때문에 시간이 많이 소요되는 문제가 있었는데, 이를 개선한 것이다. 책에서 해당 내용에 대해 다루는지 아직 파악이 안되었는데, 다루지 않을 경우 별도로 정리하겠음.]

 이 SGD 분류는 매우 큰 데이터 셋을 다루는 데 매우 효율적이다. 아래의 예제는 some_digit을 X[36000]으로 맞춰 놓은 상태

 이 분류기는 특정한 상황에서만 올바르게 판단된다. 이제 우리는 모델의 성능에 대해 평가해보고자 한다.


1. Performance Measures

 분류기를 평가하는 것은 종종 regressor를 평가하는 것보다 훨씬 더 까다로울 수 있다. 그래서 우리는 이 주제에 대해 이번 장에서 많은 부분을 할애하고자 한다. 사용할 수 있는 성능 측정 방법은 상당히 많다.


1) Measuring Accuracy Using Cross-Validation

 모델을 평가하는 좋은 방법으로는 교차 검증(cross-validation)을 이용하는 것이다.

경우에 따라서, Scikit-learn이 제공하는 것보다 교차-검증을 더욱 제어해야 할 수 있다. 

이러한 경우 쉽게 구현할 수 있다.

다음의 코드는 Scikit-learn의 cross_val_score() 함수와 거의 동일한 결과를 보인다.


 이번에는 cross_val_score() 함수를 사용하여, 3개의 fold를 가진 K-fold cross-validation을 이용한 SGDClassifier 모델을 평가해보자. 


 Wow! 95% 이상의 정확도를 보인다? 

흥분하기 전에, "not-5"클래스의 모든 단일 이미지를 분류하는 멍청한(dumb) 분류기를 살펴보자.

 그렇다. 90%의 정확도를 보인다. 5라는 이미지가 대략 10%정도 차지하고 있기 때문에 나오는 간단한 결과이다.


위 예는 분류를 측정하는, 특히 왜곡(skewed)된 데이터를 다룰 때 선호되는 척도가 아닌지에 대해 증명하고 있다.


2) Confusion Matrix

 분류기의 성능을 평가하는 더 좋은 방법은 confusion matrix를 사용하는 것이다. 일반적인 아이디어는 클래스 A의 인스턴스가 클래스 B로 분류되는 수를 계산하는 것이다. 예를 들어, 5라는 이미지를 3으로 혼동하는 횟수를 알기 위해서는, confusion matrix의 5행 3열을 보면 된다. confusion matrix를 계산하기 위해, 처음으로 예측 Set을 구하고, 그 다음 실제 타겟과 비교하면 된다. test set에서 예측을 할 수 있지만, 일단은 그대로 두자. (test set은 학습을 마치고, 나중에 평가할 때 사용하기 위해 아껴두는 것을 잊지말자!) 대신 cross_val_predict() 함수를 사용할 수 있다. 

 cross_val_score() 함수와 마찬가지로, cross_val_predict() 함수는 K-fold cross-validation을 수행한다. 그러나 evaluation scores값을 반환하는 것 대신에 각각의 test fold에서 만들어지는 예측에 대해서 반환하게 된다. 

 이제 confusion_maxtrix() 함수를 이용하여 confusion matrix를 얻어보자. 

  confusion matrix의 각 행은 actual class를 나타내고, 각 열은 predicted class를 나타낸다. 이 행렬의 첫번째 행은 5가 아닌 것(negative class)을 53,800개로 분류하고(true negatives), 나머지 779개는 5로 잘못 분류한 것이다(false positives).

 다음 행은 5인 것(positive class)을 분류하는 것인데, 1,583개가 잘못 분류 되었지만(false negatives), 나머지 3,838개는 정상적으로 분류 되었다(true positives).

 글로 보려니 아무래도 헷갈린다! 그리고 무엇보다 헷갈렸던 것은 알고 있던 confusion matrix는 행이 예측 클래스, 열이 actual 클래스였는데, scikit-learn에서 반대로 출력되어 헷갈렸다..

 나는 매번 헷갈렸지만, 그래도 그나마 외우기 위한 방법으로는 다음과 같은 방법을 이용했다. 다소 유치할 수는..

"예측을 했는데 5가 아니었네. 실제는 뭐지? 아! 5가 아니네! True Negatives = (뒤에서부터) 아닌게 맞구나~"

"예측을 했는데 5네! 실제는 뭐지? 이런..5가 아니었네... False Positives = (뒤에서부터) 예측이 맞은게 아니었구나.."

"예측을 했는데 5가 아니었네. 실제는 뭐지? 아! 5군... False Negatives = (뒤에서부터) 아닌게 아니었구나.."

"예측을 했는데 5네! 실제는 뭐지? 오! 5가 맞았군! True Positives = (뒤에서부터) 맞춘게 맞구나~"


 confusion matrix는 많은 정보를 줄 수 있지만 때로는 더욱 간결한 것을 원할 수 있다. 이 때 사용되는 것으로는 precision(정밀도)과 recall(재현율) 이 있다.

 정밀도란, 맞다고 예측한 것실제로 맞았는지에 대해 확인하는 지표이다.

 재현율이란, 실제 정답의 True얼마나 많은 true를 예측 했는지에 대해 확인하는 지표이다.


3) Precision and Recall

 scikit-learn은 precision과 recall을 포함하여 classifier metrics 를 계산하는 몇개의 함수를 제공한다.

 이제 숫자 5의 검출기는 이전의 높은 정확도를 보이는 것처럼 높게 나오지는 않는다. 이미지가 5일 때, 83%정도 맞다고 주장한다. 더욱이 5의 70%만 탐지하게 된다.

두 분류기준을 비교하는 간단한 방법이 필요할 때, F1 score라고 불리는 (Precision과 recall을 하나의 metric으로 결합)방법을 사용할 수 있는데, 우리가 알고 있는 조화 평균이 해당 방법이다.

F1 score를 계산하기 위해, 간단하게 f1_score() 함수를 호출하면 된다.


그런데 F1 score 값은 항상 우리가 원하는 값만 출력하는 것은 아니다. 상황에 맞게 precision과 recall 중 중점적으로 생각할 수 있을 것이다.

 예를 들어, 어린이에게 안전한 동영상을 볼 수 있도록 분류기를 훈련하는 가정을 생각해보자. 이러한 경우, 많은 좋은 동영상을 나쁘다고 판단할 수 있다(low recall). 하지만, 많은 분류 기준을 사용하는 분류 기준보다는 안전하다고 판단되는 분류 기준을 선호하게 된다(high precision).

 반면에, 감시 이미지에서 좀도둑을 탐지하도록 분류기를 훈련시킨다고 가정해보자. 이 때에는 precision보다는 recall이 높은게 좋다. 잘못된 알람(맞다고 예측했는데 아닌 경우: low precision)이라고 해도 (경비원들은 엄청 고생하시겠지만..) 실제 도둑을 탐지하는 비율이 높게 된다. 

 위의 두 가지 예를 보면 precision과  recall은 반비례적인 것을 확인 할 수 있다. 이를 Precision/recall tradeoff라고 부른다.


4) Precision/Recall Tradeoff

 tradeoff를 이해하기 위해서, SGDClassifier가 어떻게 분류 결정을(classification decisions) 하는지 살펴보자. 각각의 instance에 대해 decision function 기반으로 점수를 계산하고, 만약 score가 임계 값(threshold)보다 높다면 positive class에 할당하고, 그렇지 않다면 negative class에 할당한다. 

위의 그림은 왼쪽으로의 낮은 점수부터 오른쪽의 높은 점수에 이르기까지 몇개의 숫자를 보여준다. 결정 임계값(decision threshold)를 중간 화살표(5사이)로 가정해보자. 오른쪽을 보면 실제 5라는 숫자가 4개라는 것을 확인할 수 있다. 6은 무엇일까?

앞서 얘기한 False Positive가 될 것이다.(예측은 5였는데 실제값은 5가 아니었군!) 따라서 해당 임계 값을 사용하면 precision은 80%가 될 것이다. TP/(FP+TP) -> 4/(1+4). 그러나 실제 5의 개수는 6개인데 해당 임계 값의 범위에는 5가 4개밖에 포함되어 있지 않다. 그렇기 때문에 recall은 67%가 된다. TP/(TP+FN) -> 4/(4+2)

 이제 임계값을 오른쪽 화살표로 움직여보자. 이 때 precision은 100%가 된다.  TP/(FP+TP) -> 3/(0+3). 그러나 recall은 50%로 감소하게 된다. 3/(3+3). 즉 precision은 높아졌지만, recall은 감소한 것을 확인할 수 있다. 왼쪽으로 화살표를 가면 반대라는 것 또한 확인할 수 있다.

 scikit-learn은 임계값을 직접 설정할 수는 없지만, 예측을 하기 위해 사용하는 의사결정점수(decision score)에 대한 접근을 제공한다. 분류기(classifier)의 predict() 함수를 호출하는 것 대신에, 각각의 인스턴스의 점수를 반환하는 decision_function() 함수를 호출하여 원하는 임계 값을 사용하여 해당 점수를 기반으로 에측을 수행할 수 있다. 

 SGDClassifier는 threshold를 0으로 사용하기 때문에 이전 predict() 함수를 사용했던 결과와 동일하게 True 값을 반환한다. 

이제 임계값을 올려보자. 200,000으로 값을 줄 것이다.

 당연히 y_score 값이 74632었기 때문에 false가 반환된다. 이것이 중요한게 아니라, 임계 값을 높이면 recall이 감소한다는 것이다. 이미지는 실제로 5를 나타내며 분류기는 임계 값이 0일때 5가 맞다는 것을 감지하지만 임계값이 200,000으로 증가하면 탐지 하지 못하게 된다. 그러면 임계값을 어떻게 결정할 수 있을까? 이를 위해서 우리는 cross_val_predict() 함수를 다시 사용하여 training set의 모든 인스턴스 점수를 얻을 것이고, 이번에는 예측하는 것 대신에 의사 결정 점수를 반환하도록 지정할 것이다.

(예측에는 false, true 형식으로 반환되지만, 결정 점수는 값으로 반환된다ㅋ)

 이제 이 점수로, precision_recall_curve() 함수를 사용하여 가능한 모든 임계값에 대해 precision과 recall을 계산할 수 있다.

 한가지 주의해야할 점은 sklearn의 0.19버전의 경우 cross_val_predict함수의 method로 decision_function의 반환 shape는 (60000,2)다. y_train의 경우 (60000,1)이기 때문에 형식이 맞지 않는다며 오류값이 나온다. 그렇기 때문에 위에서 y_scores[:,1]로 했다는 점에 대해 유의 필요.

 위의 그래프에서 precision이 recall보다 왜 왜곡되었는지 궁금해 할 수 있다. (recall 커브가 부드럽게 보인다. )

이유는 임계 값을 올릴 때 정확도가 떨어질 수 있기 때문이다.  위의 precision/recall tradeoff 그림을 보면 설명이 되는데, 중간 화살표인 임계값을 보자. 임계값에서 한 자리수만큼 오른쪽으로 이동시켜보자. 정밀도는 4/5(80%)에서 3/4(75%)라는 것을 확인 할 수 있다. 반면에 recall은 임계 값이 증가 할 때만 값이 내려가는 것을 확인할 수 있다. 그렇기 때문에 커브가 부드럽게 보이는지 설명할 수 있다. 

 위의 방법으로 최적의 precision/recall tradeoff 값을 제공하는 임계값을 간단하게 선택할 수 있게 되었다.  

또 다른 방법으로는 recall에 대한 precision을 직접 plotting 하는 것이다.

 위의 그래프를 보면 precision은 80%가량에서 급격히 떨어지는 것을 확인할 수 있다. 


이제 90%의 precision을 목표로 한다고 가정해보자. threshold를 통한 precision/recall 그래프를 다시 보자. (위의 위의 그래프)

만족하는 threshold는 약 70,000 정도로 보인다. 

70,000을 기준으로 threshold에 값을 준 결과, precision은 90%인 것을 확인 할 수 있다. 참 쉽죠잉~


5) The ROC Curve (Receiver Operating Characteristic)

 ROC Curve는 precision/recall curve와 매우 유사하지만, recall에 대한 precision을 plotting 하는 것 대신, False Positive Rate(FPR)에 대해 True Positive Rate(TPR; recall)을 plotting 하는 것이다. 

 설명이 어려웠다. 그냥 precision/recall은 precision과 recall이 반비례 관계이고, ROC의 경우 TPR과 FPR이 반비례다.

이것을 설명하기 위해서는 민감도(sensitivity)와 특이도(specificity)에 대해 알아야 된다.

1. 민감도(sensitivity): 1인 케이스에 대해 1이라고 예측

2. 특이도(specificity): 0인 케이스에 대해 0이라고 예측

True Positive Rate(TPR)는 민감도와 같다. recall과 같다. (ex: 스팸 메일을 스팸메일이라고 판단함)

False Positive Rate(FPR)는 (1-특이도)와 같다. 즉 0인 케이스에 대해 1로 잘못 예측한 비율이다.
(ex: 스팸 메일이 아닌데 스팸 메일이라고 잘못 판단함)

True Negative Rate(TNR)은 특이도와 같다. (ex:스팸 메일이 아닌 것에 대해 정확하게 판단함)

즉 FPR은 (1-TNR)이다.


 ROC curve를 plot하기 위해, 우선 roc_curve()함수를 이용하여, 다양한 임계값을 통해 TPR과 FPR을 계산하여야 한다.

 recall(TPR)이 높을 수록 분류기가 생성하는 오탐(FPR)이 높아진다. 점선은 purely random classifier를 나타낸다. 좋은 분류기 일수록, 해당 선에서 멀리 떨어진다(왼쪽 상단 모서리 방향).

 분류 기준을 비교하는 한가지 방법으로 AUC(Area Under the Curve)를 측정이 있다. 완벽한 분류기는 ROC AUC의 크기가 1인 반면, purely random classifier는 0.5가 된다. scikit-learn은 ROC AUC를 계산하는 함수를 제공한다.


Tip

 ROC 커브와 Precision/recall 커브가 매우 유사하여, 어떤 것을 언제 써야되는지에 대해 결정하는 방법에 대해 궁금할 수 있다. 만약, positive class가 드물거나, False Negatives보다 False Positives에 대해 유의해야 되는 상황이라면, precision/recall 커브를 사용해야 한다. 그렇지 않은 상황이라면 ROC 커브를 사용한다. 

 예를 들어, 위의 roc_auc_score에서 0.95라는 점수를 보면 실제로 좋다고 생각 할 수 있다. 그러나 이것은 대부분 negative(5가 아닌)에 비해 positive가 너무 적기 때문이다.


 지금까지는 바이너리 즉 5와 5가 아닌 것에 대해 분류기를 훈련하였다. 

분류기의 성능을 측정하기 위해 교차검증(cross_validation)을 이용하였고, 필요에 따라 Precision/recall 곡선과, ROC 곡선 및 ROC AUC 점수를 통해 모델을 비교하는 방법을 알았다. 

 이제는 5와 5가 아닌 것에 대해 판단하는 것이 아닌, multi class 분류 방법에 대해 살펴보고자 한다.


Multiclass Classifier

 바이너리 분류기가 두 클래스를 구별하는 방식이라면, 멀티 클래스 분류기(다항)는 두 개 이상의 클래스를 구별하는 방식이다. 

Random Forest classifiers 나 naive Bayes classifiers같은 몇몇 알고리즘은 직접적으로 멀티 클래스를 다룰 수 있다.

그 외 Support Vector Machine이나 Linear classifiers과 같은 알고리즘은 엄격하게 바이너리 분류 방법이다. 그러나 멀티 바이너리 분류기를 이용하여 멀티 클래스 분류기를 수행할 수 있는 다양한 방법들이 존재한다.

 예를 들어, 숫자 이미지를 10개의 클래스(0에서 9까지)로 분류할 수 있는 시스템을 생성하는 방법 중 하나는, 10개의 바이너리 분류기를 각 숫자(0-탐지기, 1-탐지기 등)에 대해 하나씩 훈련 시키는 방법이 있다. 그다음 이미지를 분류하길 원할 때, 각 분류기로부터 decision score를 얻고, 가장 높은 점수를 출력하는 클래스를 선택할 수 있다. 이것을 one-versus-all(OvA) 전략이라고 한다. (또는 one-versus-the-rest)

 다른 전략으로는 모든 숫자의 쌍(pair of digits)에 대해 바이너리 분류기를 훈련시키는 방법이 있다. 0과 1을 구별하고, 0과 2를 구별하고, 1과 2를 구별하고 이를 모든 숫자에 대해 쌍으로 만드는 것이다. 이는 1대1(OvO) 전략이라고 한다. N개의 클래스가 있다면 N*(N-1) / 2 개의 분류기를 학습해야 한다. (숫자 10개이기 때문에 45개를 학습해야한다 OTL..) 이미지를 분류하려면 45개의 모든 분류기를 통해 어떤 클래스가 가장 많이 차지하는지 확인 해야 된다. OvO의 가장 큰 장점은 각각의 분류기를 구분하고자 하는 두개의 클래스를 위해 training set의 부분에 대해서만 학습하면 된다는 장점이 있다. (즉 45개의 분류기일지라도 1과 2를 분류하는 분류기의 경우 1과 2의 데이터에 대해서만 학습하면 된다) 

 Support Vector Machine 분류기와 같은 일부 알고리즘은 training set의 크기에 따라 가늠할 수 없기 때문에, 대규모의 training sets에서 조금의 분류기를 학습하는 것 보다 작은 training sets에서 많은 분류기를 훈련시키는 것이 빠르기 때문에 OvO 방식이 선호된다. 그러나 대부분의 바이너리 분류 알고리즘은 OvA가 선호된다.

 scikit-learn은 멀티 클래스 분류 작업에 바이너리 분류 알고리즘을 사용하려고 할 때 이를 감지하고 자동으로 OvA를 실행한다. (OvO를 사용하는 SVM 분류를 제외하고는)

 SGDClassifier를 이용해보자.

 매우 간단하다. 이 코드는 숫자 5랑 모든 타겟 클래스(y_train_5) 대신에 0에서부터 9까지의 original target 클래스인 y_train을 이용하여 학습하였다.  그런 다음 예측을 하였다. 

 scikit-learn은 실제로 10개의 바이너리 분류기를 학습하고, 이밎에 대해 decision scores를 얻었으며, 가장 높은 점수의 클래스를 선택하였다. 실제로 이것이 사실인지 확인하려면 decision_function() 함수를 호출하면 된다. 인스턴스 당 하나의 점수를 반환하는 대신 클래스 당 10개의 점수를 반환한다. 0부터 시작하기 때문에 6번째 값인 5가 가장 높은 점수임을 확인할 수 있다.

 만약 OvO 또는 OvA를 사용하고 싶다면 OneVsOneClassifier나 OneVsRestClassifier 클래스를 사용하면 된다. 

간단히 인스턴스를 생성하고 바이너리 분류기를 생성자에 보내주면 끝이다. 예를 들어, 아래의 그림은 SGDClassifier를 기반으로 OvO 전략을 이용한 멀티 클래스 분류기를 만든 것이다.

 이제는 배운 내용을 적용해보자. 분류기를 생성한 다음에는 무엇을 해야할까? 그렇다. 평가를 해봐야 된다. 이를 위해 교차 검증(cross_validation)을 적용해보자. cross_val_score() 함수를 사용하여 SGDClassfier의 정확도(accuracy)을 평가해보자.


 모든 test fold에서 85% 이상의 값이 나왔다. 정확도를 향상시킬 수는 없을까? 당연히 있다.

우리는 2장의 4-4에서 feature scaling에 대해 배우지 않았던가. 입력 값을 scaling 해보자.

단순 표준화(standardization)를 통해 성능이 90%이상까지 끌어 올릴 수 있었다.


Error Analysis

 우리는 제법 괜찮은 모델을 발견하였다고 가정해보자. 이제 이 모델을 향상시킬 방법을 찾고 싶다. 이를 위한 방법 중 하나는 오류 유형을 분석하는 것이다.  

 우선적으로, 앞 부분에 배웠던 confusion matrix를 살펴보자. 이전에 했던 것 처럼 cross_val_predict() 함수를 이용하여 예측을 수행 한 후, confusion_matrix() 함수를 호출 해야 한다.

숫자가 너무 많아 보기 힘들다. matplotlib의 matshow() 함수를 이용하여 confusion matrix의 이미지 표현을 보는 것이 더 편리하다. (대각선 값들이 심상치 않아보인다..)

위의 confusion matrix는 대부분의 이미지가 대각선에 위치하고 있기 때문에 상당히 잘 보인다. 즉, 올바르게 분류 되었다는 것을 의미한다. 5는 다른 자릿수보다 약간 더 어둡게 보인다. 이는 dataset에서 5의 이미지가 적게 있거나, 또는 분류기가 다른 숫자만큼 5에서 제대로 수행되지 않음을 의미할 수 있다. 실제로 둘 다 사실인지 확인할 수 있다. 

 오류에 관한 그림을 집중적으로 살펴보자. 먼저 confusion matrix의 각 값을 해당 클래스의 이미지 수로 나누어, 에러율을 비교할 수 있다. 

기억하자. 행은 실제 클래스를 나타내고 열은 예측 클래스를 나타낸다. 클래스 8과 9의 열은 꽤 밝은 것을 확인할 수 있다. 이는 8과 9를 잘 분류하지 못한다는 것을 의미한다. 마찬가지로, 클래스 8과 9의 행 또한 밝은 것을 확인 할 수 있는데, 이 또한 8과 9를 다른 숫자들과 종종 혼동이 된다는 것을 의미한다.

 이와는 반대로, 클래스 1의 경우, 어두운 것을 확인할 수 있다. 이는 대부분의 1이 정확하게 분류 된다는 것을 의미한다. 

오류는 완벽하게 대칭적은 아니다. 예를 들어, 실제는 5인데 예측은 8로 잘못 분류 된 것이 그 반대의 경우보다 더 많다.


 위의 두 개의 5*5 행렬은 3으로 분류된 것을 표시하고, 아래쪽의 두 개는 숫자 5로 분류 된 이미지이다. 

왼쪽 하단과 오른쪽 상단의 경우, 사람이 구분하는 것에도 문제가 있어보인다. 예를 들어 8행 1열의 5는 3처럼 보인다.
분류기가 잘못 분류를 한 이유를 이해하는 것은 상당히 어렵다. 그 이유는 선형 모델인 간단한 SGDClassifier 모델을 사용했기 때문이다. 클래스마다 가중치를 각 픽셀에 할당하고, 새로운 이미지를 확인할 때 가중치 픽셀 강도를 합산하여 각 클래스의 점수를 얻는다. 그렇기 때문에 3과 5는 단지 몇 픽셀만 다르기때문에 해당 모델은 쉽게 혼동 될 수 밖에 없다. 
 3과 5의 가장 큰 차이점은 하단의 arc(3과 5의 밑에부분인 둥근 부분)와 상단의 line(3과 5의 맨 위 선)을 연결하는 작은 선의 위치이다. 

 이 분류 기준은 이미지 이동 및 회전에 매우 민감하다. 따라서 혼란을 줄이는 한가지 방법은 이미지를 전처리하여 이미지가 중심에 있도록하고, 회전되지 않도록 하는 것이다. 이렇게 하면 다른 오류도 줄일 수 있다. 


Multilabel Classification

 지금까지의 각 인스턴스들은 하나의 클래스에만 할당 되었다. 경우에 따라 분류기는 각 인스턴스들에 대해 여러 클래스를 출력하도록 할 수 있다. 예를 들어 얼굴 인식 분류에 대해 생각해보자. 페이스북에서 단체 사진을 찍었다고 생각해보자. 이 사진에서 여러 사람을 인식해야 한다면 어떻게 해야할까? 물론 각각의 사람마다 하나의 라벨은 주어져야 된다. 분류기가 alice, bob, charlie의 얼굴을 인식하도록 학습했다고 가정해보자. 앨리스와 찰리의 사진만 있다면 [1,0,1]를 출력해야 된다. 이러한 multiple binary 라벨이 출력되는 분류 시스템은 multilabel classification system 이라고 부른다. 

 간단한 예를 살펴보자.

위의 코드는 각 자리수 이미지에 대해 두개의 타겟을 포함하는 multilabel 배열을 만든다. 첫 번째는 , 7 이상인 수인지 아닌지에 대해 나타내며, 두 번째는 홀수인지 짝수인지에 대해 나타낸다. 이 두 개를 합칠 때 np.c_ 를 사용하여 2열로 만든다.

 그 다음 KNeighborsClassifier 인스턴스를 만들고, multiple targets 배열을 이용하여 학습한다. 이제 우리는 예측을 할 수 있으며, 두 개의 라벨(label)이 출력 된다는 것을 알 수 있다.

위의 some_digit은 위에서부터 계속 사용했던 숫자 5다. 그렇기 때문에 앞의 False는 7이상이 아니기 때문에 출력된 것이며, 뒤의 True는 5가 홀수이기 때문에 true로 출력된다.

 

 multilabel 분류기를 평가하는 방법은 많이 있으며, 실제로 프로젝트에 따라 측정 항목을 선택하는 것도 다르다. 
예를 들어, 한가지 방법으로 각각의 라벨에 대한 F1 score를 측정한 다음, 간단하게 평균 점수만 계산하는 것이다. 아래 코드는 모든 라벨에서 F1 score의 평균을 계산한 것이다.

 이것은 모든 라벨이 똑같이 중요하다고 가정한 것이다. 특히, 만약 alice의 사진이 bob이나 charlie보다 많을 경우, alice의 사진에서 분류기의 점수에 더 많은 가중치를 주는 것이 좋다. 한가지 간단한 옵션은 각각의 라벨에 동일한 가중치(target label을 갖는 인스턴스의 수)를 부여하는 것이다. 이렇게 하려면 위의 코드에서 average = "weighted"를 설정하면 된다.

  • macro 평균은 클래스별 f1 score에 가중치를 주지 않는다. 클래스 크기에 상관없이 모든 클래스를 같은 비중으로 다룸

  • weighted 평균은 클래스별 샘플 수로 가중치를 두어 f1 score의 평균을 계산한다.이 값이 분류 report에 나타나는 값이다.

  • micro 평균은 모든 클래스의 FP(False Positive), FN(False Negative), TP(True Positive)의 총 수를 헤아린 다음 precision, recall, f1 score를 수치로 계산한다.

[파이썬 라이브러리를 활용한 머신러닝 책에서 macro/weighted/micro 평균 내용 발췌]
각 샘플을 똑같이 간주한다면 micro 평균을, 각 클래스를 동일한 비중으로 고려한다면 macro 평균 점수를 추천


Multioutput Classification

 이 장에서 논의 할 마지막 유형의 분류 작업은 multioutput-multiclass classification(또는 단순히 multioutput classification)이다. 이것은 간단히 각 라벨이 multiclass를 가질 수 있는 multilabel classification의 일반화이다. (즉 두 개 이상의 값을 가질 수 있음)

 이를 설명하기 위해 이미지로부터 노이즈를 제거하는 시스템을 만들어 보자. 입력 값으로 잡음이 많은 숫자 이미지를 선택 할 것이고, MNIST 이미지처럼 픽셀의 배열로 표현하는 숫자 이미지를 출력 할 것이다. 

 분류기의 출력 값은 multilabel(픽셀 당 하나의 라벨)이고, 각각의 라벨은 multiple 값 (픽셀 범위는 0에서 255)을 가질 수 있다. 

 

 먼저 Numpy의 randint() 함수를 사용하여 MNIST 이미지를 가져오고, 픽셀에 노이즈를 추가하여 학습 및 테스트 셋을 만들어보자. target 이미지는 원본 이미지이다.

 테스트 set으로부터 이미지를 살펴보자. 왼쪽에는 noisy 입력 이미지가 있고, 오른쪽은 타겟 이미지가 있다.

이제 분류기를 학습시키고 이 이미지를 clean하게 만들자.

 목표에 충분히 근접해 보인다. 이것으로 분류에 대한 결론을 마친다. 

본 3강을 통해, 이제 분류 작업에 적합한 metrics를 선택하고, 적절한 precision/recall tradeoff를 고르고, 분류기를 비교하는 방법을 알고, 좋은 분류 시스템을 구축할 수 있어야 된다.


하지만 아직 갈 길이 멀어 보인다.... 갈수록 어려워 지는 것 같은 기분이 들었다.....

4강에서는 모델을 학습하는 방법에 대해 다룬다.

+ Recent posts