카테고리 보관물: 데이터마이닝 Data mining

A/B 테스트를 95% 대 5% 비율로 해도 괜찮을까?

답부터 말하면

안 괜찮습니다.

사실 비율 보다는 샘플의 크기가 중요하지만 어쨌든 95%대 5%로는 A/B테스트는 문제를 만듭니다.

A/B테스트 흔히 온라인서비스에서는 버킷테스트라고 하는데 이 테스트에서 A와 B 두개의 샘플의 수가 서로 불균등하다고 하면 대부분의 통계학자들은 표정이 안좋아지며 심각하게 생각하지만 개발자들은 별것 아니라고 생각합니다.

이게 논란의 여지가 항상 있는 것이므로 조심스럽지만

A/B테스트는 통계학에서 나온 것이므로 통계학자들이 더 잘 알것이므로 이쪽을 더 신뢰하는 것이 맞습니다. 통계학자들은 경험과 이론을 통해 그게 왜 문제인지 설명하지만 개발자들은 그런 설명을 하지 않습니다.

개발자들은 근거를 말하지 않지만 통계학자들은 근거를 말합니다.

A/B 테스트, 버킷테스트는 여러개의 샘플에 각기 다른 처치(작용 또는 변화를 주는것)를 하고 그게 정말 효과가 있는 지 살펴보는 것이라는 것을 기억해야 합니다.

A/B 테스트는 샘플이 중요하다

A/B 테스트는 통계학에서 다루는 실험 운영 방법입니다. 실험계획법이라는 통계학 과목이 있습니다. 과목이 따로 있을 만큼 만만한 것이 아닙니다

통계학은 샘플을 매우 중요하게 생각합니다.
적어도 통계학파 중에 빈도주의자(Frequatist)들은 매우 중요하게 생각합니다.
그와 비교되는 다른 학파들도 있지만 이건 다음에 얘기하겠습니다.

샘플은 어떤 집합에서 일부를 떼어 낸 것을 말합니다.

통계학에서 샘플을 중요하게 생각하는 이유는 샘플을 통해서 원래 전체 데이터의 특성을 파악해야 하기 때문입니다. 샘플을 사용해서 원래 집합을 알아내려고 하는 이유는
대부분의 전체 데이터를 다 확인하는 것은 불가능하기 때문입니다.

“빅데이터 플랫폼도 있는데 전체데이터 확인을 왜 못하냐?” 라고 물어볼 수 있습니다. 잘못 이해한 것인데 거기서 말하는 전체데이터는 실제로 알고자 하는 사실을 얻어야 하는 대상의 전체가 아니기 때문입니다.
예를 들어서
어제까지 가입한 쇼핑몰 전체의 고객 데이터는 전체 데이터가 맞긴 하지만 쇼핑몰의 고객 전체는 아닙니다. 앞으로도 가입할 사람이 있을 것이고 탈퇴할 사람도 있을 것이기 때문입니다. 그런 관점에서 통계학에서 생각하는 전체 데이터를 얻는 것은 불가능하다고 할 수 있습니다.

어제까지 전체 고객 데이터는 통계학에서는 전체데이터가 아닌 그냥 매우 큰 샘플데이터입니다.

A/B테스트에서 A와 B는 각각 전체 모집단에 대한 샘플이라고 봅니다.

A/B테스트에서 샘플 수가 균등하지 않으면 통계 검정을 할 수 없는가?

그래서 A/B의 비율이 5:5로 균등하지 않으면 정확한 비교를 하지 못하는가?
라는 의문이 있을 것이다.
할 수는 있습니다.
다만 꽤 복잡한 방법을 써야 하고 정확하지 않은데다 선택한 검정 자체를 적용하는 것 자체가 맞는지 안맞는지는 확인하려고 하는 것은 노련하고 뛰어난 통계학자도 매우 어렵게 하는 것입니다.

간단하게 공식 몇 개 넣어서 계산하면 되는 것이 아닙니다.

그래서 이렇게 불균등한 샘플 비교를 최대한 피해야 합니다.

샘플 간의 성능 비교를 한다면 균등한 것이 낫다

균등하지 않은 샘플로 샘플의 불균형성을 극복하면서 테스트하는 것 보다 균형 샘플을 만들어서 테스트하는 것이 더 쉽고 돈도 더 적게 듭니다. 균등하지 않은 샘플로 서로를 비교하는 것은 일반적으로 실험계획이 잘못된 경우나 하지 못한 후시 테스트일 가능성이 높습니다.

A/B 테스트와 관련되었대고 하면 무조건 샘플 수를 맞추고 시작합니다.

불균등한 것이 뭐가 그리 문제인가?

