본문 바로가기

자연어처리(NLP)

2-1. BOW(Bag of Words)기반 'TF-IDF' [초등학생도 이해하는 자연어처리]

반응형

 안녕하세요 '코딩 오페라'블로그를 운영하고 있는 저는 'Master.M'입니다.

현재 저는 '초등학생도 이해하는 자연어 처리'라는 주제로 자연어 처리(NLP)에 대해 포스팅을 하고 있습니다. 제목처럼 진짜 핵심 내용을 쉽게 설명하는 것을 목표로 하고 있으니 자연어 처리(NLP)에 입문하고 싶은 분들은 많은 관심 부탁드립니다. 오늘 알아볼 내용은 'TF-IDF(Term Frequency-Inverse Document Frequency)'입니다.

 

https://codingopera.tistory.com/36?category=1094804

 

2. BOW(Bag of Words)기반 '카운트 벡터(Count Vector)' [초등학생도 이해하는 자연어처리]

 안녕하세요 '코딩 오페라'블로그를 운영하고 있는 저는 'Master.M'입니다. 현재 저는 '초등학생도 이해하는 자연어 처리'라는 주제로 자연어 처리(NLP)에 대해 포스팅을 하고 있습니다. 제목처럼

codingopera.tistory.com

 

 우리는 저번 시간에 '카운트 벡터(Count Vector)'에 대해 알아보았습니다. 이는 간단히 문서에서의 단어 빈도수를 이용해 단어를 벡터화하는 것입니다. 자세히 알고 싶으신 분들은 위의 글을 참고하시기 바랍니다. 이러한 방식의 표기법을 DTM(Document-Term Matrix)라고 합니다. 이 방식은 간단하지만 아래와 같은 문제점들을 가지고 있습니다.

 

- 원핫벡터를 이용한 '희소 표현(Sparse Representation)'을 사용함으로써 시공간적 리소스를 낭비.(벡터에 무의미한 '0'이 많다.)

- 단순히 빈도수를 사용하므로 'the'나 'a'와 같은 불용어들로 인한 문제가 발생.

 

때문에 이러한 문제점을 극복하기 위해 제안된 방식이 'TF-IDF(Term Frequency-Inverse Document Frequency)'입니다.

 

TF-IDF(Term Frequency-Inverse Document Frequency)

'TF-IDF'의 기본적인 개념은 "위의 단순 빈도수 문제를 개선하기위해 단어의 빈도수에 역 빈도수를 곱해준다"는 것입니다.

 

1. TF(d, f) : 특정 문서 d에서의 특정 단어 t의 등장 횟수

예를 들어 문서 [I am a great great boy]가 있을 때 각 단어의 'TF'는 [I, am, a , great, boy] == [1, 1, 1, 2, 1]이 됩니다.

 

2. df(t) : 특정 단어 t가 등장한 문서의 수

 예를들어 문서 1 [I am a great great boy], 문서 2 [I am a boy]가 있을 때 'df'는 [I, am, a , great, boy] == [2, 2, 2, 1, 2]이 됩니다. 여기서 great == 1로 정의되는데 그 이유는 great이 문서 1에서 2번 사용되었지만, 사용된 문서수는 문서 1 하나이므로 1로 대응됩니다.

 

3. idf(d, t) : df(t)에 반비례하는 수

log scale


 이 수는 'the'나 'a'와 같이 너무 많이 사용되는 단어의 가중치를 줄이기위해 필요한 값입니다. 공식은 위와 같습니다. 여기서 'n'은 문서의 수를 나타냅니다. log를 취한 이유는 숫자의 스케일을 줄여주기 위함입니다. 그 이유는 숫자간의 편차가 너무 크면 알아보기 힘들 뿐만 아니라 학습도 잘 되지 않습니다. 예를들어 위 표와 같이 [100000, 200000]이라는 벡터가 있습니다. 이는 너무 큽니다. 이 벡터에 log10을 취해주면 [log10(100000), log10(200000)] == [5, 5.3]으로 알아보기가 훨씬 수월해졌습니다. 또한 분모에 '1'를 더해주는 이유는 분모가 '0'이 되는 것을 방지하기 위함입니다.

 

 마지막으로 TF-IDF는 TF와 IDF를 서로 곱해주면 됩니다. 

 

