사전 TF-IDF
알고리즘

TF-IDF

gabury1고친 사람 github-actions[bot]

TF-IDF 는 한 낱말이 한 문서에서 얼마나 중요한지를 숫자로 매겨 줍니다. 그 문서에 자주 나올수록 점수가 오릅니다. 다른 문서에도 흔히 나오는 낱말이면 점수가 깎입니다. 검색 엔진이 문서의 순서를 정하는 점수의 출발점입니다.

쉽고 빠른 이해

TF-IDF 는 낱말마다 무게를 달아 줍니다. 「파이썬 설치 방법」을 찾을 때 「방법」은 어느 글에나 있어서 가볍습니다. 「파이썬」은 드물어서 무게가 큽니다.

무게가 없으면 낱말이 맞은 횟수만 셉니다. 그러면 「방법」을 여덟 번 쓴 엉뚱한 글이 파이썬 글보다 위로 올라옵니다.

  1. 문서마다 낱말이 몇 번 나왔는지 셉니다
  2. 낱말마다 그 낱말이 든 문서가 몇 개인지 셉니다. 적을수록 드문 낱말이라 무게가 커집니다
  3. 두 값을 곱해 점수로 씁니다

대가가 있습니다. 긴 문서가 횟수를 쉽게 쌓아 유리해집니다. 뜻이 같아도 글자가 다른 낱말은 못 알아봅니다.

작은 문서 모음에서 비슷한 글을 찾거나 핵심어를 뽑을 때 씁니다. 검색 순위를 매길 때는 요즘 이 약점을 고친 다른 셈식을 더 씁니다.

상세

TF-IDF(Term Frequency-Inverse Document Frequency, 낱말 빈도-역문서 빈도)는 낱말 하나와 문서 하나를 받아 점수 하나를 내는 셈식입니다. 이 점수가 그 문서에서 그 낱말이 갖는 무게입니다.

질의는 사용자가 검색창에 넣은 낱말들입니다. 검색 엔진은 질의의 낱말마다 이 무게를 구해 더합니다. 그 합으로 문서의 순서를 정합니다.

낱말마다 무게가 다른 까닭

「파이썬 설치 방법」으로 문서를 찾는다고 합시다. 가장 쉬운 방법은 질의 낱말이 문서에 나온 횟수를 다 더하는 것입니다. 많이 맞은 문서가 위로 갑니다.

이 방법은 낱말을 모두 같은 무게로 셉니다. 「방법」은 설명글이라면 거의 다 들어 있는 낱말입니다. 그래서 「방법」을 여러 번 쓴 글이 파이썬과 상관없어도 위로 올라옵니다.

사람은 「파이썬」이 이 질의의 핵심이라는 것을 압니다. 드문 낱말일수록 문서를 가려 주는 힘이 셉니다. TF-IDF 는 이 직관을 숫자로 옮긴 것입니다.

문서가 질의와 얼마나 잘 맞는지를 나타낸 점수를 관련도라고 부릅니다. TF-IDF 는 관련도를 매기는 고전적인 방식입니다.

낱말 빈도

낱말 빈도는 한 낱말이 한 문서에 나온 횟수입니다. 용어 빈도라고도 부릅니다. 줄여서 TF(Term Frequency)라고 적습니다. 「파이썬」이 어떤 글에 세 번 나오면 그 글에서 「파이썬」의 낱말 빈도는 3 입니다.

횟수를 세려면 먼저 문서를 낱말로 쪼개야 합니다. 이 일을 토큰화라고 부릅니다. 검색 엔진에서는 분석기가 토큰화와 함께 대소문자 맞추기 같은 다듬기를 맡습니다.

낱말 빈도가 높다는 것은 그 낱말을 두고 쓴 글일 가능성이 크다는 뜻입니다. 파이썬 설치를 다룬 글은 「파이썬」을 여러 번 씁니다. 지나가듯 언급한 글은 한 번 쓰고 맙니다.

역문서 빈도

역문서 빈도는 낱말이 얼마나 드문지를 잽니다. 줄여서 IDF(Inverse Document Frequency)라고 적습니다.

재료는 문서 빈도입니다. 문서 빈도는 그 낱말이 든 문서의 개수입니다. 한 문서에 몇 번 나왔는지는 안 따집니다. 들었는지만 셉니다.

문서 빈도가 클수록 흔한 낱말입니다. 무게는 거꾸로 작아져야 합니다. 그래서 전체 문서 수를 문서 빈도로 나눕니다. 이름의 「역」이 이 나눗셈을 가리킵니다.

나눈 값에는 로그를 씌웁니다. 밑이 10 인 로그는 값이 열 배 커질 때마다 결과가 1 씩만 늘게 바꾸는 함수입니다. 10 은 1, 100 은 2, 1000 은 3 이 됩니다.