샘플이라고해서 그렇게 거창한 것은 아닙니다.
A그룹에서 추출한 숫자들, B그룹에서 추출한 숫자들을 비교하는 것인데
샘플이 균등하지 않으면 크게 달라질 수 있는 것이 파라미터(모수, parameter)인데 평균과 분산입니다.
A/B테스트는 A와 B의 평균과 분산이 실험 후에 많이 차이가 나는지 아닌지를 보는 것입니다.
이때 샘플의 수 그러니까 숫자의 갯수가 많이지면 숫자의 갯수가 적을 때 보다 분산은 무조건 커집니다.
이게 자연적인 현상입니다.
그래서 샘플의 수가 50대 50으로 균등하지 않으면 샘플 수로 인해서 생길 수 있는 기본적인 분산의 차이를 보정하고 검정을 해야 하는데 보정이 매우 어렵고 보정을 해도 정확도가 떨어집니다.

실험결과를 잘못 해석하게 됩니다.
이런 결과로 결정을 하면 비즈니스에 큰 실패를 가져올 수 있습니다.

실험 자체를 잘못하는 것은 그 실험을 없었던 것으로 하면 되기 때문에 피해가 덜하지만
결과를 잘못해석하면 틀렸다는 것 자체를 의심하지 않기 때문에 큰 문제를 생깁니다.

한쪽을 언더샘플링(Under sampling)을 하면 어떤가?

크기(갯수)가 다른 두 샘플들이 있을 때 크기가 적은 샘플 수만큼을 크기가 큰 색플에서 도려내서 숫자를 맞추는 것이 언더샘플링(under sampling)이라는 방법입니다.
간단히 말하면
그냥 큰 쪽을 작은쪽의 크기 만큼 잘라서 맞추는 것입니다.
보통 자를 때 무작정 자르지 않고 랜덤으로 샘플링을 합니다. (확실하게 랜덤으로 분할 한 것과 같은 것으로 분할 할 수 있으면 랜덤 샘플링을 하지 않아도 됩니다. 이건 따로 설명하지요)

어쨌든 이러면 괜찮지 않은가?
라고 생각할 수 있는데
이것도 괜찮지 않습니다.

언더샘플링을 하는 순간 샘플의 모집단이 달라지게 됩니다. 샘플이 뽑힌 것의 자유도라는 것도 다르기 때문에 두 샘플은 비교하기 어렵게 됩니다.
부모가 낳은 형과 동생을 비교하다가 형과 동생의 아들인 조카를 비교하는 꼴인 것이다.
부트스트래핑을 쓰면 하게되면 이러 불균형에서 샘플링을 통해 문제를 해결할 수 있지만 역시 그 보다는 샘플 수를 맞추는게 편하고 낫습니다.

대부분의 버킷시스템은 샘플 수를 맞추도록 설계되어 있다

빅테크 회사들의 버킷시스템이 존재합니다. A/B테스트를 할 수 있도록 플랫폼이 갖춰줘 있고 샘플의 수 등을 수정할 수 있습니다.
저런 대형 기업들의 시스템에서도 기본으로 두 비교군의 샘플 수를 맞추도록 설계되어 있습니다.
다른 대기업들도 모두 마찬가지입니다.
그들은 왜 모두 그렇게 하는지 생각을 해보기로 합시다.
그냥 그렇게 하거나 단순한 전통이어서 그렇게 하는 걸이다 아닙니다.
그것이 통계적이고 과학적으로 실험의 결과를 오해석하지 않는 최선의 방법이기 때문입니다.

통계학자들이 무능하고 실력이 없으면서 복잡해 보이기 좋아하기 때문에 저렇게밖에 못한다고 생각할 수도 있지만.

버킷시스템에서는 언더샘플링이 가능하다

추가로 말하면 대부분의 버킷시스템은 사용자의 ID 또는 비식별ID를 비트연산을 통해 그룹을 나눠서 관리하도록 되어 있습니다.

따라서 많은 쪽의 비트 몇개를 무시해서 언더샘플링을 하면 샘플비교를 할 수 있습니다.
조금 복잡하니 자세한 것은 따로 설명하겠습니다.

참고

A/B테스트에 대해 포스트를 올린적이 있습니다. 시간이 있다면 자세한 내용은 그걸 참조하세요.

매크로 평균(Macro-average)과 마이크로 평균(Micro-average)

머신 러닝 분야에서 평가 지표는 모델이 얼마나 잘 동작하는지를 측정하는 데 중요한 역할을 합니다. 이 중에서도 다중 클래스 분류 문제에서는 클래스별 성능을 평가하는 데 있어서 다양한 방법들이 있습니다. 그 중에서도 대표적으로 사용되는 방법으로는 Macro-average와 Micro-average가 있습니다.

Macro-average 매크로 평균