지금부터는 코드를 통해 TF-IDF를 실습해 보도록 하겠습니다. 

 

text = ["I am a great great elementary school student", "And I am a boy"]

# Word tokenized sentence
from nltk import word_tokenize

text_tokenzied = [word_tokenize(sentence) for sentence in text]

 처음에 문서인 text를 정의해줍니다. 여기서 우리는 각 문장을 문서라고 정의하겠습니다. 즉 text에는 문서 1, 문서 2 이렇게 2개의 문서가 있는 것입니다. 

 

 

# Remove stopwords that are too short.
text_tokenzied2 = []
for sentence in text_tokenzied:
    sent = []
    for word in sentence:
        if len(word) >= 2:
            sent.append(word)
    text_tokenzied2.append(sent)

print(text_tokenzied2)

 

[['am', 'great', 'great', 'elementary', 'school', 'student'], ['And', 'am', 'boy']]

 그 다음 학습의 성능을 향상하기 위해 글자 수가 하나인 단어를 제거해줍니다. 토크 나이저 된 리스트는 위와 같이 문서들에서 단어 'I'가 사라진 것을 알 수 있습니다. 

 

 

# Vocabulary list
from collections import Counter

vocab_counter = Counter()
for sentence in text_tokenzied2:
    vocab_counter.update(sentence)

vocab = []
for key, value in vocab_counter.items():
    vocab.append(key)
    
print(vocab)

 

['am', 'great', 'elementary', 'school', 'student', 'And', 'boy']

 전체 문서들의 중복제거한 단어들을 list로 만들어 줍니다.

 

 

# Count word of each sentence
from collections import Counter

count = []
for sentence in text_tokenzied2:
    vocab_counter = Counter()
    vocab_counter.update(sentence)
    count.append(vocab_counter)
print(count)

 

[Counter({'great': 2, 'am': 1, 'elementary': 1, 'school': 1, 'student': 1}), Counter({'And': 1, 'am': 1, 'boy': 1})]

 'Counter()'를 이용해 각 문서별로 단어수들을 취합합니다. 위와 같이 문서 2개에 해당하는 count라는 list가 만들어진 것을 보실 수 있습니다. 

 

 

def TF(vocab, counter):
    vector = []
    for word in vocab:
        if counter[word] != False:
            vector.append(counter[word])
        else:
            vector.append(0)
    return vector

print(vocab)
print(TF(vocab, count[0]))

 

['am', 'great', 'elementary', 'school', 'student', 'And', 'boy']
[1, 2, 1, 1, 1, 0, 0]

 TF함수를 이용해 각 문서별로 사용된 단어수를 구해줍니다. 위와 같이 'am'은 1번, 'great'은 2번 ... 사용된 것을 확인할 수 있습니다. 

 

 

def DF(text_tokenzied2, vocab):
    text = []
    for sentence in text_tokenzied2:
        for word in list(set(sentence)):
            text.append(word)
    vocab_counter = Counter()
    vocab_counter.update(text)
    
    df = []
    for word in vocab:
        df.append(vocab_counter[word])
    return df

print(vocab)
print(DF(text_tokenzied2, vocab))

 

['am', 'great', 'elementary', 'school', 'student', 'And', 'boy']
[2, 1, 1, 1, 1, 1, 1]

 DF함수를 통해 각 단어가 사용된 문서수를 알아냅니다. 위를 보시면 'am'은 문서1과 문서 2 두 군데 에서 사용된 것을 확인할 수 있습니다. 

 

 

import math

def IDF(df, n):
    idf = []
    for i in df:
        idf.append(math.log((n)/(i+1))+1)
    return idf

