카테고리 보관물: 데이터마이닝 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번째 축의 값에 의해 가장 큰 영향을 받습니다. 다른 차원의 값들은 구실을 못하게 됩니다.

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

TFIDF – Term Frequency Inverse Document Frequency

TFIDF(TF-IDF)에 대한 포스트입니다.
자세히 쉽게 그리고 조금 길게 적었습니다.

TF-IDF 관련 강연을 하기 위해서 전에 작성해 놓은 자료를 찾다가 못찾고 말았습니다. 그래서 혹시 이 블로그에 적어 놨는지해서 찾아봤는데 없더군요(‘ㅡ’).  결국 다시 작성해야 하게 생겼는데 했던걸 도로 하려고하니 정말 하기 싫더군요. 그래서 먼저 블로그로 포스팅을 다시 하기로 했습니다.

이걸 강연자료로 다시 바꿔야 하지만 모르겠고.

언제나 그렇듯이 이런 기본 개념에 대한 명은 위키피디아에 참 잘 돼 있습니다. 잘 돼있긴하지만 위키피디아는 이해하기 어렵게 돼 있습니다. 시간 여유가 있거나 관심이 있으면 먼저 살펴보세요.

영문 위키피디아의 내용이 한글 위키피디어 내용 보다 더 상세합니다만 영어에 압박을 느끼는 분들은 한글판을 보고 그것도 싫으면 위키피디아를 읽는 것은 통과하세요.

TFIDF에 대한 내용을 누군가의 이해를 돕기 위해서 주욱 풀어서 설명하다보면 제 경험으로는 대부분 내용이 매우 장황해집니다.

TFIDF는 간단히 잘 설명하기 어려운 것 중에 하나입니다.
쉽게 설명하려면 설명을 매우 길게 해야하고 길게하면 너무 늘어져서 맥락을 놓칩니다.

너무 간단하게 짧게 적어버리면 처음 접하는 분들께는 오해와 착각을 유발할 여지가 있는 부분도 생깁니다. 그것은 장황해지는 것 보다는 더 큰 문제가 있습니다.

가능한 쉽게 풀어서 쓴다는 블로그의 취지에 맞게 이 포스트는 어째든 길게 풀어서 작성하겠습니다. 장황하다는 말이지요.

긴 글을 싫어하시는 분들은 인터넷의 다른 글들도 한 번 찾아보세요. 짧게 잘 설명된 글도 많이 있습니다. 관련지식이 있는 분들은 그 정도로도 충분할 것입니다.

TFIDF는 어디 쓰이는가?

먼저 어디 쓰이는지 알면 이해하는데 도움이 됩니다.

검색엔진(Google, Bing, Naver 이런것들이요)이나 텍스트마이닝에서 흔히 볼 수 있을텐데요.  문서의 유사도나 중요도 같은 것을 계산하기 위해서 문서내의 단어들을 계량화(quantization)를 하는데 그 계량화를 할 때 중요한 단어와 중요하지 않은 단어를 다르게 가중치 고려하고 싶을 때 사용하는 가장 잘 알려진 방법입니다.

간단히 말해서 “문서 내의 단어들에 각각 부여한 중요도를 나타내는 숫자값” 입니다.

TFIDF의 정의

TFIDF의 포괄적인 의미에서의 정의는 이렇습니다.

여러 개의 문서가 있을 때 각 문서 내의 있는 텀(term)들에 가중치가 적용된 어떤 수치값을 부여해 놓는 방법

텀(term)이 뭔지는 뒤에서 설명하겠습니다.

이렇게 TFIDF값을 부여해 놓고 나중에 코사인 유사도(Cosine Similarity)등을 이용해서 문서들의  유사도를 구하는데 흔히 사용합니다. (코사인 유사도는 제 블로그의 다른 포스트를 참조하셔도 되고 역시 위키피디아를 보셔도 됩니다)

TFIDF를 계산하면 문서 내에서 상대적으로 더 중요한 단어가 어떤 것인지 알 수는 있지만 그 외에 문서끼리의 유사도라든가 이런것은 계산이 되어 있지 않은 상태입니다.

즉 문서와 문서의 유사도를 구하거나 여러가지 용도로 사용하기 이전에 단어들에게 부여하는 어떤 수치라고 기억해 두고 시작하시면 됩니다.

문서는 글들이 적혀 있는 텍스트(text)를 말합니다.

TFIDF 공식

우선 TFIDF scheme 공식을 잠깐 확인하고 넘어갑니다.  당장은 이 공식이 아주 중요하지 않습니다. 그래서 너무 공식을 자세히 볼 필요는 없습니다.

초간단 버전의 공식입니다. 공식이라기 보다는 의사코드(Pseudo code)에 가깝습니다.

\(
\begin{aligned}
\huge{
\mbox{tf-idf}_{t,d} = \mbox{tf}_{t,d} \times \mbox{idf}_t
}
\end{aligned}
\)

그냥 공식대로라면 TF와 IDF라는 것은 곱하면 됩니다. 물론 저기에는 TF와 IDF가 각각 뭔지는 나와있지 않습니다. 그래서 저건 사기입니다.

밑에 제대로 된 공식이 나옵니다만 사실 기본 컨셉으로는 저 공식이 전부라고도 할 수 있습니다.

사실 TFIDF의 공식은 수치계산 부분 보다는 프로세싱을 어떻게 하는지에 대한 주변의 설명 부분이 더 많습니다. 그래서 처음보면 저 공식만 보고는 아무것도 이해할 수 없습니다.

이해를 위해서 이제 TFIDF를 각각 파트별로 3단계로 구분해서 적어보면 다음과 같습니다. 각각 TF, IDF그리고 TFIDF를 구하는 공식으로 나뉘어 있습니다.
다음은 덜 간단한 공식입니다.

\(\begin{aligned}\huge{t{f_{ij}} = \frac{{{f_{ij}}}}{max\{ {f_{1j}},{f_{2j}},{f_{3j}},\ldots,{t_{\left| v \right|j}} \}},}\end{aligned}\)

\(\begin{aligned}\huge{id{f_i} = \log \frac{N}{{d{f_i}}}.}\end{aligned}\)

\(\begin{aligned}\huge{{w_{ij}} = t{f_{ij}} \times id{f_i}.}\end{aligned}\)

하지만 이것도 주변설명이 여전히 안돼 있습니다. 이것만 가지고 이해하는 것은 사실상 불가능합니다. 주변지식없이 이것만 가지고 이해를 했다고 한다면 거짓말입니다.

공식내에 있는 각 문자에 대한 정의나 설명도 하지 않고 f나 df같은 것을 어떻게 추출해서 뽑아내는지도 설명을 하지 않았습니다.
이것은 이 글 뒤쪽에서 설명할테니 위의 공식도 그냥 훑어 보고 넘어가도 됩니다.

위에 적은 공식은 텍스트마이닝의 대학원 과정의 교재에서 복사해 온 것으로 TFIDF 공식의 생김새는 대충 저렇습니다. 착각할 수 ㅇㅆ어 따로 덧붙이면 TFIDF의 공식은 딱 1개가 정해진 것이 아닙니다.
그래서 무조건 하나를 보고 “그게 맞다”라고 생각하시면 안됩니다. 공식은 상황이 수집된 문서의 내용들 등에 따라 다르게 변형해서 쓸 수 있습니다. 이 부분은 다른 포스트에서 기회가 되면 따로 적어보겠습니다.

내가 알고 있는 공식과 다르므로 무효! 이러시면 곤란해요

공식부분은 우선 대충 넘어가고 나중에 다시 설명하겠습니다.
이제 TFIDF란 용어의 해설부터 먼저 분해해서 확인해 보겠습니다.

TFIDF란?

앞서 설명했지만 반복해서 말하자면 TFIDF는 정보검색(그러니까 검색엔진)이나 텍스트마이닝과 관련된 곳에서 흔히 등장하는 용어입니다.  조금 구체적이면서도 간단히 정의를 적어보면 이렇습니다.

TFIDF = 검색엔진, 텍스트마이닝, 텍스트 처리와 관련된 것으로 텀에 부여된 가중치 값

TFIDF는 단어자체가 주는 알파벳의 나열과 다소 딱딱한 발음으로 인해 처음 접하는 사람들에게 공포감을 조성합니다만 알고보면 그렇게 공포스러운 것은 아닙니다.

TFIDF는 “티에프아이디에프”라고 침을 튀기면서 공격적으로 읽습니다. 발음이 주는 포스가 조금 있긴합니다.

단어를 다 풀어서 원래 제목으로 적어서 해석을 시도해 보면 좀 더 이해가 쉽습니다.  TFIDF의 원래 명칭은 다음과 같습니다.

Term Frequency  Inverse Document Frequency weighting scheme

