chapter 3 - 분류 / 3.4, 3.5, 3.6, 3.7

dldzm·2021년 2월 10일
0

노션 정리 : https://www.notion.so/bmo29/chapter-3-0b3c96ef54fc492aa5bbbff553dc8312


3.4 다중 분류

이진 분류가 두 개의 클래스를 구별하는 반면 다중 분류기 ; multiple classifier (또는 다항 분류기 ; multinomial classifier)는 둘 이상의 클래스를 구별할 수 있다.

여러 개의 클래스를 직접 처리할 수 있는 SGD 분류기, 랜덤 포레스트 분류기, 나이브 베이즈 ; naive Bayes 와 반면 이진 분류만 가능한 로지스틱 회귀나 서포트 벡터 머신 분류기가 존재한다. 이진 분류기를 여러 개 사용하여 다중 클래스를 구별할 수도 있다.

이렇게 결정 점수 중에서 가장 높은 것을 클래스로 선택하여 이미지를 이진 분류기들로만 구성한 것을 OvR ; one-versus-the-rest 전략이다. (또는 OvA ; one-versus-all)

또 다른 전략은 0과 1의 구별, 0과 2의 구별, 1과 2의 구별과 같이 각 숫자의 조합마다 이진 분류기를 훈련시키는 것이다. 이를 OvO ; one-versus-one 전략이라고 한다. 클래스가 N개라면 분류기는 N×(N1)2\frac{N\times(N-1)}{2}개가 필요하다. (NC2_{N}\mathrm{C}_{2})

하나의 이미지를 분류하기 위해서 45개의 분류기를 모두 통과시켜서 가장 많이 양성으로 분류된 클래스를 선택하는 방법인데 이러한 OvO 전략의 주요 장점은 각 분류기의 훈련에 전체 훈련 세트 중 구별할 두 클래스에 해당하는 샘플만 필요하다는 것이다.

서포트 벡터 머신(SVM)과 같은 일부 알고리즘은 훈련 세트의 크기에 민감해서 큰 훈련 세트에서 몇 개의 분류기를 훈련시키는 것보다 작은 훈련 세트에서 많은 분류기를 훈련시키는 쪽이 빠르므로 OvO를 선호한다. 하지만 대부분의 이진 분류 알고리즘에서는 OvR을 선호한다.

다중 클래스 분류 작업에 이진 분류 알고리즘을 선택하면 사이킷런이 알고리즘에 따라 자동적으로 OvR 또는 OvO를 실행한다. sklearn.svm.SVC 클래스를 사용하여 서포트 벡터 머신 분류기를 테스트해보자.

이렇게 코드를 짜면 이미 5를 구별한 타깃 클래스(y_train_5) 대신에 0에서 9 까지의 원래 타깃 클래스(y_train) 을 사용하여 SVC를 훈련시킨다. 그런 다음에 예측 하나를 만든다.

내부에서는 사이킷런이 OvO 전략을 사용해 45개(== 10C2_{10}\mathrm{C}_{2}개)의 이진 분류기를 훈련시키고 각각 결정 점수를 얻어 점수가 가장 높은 클래스를 선택한다.

decision_function() 메서드를 호출하여 샘플당 10개의 점수를 받을 수 있다. 즉 각 숫자 클래스에 대한 점수를 얻을 수 있다.

가장 높은 점수가 클래스 5에 해당하는 값이다. 순서대로 0부터 9까지, 5 클래스의 점수가 9.29로 가장 높음을 확인할 수 있다.

OvO나 OvR을 사용해보자. 간단하게 이진 분류기 인스턴스를 만들어 객체를 생성할 때 전달하면 된다. 참고할 것은 굳이 이진 분류기가 아니여도 된다는 점이다. 다음 코드는 SVC 기반으로 OvR 전략을 사용하는 다중 분류기이다.

SGDClassifier (또는 RandomForestClassifier)를 훈련시키는 것도 간단하다.

이 경우 SGD 분류기는 직접 샘플을 다중 클래스로 분류할 수 있기 때문OvOOvR을 적용하지 않아도 된다. 따라서 decision_function() 메서드 또한 클래스마다 하나의 값을 반환한다.

결과를 확인하면 다른 클래스의 값은 음수인데 반해 확신하는 클래스의 점수는 엄청 높은 양수를 취한다. 자세히 보면 클래스 3에서는 약간 높은 양수값을 갖는다.

분류기를 평가하고자 한다. 분류기 평가에는 일반적으로 교차 검증을 사용한다. cross_val_score() 함수를 사용하여 SGDClassifier 의 정확도를 평가해보자.

모든 테스트 폴드에서 준수한 성적을 얻게 되었다. 랜덤 분류기를 사용했다면 10%의 정확도를 얻었을 것이다.

간단하게 입력의 스케일을 조정하면 정확도를 더 높일 수 있다.

이것은 coursera 수업에서 교수님이 계속 강조하시던 부분 → 자료의 scale, 표현 범위를 맞춰야 평평

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train.astype(np.float64))
cross_val_score(sgd_clf, X_train_scaled, y_train, cv=3, scoring="accuracy")

3.5 에러 분석