print(vocab)
print(IDF(DF(text_tokenzied2, vocab), len(text_tokenzied2)))

 

['am', 'great', 'elementary', 'school', 'student', 'And', 'boy']
[0.5945348918918356, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]

 위에서 구한 DF를 이용해 IDF를 만들어줍니다. 여기서는 위의 idf 로그식 안에 '1'을 더하여 조금 변형을 시켰습니다. 결과는 다음과 같습니다. 

 

 

def TFIDF(tf, idf):
    product = [x*y for x, y in zip(tf, idf)]
    return product

print(vocab)
print(TFIDF(TF(vocab, c), IDF(DF(text_tokenzied2, vocab), len(text_tokenzied2))))

 

['am', 'great', 'elementary', 'school', 'student', 'And', 'boy']
[0.5945348918918356, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0]

 TFIDF 함수를 이용해 TF와 IDF를 곱해 최종적인 벡터를 구해줍니다. 

 

 

tfidf = []
for c in count:
    tfidf.append(TFIDF(TF(vocab, c), IDF(DF(text_tokenzied2, vocab), len(text_tokenzied2))))

print(vocab)
print(tfidf)

 

['am', 'great', 'elementary', 'school', 'student', 'And', 'boy']
[[0.5945348918918356, 2.0, 1.0, 1.0, 1.0, 0.0, 0.0], [0.5945348918918356, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0]]

 전체 문서에 대해 TF-IDF를 적용하면 다음과 같이 됩니다. 

 

저희는 위와 같이 TF-IDF를 적용하여 문서를 벡터화 하였습니다. 좀 더 쉽게 이를 진행하는 방법이 없을까요? 정답은 'sklearn TfidfVectorizer 패키지'를 사용하는 것입니다.

 

from sklearn.feature_extraction.text import TfidfVectorizer

tfidfv = TfidfVectorizer().fit(text)
print(tfidfv.transform(text).toarray())
print(tfidfv.vocabulary_)

 

[[0.25969799 0.         0.         0.36499647 0.72999294 0.36499647
  0.36499647]
 [0.44943642 0.6316672  0.6316672  0.         0.         0.
  0.        ]]
{'am': 0, 'great': 4, 'elementary': 3, 'school': 5, 'student': 6, 'and': 1, 'boy': 2}

 

Self vs Sklearn

 

 위와 같이 sklearn에서 'TfidfVectorizer'를 가져와 문서를 벡터화시켰습니다. 정규화 과정이 약간식 다르기 때문에 벡터가 전에 진행했던 것과 완벽하게 동일하지는 않지만 개형이 비슷한 것을 알 수 있습니다. 프린트 문에서 첫 번째 리스트는 벡터를, 두번째 딕셔너리는 각 단어의 인덱스를 나타냅니다. 위의 표를 보시다 싶이 저희가 직접 만든 알고리즘과 sklearn 알고리즘의 결과가 비슷한 것을 확인할 수 있습니다.

 

 

정리

- TF(d, f) : 특정 문서 d에서의 특정 단어 t의 등장 횟수

 

- df(t) : 특정 단어 t가 등장한 문서의 수

 

- idf(d, t) : df(t)에 반비례하는 수. idf = log(n/(1+df)), n=문서의수

 

- TF-IDF: TF*idf로 단순 빈도수 문제를 개선하기 위해 단어의 빈도수에 역 빈도수를 곱해줌

 

  지금 까지 저희는 'TF-IDF'에 대해 알아보았습니다. 도움이 되셨나요? 만약 되셨다면 구독 및 좋아요로 표현해 주시면 정말 많은 힘이 됩니다. 궁금한 사항 혹은 앞으로 다루어 주었으면 좋을 주제가 있으시면 댓글 남겨주시면 감사하겠습니다. 저는 '코딩 오페라'의 'Master.M'이었습니다. 감사합니다.

반응형