TFIDF는 Term Frequency Inverse Document Frequency의 앞글자만 따서 줄여놓은 단어입니다.
짧게 TF-IDF weighting scheme 라고 합니다.

줄임말을 펼쳐봤지만 여전히 이해 안가기는 마찬가지일 것입니다.
다시 명칭을 단어들을 각각 더 자세히 풀어보겠습니다.

Term 텀

Term Frequency에서의 Term은 “색인어”를 말합니다. 색인어는 흔히 교과서나 기술서적의 맨 뒤에 단어를 알파벳순(또는 가나다순) 나열하고 몇 페이지에 등장하는지 적어 놓은 것을 보셨을텐데요. 거기에 있는 단어 목록을 색인어라고 합니다. 그렇게 단어들을 정리한 것들을 “색인”이라고 합니다. (Term은 원래 포괄적인 의미로 “용어”라는 뜻이긴 하지만 다소 협소한 의미이긴 하지만 여기에서는 “색인어”라고 이해하는 것이 더 도움이 될 것입니다)

색인어=Term이고 색인=Index 입니다.

IT계열의 일을 하는 분들은 검색엔진과 관련된 용어나 RDBMS(데이터베이스)에서 인덱싱(indexing)한다고 말하는 것을 들은 적이 있을텐데요. 그것들과 하는 것이 사실상 같아서 기계를 이용해서 색인을 만들어서 정리해 두는 것을 말합니다.

보통 출판된 책에서 색인어를 구성할 때는 책의 내용중에 포함된 중요한 명사만 골라서 색인으로 만들고 과거에는 검색엔진도 그런 방식을 사용했던 적이 있습니다.
최근의 검색엔진에서는 그렇지만은 않습니다. 검색엔진의 색인을 어떻게 구성하느냐는 하기 나름입니다.  하지만 잘 이해하기 어려우시면 우선 명사 위주로 하는 것이 검색엔진이나 텍스트마이닝에서 가장 기본적인 쉬운 방법이다라고 기억해 두시면 됩니다.

색인의 예를 간단히 적어보면 다음과 같이 색인어와 색인어의 위치를 매핑한 것입니다.

마이닝 – 23, 56, 128
텍스트 – 12, 23, 59

요약해서 어떤 단어가 어디에 있는지(어느 문서에 있는지)를 기록해 놓은 것에서 그 기록의 대상이 되는 단어들을 말합니다.

저 위에서 “마이닝”, “텍스트” 같은 것이 텀(term)입니다.

Term Frequency

Term Frequency를 그대로 해석해 보면 텀(term)의 빈도수입니다. 색인어의 빈도수를 말합니다. 즉 색인어가 몇 번 나왔는지 숫자를 센 것입니다.

Frequency를 주파수 같은 것으로 해석을 해버리시면 아주 이상해지지요.

어디에서의 빈도수인지는 설명이 안되어 있는데 특별한 언급이 없으면 어떤 한 문서내에서 색인어가 출현한 빈도수를 말합니다.

Inverse Document Frequency

Inverse는 우선 나중에 설명하고 Document Frequency(문서 빈도)부터 설명하면 다음과 같습니다.

Document Frequency = Term이 출현한  문서의 빈도수

Document Frequency는 그냥 해석하면 문서 빈도수가 될텐데요. 처음 보는 분들은 여기서 부터 조금 헷갈릴 것 같습니다.

제목에 Term이 출현한다는 말은 없지만 텀(term)이 출현하는 문서의 수를 세어 놓은 것입니다.

텍스트마이닝이나 검색엔진이 다루는 문서는 수십개에서 수십억개까지의 문서가 있을 수 있습니다. 1개는 아닙니다. 여기에서 말하는 것은 가지고 있는 모든 문서를 다 뒤져서 텀(term)이 포함된 모든 문서의 숫자를 세어 놓는다는 의미입니다.

단, 한 문서에 term이 여러번 나와도 1번(한 번)만 카운트합니다.

요약해서 다시 설명하면

  • TF(Term Frequency)는 한 문서에서 term이 몇  번 나왔는지를 모두 카운트한 것이고
  • DF(Document Frequency)는 한 단어가 몇개의 고유한 문서에서 나왔는지를 카운트한 것입니다.

이제 Inverse Document Frequecy에서 Inverse부분입니다.

Inverse = 역수

역수는 사실 초급 수학에 나오는 것이지만 기억이 안나는 분들을 위해서 다시 설명합니다. 역수는 쉽게 설명하면 어떤 수를 1로 나누라는 것입니다. 즉 1을 분자로 두고 그 숫자를 분모로 두라는 것입니다. (수학책에서는 저렇게 정의해 놓지는 않았을 것입니다만)

예를 들어 \(\huge{x}\)의 역수는 \(\huge{\frac{1}{x}}\) 입니다. \(\huge{x^{-1}}\)라고 표현하기도 합니다.

그래서

Inverse Document Frequency는 Document Frequency의 역수입니다.

\(\huge{idf = \frac{1}{df}}\)

위와 같습니다.
나누기를 하지 않고 역수를 쓴 것은 수학적으로 이쁘게 쓰기 위함도 있고 하다보니 그렇게 된 것도 있습니다. TF와 IDF가 원래 각각 별도로도 쓰일 수 있기 때문이기도 합니다.

Document Frequency를 왜 역수를 취하는지는 뒤에 설명하겠습니다.

TFIDF 계산

자 이제 TF와 IDF를 합치면 공식이 완성 됩니다. IDF는 DF의 역수이므로 TF를 DF로 나누면 그게 TFIDF가 됩니다.

물론 이게 전부가 아닙니다. 아직 계산법 연습도 안했고 설명을 안한 것이 더 많습니다.

TFIDF weighting scheme

TFIDF를 대충 알았으니 이제 원래의 전체 명칭을 다시 살펴보죠. TFIDF는 위에서 설명했고 (예제를 두고 다시 설명할 것입니다만)

나머지 부분을 해석하면 다음과 같습니다.

  • weighting = 가중치를 부여하는
  • scheme = 계획, 방안

완전하게 해석하면

TFIDF weighting scheme = TFIDF를 사용하는 가중치를 부여하는 방법

이 됩니다.
간단히 의역하자면 “TFIDF: 가중치를 주는 방안(또는 설계)”이라는 뜻이 됩니다. 방안이기 때문에 원래 TFIDF는 정해진 공식은 없고 TF를  DF로 나누는 컨셉을 가진 것을 모두 TFIDF weighting scheme이라고 부릅니다.

앞서 공식에서는 단순히 TFIDF가 TF를 DF로 나누지만은 않고 뭔가 계산을 좀더 한 것을 볼 수 있습니다. 그런 의미로  공식이 여러 개 존재할 수 있다는 얘기입니다.

TFIDF scheme

방안이라고 해야하지만 단어가 좀 어색하니 여기에서는 이제 컨셉(concept, scheme)이라고 하겠습니다. 이 부분에 대해서 명확히 설명하지 않고 넘어왔습니다. 사실 이게 가장 중요한 것입니다. TFIDF는 문서에서 term을 추출하고 term들에 대해 가중치값을 부여하는 방법입니다. 가중치에 대해서는 나중 설명하고 TFIDF를 사용하는 컨셉이라는 것이 중요할 뿐입니다.

TF를 DF로 나누는 것에 대한 것을 이해하면 사실 TFIDF의 가장 중요한 부분을 이해한 것입니다.

TFIDF는 여러 단계를 거쳐 고쳐지고 발전했는데 대략 초기에 다음과 같은 순서로 고안되었다고 이해하시면 좋습니다.

1

어떤 문서 내에서 그 문서를 표현할 수 있는 또는 구성하는 term들 중에 상대적으로 중요한 term과 상대적으로 중요하지 않은 term이 있을 것이고 그런 방법을 누군가가 고민했습니다.  그래야 문서를 검색해서 찾거나 다룰때 더 중요한 term을 다뤄서 정확도도 더 높이고 다른 용도로도 더 유용하게 수 있다고 생각했었습니다.

2

우선 term에 어떤 중요도라는 수치를 부여할 수 있는 가장 쉬운 방법이 그 문서에서 가장 빈번하게 나오는 term의 수를 세서 가중치로 주자는  방법이었습니다.  단순하게 term이 그 문서에서 몇 번 나오는지 세서 그냥 부여하고 쓰자는 것입니다.
이게 앞에서 설명한 TF이지요.
그런데 이게 문제가 있습니다.
우리말에서는 은, 는,이, 가와 같은 조사들이 너무 빈번하게 나오는데 사실 이 조사들은 어느 문서에서나 흔하게 나오는 것들입니다. 물론 조사를 겉어내고 명사면 쓰면 되지만 명사만 쓰더라도 사람, 생각 같은 정말 흔하디 흔한 것에 가중치가 높아지는 문제가 생깁니다.
영어라면 be 동사나 전치사, 관사 같은 것이 같은 문제를 발생시킵니다.