에러의 종류를 분석해보자. 먼저 오차 행렬을 살펴볼 수 있다. cross_val_predict() 함수를 사용해 예측을 만들고 confusion_matrix() 함수를 호출하자.

y_train_pred = cross_val_predict(sgd_clf, X_train_scaled, y_train, cv=3)
conf_mx = confusion_matrix(y_train, y_train_pred)

결과로 오차 행렬이 출력되는데 맷플롯립의 matshow() 함수를 사용해 이미지로 표현하자.

plt.matshow(conf_mx, cmap_plt.cm.gray)

배열에서 가장 큰 값은 흰색으로, 가장 작은 값은 정규화되어 그려진 것이다.

잘 보면 5가 다른 숫자보다 어두운데 이는 다른 대각선의 숫자들은 5-6천인데 반해 5만 4천에 머물러 있기 때문이다. 즉 데이터 셋에 숫자 5의 이미지가 적거나 분류기가 숫자 5를 다른 숫자만큼 잘 분류하지 못한다는 것이다.

먼저 오차 행렬의 각 값을 대응되는 클래스의 이미지 개수로 나눠 에러 비율을 비교한다. 여기서 이미지의 개수이지 에러의 절대 개수가 아니다. 개수로 비교하면 이미지가 많은 클래스가 상대적으로 나쁘게 보인다. MNIST는 클래스별 이미지 개수가 동일하지 않기 때문이다.

row_sums = conf_mx.sum(axis=1, keepdims=True)
norm_conf_mx = conf_mx / row_sums

다른 항목은 그대로 유지하고 주대각선만 0으로 채워서 그래프를 그리면,

np.fill_diagonal(norm_conf_mx, 0)
plt.matshow(norm_conf_mx, cmap=plt.cm.gray)

오차 행렬은 반드시 대칭인 것이 아니다.

행은 실제 클래스를 나타내고 열은 예측한 클래스를 나타내는데 클래스 8의 열이 상당히 밝으므로 많은 이미지가 8로 잘못 분류되었음을 예측할 수 있다. 하지만 클래스 8의 행은 그리 나쁘지 않은 것으로 보아 실제 8이 적절히 8로 잘 예측되었다는 것을 볼 수 있다.

클래스 5와 클래스 3이 서로 많이 혼동되었다는 것을 밝은 회색으로 알 수 있다.

에러가 일어난 원인은 선형 모델인 SGDClassifier 을 사용했기 때문이다. 선형 분류기는 클래스마다 픽셀에 가중치를 할당하고 새로운 이미지에 대해 단순히 픽셀 강도의 가중치 합을 클래스 점수로 계산한다. 3과 5는 몇 개의 픽셀만 다르기 때문에 쉽게 혼동된 것이다.

결국 이런 에러를 줄이기 위해서는 이미지를 중앙에 위치시키거나 회전되어 있지 않도록 전처리하는 것이다. preprocessing의 중요성이 다시 한번 강조된다!

3.6 다중 레이블 분류

지금까지는 각 샘플이 하나의 클래스에만 할당되었지만 분류기가 샘플마다 여러 개의 클래스를 출력해야 할 때도 있다. 이처럼 여러 개의 이진 꼬리표 ; tag 를 출력하는 분류 시스템을 다중 레이블 분류 ; multilabel classification 시스템이라고 한다.

from sklearn.neighbors import KNeighborsClassifier

y_train_large = (y_train >= 7)
y_train_odd = (y_train%2==1)
y_multilabel= np.c_[y_train_large, y_train_odd]

knn_clf = KNeighborsClassifier()
knn_clf.fit(X_train, y_multilabel)

따라서 5를 가지고 있는 some_digit은 7보다 크지 않아 False를, 홀수이기 때문에 True를 갖는다.

y_train_knn_pred = cross_val_predict(knn_clf, X_train, y_multilabel, cv=3)
f1_score(y_multilabel, y_train_knn_pred, average="macro")

이 코드는 모든 레이블에 대한 F1F_1점수의 평균을 구하는 것이다. 모든 레이블의 가중치가 같다고 가정한 상황이다.

레이블에 클래스의 지지도 ; support (여기서는 타깃 테이블에 속한 샘플의 수. ex) 앨리스는 찰리의 여자친구라 유독 찰리의 사진에서 많이 등장한다 → 앨리스는 찰리의 친구 밥보다 가중치가 높다.)를 가중치로 주는 것이다. 이는 average="weighted" 로 구현할 수 있다.

3.7 다중 출력 분류

다중 출력 다중 클래스 분류 ; multioutput-multiclass classification (또는 간단히 다중 출력 분류 ; multioutput classification)은 한 레이블이 다중 클래스가 될 수 있도록, 즉 값을 두개 이상 가질 수 있도록 한 것이다.

noise = np.random.randint(0, 100, (len(X_train), 784))
X_train_mod = X_train + noise
noise = np.random.randint(0, 100, (len(X_test), 784))
X_test_mod = X_test + noise
y_train_mod = X_train
y_test_mod = X_test
knn_clf.fit(X_train_mod, y_train_mod)
clean_digit = knn_clf.predict([X_test_mod[some_index]])
plot_digit(clean_digit)
profile
🛰️ 2021 fall ~

0개의 댓글