콘텐츠로 건너뛰기

분류(Classification) – kNN, Rocchio, 나이브 베이즈

지도학습의 대표적인 예시인 분류(Classification)에 대해 알아본다.

■ 분류와 프로그래밍

전통적인 프로그래밍은 사람이 알 수 있는 규칙 위에서 동작했다. 최댓값을 구하라, 오름차순으로 정렬하라 등이 그 일례이다. 그런데 만약 ‘자동차’를 분류해야한다면? 어떤 규칙이 자동차를 자동차로 구분짓는걸까?

아마 한 번에 대답하기 어려울 것이다. 빨간 자동차, 파란 자동차, 검은 자동차… 색깔에 상관없이 자동차 형태를 띄면 자동차라고 말할 수 있어야하며 승용차, SUV, 컨버터블… 차종에도 관계없이 자동차를 구분해야한다. 자동차의 바퀴만 확대해서 보여주거나 자동차의 내부 인테리어를 보여줘도 자동차임을 알 수 있어야 한다.

사람이라면 어렵지 않게 자동차를 골라낼 수 있을 것이다. 그러나 이러한 조건을 만족하는 게 자동차다, 라고 정의내리기에는 현실 문제는 너무 복잡하고 정보가 불완전하다. 이렇게 사람이 정의하기 어려운 것을 컴퓨터보고 알아서 알아내보라고 시키는 것이 머신 러닝(machine learning)이다.

다시 분류로 돌아가자면, 라벨링된 데이터를 기반으로 ‘분류’라는 작업을 하는 어떤 함수를 만드는 작업이다. 함수의 아웃풋은 단순히 예/아니오로 나눠지는 싱글 클래스(single class) 문제일 수도 있고, 좋음/보통/나쁨과 같이 여러개의 클래스로 나눠지는 멀티 클래스(multi class)문제일 수도 있다. 또는 뉴스의 카테고리처럼 사회/경제/국내/해외 등 여러 레이블로 이뤄진 멀티 레이블(multi label)문제이거나 제품 카테고리처럼 클래스의 계층이 있는 계층적 멀티 클래스/레이블(hierarchical multi class/label) 문제일 수도 있다.

분류의 워크플로우는 다음과 같이 트레이닝 세트를 학습하여 모델을 만들어 새로운 데이터를 입력해 적합한 아웃풋을 얻는 것이다.


분류를 활용하는 분야 역시 무궁무진하다. 어떠한 증상들을 보고 어떤 병에 걸렸는지 분류한다거나 이 메일이 스팸인지 아닌지를 분류한다거나 나이, 수입, 부채 등을 보고 재정 보증을 받는다거나 말이다.

■ 분류 방식 1. k 최근접 이웃(k Nearest Neighbors, kNN)

예를 들어 어떤 지역의 날씨를 예측하려고 한다. 그런데 그 지역엔 기상 관측소가 없다. 어떻게 하면 좋을까? 방법은 주위에 위치한 기상 관측소의 기상 예보를 가져와 평균을 내보는 것이다. 만약 그 지역과 가까운 5개의 기상 관측소의 기상 예보 중 3개는 날씨 쨍쨍, 2개는 구름 낌이라면 날씨 쨍쨍이란 결과를 얻는다.

이러한 접근 방식은 k 최근접 이웃(kNN)이라고 한다. 위의 예시에서 k값는 5였다. 여기서 ‘이웃’이란 지리적인 근접도(proximity)를 뜻한다. 군집 분석에서는 K-Means를 사용한다면, 분류 분석에서는 kNN을 쓴다고 이해하면 쉽다.

그렇다면 최적의 k값은 무엇일까? k값이 너무 작다면 노이즈 포인트에 취약할 것이고, k값이 너무 크다면 다른 클래스의 포인트까지 오분류할 가능성이 크다. 주어진 데이터에 따라 적당한 k값은 바뀌겠지만 경험적으로 k값은 1에서 10 사이가 적당하다.

kNN은 꽤나 정확하게 분류한다. 그러나 모든 트레이닝 데이터를 찾아야하기 때문에 속도가 빠른 편은 아니다. 또 다른 특징으로는 kNN은 경계선이 불분명해도 데이터를 핸들링할 수 있다는 장점이 있다. 또한 kNN 알고리즘은 모든 속성(attribute)를 동일하게 중요하다고 가정한다. 이러한 한계는 주요 속성을 선택하거나 가중치를 주면서 해결할 수도 있다.