Macro-average는 클래스별 성능 지표를 각각 계산한 후 평균을 내는 방식입니다. 이 방법은 클래스별 데이터 셋이 균등하게 분포되어 있을 때 적합합니다. 예를 들어, 10개의 클래스가 있는 다중 클래스 분류 문제에서 5개의 클래스는 100개의 데이터를 가지고 있고, 나머지 5개의 클래스는 10개의 데이터만 가지고 있다고 가정해 봅시다. 이 경우, Macro-average는 모든 클래스의 성능을 동일하게 취급하므로, 각 클래스의 데이터 수에 관계 없이 모든 클래스가 동일한 비중으로 반영됩니다.

다음은 Macro-average를 계산하는 과정입니다. 클래스 개수가 k개라고 할 때,

  1. 클래스별로 TP(True Positive), FP(False Positive), FN(False Negative), TN(True Negative)를 계산합니다.
  2. Precision, Recall, F1-score 등 성능 지표를 클래스별로 계산합니다.
  3. 클래스별로 계산된 지표들을 모두 더한 후 클래스 수(k)로 나눕니다.

Micro-average 마이크로 평균

Micro-average는 클래스별로 성능 지표를 계산하기 전에 모든 클래스를 하나의 클래스로 간주하고 전체적인 성능 지표를 계산합니다. 이 방법은 클래스별 데이터 수가 다르더라도 모든 클래스의 성능을 동일하게 고려하므로, 클래스별 데이터 수가 차이가 많이 나는 문제에서 유용합니다.

다음은 Micro-average를 계산하는 과정입니다.

  1. 전체 데이터셋에서 TP, FP, FN, TN을 계산합니다.
  2. Precision, Recall, F1-score 등 성능 지표를 전체 데이터셋에 대해 계산합니다.

이제 간단한 예시를 들어보겠습니다. 다음과 같은 3개의 클래스(0, 1, 2)가 있고, 각각의 클래스에 대해 TP, FP, FN, TN의 개수가 다음과 같다고 가정해 봅시다.

클래스TPFPFNTN
0102385
1154781
281494

이 경우, Macro-average와 Micro-average를 각각 계산해 보겠습니다.

Macro-average 계산

  1. 클래스별 TP, FP, FN, TN 계산
클래스TPFPFNTN
0102385
1154781
281494
  1. 클래스별 Precision, Recall, F1-score 계산
클래스PrecisionRecallF1-score
00.83330.76920.8000
10.78950.68180.7317
20.88890.66670.7619
  1. 모든 클래스의 지표를 더한 후 클래스 수로 나눔

$$ \text{Macro-average Recall} = \frac{0.7692+0.6818+0.6667}{3} = 0.706 \\\\ \text{Macro-average F1-score} = \frac{0.8000+0.7317+0.7619}{3} = 0.764 $$

Micro-average 계산

1. 전체 데이터셋에서 TP, FP, FN, TN 계산

전체 데이터셋TPFPFNTN
33714260

2. 전체 데이터셋에서 Precision, Recall, F1-score 계산

$$ \text{Micro-average Precision} = \frac{33}{33+7} = 0.825 \\\\ \text{Micro-average Recall} = \frac{33}{33+14} = 0.702 \\\\ \text{Micro-average F1-score} = 2 \times \frac{0.825 \times 0.702}{0.825 + 0.702} = 0.759 $$

이처럼 average Micro-average는 다중 클래스 분류 문제에서 클래스별 성능을 평가하는 데 사용되는 방법 중 두 가지입니다. Macro-average는 클래스별 데이터셋이 균등하게 분포되어 있을 때 유용하며, Micro-average는 클래스별 데이터 수가 차이가 많이 나는 문제에서 유용합니다.

계산 법의 선택

어떤 방법을 선택할지는 데이터셋의 특성에 따라 달라질 수 있습니다.

예를 들어, 클래스별로 데이터 수가 크게 차이가 나지 않고 균등하게 분포된 경우에는 Macro-average를 사용하는 것이 적절할 수 있습니다.

반면에 클래스별로 데이터 수가 크게 차이가 나는 경우에는 Micro-average가 더 적절할 수 있습니다.

클래스별로 데이터가 균등한지 아닌지는 카이제곱검정을 하면 알 수 있습니다.

또한, Macro-average는 클래스별 성능을 독립적으로 평가하기 때문에, 각 클래스가 동등하게 중요한 경우에 적합합니다. 반면에 Micro-average는 모든 클래스가 동일한 중요도를 가지는 것이 아니라, 전체적인 성능이 중요한 경우에 적합합니다.

마지막으로, Macro-average와 Micro-average는 모델의 성능을 평가하기 위해 단독으로 사용하기 보다는, 다른 지표와 함께 사용하는 것이 좋습니다. 또한, 클래스별로 데이터 수가 매우 작거나 없는 경우에는 이를 해결하기 위해 Weighted average 방법을 사용하기도 합니다. 이는 클래스별 데이터 수를 고려하여 평균을 계산하는 방법으로, Macro-average와 Micro-average의 중간 형태라고 볼 수 있습니다. 이러한 방법들을 적절히 사용하여 다중 클래스 분류 모델의 성능을 정확하게 평가할 수 있습니다.