보통 stop word(불용어, 실제 문서에서 그리 중요하지 않은 쓸모가 없는 것들)라고 부르는 것들에 가중치가 높게 부여되는 문제가 있고 불용어가 아니더라도 정도는 덜하지만 중요하지 않은 것들 중에 문서내에서 빈번히 발생하는 것들 때문에 단순히 횟수를 카운트해서 중요도로 사용하면 문제가 생깁니다.

가장 흔한 것은 가장 중요한 것일 수도 있지만 반대로 가장 특징없이 것일 수도 있습니다.

3

그래서 다른 문서에서는 잘 안 나오지만 그 문서에서 굉장히 많이 나오거나 상대적으로 더 많이 나오는 term이 있다면 그 term에 가중치를 더 주자고 생각했습니다. 그렇게만 할 수 있다면 그 문서를 더 잘 대표하게 할 수도 있고 굉장히 중요하게 그 문서의 특징을 표현할 수 있는 것들을 잘 골라낼 수도 있을 것입니다.

이게 TFIDF의 핵심입니다.

이것을 위해서 DF(Document Frequency)를 구합니다. 즉 전체 문서를 다 스캔(scan, 다 뒤져서)해서 term이 몇개의 문서에서 나오는지를 따로 세둡니다.  모든 term들에 대해서 이것을 다 구합니다.

여기에서 중요한 것은 많은 여러 문서에서 공통으로 나오는(출현하는) term일 수록 덜 중요한 것이라고 볼 수 있습니다. 흔하디 흔한 term이라는 것입니다.
즉 결론적으로 DF의 값이 큰 term일수록 특정 문서의 관점에서 볼 때는 가치가 떨어지는 term입니다.

많은 문서에서 출현하는 term이면 중요한 term이 아닌가?
라고 생각하실 수도 있는데요.  전체 문서를 다 모아놓은 것의 입장에서는 중요할지 모르지만 각각의 문서 입장에서는 중요한 것이 아니게 됩니다.  바꿔 말하면 특정 문서의 입장에서 특정 문서의 특징을 발현해서 대표할 만큼 가치 있는 term이 아닐 가능성이 높습니다.

자 이제 2개의 컨셉을 결합합니다.

문서내에서 term의 출현횟수 / term이 출현한 문서의 (중복을 제거한) 수

위와 같이 하는데 정규화(Normalization) 비슷한 작업입니다. 위에 것을 잘 곰곰히 생각해보시면 실제로 특징을 가진 것들, 중요한 것들의 가중치가 높아진다는 것을 이해할 수 있습니다.

TFIDF값은 1보다 클 수 있기 때문에 정규화라고 부르기에 무리가  있다고 볼 수 있습니다만 이것도 정규화 과정이라고 고전 문서에 적혀 있습니다.

앞에서 말한 공식도 이런 골격으로 되어 있습니다.

이게 TFIDF의 컨셉입니다.
앞서 말했듯이 TFIDF는 TF를 DF로 나누는 방법을 말합니다.

하지만 그냥 나누지는 않고 나눌 때 단어의 출현 빈도에 의한 스케일의 문제나 여러 문제로 약간의 변형을 하게 됩니다. 그래서 여러 변형들이 생기고 여전히 조합해서 쓰고 있어서 공식에 이름을 TFIDF 1,2,3,4 라고 이름을 일일히 붙일 수도  없겠구요.
그래서 TFIDF weighting scheme이라고 묶어서 부릅니다.

TFIDF의 간단한 계산 예제

TF는 앞서 말씀드렸지만 1개의 문서내에 있는 term들이 그 문서에서 얼마나 나오는가 횟수를 센 것을 말합니다. 각각 term별로 숫자가 달라붙습니다.

이제 간단한 예제를 풀어볼텐데요.

계산방식은 공식과 다르게 기본 컨셉대로만 해보겠습니다.

아래와 같이 doc1이라는 id를 가진 짧은 문서가 있다고 하고 여기에서 명사만을 term으로 취급하고 조사와 기타 의미와는 직접 관련이 없는 품사 등은 다버리고 TF를 아주 간단한 방법으로 구해봅니다.

형태소 분석기가 있으면 좋습니다만 그건 설명 안하겠습니다.

doc1 = 무궁화꽃이 한라산에 피었습니다. 한국의 무궁화꽃은 아름답습니다.

doc1의 TF들은 구해보면 다음과 같습니다.

  • 무궁화꽃: 2
  • 한라산: 1
  • 한국: 1

단순히 단어가 몇 번 나왔는지 확인하는 것이므로 어렵지 않습니다.

doc2 라는 id의 문서를 하나 더 가지고 있다고 하겠습니다. 그리고 명사로만 TF를 뽑아봅니다.

doc2 = 무궁화꽃이 백두산에 피었습니다. 대한민국의 무궁화꽃은 아름답습니다.

doc2의 TF들

  • 무궁화꽃: 2
  • 백두산: 1
  • 대한민국: 1

2개만 하면 너무 쉬우니까

doc3이라는 문서 1개를 하나 더 추가해 보겠습니다.

doc3 = 무궁화꽃은 대한민국의 국화입니다.

doc3의 TF들

  • 무궁화꽃: 1
  • 대한민국: 1
  • 국화: 1

자 이제 지금 가진 문서가 위의 3개 밖에 없다고 가정하고 위의 것들에 DF를 구하겠습니다. 실제 현실에서는 문서가 3개밖에 없는데 굳이 TFIDF를 구하거나 그런 낭비는 하지 않겠지요. 현실에서는 적게는 수만개 많게는 수억개가 있다고 생각해야 합니다.

DF는 term이 나오는 문서가 몇개인지를 세는 것이기 때문에 문서별로 계산하는 것이 아니라 전체 문서에 걸쳐서 한 벌이 계산됩니다.

3개의 문서 전체의 DF들

  • 무궁화꽃: 3 (무궁화꽃이 출현하는 문서는 3개, doc1과 doc2, doc3)
  • 백두산: 1 (백두산이 출현하는 문서는 1개, doc2에만)
  • 한라산: 1 (한라산이 출현하는 문서는 1개, doc1에만)
  • 한국: 1 (한국이 출현하는 문서는 1개, doc1에만)
  • 대한민국: 2 (대한민국이 출현하는 문서는 1개, doc2, doc3)
  • 국화: 1 (국화가 출현하는 문서는 1개, doc3)

자 이제 이걸로 각 문서들에 대해서 TFIDF를 구합니다. 공식에 있는 것처럼 IDF를 구할 때 log같은거 쓰지 않고 그냥 TF를 DF로 나누겠습니다.
설명을 쉽게 하기 위함입니다.

log를 꼭 해줘야 한다고 되어 있지 않습니다. 그래서 꼭 써야 하는 것도 아닙니다

doc1의 TFIDF 값들 (log 안 쓴 어설픈 버전)

  • 무궁화꽃: 2/3=0.66
  • 한라산: 1/1=1
  • 한국: 1/1=1

doc2의 TFIDF 값들 (log 안 쓴 어설픈 버전)

  • 무궁화꽃: 2/3 = 0.66
  • 백두산: 1/1=1
  • 대한민국: 1/2=0.5

doc2의 TFIDF 값들 (log 안 쓴 어설픈 버전)

  • 무궁화꽃: 1/3 = 0.33
  • 대한민국: 1/2=0.5
  • 국화:1/1=1

위의 결과를 보시면 모든 문서 3개에서 공통으로 출현하는 “무궁화꽃”은 각 문서의 TFIDF값을 보면 상대적으로 낮은 것을 알 수 있습니다.
반면 백두산, 한라산, 국화 같은 것은 1로 비교적 높습니다.

각 문서에서 계산된 TFIDF값이 가장 높은 것이 각 문서내에서는 가장 중요한 단어들입니다. 가중치가 부여된 것입니다.

스스로 예제를 조금씩 바꿔서 문장을 만들어가면서 해보시면 이해가 더 빠릅니다.
위의 예제는 너무 단순하고 억지로 만든 것입니다. 실제 계산을 할 때는 log를 사용해서 스케일을 맞춰가면서 하면 더 합리적이라는 것을 알 수 있습니다.

Bag of words – 단어 가방

공식을 다시 설명하기 전에 “bag of words”라는 것을 설명하고 넘어가겠습니다. 보통 TFIDF를 설명할 때 같이 설명을 하기 때문입니다.