■ 분류 방식 2. 로키오 분류(Rocchio classifier)

또 다른 방식으로는 Rocchio classifier가 있다. 이는 군집의 중심을 계산했을 때와 비슷하게 각 클래스의 중심(centroid)을 정의한 뒤 각 데이터 포인트를 가장 가까움 중심에 할당하는 것을 말한다.

kNN과 Rocchio classifier의 차이점은 말보다는 여러가지 데이터의 예시를 그림으로 보면 훨씬 이해가 쉽다.

kNN과 Rocchio 둘 다 잘 작동한다.
원데이터의 레이블이 잘못되었을 때는 Rocchio의 성능이 더 우수하다.
클래스의 사이즈가 다를 경우에는 Rocchio의 성능이 더 우수하다.
아웃라이어가 존재한다면 kNN의 성능이 더 우수하다.

정리하자면, kNN은 계산하는 시간이 오래 걸리고 메모리도 많이 필요하나 아웃라이어에 강하다는 장점이 있다. 반면 Rocchio는 계산 시간이 짧고 메모리가 크지 않아도 되나 노이즈에 취약하고 클래스의 사이즈가 다를 경우 제대로 작동하지 않는다.

■ 분류 방식 3. 나이브 베이즈 분류(Naive Bayes classifier)

드디어 간단하고, 강력한 나이브 베이즈 분류이다. 베이즈 분류는 베이즈 이론에 기반으로 만들어졌는데, 이 분류에 대해 알기 위해선 조건부 확률(conditional probability)와 베이즈 이론(Bayes Theorem)에 대한 이해가 선행되어야 한다.

조건부 확률이란 B라는 상황이 벌어졌을 때 A가 발생할 확률을 구하는 공식을 말한다. 수식으로 표현하자면 다음과 같다.

이 식에서 곱 규칙을 사용하면 p(A,B) = p(B|A)p(A) 라는 것을 알 수 있고, 이를 다시 위의 조건부 확률에 대입한 것이 바로 베이즈 공식이다.

원래 식과 비교하자면, 각각의 확률과 A 조건 하에서 B의 확률만 알면 B 조건 하에서 A의 확률을 구할 수 있게 된다. 보통 p(A|B)보다 p(B|A)를 알기 쉽기 때문에 많은 문제에서 유용하게 쓰인다.

이번엔 “어떤 증상이 나타날 때 내가 그 질병에 걸릴 확률은 어떻게 될까?” 라는예시를 통해 다시 한 번 살펴보자. 감염자가 코로나 검사를 했을 때 양성으로 나올 확률이 95%라고 하고, 비감염자가 코로나 검사를 했을 때 음성이 나올 확률이 95%라고 하자. 우리는 테스트에서 양성 판정을 받았을 때 코로나에 진짜 걸릴 확률, 즉 사후 확률(Posterior Probability)인 p(코로나|양성)을 알고 싶다.

베이즈 이론을 적용하면,

p(코로나|양성) = \frac{p(양성|코로나) * p(코로나)}{p(양성)}

p(양성|코로나)는 앞서서 95%로 우리에게 알려져 있고(이를 사전 확률 Prior Probability 라고 한다), p(코로나) 역시 데이터를 통해 쉽게 알 수 있다. 2020년 8월 31일 기준, 한국의 코로나 감염율은 0.03%라고 한다.

여전히 p(양성) 데이터가 필요하다. p(양성)은 p(양성|코로나∨¬코로나)로 계산할 수 있고, 이를 다시 한 번 정리하면 다음과 같다.

p(양성) = p(양성|코로나)*p(코로나) + p(양성|NOT코로나)*p(NOT양성) \\  0.95 * 0.03 + 0.05 * 0.97 = 0.077

다시 처음의 베이즈 식으로 돌아가서 숫자를 대입하면 p(코로나|양성) 값을 구할 수 있다.

p(코로나|양성) = \frac{0.95 * 0.0003}{0.077} = 0.0037

계산을 하면 0.0037이 나온다. 그렇다면 코로나 검사에서 양성으로 나와도 실제로 감염되지 않을 확률이 99.6%에 달한다는 뜻????…. 은 아니다.