Faiss – 고속 벡터 검색 엔진으로 유사도 검색하기, Vector Search Engine

Faiss는 Facebook Lab에서 만든 벡터 검색 엔진입니다.

Faiss는 벡터 갬색 엔진이고 유사도 검색을 하거나 추천, 기계학습로 만든 모델을 활용해서 응용 서비스를 만들 때 사용합니다.

별거 아닌거처럼 보이지만 불가능한 것을 가능하게 만들어 주는 매우 유용한 라이브러려입니다.

라이브러리이기 때문에 자체로 서비를 제공하는 것은 아니고 이 라이브러리를 이용해서 Backend, Frontend 서비스를 개발하거나 응용 프로그램에 넣을 수 있습니다.

벡터 검색 엔진

벡터 검색 엔진이 뭔지를 설명해야 하는데요. 보통 그래프 서치라고도합니다. 이것들은 주로 수치를 찾는 것을 말하는데 지도검색 같은데서도 사용하는 것으로 매우 쓸모가 많은 엔진입니ㅏㄷ.

일반적으로 검색 엔진이라고 말하면 흔히 텍스트를 검색하는 것을 생각합니다. 구글의 웹 검색, 네이버 검색, 다음 검색 같은 것은 검색 포털이요. 그게 아니면 Elastic Search나 Lucene갈은 검색 엔진을 생각할 텐데요.

하지만 벡터 검색은 텍스트가 아닌 벡터를 빠른 속도로 찾는 것을 말합니다. 벡터는 수열을 말합니다.

아래와 같이 10개의 숫자가 묶여 있으면 이걸 10차원 벡터라고 합니다. 숫자가 100개 있으면 100차원 벡터, 1000개면 1000차원 벡터입니다.

[-0.00709967 -0.01956441  0.03790117 -0.00150699 -0.02145613 -0.06128123
  0.04965064 -0.05588596  0.08241648 -0.05128423]

이런 것들이 수억개가 있고 수억개 중에 어떤 벡터와 가장 가까운 벡터를 찾아야 한다면 문제가 어려워집니다.

가장 가까운 것을 주어진 입력 벡터와 수억개의 벡터를 모두 하나씩 연결해서 서로의 거리를 계산한 다음 가장 가까운 것을 찾아야 하기 때문입니다.

가장 가까운 것을 찾는데 수십분이 걸릴 수 있습니다. 이러면 실제 서비스에서는 쓸 수 없습니다.

어떤 사용자가 온라인 서적 판매사이트에 접속했을 때 그 사람에게 책을 추천해줘야 하는데 추천할 책 목록을 검색하는데 10분씩 걸린다면 서비스에 적용하지 못합니다. 다른 서비스도 마찬가지구요.

Faiss는 인덱싱 방식을 다르게 해서 데이터가 많아도 짧게는 밀리초 단위 길게는 수초 이내에 결과를 찾아 줍니다. 즉 온라인 추천 서비스에 빠르게 적용하는 추천 시스템 등을 개발하는데 사용할 수 있습니다.

Python Faiss library

Faiss는 Python wrapper를 공식 지원하고 있습니다. c++로 만들어졌으니까 다른 언어로도 연결해서 사용할 수 있습니다. Go lang이나 Node.js, Kotlin 같은 것을 쓰면 Python 보다는 성능이 더 좋을 것입니다.

깃헙 레파지토리: https://github.com/facebookresearch/faiss

레파지토리에 있는 것을 설치해도 되고 그냥 pip를 이용해서 설치해도 됩니다.

pip3 install faiss-cpu

gpu 버전을 설치하고 싶으면 gpu 버전ㅇ로 명시해서 설치하면 됩니다.

pip3 install faiss-gpu

사용법은 매뉴얼을 봐야 하겠지만 기본 사용법은 쉽습니다.

Faiss로 유클리디안 거리로 벡터 검색하기

아래 코드는 유클리디안 거리(Euclidean Distance)로 찾는 예제입니다.

이런 것은 KNN (K-nearest-neighbor) 와 같은 기계학습 모델에 사용하는 것입니다. KNN은 판별 모델에서 사용할때 매우 강력한 알고리즘이지만 검색할 때 너무 느리고 자원을 많이 사용하는 문제로 인해서 실제로는 거의 사용을 못하는 알고리즘이지만 Faiss를 이용하면 이걸 쓸 수 있습니다.

Faiss 색인을 생성할 때 벡터의 차원을 지정해주고, Index의 유형도 결정을 해줘야 하는 것이 중요합니다. 검색은 입력한 k의 갯수만큼 리턴하게 되어 있고 벡터의 색인 번호와 거리를 리턴하게 되어 있늡니다.

색인 번호는 그냥 입력한 입력한 벡터의 순번입니다.

import faiss
import numpy as np
import random