“문서가 유사하다 유사하지 않다”는 사람이라면 문서를 읽어버고 판단하면 되지만 기계는 모든 것을 수치화해서 수리적으로 계산할 수 있게 해주어야 합니다.
그러려면 문서를 숫자로 표현해야 합니다.
그런데 문서를 숫자로 표현하기가 참 애매하기 그지 없습니다.

그래서 문서내 출현하는 단어들을 출현 순서를 고려하지 않고 갯수를 세거나 TFIDF처럼 이렇게 쿵 저렇게 쿵해버립니다. 이렇게 순서를 고려하지 않고 단어의 집합으로 취급하는 것을 “bag of words”라고 합니다. TFIDF가 부여된 단어 집합들을 순서 없이 쓰면 “bag of words”가 됩니다. 코사인 유사도역시 순서를 고려하지 않게 계산하기 때문에 “bag of words”방식의 유사도 계산이 됩니다. 순서를 고려하게 되면 매우 복잡해지는데 그것도 여기서는 다루지 않겠습니다.

TFIDF 공식 설명

아직까지도 공식 설명을 제대로 하지 않았습니다.

어차피 컨셉은 TF를 DF로 나누면 되는 것이니까요. 아주 오래전에는 TFIDF는 그냥 TF를 DF로 나눠서 쓰기도 했다고 합니다. 하지만 상황이나 문서의 집합이나 문서의 종류들이나 이런 것 주변 문제들로 인해 공식을 조금더 각각의 문제를 해결하는데 더 합리적인 방법으로 변형하거나 계량하기도 했습니다. 그래서 공식은 많습니다만 여기서는 아래의 공식으로 하겠습니다.

TF 공식 설명

\(\begin{aligned}\huge{t{f_{ij}} = \frac{{{f_{ij}}}}{max\{ {f_{1j}},{f_{2j}},{f_{3j}},\ldots,{t_{\left| v \right|j}} \}},}\end{aligned}\)

보통 TF에 대해서 term의 빈도수를 구하라는 것은 공식으로 되어 있지 않고 의사코드(Pseudo code)나 설명으로 하는 것이 많습니다. 그래서 공식에 왜 TF구하는 것이 없냐고 의문을 갖지 마세요.

앞의 예제에서는 TF는 갯수만 세면 된다고 했는데 위의 공식을 보면 갯수를 세고 난 뒤에 계산을 한 번 더 하는 것이 조금 다르다는 것을 알 수 있습니다.

우선 표기(annotation)에 대한 설명이 빠졌는데 j는 문서들 중에 1개를 말합니다. 문서가\(\begin{aligned}\huge{N}\end{aligned}\)개 있다고 생각하시면 됩니다.\(\begin{aligned}\huge{N}\end{aligned}\)는 뒤의 공식에 나옵니다. i는 문서 내에서의 term의 순번을 말합니다. 즉 문서내에 있는 term 중에 하나라는 뜻입니다. \(\begin{aligned}\huge{f}\end{aligned}\) 는 frequency를 뜻합니다. 그냥 갯수를 세면 됩니다.
max가 들어간 것이 문제인데 max를 구하고 term의 수를 나누게 되는데 문서에서 구한 TF값들 중에 가장 큰 것으로 각각을 모두 나누라는 것을 말합니다.

문서들은 단어가 몇개 안되는 것도 있고 굉장히 많은 단어들을 가진것도 있습니다. 길이가 각각 다르지요. 길이가 긴 문서내에서 한 단어가 너무 반복해서 나오면 수치가 커지는 경향이 있어서 정규화를 한 것입니다.
그것 뿐입니다.

IDF 공식 설명

\(\begin{aligned}\huge{id{f_i} = \log \frac{N}{{d{f_i}}}.}\end{aligned}\)

DF도 다른 것은 없습니다.  DF를 구하는데  1/DF가 아닌 N/DF 이고 log를 취했습니다.
이 부분 설명이 쉽게하기 어려운데요. 사실 IDF의 공식이 왜 저렇게 되어 있는지를 이해했다면 TFIDF를 다 이해한 것이나 마찬가지입니다.

Spa¨rck Jones라는 사람이 IDF에 대한 논문을 처음 썼을때 이 부분에 대해서 자세히 설명하지도 않았다고 합니다. 그래서 다른 논문에서 이게 원리가 왜 이런지에 대한 해석이 있습니다.

IDF는 DF로 단순히 TF를 나누는 무식한 방법을 그대로 쓰기 보다는 IDF를 하나의 확률값으로 처리하는 것으로 개선했다고 합니다. 확률이론을 도입한 것입니다. 확률 이론을 도입한 이유는 가장 DF가 큰 것을 1이 되게 하고 싶은 것이고 그래서 IDF들의 덧셈 연산이 가능하도록 하고 싶었다고 합니다.

확률이 가장 큰 것은 1이고 확률은 항목이 같으면 더할 수 있습니다.

IDF의 덧셈연산이 왜 필요했는지는 정확히 이해하지는 못하겠습니다. 제 생각으로는 증분 업데이트 때문 일 것 같습니다.

계속해서 문서에서 어떤 term이 출현할 확률이 얼마인가로 IDF를 계산하도록 바꿔서 써보면 원래 이렇게 됩니다.
\(\begin{aligned}\huge{\frac{df}{N}}\end{aligned}\)

N은 전체 문서의 수이고 DF는 term이 출현한 문서의 수 이니까요.
역수(inserse)를 하기 때문에 위아래를 바꿉니다.
\(\begin{aligned}\huge{\frac{N}{df}}\end{aligned}\)
이렇게 됩니다.

공식에서 볼 때 log를 씌운 것도 의문일텐데요.  이건 위키피디아 문서 지프의 법칙과 Zipf’s law를 읽어 보면 됩니다.  물론 시간관계상 나중에 읽어 보셔도 됩니다.

log를 씌운 이유에 대해서 먼저 복잡하게 설명하면
문서 집합 내에서 단어의 빈도에 대한 분포를 보면 가장 적게 나타나는 것 부터 2의 배수로 증가하는 경향이 있습니다. 이것을 선형스케일로 바꾸려면 밑수가 2인 log를 씌우면 됩니다. 하지만 밑수가 2인 log나 자연로그나 별차이가 없어 그냥 자연로그인 log를 씌운 것입니다.

간단하게 설명하면
단어의 문서내에서 출현 빈도를 테스트해서 구해봤더니  가장 많이 출현하는 것에서 부터 그 다음으로 갈 수록 반절씩 감소하는 경향이 있더라! 라는 것입니다.

실제로 몇개의 문서에서 term들을 추출하고 빈도를 시각화해 보면 그렇다는 것을 볼 수 있습니다

그래서 모든 문서에서 빈번하게 나타타는 단어와 아닌 단어의 빈도수의 정도가 서로 다르고 차이도 커서 그것을 고르게 차이가 나도록 보정한 것입니다. 이 것도 정규화(Normalization)의 일종이고 스케일을 보정해 준 것입니다.  더 자세한 원리가 궁금하시면 관련 논문을 찾아보는것이 좋겠습니다.

TFIDF 공식 설명

\(\begin{aligned}\huge{{w_{ij}} = t{f_{ij}} \times id{f_i}.}\end{aligned}\)

w는 weight를 말하는 것입니다.  즉, TFIDF를 말합니다. TFIDF는 TF와 IDF를 그대로 곱하면 됩니다. 뭐 별것이 없습니다.

위의 예제에서의 계산법도 실제로는 위의 공식으로 계산하셔야 합니다. 계산은 사실 계산기를 쓰거나 짜면 됩니다만 시간 날 때 직접 해 보시는 것을 권합니다.

Term vector – 텀벡터

위에서 “bag of words” 방식으로 각 문서들을 term에 수치값(TFIDF값 같은 것들)을 부여해 놓은 것을 텀벡터(term vector)라고도 합니다. 이 텀벡터를 이용해서 유사도 같은 것을 구할 수 있거나 여러가지 연산을 할 수 있습니다.

코사인 유사도(Cosine Similarity)도 이 텀벡터를 가지고 계산하는 것입니다. 텀벡터에 부여된 수치가 반드시 TFIDF여야 한다는 것은 없습니다만 일반적으로 TFIDF를 가장 많이 쓰는 것 같습니다.

부족한 것은 나중에 보강하기로 하고 설명은 여기까지입니다.
역시 써놓고 보니 두서없이 무지 장황해졌습니다.

여기까지 읽으셨다면 인간승리입니다.

Cosine Similarity – 코사인 유사도

삼각함수와 선형대수학에 대한 기본적인 배경지식이 있다면 코사인 유사도는 이해하기 매우 쉽습니다.  그게 아니라면 처음에 개념을 잡는 것이 어려울 수 있습니다.  그렇다고해서 겁 낼 필요는 전혀 없습니다. 어마무지하게 어려운것이 아니라서 포기하지 않고 조금 신경쓰고 조금 시간을 쓰면 누구나 이해할 수 있는 것입니다.