이 계산이 현실적으로 말이 안되는 이유는 우리가 사전에 가정한 것이 틀렸기 때문이다. 보통 코로나 검사는 증상이나 의심이 될 경우에만 진행하기 때문이다. 랜덤 샘플링을 하지 않기 때문에 우리가 위에서 계산과는 달리 p(코로나)와 p(NOT코로나)는 같지 않다. 따라서 베이즈 이론과 익숙해지기 위한 예시로만 생각해야 한다. ㅎㅎ

■ 다시 나이브 베이즈!

확률 이론에서 다시 나이브 베이즈로 돌아오자. 나이브 베이즈에서는 ‘모든 속성이 통계적으로 독립적이다’라고 가정한다. 이러한 가정이 나이브 베이즈를 “나이브”하게 만든다(현실 세계의 속성들은 보통 서로서로 연관이 있게 마련임으로…).

아무튼 모든 속성이 통계적으로 독립하기 때문에 우리는 조건부 확률 p(A|C)를 개별 확률 p(Ai|Cj)의 곱으로 재구성할 수 있고, 각각의 확률은 모두 트레이닝 데이터에서 얻을 수 있게 된다.


예를 들어 맑은 날씨, 추운 온도, 높은 습도, 강한바람 이라는 조건들이 주어졌을 때 야외에서 피크닉을 갈 수 있는지 없는지 알고 싶다고 해보자.

p(피크닉간다 | 조건들) = p(맑음|피크닉간다) * p(추움|피크닉간다) * p(강한바람|피크닉간다) * p(피크닉간다)/p(사전확률로 주어진 조건들)

위와 같이 공식을 유도한 뒤는 숫자를 계산하면 끝! 참고로 p(사전확률로 주어진 조건들)을 쉽게 구하려면 p(피크닉간다)와 p(피크닉안간다)를 더해줘서 구할 수도 있다. 관련해서 실제 데이터와 자세한 설명을 알고 싶다면 다음 블로그를 참고하면 된다.

위의 예시는 명목 데이터 위주였다. 숫자 데이터를 다룰 땐 어떻게 하면 좋을까? 첫 번째 방법은 숫자를 다시 명목으로 변환해서 사용하는 방법이다. 두 번째는 숫자 데이터가 정규 분포를 띈다고 가정하고 평균과 표준 편차 등의 파라미터를 구한 뒤 조건부 확률 p(Ai|Cj)를 구하는 방식이 있다.

단 모든 숫자 데이터가 정규 분포를 띄지 않기 때문에 주의해서 사용해야 한다.

나이브 베이즈의 특징은 노이즈 포인트에 강하고, 결측치 데이터의 경우 확률 계산 시 빼고 계산할 수 있다는 점과 관계없는 속성에 영향 받지 않는다는 점이 있다.

그러나 독립성 가정이 모든 속성에 적용되지 않는다는 한계가 있고, 이를 극복하기 위해 BBN(Bayesian Belief Networks)과 같은 다른 기법이 도입되기도 한다. 대부분의 속성은 독립적이기 어렵다. 예를 들어 사람의 속성인 성별, 무게, 키…와 같은 것들이 서로 독립적일 수 없기 때문이다.

나이브 베이즈가 여전히 유효한 이유는, 비록 속성들이 모두 독립적이라고 가정하고 계산했지만 그 결과가 꽤나 정확하기 때문이다. 분류 문제에서는 60:40 이건 90:10 이건 구분만 잘 하면 OK인 경우가 왕왕이기도 하고. 여러모로 계산 비용이 싸기도 하고, 메모리를 많이 요구하지 않는데 결과까지 적당하니… 여전히 나이브 베이즈를 많이 쓰는 이유다.

■ kNN과 나이브 베이즈 비교

계산은 나이브 베이즈가 종종 더 빠르다. 나이브 베이즈는 모든 데이터 포인트를 사용하기 때문에 잘못 레이블된 노이즈에 덜 민감하고, kNN은 아웃라이어에 덜 민감하다. 중복된 속성의 문제는 나이브 베이즈에서 더 문제가 되고, 상관없는 속성 문제는 kNN에 더 문제가 된다.

kNN과 나이브 베이즈 모두 게으른(lazy) 방식인데 명시적(explicit)인 모델을 만들지 못하기 때문이다. 반명 Rocchio는 간단한 eager 방식이다. 다음은 파이썬 연습을 통해 각각에 대해 좀 더 연습을 해보겠다.