# Euclidean distance 기반으로 가장 가까운 벡터를 찾는다.

# 랜덤으로 10차원 벡터를 10개 생성
vectors = [[random.uniform(0, 1) for _ in range(10)] for _ in range(10)]
# 10차원짜리 벡터를 검색하기 위한 Faiss index 생성
index = faiss.IndexFlatL2(10)
# Vector를 numpy array로 바꾸기
vectors = np.array(vectors).astype(np.float32)
# 아까 만든 10x10 벡터를 Faiss index에 넣기
index.add(vectors)
# query vector를 하나 만들기
query_vector = np.array([[random.uniform(0, 1) for x in range(10)]]).astype(np.float32)
print("query vector: {}".format(query_vector))
# 가장 가까운 것 10개 찾기
distances, indices = index.search(query_vector, 10)
# 결과룰 출력하자
idx = 0
for i in indices:
    print("v{}: {}, distance={}".format(idx+1, vectors[i], distances[idx]))
    idx += 1

Faiss로 코사인 유사도로 검색하기

유클리디안 거리(Euclidean Distance)로 가장 가까운 벡터를 찾으면 특정 차원의 양적 수치에 따라는 거리가 가깝다고 판별되는 편향의 문제가 있습니다. 이게 문제가 될 때가 있고 그렇지 않을 때가 있는데 이것은 문제의 도메인에 따라 다릅니다. 그러니까 문제가 주어진 환경에 따라 그때그때 다르다는 뜻입니다.

이런 문제를 피하는 방법은 유사도를 계산할 때 거리측정 방법을 유클리디안 거리를 사용하지 않고 코사인 유사도를 사용해서 벡터의 방향이 가까운 것을 찾는 것입니다. 보통 검색엔진들도 이 방법을 기본으로 사용합니다.

Faiss도 이걸 지원하는데 예제는 아래 코드를 보시면 되고 앞서 설명했던 유클리디안 거리 기반의 검색과 다른 점은 index를 생성할 때 타입을 다르게 생성해야 하고 벡터를 노말라이즈 해줘야 한다는 것입니다. 벡터가 이미 노말라이즈되어 있다면 안해도 됩니다.

import faiss
import numpy as np
import random

# 코사인 유사도 (Cosine Similarity) 를 이용해서 가장 가까운 벡터를 찾으려면 몇가지를 바꿔줘야 한다.
# 코사인 유사도 (Cosine Similarity) 를 사용하려면 벡터 내적으로 색인하는 index를 만들면 된다.
# 코사인 유사도를 계산하라면 벡터 내적을 필연적으로 계산해야 하기 때문이다.

# 랜덤으로 10차원 벡터를 10개 생성
vectors = [[random.uniform(0, 1) for _ in range(10)] for _ in range(100)]
# 10차원짜리 벡터를 검색하기 위한 Faiss index를 생성
# 생성할 때 Inner Product을 검색할 수 있는 index를 생성한다.
index = faiss.IndexFlatIP(10)
# 아래는 위와 동일하다.
# index = faiss.index_factory(300, "Flat", faiss.METRIC_INNER_PRODUCT)

# Vector를 numpy array로 바꾸기
vectors = np.array(vectors).astype(np.float32)
# vectors를 노말라이즈 해준다.
faiss.normalize_L2(vectors)
# 아까 만든 10x10 벡터를 Faiss index에 넣기
index.add(vectors)
# query vector를 하나 만들기
query_vector = np.array([[random.uniform(0, 1) for x in range(10)]]).astype(np.float32)
print("query vector: {}".format(query_vector))
# 가장 가까운 것 10개 찾기
distances, indices = index.search(query_vector, 50)
# 결과룰 출력하자.
idx = 0
for i in indices:
    print("v{}: {}, distance={}".format(idx+1, vectors[i], distances[idx]))
    idx += 1

노트북 코드

위 코드의 노트북은 깃헙 레파지토리에 올려 두었습니다.

https://github.com/euriion/python-exams/blob/main/faiss/faiss-exam.ipynb

다음 번에는 기회가 되면 Faiss를 이용한 간단하고 빠른 추천 엔진을 만드는 예제를 올려보겠습니다.

CART – 결정 트리와 회귀 트리 Decision Tree and Regression Tree #1

결정트리(Decision Tree)인 CART 알고리즘에 대해 포스팅합니다.

제 계획대로라면 벌써 몇년전에 포스팅을 했어야 했지만 계획대로 되는 것은 언제나 그렇듯이 없습니다.

CART는 GBDT, Random Forest, XGboost, LightGBM 등의 트리계열 알고리즘의 근간이 되는 매우 중요한 알고리즘입니다. 요즘 트리 계열 알고리즘에서 가장 좋은 성능을 보이는 XGboost에게는 할아버지쯤 되는 알고리즘입니다.