이 글에 대해서

이 글은 조금 쉽게 풀어서 설명하려다 보니 내용이 조금(많이) 길게 되었습니다. 그러니 만약 이 글을 읽을 것이라면 시간적 여유가 있을 때 보시면 좋겠습니다.  보통 코사인 유사도는 교과서에서 매우 간단하게 설명하고 넘어가는데 그건 다 알고 있다는 가정에서 설명하기 때문입니다. 이것은 문제가 있는데 보는 사람은 모르기 때문에 교과서를 보는데 아는 것을 가정하고 간단하게 설명한다는 것은 매우 이상합니다.  어쨌든 그런 부분까지 쉽게 풀어서 쓰다 보니 글이 장황해 졌습니다. 제 탓입니다.

코사인유사도는 대체 뭘까?

200px-Dot_Product.svg

우선 개념(concept) 이해를 위해서 코사인유사도를 아주 짧게 정의해서 설명하면 이렇습니다.
두 벡터(Vector)의 사잇각을 구해서 유사도(Similarity)로 사용하는 것

혹시 벡터라는 용어에 울렁증이 있더라도 여기에서 포기하지마세요. 별거 아닙니다.

여기서 유사도를 구할 때 두 벡터 사이의 각을 코사인(Cosine)값으로 구해서 유사도값으로 사용하기 때문에 코사인 유사도(Cosine Similarity)라고 부릅니다. 더 줄여서 말하면  두 벡터의 사잇각을 코사인으로 구하면 되는 것입니다.

수포자라면 아직도 이 설명 자체로는 이해가 어렵겠습니다만 괜찮습니다. 좀더 읽어 보세요.

코사인이 나왔으니 말인데 코사인 외에도 삼각함수에서 늘 같이 딸려 나오는 사인(Sine)이나 탄젠트(Tangent)로 값을 계산했다면 아마 “사인 유사도”나 “탄젠트 유사도”로 불렀을 것입니다만, 굳이 코사인을 사용한 것은 이런 계산을 하는데 코사인값으로 계산하는 것이 더 쉽고 편하기 때문입니다. 코사인으로 계산하면 계산을 덜 해요. 그것 뿐입니다.

벡터의 유사도를 구하는 방법은 여러가지가 있습니다. 코사인 유사도는 그 중 하나입니다. 다른 것과는 다르게 코사인 유사도의 특징은 사잇각을 쓴다는 것입니다.

저 위에 설명을 위한 매우 간단한 삼각형 그림이 있습니다. 코사인 유사도를 설명하는 문서에서 예제로 많이 보여주는 매우 흔하고 유명한 그림입니다.
저 그림을 보고 설명하면 그림에서 A와 B는 유사도를 계산할 대상이 되는 두 벡터(vector, 또는 두개의 수열값 세트)이고 두 벡터의 A와 B의 사잇각 쎄타를 코사인으로 구하는 것이고 구해진 사잇각의 값을 코사인 유사도로 사용하는 것입니다.

여기에서 각이란 원점에서 각 벡터의 위치까지 선으로 이어서 선을 만든 다음 두 선의 각을 말하는 것입니다. 원점은 모든 좌표의 값이 0이 되는 곳입니다.  이건 수학시간에 배우셨지요?

벡터는 무엇인가?

이 지면에서 벡터까지 설명해야 하는 것은 너무 길어서 무리이겠지만 그래도 벡터에 대해 익숙하지 않은 분들을 위해서 먼저 벡터에 대해서 간단히 설명을 하고 넘어가겠습니다. 이미 잘 아신다면 당연히 이 부분은 건너 뛰어도 됩니다.

위의 그림에서 두 벡터는 2차원 벡터를 표현해 놓은 것입니다. 그림은 가로와 세로로 축이 2개있는 평면이니까요. 축이 2개라서 숫자도 2개씩입니다.
벡터는 2차원 이상의 고차원 벡터도 있습니다. 현실의 데이터는 대부분 수십차원에서 수백만차원까지 가는 고차원 벡터를 다루는 일이 더 많습니다.  숫자 2개가지고는 뭐 할 수 있는 것이 별로 없습니다. 정보량도 적구요. 고차원 벡터는 그림에서 보이는 x축, y축과 같은 축들이 더 많다고 생각하면 됩니다. z, l, m, n, … 등으로 말이지요.

벡터의 차원에 대해서 잠깐 설명하고 넘어가면 예를 만들어 보면 10차원 벡터는 아래와 같습니다.

10차원의 두 벡터의 예:

  • A = 1,2,3,4,5,6,7,8,9,10
  • B = 2,3,4,5,6,7,8,9,10,11

A와 B는 각각 수열(수의 나열)이고 각각 10개씩 숫자를 가지고 있으므로 둘 다 10차원의 벡터입니다. 앞서 말씀드렸듯이 10차원 벡터는 눈으로 볼 수 있게 시각적으로 표현할 수 없습니다. 삼각형이나 선으로 좌표축에 그릴 수 없습니다.

수학 수업에서 배우기도 하는데 배우셨더라도 아마 잊어버렸겠지만 3차원까지는 시각적으로 표현이 가능하지만 4차원부터는 인간의 눈으로 한 눈에 볼 수 있게 시각적으로 표현하는 것은 불가능합니다. 인간이 통제할 수 있는 차원이 3차원까지만 가능하기 때문입니다. 그 이상은 머리속으로 추상적으로만 연상(imagination)을 해야 합니다. 이건 아인슈타인도 못 그립니다.

어쨌든 10차원 보다 숫자들이 하나씩 더 있으면 11차원이고 1000개가 되면 1000차원입니다. 그냥 각 축에서 위치를 나타내는 숫자가 1000개 있으면 1000차원 벡터, 10000개면 10000차원 벡터입니다.

물론 여러 벡터가 있다면 각 벡터에서 숫자의 순서들은 서로 같은 의미를 가지는 것에서 가져온 숫자여야 합니다. 이것 마저 설명하면 매우 복잡해지니 여기서는 그냥 잊어버리셔도 됩니다.

2차원이든 100차원이든 1000차원이든 시각적으로 표현이 불가해도 코사인 유사도는 정해진 공식으로 구할 수 있고 구하는 방법도 간단합니다.

코사인 유사도의 용도

계산법을 보기전에 아마도 많은 분들이 궁금할 것이 “두 벡터의 유사도를 구해서 어디에 쓰는가?” 라는 것일텐데요. 용도를 알게 되면 이해하는 것이 더 쉽습니다.

그런데 그전에 또 먼저, 벡터의 유사도는 무엇인가?

용도를 설명하기 전에 유사도를 먼저 설명해야 합니다. 그 참 설명을 하기위해서 먼저 설명해야할 뭔가가 정말 많습니다만. 벡터의 유사도를 구하는 것은 비슷하다는 것을 어떻게 정의하냐에 따라 계산하는 방법이나 결과들이 마구 달라집니다. 우선 벡터는 그냥 숫자들의 묶음이라고 생각해보면 이것만으로는 무작정 비슷한지 아닌지를 알 수 없는 문제도 있습니다.

유사도를 직선거리(유클리디안 디스턴스; euclidean distance)로 구하거나 사잇각이 얼마나 좁은지를 계산해서 구하거나 하는 것이 가장 잘 알려진 것입니다.  사잇각을 계산해서 유사도를 구하는 것 중에 가장 잘 알려진 것이 코사인 유사도입니다.

또 고등학교 수학책을 보면 두 벡터의 내각을 구하는 것으로 코사인 유사도가 이미 나와있습니다.  아마도 배웠지만 잊어 버린 사람들이 많을 것입니다.

코사인 유사도의 용도

코사인 유사도는 대충 다음과 같은 구체적인 용도가 있습니다. 흔히 볼 수 있는 것들입니다.

  1. 검색 엔진에서 검색어(Query)와 문서(Document)의 유사도를 구해서 가장 유사도가 높은 것을 먼저 보여주기 위한 기본 랭킹 을 위한 알고리즘으로 사용됩니다.
  2. 텍스트마이닝에서 쓰입니다. 검색엔진과 텍스트마이닝이 상당히 관련이 있기 때문에 사실 1번과 관련성이 깊습니다. 텍스트 마이닝은 흔히 벡터 스페이스 모델(Vector Space Model)을 사용하고 TF-IDF(Term Frequency – Inverse Document Frequency)를 사용하는데 단어집합들 간의 유사도를 구하기 위해서 코사인 유사도를 사용하는 것이 빈번하게 나옵니다. 그래서 word2vec 같은 딥러닝 모델에서도 나옵니다.
  3. 그 외에도 다른 분석이나 수리 모형에서도 유사도를 구할 때 사용합니다. 가끔 나옵니다. 매우 빈번하게 사용하지는 않습니다.
  4. 클러스터링(Clustering, 군집화) . 군집화 모델에서 데이터 포인트를 서로 묶을 때 쓰이긴 합니다.