로그를 씌우는 까닭은 차이가 너무 벌어지지 않게 하려는 것입니다. 문서 천 개에서 한 문서에만 든 낱말은 나눈 값이 1000, 열 문서에 든 낱말은 100 입니다. 열 배 드물다고 무게까지 열 배가 되면 드문 낱말 하나가 점수를 다 차지합니다. 로그를 씌우면 3 과 2 가 되어 차이가 한 단계로 줄어듭니다.

셈식으로 적으면 아래와 같습니다. N 은 전체 문서 수, df 는 문서 빈도입니다.

IDF = log(N / df)

모든 문서에 든 낱말은 N 과 df 가 같습니다. 나눈 값은 1 입니다. 1 에 로그를 씌우면 0 이 됩니다. 「방법」처럼 어디에나 있는 낱말은 점수에 아무것도 보태지 못합니다.

이 성질 덕에 「그리고」·「하다」 같은 낱말을 따로 빼지 않아도 무게가 저절로 0 에 가까워집니다. 검색에서 빼 버리는 흔한 낱말을 불용어라고 부릅니다. TF-IDF 는 불용어 목록 없이도 그 효과를 어느 정도 냅니다.

두 값을 곱해 점수를 낸다

낱말 하나가 문서 하나에서 갖는 TF-IDF 는 두 값의 곱입니다.

TF-IDF = TF × IDF

곱이라서 둘 중 하나가 0 이면 점수도 0 입니다. 문서에 안 나온 낱말은 TF 가 0 입니다. 모든 문서에 나온 낱말은 IDF 가 0 입니다.

점수를 얻으려면 두 조건을 다 채워야 합니다. 이 문서에는 나와야 합니다. 다른 문서에는 드물어야 합니다.

아래 그림은 두 값이 어디서 오는지를 보입니다. TF 는 문서 하나만 보면 나옵니다. IDF 는 문서 모음 전체를 봐야 나옵니다.

flowchart TD
    C["문서 모음"] --> A["분석기 · 낱말로 쪼갠다"]
    A --> T["문서마다 · 낱말이 나온 횟수 · TF"]
    A --> D["낱말마다 · 든 문서의 개수 · df"]
    C --> N["전체 문서 수 · N"]
    D --> I["IDF = log(N / df)"]
    N --> I
    T --> S["TF × IDF"]
    I --> S

질의에 낱말이 여럿이면 낱말마다 TF-IDF 를 구해 더합니다. 그 합이 문서의 점수입니다. 질의에 없는 낱말은 계산에 들어가지 않습니다.

문서 세 개로 끝까지 계산하기

문서 천 개짜리 모음이 있다고 합시다. 질의는 「파이썬 설치 방법」입니다. 세 낱말의 문서 빈도와 IDF 는 아래와 같습니다. 로그의 밑은 10 입니다.

낱말 문서 빈도 N / df IDF
파이썬 10 100 2
설치 100 10 1
방법 1000 1 0

「파이썬」은 천 개 중 열 개에만 있어서 무게가 2 입니다. 「방법」은 모든 문서에 있어서 무게가 0 입니다.

이제 후보 문서 A·B·C 에서 각 낱말이 몇 번 나왔는지 셉니다. 표의 칸마다 앞 숫자가 TF, 뒤 숫자가 그 TF 에 IDF 를 곱한 값입니다.

문서 파이썬 설치 방법 횟수만 더한 값 TF-IDF 합
A 1 → 2 1 → 1 5 → 0 7 3
B 0 → 0 2 → 2 8 → 0 10 2
C 3 → 6 0 → 0 1 → 0 4 6

횟수만 더하면 B 가 1등입니다. B 는 「파이썬」이 한 번도 안 나온 글입니다. TF-IDF 로 매기면 「파이썬」을 세 번 쓴 C 가 1등이 됩니다. B 는 꼴찌로 내려갑니다. 「방법」이 여덟 번 나온 것은 B 에게 아무 도움이 안 됩니다.

같은 계산을 코드로 옮기면 몇 줄이면 됩니다. 각 호출 오른쪽 주석이 돌려받는 값입니다.

Python
import math

N = 1000
df = {"파이썬": 10, "설치": 100, "방법": 1000}

def score(query, tf):
    return sum(tf.get(t, 0) * math.log10(N / df[t])
               for t in query)

q = ["파이썬", "설치", "방법"]
A = {"파이썬": 1, "설치": 1, "방법": 5}
B = {"설치": 2, "방법": 8}
C = {"파이썬": 3, "방법": 1}

score(q, A)  # 3.0
score(q, B)  # 2.0
score(q, C)  # 6.0