CART에는 결정 트리(Decision Tree)와 회귀 트리(Regression Tree)라는 알고리즘이 2개 들어 있습니다. 한 개가 아닙니다. 그런데 둘은 거의 비슷하기 때문에 1개라고 봐도 무리가 없긴 합니다. 일란성 쌍둥이라고 생각하면 됩니다. CART는 Classification And Regression Trees의 약어인데 여기에도 분류와 회귀를 하는 트리라는 것을 알려주고 있습니다.

어쨌든 이 둘의 차이는 뒤에 설명하겠습니다.

알고리즘의 컨셉

알고리즘의 컨셉은 간단합니다. 학습 데이터를 해석해서 알아서 트리 구조를 자동으로 만든 다음 만들어진 트리를 이용해서 분류, 예측 문제를 해결하는 모델을 만드는 것입니다.

여기서 중요한 것은 트리를 자동으로 만든다는 것입니다. 트리를 사람이 만들어줘야 한다고 상상하시는 분들이 있는데 아닙니다.

분류 트리와 회귀 트리의 차이

분류 트리와 회귀 트리의 차이를 살펴보겠습니다.

결정 트리(Classification Tree)는 클래스(등급) 또는 레이블(표 딱지)을 예측하는 것으로 “남”또는 “여”, “예” 또는 “아니오”, “A”, “B”, “C” 와 같이 어떤 것인 맞추는 것(판별)이고

회귀 트리(Regression Tree)는 연속형 숫자인 1,2,3 123, 28.5와 같은 숫자를 맞추는 것입니다. 사람의 키를 맞춘다거나, 대출 상환 예상액을 맞춘다거나 하는 것(예측)입니다.

CART의 탄생

뭐든 그렇지만 자세한 설명 전에 역사를 조금 살펴보면 이해하는데 도움이 됩니다.

CART는 1980년대에 발표된 것으로 이제 나이가 들대로 든 알고리즘이지만 아직도 현역에서 많이 쓰입니다. 기계학습 알고리즘이 다들 독특한 면이 있긴하지만 CART도 상당히 독특한 기계학습 알고리즘입니다. 이 알고리즘은 학술상의 갈래로 보면 데이터마이닝 계열로 많이 분류됩니다. 비즈니스적인 결정을 과학적으로 해서 가치 창출얼 하기 위한 것. 그러니까 좋은 결정을 내리는 것을 자동화하기 위한 용도로 만들어진 것입니다.

만든 분들의 말에 의하면 통계 분석이나 문제 해결을 할 때 마다 회귀모델같은 통계 모델이나 여러 수리 모델을 매번 디자인하고 적용하는 것이 비효율적이라서 싫었다고 합니다.

데이터마이닝 계열이라고 했지만 이것도 기계학습 알고리즘이고 만들어진 원리를 보면 꽤 깊은 고급 통계 이론과 정보 이론이 함축되어 있습니다. 만만한 알고리즘은 아닙니다. 이 알고리즘의 저자들이 총 4명인데 통계학, 과학, 의학, 경제학, 컴퓨터 공학에 매우 뛰어난 석학들입니다. 이 알고리즘이 이런 분야에 고루 쓸 수 있는 다목적 도구라고 해석해 볼 수 있습니다.

CART의 저자와 논문

보통 알고리즘은 논문으로 많이 발표되는데 CART 알고리즘은 논문이 없습니다. 논문으로 발표된 알고리즘이 아니고 책으로 발표된 알고리즘입니다. 논문을 찾다가 논문이 없고 대신 책이라는 사실에 충격받았던 기억이 있습니다.

오래된 책인 만큼 표지가 매우 촌스럽습니만 아마존에서 아직도 판매하고 있습니다. 출판일을 보면 1984년 1월 출판이 첫판입니다.

CART의 저자들

앞서 말씀드렸듯이 책 표지에 있는 저자 4명은 모두 각 분야에서 상당히 유명한 분들입니다. 아래의 4명입니다.

  • Leo Breiman – University of California, Berkeley
  • Jerome H. Friedman – Stanford University
  • Richard A. Olshen – Stanford University
  • Charles J. Stone – University of California, Berkeley

책의 저자들까지 나열한 것은 위의 저자들 중에
Leo Breiman (레오 브라이먼)
Jerome H. Friedman (제롬 프리드먼)
이름을 굳이 외우실 필요는 없습니다만 이 두 사람은 알아두는 것이 기계학습을 깊이 공부하신다면 도움이 되기 때문입니다. 이 두 사람은 CART 발표 이후에 Random Forest(랜덤 포레스트)와 GBDT (Gradient Boosted Decision Tree), GBRT(Gradient Boosted Regression)를 만든 사람들입니다. 아마 기계학습을 조금이라도 공부하신 분들은 이 알고리즘들의 이름을 알고 있을 것입니다. 특히 랜덤포레스트는 너무도 유명하지요. 그리고 부럽게도 위키피디아에도 이 분들 이름이 등록되어 있습니다.