위의 예에 대해서 부연설명을 더 해보겠습니다. 역시 매우 장황하니 관심 없으시면 건너 뛰셔도 됩니다.

*위의 1번의 검색 랭킹의 문제*

검색엔진이나 텍스트 마이닝에서 주로 쓰이는 이유는 문서를 숫자로 표현하는 방법중에 가장 쉽고 잘 알려진 방법이 포함된 단어들의 출현 횟수를 세고 그걸 숫자로 만드는 것이기 때문입니다.

검색엔진에서 흔히 비교할 문서들은 검색엔진의 검색창에 입력한 질의어(query라고 합니다)와 검색엔진이 가지고 있는 문서들을 비교해서 가장 비슷한 것을 찾기 때문입니다. 여기서 코사인 유사도를 구하는 대상이 사용자가 입력한 질의어와 검색엔진이 가지고 있는 모든 문서들과의 쌍입니다. 그렇게 해서 코사인 유사도를 구해서 가장 유사도가 큰 것을 가장 위에 보여줍니다. (현재의 검색엔진은 이렇게 단순하게 작동하지 않습니다. 오해를 방지하기 위해서 적어둡니다).

이때 검색엔진이나 텍스트마이닝에서는 유사도를 비교할 때 단순히 단어의 출현횟수만을 가지고 문서를 수치데이터(벡터)로 바꾸지 않고 TFIDF라는 수치값을 계산해서 씁니다. 그래서 코사인유사도와TFIDF는 늘 쌍으로 같이 언급이 됩니다. 이건 나중에 기회가 되면 설명하겠습니다. (TFIDF에 대한 포스트를 참고하세요)

*위의 3번의 클러스터링에서의 문제*

클러스터링에서의 거리 계산은 모두 연산 자원(Computational Cost) 문제와 관련이 있습니다. 코사인 유사도 역시 그렇습니다.

유사도를 구하는 목적의 근본적인 목적이 $latex A$가 $latex B$와 유사한지 $latex A$와 $latex C$가 더 유사한지와 같은 상대적인 비교를 하기 위한 것입니다.

A와 B가 둘만 있다면 둘을 비교해서 둘이 얼마나 유사한지는 사실 알 수 없습니다.  알 필요도 없습니다.

예를들어 세상에 사람이 둘 만 남았다면 이 두 사람은 서로 가장 비슷하게 생긴 사람일까요? 아니면 서로 전혀 다른 사람일까요? 두가지 모두 해당되기도 하겠지만 실제는 이게 쓸모가 없습니다. 그냥 모르는 것입니다.

즉 A와B, C, …등등이 있으면 가장 유사한 것들끼리 묶어보거나 A와 가장 비슷한것을 B, C 와 같은 것 중에서 찾아서 고르는 경우가 대부분이기 때문입니다. 그래서 여러 개의 벡터를 대상으로 각각 서로 서로 쌍을 맺어 유사도를 구해서 가장 유사도가 높은 순으로 정렬해서 가까운 것 1개를 선택한다거나 여러개를 선택해서 여러가지 목적으로 사용하게 됩니다.

클러스터링을 할 때도 마찬가지겠지요 벡터의 개수 즉, 비교할 데이터가 $latex n$개고 벡터로 표현할 수 있다면 $latex \frac{n \times (n-1) }{2}$번 만큼 연산을 해야 합니다. RDMBS에 100개의 레코드가 있고 컬럼이 여러개 있는데 모두 숫자라면 각 레코드들 간의 유사도를 모두 구하면 $latex \frac{100(100-1)}{2}$ 만큼 유사도값을 뽑아야 합니다. 에… 계산하면 4950번 입니다.

코사인 유사도를 위한 전제 조건

매우 기본이 되는 것이라 설명을 할 필요가 없겠지만 이왕 이 글이 너무 장황해진 김에 적어 보자면 다음과 같은 전제 조건이 있습니다.

  • 두 벡터의 원소들은 모두 양수(플러스!)여야 합니다. x, y 직교 좌표축에서 1사분면에 오는 것들입니다. (모눈종이에서 중심을 기준으로 오른쪽 위)
    그래서 원소들의 값이 음수가 되지 않는 문제에만 가져다 씁니다.
  • 벡터의 원소수는 같아야 합니다.
    너무 당연한 것입니다. 비교하는 벡터의 원소 갯수가 일치하지 않으면 각각 빠진 것을 0으로 채워서 동일하게 만들어야 합니다. 벡터의 원소 갯수가 좌표축에서의 축의 갯수이기 때문입니다.

코사인 유사도의 특징

평면 좌표에서 1사분면의 두 벡터의 코사인 값은 0 ~ 1 사이의 값입니다. 벡터의 각이 작을 수록 1에 가까워지고 클수록 0에 가까워집니다. 따라서 결과를 재가공(rescaling)하지 않고 바로 쓰기 편합니다. 두 벡터가 정확히 직교이면 값이 0이 됩니다.

삼각함수를 배울 때 이미 배우는 것이기 때문에 이걸 기억 하고 있다면 좋겠습니다만 생각하지 않는다면 다시 한 번 찾아보세요.

공식

공식입니다. 자세히 보지 마세요.

$latex \text{similarity} = cos(\theta) = {A \cdot B \over |A| |B|} = \frac{ \sum\limits_{i=1}^{n}{A_i \times B_i} }{ \sqrt{\sum\limits_{i=1}^{n}{(A_i)^2}} \times \sqrt{\sum\limits_{i=1}^{n}{(B_i)^2}} }$

아주 간단한 공식이고 고등학교 수학교과서에도 나옵니다. 공식은 매우 쉽기 때문에 한 번 이해를 하고 나면 볼 필요도 없습니다.

공식 풀이

공식을 가능한 자세히 설명을 적어 보겠습니다. 먼저 공식에서 분자 부분과 분모 부분을 나눠서 설명하면 다음과 같습니다.

분자 부분 – 벡터 내적 (vector inner product, dot product)

코사인 유사도 공식에서의 분자 부분은 벡터의 내적(dot product)을 구하는 것입니다. 영어로는 inner product(이너 프러덕) 또는 dot product(닷프러덕)이라고 흔히 말합니다.

$latex \ll A \cdot B \gg $이 벡터의 내적(dot product) 표기입니다.
벡터의 내적은 계산이 매우 쉽습니다.
두 벡터의 각 원소들을 순서대로 짝맞춰서 곱한 다음에 결과들을 다 더하면 됩니다.

아래와 같은 두 벡터가 있다고 하겠습니다. 차원이 5차원인 2개의 벡터입니다.  요소가 5개이기 때문에 5차원입니다.  (외계인이 산다는 그 5차원이 아닙니다) 값은 현실의 예제가 아닌 제가 임의로 마구 넣은 것입니다.

$latex A = (1,2,3,4,5) $
$latex B = (6,7,8,9,10) $

  • 각각 짝을 지어 잘 곱합니다. 순서를 맞춰서 잘 해줍니다.
    $latex 1 \times 6 = 6$
    $latex 2 \times 7 = 14$
    $latex 3 \times 8 = 24$
    $latex 4 \times 9 = 36$
    $latex 5 \times 10 = 50$
  • 곱한 것을 다 더합니다.
    $latex 6 + 14 + 24 + 36 + 50 = 130$

위의 과정이 벡터의 내적을 구한 것입니다.
끝입니다. 너무 힘든 계산이었습니다. 썰렁한 농담입니다.

그런데 여기서 벡터의 내적이 왜 나오는지 궁금할 수 있습니다. 각을 구하는데 왜 저런게 필요한 것이지?  이것은 뒤에 설명하겠습니다.

분모 부분 – 두 벡터의 크기를 곱한다

분모 부분은 두 벡터의 크기를 각각 구해서 곱하면 됩니다.

$latex |A|$는 A벡터의 크기를 말합니다.
$latex |B|$는 B벡터의 크기를 말합니다.

분모는 두벡터의 크기를 구해서 곱하면 되는데요 벡터의 크기(norm 이라고 부릅니다)가 기억이 안나실 수 있는데요. 원점에서부터의 거리를 말하는데. 이건 기하학적으로 보면 사실 ‘피타고라스 정리‘에서 직각삼각형의 빗변을 구하는 것을 말합니다.

그냥 “피타고라스 정리를 이용해서 빗변을 구하는 것을 하면된다”는 말입니다.