tf.get(t, 0) 은 문서에 없는 낱말의 횟수를 0 으로 봅니다. 그래서 B 의 「파이썬」 항은 곱해도 0 입니다.

계산에 드는 비용

TF 와 df 는 문서를 한 번 훑으면 다 셀 수 있습니다. 모음 전체의 낱말 수를 T 라 하면 드는 시간은 O(T) 입니다. O(T) 는 낱말 수가 두 배면 시간도 두 배라는 뜻입니다.

검색 엔진은 이 숫자들을 역색인에 담아 둡니다. 역색인은 낱말마다 그 낱말이 든 문서 번호를 적어 둔 목록입니다. 한 낱말의 목록 길이가 곧 그 낱말의 df 입니다. 목록의 각 칸에 그 문서에서의 TF 를 함께 적어 두면 질의 때 문서를 다시 열지 않아도 됩니다.

질의가 오면 질의 낱말의 목록만 읽습니다. 드는 시간은 그 목록들의 길이를 더한 것에 비례합니다. 전체 문서 수가 아니라 질의 낱말이 든 문서 수만큼만 일합니다.

문서가 새로 들어오면 N 과 df 가 바뀝니다. 그러면 모든 낱말의 IDF 가 조금씩 움직입니다. 그래서 IDF 는 미리 곱해 저장하기보다 질의 때 그 시점의 N 과 df 로 셈하는 편이 흔합니다.

셈식의 여러 변형

TF-IDF 는 하나로 고정된 셈식이 아닙니다. TF 와 IDF 를 어떻게 세느냐에 따라 변형이 여럿입니다. 여기에 문서 길이로 점수를 나누는 보정이 더 붙기도 합니다. 흔한 변형을 어디를 바꾸는지에 따라 모으면 아래와 같습니다.

어디를 바꾸나 흔한 변형 고치려는 것
TF 나온 횟수 그대로 가장 단순한 꼴
TF 1 + log(TF) 스무 번 나온 낱말이 열 번보다 두 배 중요하지는 않다
TF 나왔으면 1, 안 나왔으면 0 횟수를 버리고 들었는지만 본다
IDF log(N / df) 가장 단순한 꼴
IDF log(N / (df + 1)) df 가 0 인 낱말(질의 낱말이 어느 문서에도 없을 때)에서 0 으로 나누는 것을 막는다
문서 길이 점수를 문서 길이로 나눈다 긴 문서가 횟수를 쉽게 쌓는 것을 누른다

변형이 여럿이라 같은 문서라도 시스템마다 TF-IDF 값이 다르게 나옵니다. 이 값은 한 시스템 안에서 문서끼리 순서를 가르는 데 씁니다. 다른 시스템의 점수와 견줄 값은 아닙니다.

문서를 숫자 목록으로 보기

TF-IDF 를 모든 낱말에 대해 구하면 문서 하나가 숫자 목록 하나가 됩니다. 모음에 든 낱말마다 칸을 하나씩 둡니다. 각 칸에는 그 낱말의 TF-IDF 를 적습니다. 이런 숫자 목록을 벡터라고 부릅니다.

모음 전체의 낱말은 수만 개입니다. 한 문서에 든 낱말은 수백 개뿐입니다. 그래서 칸 대부분이 0 입니다. 이렇게 거의 0 으로 찬 벡터를 희소 벡터라고 부릅니다.

숫자 목록에도 방향과 길이가 있습니다. 방향은 칸끼리 값이 어떤 비율로 놓였는지입니다. 길이는 값 전체가 얼마나 큰지입니다.

같은 글을 두 번 이어 붙인 문서를 떠올려 봅시다. 칸마다 값이 두 배가 됩니다. 비율은 그대로라 방향은 원래 글과 같습니다. 길이만 두 배로 늘어납니다.

질의도 같은 꼴의 벡터로 만들 수 있습니다. 그러면 두 벡터가 얼마나 같은 방향을 가리키는지로 문서와 질의가 얼마나 닮았는지를 잴 수 있습니다. 이 잣대가 코사인 유사도입니다. 길이는 빼고 방향만 보므로 긴 문서가 값을 부풀리는 것을 덜어 줍니다.

문서와 질의를 벡터로 놓고 닮음으로 순서를 정하는 틀을 벡터 공간 모델이라고 부릅니다. TF-IDF 는 이 틀에서 칸을 채우는 대표적인 방법입니다.

TF-IDF 가 놓치는 것

기본 셈식은 네 가지를 놓칩니다. 하나씩 보면 뒤에 나올 BM25(Best Matching 25)가 무엇을 고치려는지가 보입니다.

첫째는 문서 길이입니다. 긴 문서는 아무 낱말이나 여러 번 품기 쉽습니다. 기본 셈식은 이를 가리지 못해서 긴 문서가 점수를 쉽게 얻습니다.