CART와 유사한 알고리즘

ID3, C4.5, C5.0 이라는 결정 트리(Decision Tree)알고리즘이 있습니다. 나열한 순서대로 개량된 버전인데 C4.5가 가장 많이 알려져 있습니다. CART와 유사하다고 하는데 동일한 시대에 발표된 것이지만 관련은 없다고합니다. 구조도 서로 비슷하다고 알려져 있습니다만 제가 이 알고리즘은 잘 알지 못합니다.

CART가 현재는 라이센스가 없이 무료인 반면 C4.5 구현체가 판매되었던 알고리즘이라서 사용자가 많지는 않습니다. 성능은 꽤 좋다고 하며 CART와 C4.5의 성능은 비슷하다고 알려져 있습니다.

CART의 개량형, 강화형

CART의 개량형, 강화형 또는 영향을 받은 것은 다음과 같은 것들이 있습니다.

  • Random Forest
  • Gradient Boosted Decision Tree / Gradient Boosted Regression Tree
  • XGboost
  • lightGBM
  • CatBoost
  • Isolation Cut Forest
  • Robust Random Cut Forest

앞서 말했듯이 요즘 각광받는 XGboost, lightGBM도 모두 CART 계열입니다. 현재 결정 트리 계열 중에 가장 주목받는 것은 lightGBM 입니다. 그래서 요즘은 CART를 사용하지 않고 바로 lightGBM이나 XGboost를 사용하는 경우도 많습니다. 다만 CART만 앙상블 모델이 아니고 다른 알고리즘은 모두 앙상블 모델입니다. 앙상블 모델은 여러 모델을 합쳐서 하나의 모델로 만든 것을 말합니다.

위의 알고리즘 중에서 저는 개인적으로 lightGBM을 매우 좋아합니다만 그 이유는 XGboost이 비해서 가볍고 범주형 변수를 지원하기 때문입니다. 자세한 얘기는 다음 기회에 하기로 하겠습니다.

Decision Tree와 Regression Tree의 차이

Decision Tree(결정 트리, 분류 트리)와 Regression Tree(회귀 트리)는 매우 유사합니다. Decision Tree가 남,녀와 같이 클래스 또는 레이블로 된 것을 분류해주는 Classification(분류) 문제 해결이라면 Regression Tree는 몸무게, 키, 확률 등의 연속형 수치값을 추정해주는 Regression 입니다.

참고로 Regression Tree에는 흔히 말하는 Linear Regression과 같은 회귀 모델이 들어 있지 않습니다. 연속형 값을 추정하는 것을 넓은 의미로 Regression(회귀)라고 하는데 Regression Tree는 종단 노드에서 평균을 사용해서 결과값을 추정합니다. 평균을 사용해서 추정하는 것은 넓은 의미에서 회귀라고 말 할 수 있습니다.

설명이 어려워졌는데 쉽게 말하자면 Regression Tree에서 말하는 Regression은 Linear Regression(선형회귀)나 Logistic Regression(로지스틱 리그레션)에서 말하는 그 회귀(Regression)이 아닙니다.

CART의 원리

복잡한 수식 같은 것을 적으려면 시간과 지면이 많이 필요합니다. 우선 원리만 적고 자세한 것은 나중에 업데이트하도록 하겠습니다.

cart algorithm에 대한 이미지 검색결과

CART 위와 같은 트리를 자동으로 만드는 것입니다. 위의 그림은 CART에서도 Decision Tree를 설명한 것인데 남 (Male), 여 (Female)를 구분하는 판별 모형을 만들때 입력 변수(Input variable, feature, 자질)인 키 (Height)와 몸무게 (Weight)를 이용하는 Decision Tree입니다.

입력 변수는 범주형 변수를 넣을 수도 있습니다. 만약 머리색을 입력 변수 중 하나로 추가해서 넣는다고 하면 Black, Brown, Pink 등과 같은 것이 됩니다.
출력 클래스도 남, 여가 아닌 남,여,모름 이렇게 3개 이상을 지정할 수 있습니다. 단 Decision Tree에 해당하고 Regression Tree는 안됩니다.

CART의 신비는 저 “트리를 어떻게 자동으로 만들어 주느냐”입니다.

이 다음 설명은 다음 편으로 짤라서 쓰겠습니다.

유클리디안 거리 – Euclidean Similarity

유클리디안 유사도라고도 하는데 원래 유클리디안 거리(Euclidean distance)라고 말하는 것이 맞는 것 같습니다. 유클리디안 유사도는 다소 이상한 단어의 조합이라는 생각이 듭니다. 하지만 유클리디안 유사도라는 말도 많이 통용되므로 이 포스트에서도 그냥 그렇게 하기로 하겠습니다.