그런데 2차원까지는 직각삼각형인데 3차원부터는 입체가 되고 4차원부터는 아예 모양을 상상도 할 수 없게 됩니다만 그래도 피타고라스 정리로 구할 수 있다고 수학자들이 증명을 이미 해놓았습니다. 믿고 쓰면 됩니다. 못 믿으시겠으면 직접 증명을 해보시면 됩니다.

자! 앞에서 설명했듯이 벡터의 길이는 피타고라스 정리를 사용하면 구할 수 있고 그걸로 2개의 값을 구해서 서로 곱하면 분모 부분은 완성됩니다.

직각삼각형의 빗변의 길이를 구하는 피타고라스 정리를 기억 못하면 매우 곤란합니다.

$latex C=\sqrt{ A^2 + B^2 }$

  • A벡터의 크기를 구합니다. 피타고라스 정리.

$latex \sqrt{ 1^2 + 2^2 + 3^2 + 4^2 + 5^2 } = 7.4161984870957$

  • B벡터의 크기를 구합니다. 피타고라스 정리.

$latex \sqrt{6^2 + 7^2 + 8^2 + 9^2 + 10^2} = 18.1659021245849$

  • 이제 마무리로 구한 것을 곱합니다

$latex 7.4161984870957 \times 18.1659021245849 = 134.7219358530751$

숫자값들이 소숫점 뒤로 길게 나와서 복잡해 보이지만 별거 아닙니다. 뭐 계산은 계산기나 컴퓨터가 하는 거니까요.

마무리 계산 – 분자를 분모로 나누기

이제 다 구했으니 분자를 분모로 나눕니다.  나누기는 매우 힘든 산술계산이지만 우리에게는 계산기가 있습니다.

$latex \frac{130}{134.7219358530751}=0.9649505$

위에 계산된 결과 값이 코사인 유사도 값입니다. 약 0.96이네요.

풀어놓고 보니 별거 아닙니다.
R 코드로 풀어보면 이렇습니다.

# 벡터가 둘이 있습니다.
vector1 <- c(1, 2, 3, 4, 5)
vector2 <- c(6, 7, 8, 9, 10)

# 그냥 구하기 (코드를 더 짧게 줄일 수도 있습니다만)
numer <- sum(vector1 * vector2)
denom <- sqrt(sum(vector1^2)) * sqrt(sum(vector2^2))
numer / denom

# lsa 패키지에서 제공하는 펑션으로 구하기
install.packages("lsa")
library(lsa)
cosine(vector1, vector2)

R도 되지만 Pyhon이나 다른 언어들로도 당연히 계산이 됩니다.  엑셀도 됩니다. 그리고 위의 코드에도 나와 있지만 특별한 경우가 아니라면 굳이 계산식을 따로 코딩으로 구현할 필요는 없습니다. 대부분 소프트웨어 내에서 자체 제공하거나 컴퓨터 언어들의 추가 패키지, 라이브러리로 제공하고 있어서 매우 쉽게 계산이 가능합니다.

여기에서 벡터 내적은 왜 나오는 것일까?

200px-Dot_Product.svg

내적에서 무너질 분들이 좀 있을텐데요. 고교 수학에 분명 나옵니다. 기억 안나도 괜찮습니다. 어차피 이것도 설명할꺼니까요.

벡터의 내적은 검색을 해 보시면 자료가 많이 나올 것입니다. 수학적으로 매우 중요한 것 중 하나입니다.  특히 선형대수학은 벡터의 내적이 없으면 있으나 마나한 물건이 됩니다. 사실 코사인 유사도를 이해못하는 대부분의 이유는 내적을 이해하지 못하기 때문입니다.

위의 삼각형 그림을 다시 살펴보면 코사인 법칙은 피타고라스 정리에서 출발했기 때문에 코사인값을 구하려면 두 벡터가 만드는 내부의 도형이 직각삼각형이어야 합니다. 그런데 위의 그림을 보시면 A, B와 원점이 만드는 도형이 직각삼각형이 아닌 것을 알 수 있습니다. 그냥 삼각형입니다. 하다 보면 저런 삼각형이 운이 좋게 직각삼각형인 경우도 있겠지만 매우 드물고 요행으로 “직각삼각형이 될지어다!” 라고 바라는 것은 일반화된 해결책이 아니기 때문에 의미가 없습니다.

직각삼각형이 되는 것은 위의 그림에서는 |A|와 B와 원점으로 만든 삼각형입니다. 직각 표시 보이시죠? 그래서 직각삼각형으로 만들어서 피타고라스 정리에서 출발한 코사인 법칙으로 사잇각을 계산하려면 |A|의 길이를 알아내야만 합니다.

|A|의 길이를 알아내는 방법이 벡터의 내적을 이용하는 것입니다.

A와 B의 두 벡터가 있을 때 A와 B의 내적을 계산하면 B * |A| 또는 A * |B|를 구할 수 있습니다.

여기서 |A|는 A를 B의 벡터의 선상(또는 연장선상)으로 직교(직각)이 되게 그대로 내린(정사영 영어로는 projection 한다고 표현합니다) 곳과 원점까지의 거리입니다. |B|는 반대편으로 B를 정사영 한 것입니다.

B 곱하기 |A| = |B| 곱하기 A

정사영을 어느 쪽으로 하던지 상관없습니다. 두 값은 항상 동일합니다. 왜 동일한지까지 설명하려면 지면이 너무 많이 필요해서 생략하겠습니다. (곰곰히 생각해 보면 같을 수 밖에 없다는 것을 알 수 있습니다)

네. 두 벡터의 내적값은 항상 1개입니다. 어느 방향으로 구해도 똑 같습니다.

결국 두 벡터의 내적을 구해서 벡터 하나의 크기로 나누면 |A| 또는 |B|의 길이를 구할 수 있어서 저 상황에서 직각삼각형을 만들 수 있습니다. 더 정확하게 말하면 직각삼각형을 이루는 구성요소인 세 변을 모두 찾아 낼 수 있습니다.그러면 비로서 코사인값을 계산할 수 있게 됩니다.

간단한 요약

벡터의 내적은 왜 필요하냐?
두 벡터 사이에 있는 직각삼각형때문에요.
직각삼각형은 왜 나오냐?
코사인값을 구해야 해서요.
코사인값이 직각삼각형과 무슨 관계냐?
코사인법칙이 피타고라스 정리에서 나왔기 때문에요.

그래서 두 벡터가 유사한지 어떻게 알 수 있는가?

이름이 코사인 유사도이니 이것의 용도가 어떤것이 유사한지 아닌지를 확인하는  것이라는것은 유추할 수 있는데 두 개의 벡터만으로는 서로 유사한지 아닌지를 알기 어렵습니다. 아니 모릅니다.  유사한지 아닌지와 가까운지 먼지 판단하는 기준은 상대적인 것입니다. 물론 절대적인 기준값을 하나 정해놓고 유사하다 아니다를 결정하는데 사용해도 됩니다. 하지만 그 기준값을 또 직접 결정해서 만들어줘야하고 적당히 좋은 값을 정하기가 더 어렵습니다.

위에서 잠깐 설명했지만 코사인의 특징으로 두 벡터의 각이 호도법으로 0도가 되면 코사인 유사도값은 1이되고 호도법으로 각이 커질수록(90도에 가까워 질수록) 0에 가까워진다는 것입니다.  조금 풀어서 설명하면 코사인유사도가 0 또는 Inf가 되면 전혀 유사하지 않은 직교(orthogonal)가 되고 1이 되면 두 벡터의 원점으로부터의 방향이 완전히 겹치게 됩니다.  그런데 이것만으로는 유사한지 아닌지를 판단하기 어렵습니다. 호도법으로 45도 보다 각이 작으면 유사하다고 해야 할까요?

유사도는 상대적인 개념이기 때문에 벡터 2개로는 두 벡터가 유사한지 아닌지를 알기는 어렵습니다. 벡터가 최소 3개 이상은 있어야 합니다.

그래서 N개의 벡터가 있고 A라는 1개의 벡터가 있을때 A벡터와 가장 가까운 벡터를 N개 중에서 찾을 때 코사인 유사도를 사용해서 코사인 값이 가장 큰 것을 선택해서 사용합니다. 당연히 코사인값은 N번 계산해야 합니다.

A벡터가 상대적으로 가장 가까운 벡터는 어떤 것인가를 찾는 것입니다.

어째서 두 벡터의 직선 거리를 계산하지 않고 각도를 사용하는가?

두 벡터가 가까운지 아닌지를 찾는 방법중에 가장 쉬운 것이 직선거리를 계산하는 유클리디안 거리(Euclidean distance)입니다.  유클리디안 거리의 문제점은 각 축의 숫자값의 크기에 따라 영향을 크게 받는다는 것입니다.  축이 수량값을 나타내는 것이고 각축의 값들이 매우 큰 벡터들가 매우 적은 벡터들이 섞여 있다면 벡터의 성향보다는 양적수치가 비슷한 벡터끼리 가깝게 계산되는 경우가 많습니다.