둘째는 횟수의 끝없는 효과입니다. 나온 횟수를 그대로 쓰면 스무 번 나온 낱말이 열 번의 두 배 점수를 받습니다. 사람이 보기에 열 번이면 이미 그 낱말을 두고 쓴 글입니다. 열 번 더 나온다고 두 배 더 맞는 글은 아닙니다.

셋째는 뜻입니다. TF-IDF 는 글자가 같은 낱말만 맞춥니다. 「자동차」로 찾으면 「차량」만 쓴 글은 점수가 0 입니다.

넷째는 낱말의 순서입니다. TF-IDF 는 낱말이 놓인 순서를 버립니다. 「개가 사람을 물었다」와 「사람이 개를 물었다」는 낱말 구성이 같아 거의 같은 벡터가 됩니다. 문서를 낱말이 담긴 가방으로만 보는 이 관점을 단어 가방 모델이라고 부릅니다.

뒤를 이은 BM25

BM25 는 TF-IDF 의 첫째와 둘째 약점을 고친 셈식입니다. 뼈대는 같습니다. 낱말 빈도와 역문서 빈도를 곱해 더합니다.

다른 점은 둘입니다. 낱말 빈도가 커질수록 점수가 한 값에 가까워집니다. 어느 선을 넘으면 거의 더 오르지 않습니다. 그리고 평균보다 긴 문서의 낱말 빈도를 깎습니다.

지금 널리 쓰이는 검색 엔진은 기본 점수 셈식으로 BM25 를 많이 씁니다. Elasticsearch와 그 밑의 Apache Lucene도 기본으로 BM25 를 씁니다. 그래서 TF-IDF 는 검색 엔진 안보다 BM25 를 이해하는 출발점으로 더 자주 만납니다.

검색 밖에서의 쓰임

TF-IDF 벡터는 글을 숫자로 바꾸는 가장 쉬운 방법이기도 합니다. 머신러닝 모델은 글자를 바로 못 받고 숫자 목록을 받습니다. 그래서 스팸 거르기 같은 텍스트 분류에서 글을 TF-IDF 벡터로 바꿔 모델에 넣습니다.

한 문서에서 TF-IDF 가 가장 높은 낱말 몇 개를 뽑으면 그 문서의 핵심어가 됩니다. 그 문서에는 자주 나오고 다른 문서에는 드문 낱말이기 때문입니다. 이 일을 키워드 추출이라고 부릅니다.

쓸 때와 안 쓸 때

TF-IDF 는 미리 학습시킬 모델이 필요 없습니다. 계산도 가볍습니다. 문서 수천 개 정도의 작은 모음에서 비슷한 글을 찾거나 핵심어를 뽑을 때 먼저 꺼내 볼 만합니다. 계산이 더 드는 방법을 쓸 때도 비교 기준으로 흔히 둡니다.

검색 엔진의 순위를 새로 짤 때는 BM25 가 먼저입니다. 같은 뼈대에 길이와 횟수 문제를 이미 고쳐 두었기 때문입니다.

뜻이 맞는 글을 찾아야 할 때는 둘 다 모자랍니다. 「차량」으로 「자동차」 글을 찾으려면 글의 뜻을 벡터로 옮기는 임베딩이 필요합니다. 그렇게 옮긴 벡터끼리 가까운 글을 찾는 벡터 검색을 함께 씁니다.

관련 항목

TF-IDF 셈식을 이루는 값과 함수

용어 빈도 · 역문서 빈도 · 문서 빈도 · 로그 함수

TF-IDF 가 속하는 검색 기법

전문 검색 · 정보 검색 · 관련도 · 관련도 점수 · 랭킹 · 벡터 공간 모델 · 불리언 검색

TF-IDF 를 대신하는 순위 매기기 방법

BM25 · BM25F · 언어 모델 검색 · 벡터 검색 · 임베딩 · 재순위화

TF-IDF 값을 담고 계산하는 구조

역색인 · 포스팅 리스트 · 희소 벡터 · 코사인 유사도 · 단어 가방 모델

TF-IDF 앞에서 글을 쪼개는 처리 단계

분석기 · 토큰화 · 형태소 분석 · 불용어 · 어간 추출 · n-gram

TF-IDF 계열 점수로 문서를 줄 세우는 검색 엔진

검색 엔진 · Elasticsearch · Apache Lucene · OpenSearch · Apache Solr

TF-IDF 로 푸는 텍스트 작업

텍스트 분류 · 키워드 추출 · 문서 군집화 · 스팸 필터 · 머신러닝

다른 이름: Term Frequency-Inverse Document Frequency · 낱말 빈도-역문서 빈도 · 단어 빈도-역문서 빈도 · tf-idf · TF*IDF