TF-IDF
고친 사람 github-actions[bot]
TF-IDF 는 한 낱말이 한 문서에서 얼마나 중요한지를 숫자로 매겨 줍니다. 그 문서에 자주 나올수록 점수가 오릅니다. 다른 문서에도 흔히 나오는 낱말이면 점수가 깎입니다. 검색 엔진이 문서의 순서를 정하는 점수의 출발점입니다.
쉽고 빠른 이해
TF-IDF 는 낱말마다 무게를 달아 줍니다. 「파이썬 설치 방법」을 찾을 때 「방법」은 어느 글에나 있어서 가볍습니다. 「파이썬」은 드물어서 무게가 큽니다.
무게가 없으면 낱말이 맞은 횟수만 셉니다. 그러면 「방법」을 여덟 번 쓴 엉뚱한 글이 파이썬 글보다 위로 올라옵니다.
- 문서마다 낱말이 몇 번 나왔는지 셉니다
- 낱말마다 그 낱말이 든 문서가 몇 개인지 셉니다. 적을수록 드문 낱말이라 무게가 커집니다
- 두 값을 곱해 점수로 씁니다
대가가 있습니다. 긴 문서가 횟수를 쉽게 쌓아 유리해집니다. 뜻이 같아도 글자가 다른 낱말은 못 알아봅니다.
작은 문서 모음에서 비슷한 글을 찾거나 핵심어를 뽑을 때 씁니다. 검색 순위를 매길 때는 요즘 이 약점을 고친 다른 셈식을 더 씁니다.
상세
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 에게 아무 도움이 안 됩니다.
같은 계산을 코드로 옮기면 몇 줄이면 됩니다. 각 호출 오른쪽 주석이 돌려받는 값입니다.
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