물론 이것도 나름대로 의미가 있기 때문에 이 자체로 큰 문제가 있는 것은 아닙니다. 하지만 양적 크기가 차이 클 것이 뻔한 데이터들은 유클리디안 거리를 사용하면 대부분 문제가 있습니다.

적용하려는 데이터의 특징과 사용하는 사람의 의도에 맞는 것을 선택하면 되는 것입니다.

텍스트마이닝에서의 코사인 유사도

앞서에서도 잠깐 설명을 했지만 코사인 유사도는 텍스트 데이터(텍스트 마이닝)에 사용하는 경우가 많습니다.  물론 다른 곳에도 많이 쓰입니다만 자료를 찾아보면 눈에 쉽게 띄는 쪽은 텍스트 마이닝과 관련된 것 입니다. 텍스트에서 추출한 텀(term, 단어, 색인어)들의 빈도의 분포가 지수 스케일이기 때문에 벡터의 사잇각을 두고 비슷한 방향인지 아닌지를 보는 방법이고 이것이 비교적 합리적인 방법이기 때문입니다.

그리고 검색엔진에서 질의어로 본문을 찾아서 유사한 것을 찾는데 질의어와 본문이 가지고 있는 단어들의 빈도수 같은 것의 양적차이가 매우 크기 때문에 코사인 유사도가 유리합니다.

텍스트마이닝에서 코사인 유사도의 주의점

텍스트 데이터는 분량(데이터 사이즈)이 많기 때문에 코사인유사도 값을 구하려고 해도 현실에서는 컴퓨터 연산이 많이 소모되어서 하지 못하는 경우도 있습니다. 이런 작업을 하려면 사실은 대부분의 경우 분산 프로세싱을 수행해서 계산해야 합니다.  크기가 적당하다면 RDBMS를 사용하고 아니면 Hadoop이나 Spark같은 분산 컴퓨팅 환경(빅데이터 플랫폼)에서 작업해야 할 수도 있습니다.

Excel이나 R, Python으로는 계산하기에 너무 무거울 정도로 문서가 많고 그 상황에서도 코사인유사도 계산을 해야 한다면 코사인 유사도를 계산하는 과정을 완전히 이해 해두는 것이 나중에 코사인 유사도를 직접 구현해서 계산할 때를 위해서는 좋습니다.

요즘은 빅데이터 플랫폼들이 좋아서 기능을 기본 제공하는 경우도 많습니다.  그리고 어렵지 않게 구현할 수 있기 때문에 빅데이터 플랫폼으로 손쉽게 대량의 벡터들이나 텍스트데이터에서의 텀들의 유사도를 구할 수 있습니다.

텍스트 데이터를 가지고 코사인 유사도(Cosine Similarity)를 구해서 문서간의 유사도를 구하는 것은 다음 포스트에 해 보겠습니다.

코사인 유사도의 공식 유도?

코사인 유사도는 코사인 제2법칙에서 유도한 것이라고 되어 있고 실제로 유도가 됩니다. 여기서 유도하는 것은 설명하지 않겠습니다.  딱 한 번 해봤는데 기억도 안나고 하기 싫습니다. 재미 없습니다. 혹시 궁금하신 분은 자료를 찾아 보시면 그렇게 어렵지 않게 해볼 수 있습니다. 그런데 실제 응용에는 공식 유도가 크게 중요하지 않습니다.

TFIDF – Term Frequency Inverse Document Frequency

numpy windows용 64bit 버전

Windows를 비롯해서 numpy를 설치하는 것이 쉬운일이 아닌데요.
그래서 따로 패키징된 것을 제공하는 곳이 몇군데 있습니다.
그중 대표적인 곳이 scipy.org 입니다.
numpy는 scipy.org에서 패키징된 버전을 받을 수 있도록 링크를 추천하고 있는데
32bit 버전만 제공하고 있습니다.
32bit용 numpy를 64bit python에 설치하게 되면 python이 없다고 에러메세지가 나옵니다.

  • numpy는 python에서 수학/과학 계산을 위해서 사용하는 라이브러리입니다.
  • scipy를 사용하기 위해서는 numpy가 필요합니다.

아래 사이트에서 공유하고 있습니다.
접속해서 OS와 Python 버전에 맞는 것을 선택해서 다운로드해서 실행하면 쉽게 설치할 수 있습니다.

http://www.lfd.uci.edu/~gohlke/pythonlibs/#numpy

사이트의 반응 속도 및 다운로드 속도가 조금 느립니다.

각종 도구로 선형회귀(Linear Regression)해보기

오다카 토모히로의 만들면서 배우는 기계학습에 나오는 예제를 여러가지 도구로 각각 간단히 선형회귀(Linear regression)을 하는 방법을 적어봅니다.
(이 정도는 뭐로 하든 한가지만 잘해도 충분한겠습니다만)
선형회귀는 간단히 설명하면 독립변수 X에 대한 종속변수 Y의 값들을 이용해 제곱합이 최소가 되도록 하는 1차식을 도출하는 방법입니다.
예측을 하거나 추이를 살펴보기 위한 방법으로 가장 쉬운 방법 중 하나인데
보통 입력값은 X값들, Y값들이고 출력은 절편(Intercept)과 기울기(Slope)이고 추가로 몇가지를 더 도출할 수도 있습니다.
최소자승법으로 결과를 구하기 위해서 1차식을 제곱합을 구하는 것으로 바꾼다음 각 항을 편미분해서 나온 식에 입력값들을 대입해서 절편과 기울기를 구하게 됩니다.
식은 구글에서 검색해 보시거나 책들을 참조하시면 되겠습니다.

C로 하는 Linear Regression

C로 구현한 예제는 scanf를 이용해서 입력값을 키보드로 입력받고 절편과 기울기로 바로 출력하는 간단한 예제입니다.
아래에 있는 코드는 책에 있는 예제 그대로입니다.
[github_cv url=”https://github.com/euriion/TomorrowWorks/blob/master/snippets/lsm/lsm.c”]

실행을 하고 아래와 같이 진행합니다.

/* 입력 값들은 이렇게 넣고 구분은 X값과 Y값의 구분은 TAB으로 합니다.*/
// 1 2.1
// 3 3.7
// 2.5 3.4
// 3.9 3.1
/* 출력된 결과 입니다. */
// 2.010294
// 0.409502

R코드 및 플로팅

R은 이런 것은 너무 쉽습니다.
간단하게 패키지를 이용합니다.
요점은 데이터를 data.frame으로 만들어주고 lm을 이용해서 모델을 구한 뒤 화면에 결과를 출력하고 plotting 해버리면 끝입니다.
물론 plotting은 더 미려하게 할 수 있습니다만 정성과 시간이 필요합니다.
[github_cv url=”https://github.com/euriion/TomorrowWorks/blob/master/snippets/lsm/lsm.R”]

R 플로팅 결과입니다.
r_plot_lsm

Excel로 하는 선형회귀

Excel도 이런 간단한 것은 무척 쉽습니다.
값들을 sheet에 입력하고 마우스로 scatter plot을 차트에서 선택해서 quick chart를 선택하면 추이선이 그대로 그려집니다.
차트의 옵션에서 display equation을 선택하면 절편과 기울기까지 차트에 표시됩니다.
물론 절편과 기울기만 따로 구해서 식을 재적용하게 할 수 있겠지만 그렇게 하려면 조금 복잡해 집니다.
lsm.xlsx

Datagraph로 선형회귀하기

Datagraph는 Mac에서 사용하는 작은 graphing 툴입니다. 가벼우면서도 싸고 괜찮고 아주 유용한 툴입니다.
Datagraph로 하는 방법은 엑셀과 비슷할 것인데 엑셀보다는 더 쉽습니다.
Datagraph에서는 data를 입력한 후 scatter plot을 그리고 fit function으로 linear를 선택해서 넣으면 그대로 나옵니다.

fit function에서 절편과 기울기가 구해져 있는 것을 알 수 있습니다.
다른 그래프 툴에서도 비슷할 것이라고 생각됩니다.

lsm.dgraph

Python으로 선형회귀하기

Python으로 하는 방법은 직접구하는 방법과 공학용 패키지인 scipy를 이용하는 방법이 있는데 이 scipy라는 패키지가 설치하기가 무척 어렵습니다.
scipy가 제가 사용하는 mac에 잘 설치가 되지 않아서 코드를 테스트를 해보지는 못했고 대략 아래와 같이 구하게 될 것 같습니다.
복잡하지 않습니다.
[github_cv url=”https://github.com/euriion/TomorrowWorks/blob/master/snippets/lsm/lsm.py”]