처음부터 시작하는 자연어처리(2)

June
None
Published in
8 min readFeb 1, 2023

안녕하세요. 휴먼스케이프 june입니다.

이전 시간에 이어서 이번엔 TF-IDF 를 이용해 벡터값을 추출해보겠습니다.

TF-IDF

TF-IDF는 단어의 중요도를 벡터값으로 활용합니다. 중요한 단어일수록 벡터값이 커지게됩니다.

TF-IDF는 다음 2가지의 전제를 가지고 있습니다.

  1. 특정 문서에서 중요 키워드는 자주 등장할 것이다.
  2. 여러 문서에 걸쳐서 등장하는 키워드는 중요 키워드가 아닌 일반적으로 사용하는 단어(the, a, is 등)일 것이다.

word cloud를 상상해보면 이해가 쉽습니다.

출처: https://commons.wikimedia.org/wiki/File:Web_foundations_word_cloud.svg
  • TF-IDF는 어떤 단어 w(word)가 문서 d(document) 내에서 얼마나 중요한지 나타내는 수치입니다.
  • TF-IDF에서 TF는 Term frequency의 약어로, 단어의 문서 내에 출현한 횟수를 의미합니다.
  • TF-IDF에서 IDF는 Inverse document frequency의 약어로, 그 단어가 출현한 문서의 숫자의 역수를 의미합니다.

TF는 단어가 문서에 출현한 횟수입니다. 따라서 그 숫자가 클수록 문서에서 중요한 단어일 확률이 높습니다.

하지만 the, a와 같은 단어도 TF값이 매우 크게 나타날 가능성이 높습니다.

이와같은 중요하지 않은 값을 배제하기 위해 IDF를 사용합니다.

IDF는 그 단어가 출현한 문서의 숫자를 의미하므로, 그 값이 클수록 여러 문장에서 일반적으로 쓰이는 단어일 확률이 큽니다.

따라서 IDF를 구해 TF에 곱함으로써 흔한 단어의 점수를 낮출 수 있습니다.

따라서 TF-IDF는 특정 문서에서 많이 나타난 단어를 구하는 알고리즘입니다.

이제 직접 데이터가 변하는걸 보며 tf-idf에 대해 이해해보도록 하겠습니다.

아래 5개의 document가 주어집니다.

  1. a b c d e
  2. a a b b c c
  3. a a a a a a
  4. c
  5. c d e

먼저 tf를 구해봅시다. tf는 단순히 해당 단어의 출현 횟수를 세면 됩니다.(bow와 동일합니다.)


+---+---+---+---+---+
| a | b | c | d | e |
+---+---+---+---+---+
| 1 | 1 | 1 | 1 | 1 |
| 2 | 2 | 2 | 0 | 0 |
| 5 | 0 | 0 | 0 | 0 |
| 0 | 0 | 1 | 0 | 0 |
| 0 | 0 | 1 | 1 | 1 |
+---+---+---+---+---+

다음은 idf를 구해봅시다. i는 역수를 뜻하니 df를 구하면 됩니다.

df는 각 단어가 몇개의 문서(document)에 츨현했는지를 세 구합니다.

(하나의 문서에서 여러번 출현했더라도 1로 카운팅하는게 핵심입니다.)

+---+---+---+---+---+
| a | b | c | d | e |
+---+---+---+---+---+
| 3 | 2 | 4 | 2 | 2 |
+---+---+---+---+---+

이제 위에서 구한 tf에 방금 구한 df를 나눠주면 tf-idf 벡터가 추출됩니다.


+-----+-----+------+-----+-----+
| a | b | c | d | e |
+-----+-----+------+-----+-----+
| 0.3 | 0.5 | 0.25 | 0.5 | 0.5 |
| 0.6 | 1 | 0.5 | 0 | 0 |
| 1.6 | 0 | 0 | 0 | 0 |
| 0 | 0 | 0.25 | 0 | 0 |
| 0 | 0 | 0.25 | 0.5 | 0.5 |
+-----+-----+------+-----+-----+

지금까지의 과정을 코드로 옮기면 다음과 같습니다.

def get_term_frequency(document, word_dict=None):
"""
하나의 문서에서 어떤 단어가 얼마나 나타났는지를 구함
예:
input: "this is a test sentence test"
output: ['this': 1, 'is': 1, 'a': 1, 'test': 2, 'sentence': 1, 'real': 1]
"""
if word_dict is None:
word_dict = {}
words = document.split()

for w in words:
w = w.replace(" ", "")
word_dict[w] = 1+(0 if word_dict.get(w) is None else word_dict[w])

return pd.Series(word_dict, dtype = 'object').sort_values(ascending=False)

def get_document_frequency(documents):
"""
각 단어가 몇개의 문서에 나타났는지를 구함
예:
input: ["this is a test sentence test", "real test", "this test is real"]
output: ['this': 2, 'is': 2, 'a': 1, 'test': 3, 'sentence': 1, 'real': 2]
"""
dicts = []
vocab = set([])
df = {}

for d in tqdm(documents):
tf = get_term_frequency(d)
dicts += [tf]
vocab |= set(tf.keys())

for v in tqdm(list(vocab)):
df[v] = 0
for d in dicts:
if d.get(v) is not None:
df[v] += 1
return pd.Series(df).sort_values(ascending=False)

def get_tfidf(docs):
"""
각 문서의 tf-idf 계산
"""
vocacb = {}
tfs = []
for d in docs:
vocab = get_term_frequency(d, vocacb)
tfs += [get_term_frequency(d)]
df = get_document_frequency(docs)

import numpy as np

stats = []
columns = ['word', 'frequency']+['doc{}'.format(i+1) for i in range(len(docs))]+['max']
for word, freq in vocab.items():
tfidfs = []
for idx in range(len(docs)):
if tfs[idx].get(word) is not None:
tfidfs += [tfs[idx][word] * np.log(len(docs) / df[word])]
else:
tfidfs += [0]
stats.append((word, freq, *tfidfs, max(tfidfs)))

return pd.DataFrame(stats,columns=columns).sort_values(by='max',ascending=False)

특이한 점은 tf-idf를 구하는 코드가 아래와 같이 나와있는데, 처음에 설명하지 않았던 np.log연산을 한다는 점입니다.

tfidfs += [tfs[idx][word] * np.log(len(docs) / df[word])]

간단히 df의 값을 줄이기 위해 나누기 연산을 수행한다고 이해하면 편합니다.

이와 관련해서 찾아보니 idf의 수치가 기하급수적으로 높아지는 것을 방지하기 위해 log를 붙여 tf값을 강조한다 라는 사실을 알 수 있었습니다.

영문 위키에서는 해당 식을 inverse document frequency smooth 라는 문장으로 표현합니다.

처음 설명에서 말한것처럼 tf-idf는 중요키워드에 더 큰 값을 할당하는 알고리즘입니다.

따라서 산출한 백터값을 정렬하면 각 문서에서의 중요 키워드를 산출할 수 있습니다.

여기까지 tf-idf에 대한 설명이었습니다.

사실 tf-idf는 사이킷-런 이라는 라이브러리에 구현되어 있어 아주 간단하게 구할 수 있습니다.

TfidfVectorizer라는 클래스에 fit_transform 메서드를 이용하면 위 구현한 코드와 같이 각 단어를 tf-idf 백터화 시켜 리턴합니다.

from sklearn.feature_extraction.text import TfidfVectorizer

tfidf_vectorizer = TfidfVectorizer()
tfidf_matrix = tfidf_vectorizer.fit_transform(docs)

--

--