유클리디안 유사도(Euclidean similarity)는 유클리디안 거리를 구해서 두 벡터의 유사도로 사용한다는 뜻입니다.

유클리디안 거리는 직선 거리다

유클리디안 거리는 기하학적으로 볼 때 두 점의 직선거리를 구하는 것입니다.  또는 선형대수에서 주로 다루는 벡터 스페이스(Vector space)라고 불리는 선형 공간에서도 동일하게 최단 거리를 구하는 것을 말합니다.

코사인 유사도를 설명할 때 언급한 적이 있습니다만 유사도는 2개의 데이터만 가지고 계산해서 결과값을 뽑아내도 그것만으로는 아무짝에도 쓸모가 없습니다.

세상에 사람이 둘 만 남았다면 두 사람은 서로 닮은 걸까요? 안 닮은 걸까요? 모릅니다.

유사도는 다음과 같은 방식으로 주로 사용합니다.

  1. 여러 개의 데이터에서 주어진 것과 가장 가까운 것이 어떤것인가?
  2. 여러 개의  데이터에서 가장 가까운 것들끼리 묶어보자

유클리디안 거리는 데이터마이닝이나 기계학습에 익숙하시다면 K-means (K민즈, K중심값, K평균 이라고 번역합니다) 같은 것에서 사용하는 것을 본 적이 있을 것입니다. 유사도라는 것이 사실은 거리를 측정하는 방법(distance measurement)일 수 밖에 없습니다. 거리를 측정하는 방법을 어떤 것을 쓰느냐에 따라 이름을 무슨 무슨 유사도 이렇게 “유사도”라는 단어를 붙여서 부릅니다.

유클리디안 거리 구하기

유클리디안 거리를 구하는 방법은 간단하고 매우 쉽습니다.
피타고라스 정리를 알면 됩니다.

직각삼각형의 빗변의 길이를 구하는 것입니다.

위키피디아를 보면 거기에 그림을 아래와 같이 넣어놓고 설명해 놨습니다.

간단 한 수식이 있지만 그림으로 보니 눈이 아프군요.

그림에서 p와  q의 유클리디안 거리는 p와 q의 직선거리를 구하면 되는 것이고 가운데 만들어진 삼각형이 직각삼각형이니까 피타고라스 정리를 쓰면 빗변의 길이, 즉 대각선의 길이를 구할 수 있습니다. 이 대각선의 길이가 유클리디안 거리입니다.

결론은 삼각형의 빗변의 길이를 계산하면 됩니다.

참고로 피타고라스 정리가 3차원 이사의 고차원에서도 되는 건지 헷갈릴 수 있겠습니다.  당연히 3차원 이상에서도 적용이 됩니다.  3차원, 4차원, 5차원, …, R차원 다 됩니다.

수학자들이 증명해 놓은 것이 있습니다. 그냥 믿고 쓰시면 됩니다.

5차원인 경우를 예를 들어서 설명하면
아래와 같이 2개의 5차원 벡터가 있다고 하고

a = (1, 2, 3, 4, 5)
b = (2, 3, 4, 5, 6)

벡터의 멤버수가 5개씩이므로 둘 다 5차원 벡터입니다.  차원이 다르면 안됩니다. 맞춰 줘야지요.

각각 차원(축)을 맞춰서 순서때로 빼준 다음에 제곱해서 더한 다음에 루트를 씌우면 됩니다.

1번째 차원: 1 – 2를 계산해서 제곱 = 1
2번째 차원: 2 – 3을 계산해서 제곱 = 1
3번째 차원: 3 – 4를 계산해서 제곱 = 1
4번째 차원: 4 – 5를 계산해서 제곱 = 1
5번째 차원: 5 – 6를 계산해서 제곱 = 1

다 더한 다음에 루트

sqrt(1 + 1 + 1 + 1 + 1)

답은 2.236068 입니다.

R코드로는 이렇게 하면 됩니다.

a_vector <- c(1, 2, 3, 4, 5)
b_vector <- c(2, 3, 4, 5, 6)

dist(rbind(a_vector, b_vector))

추가로 유클리디안 거리는 양적인 것을 기반으로 하는 것이라서 축의 스케일이 맞지 않으면 이상한 측정이 됩니다.  축의 스케일을 맞춰야 할지 말아야 할지는 그때 그때 다릅니다.

이런 말이 나오면 항상 골치만 아픕니다만 어쨌든 뭐든 쉽게 쓸 수 있는 것은 없는 것 같습니다.

예를 들면 이런 경우입니다.

a = (1, 2, 3000000, 4, 5)
b = (2, 3, 4000000, 5, 6)
c = (3, 4, 5000000, 6, 7)

3번째 차원, 3번째 축의 값에 의해 가장 큰 영향을 받습니다. 다른 차원의 값들은 구실을 못하게 됩니다.

기회가 되면 다른 포스트에 스케일을 맞추는 여러가지 방법도 적어 보겠습니다.