Deep Learning/NLP

05. TF-IDF, N-gram

해파리냉채무침 2023. 3. 13. 17:49

TF-IDF

역문서 빈도, 각 단어의 중요성을 가중치로 표현 

tf(d,t) -한 문서 안에서 어떤 단어가 몇번 등장했는지

df(t) -단어가 얼마나 많은 문서에 등장했는지

idf(d,t) -df(t)의 역수

idf가 높으면, df가 낮은 경우 의미

출처: 위키백과

tf와 달리 idf쪽에는 주로 로그를 사용한다.

https://github.com/insightcampus/sesac-nlp

 

 

idf에 로그를 사용하는 이유는 단어A 와 B/ 단어 C와 D는 한개 차이지만,

빈도수가 적을때, 그 차이가 적더라도 가중치를 높게 측정한다.

단어 C와 D를 봤을 때 빈도수가 둘 다 많으면, 차이가 미미하다.

그래서 이러한 효과를 상쇄하기 위해 로그를 적용한다.

 

TF-IDF의 계산 절차

 

토큰별 인덱스는 위치를 부여한다.

TF 계산은 각 토큰의 등장 빈도를 계산하고, IDF 계산은 각 단어의 문서 등장빈도를 계산하여 역수를 취한다.

마지막으로 TF와 IDF를 곱하여 계산한다. 

문서1 The cat sat on my face I hate a cat

문서 2 The dog sat on my bed I love a dog 가 있다고 가정하자.

 

 

tf를 알기 위해서는문서내 전체 토큰 빈도(전체 단어 수) 와 각 단어의 문서내 토큰빈도(문서내 단어가 나온 수)를 알아야한다.

문서내 토큰 빈도/ 문서내 전체 로 나누어 tf를 구한다.

N = 문서수, nt = 토큰이 등장한 문서수

idf계산하기 위해서 전체 문서수 알아야한다. 

The는 공통적으로 등장하는데, log1=0 결론은 idf=0이 나옴. 

해당문서안의 단어가 빈도수가 많고, 타 문서 안에서 낮게 나오면  tf-idf가 높게 

해당문서에 많이 나오지 않고, 타문서에 많이 등장하면 tf-idf가 낮다. 

tf-idf 스코어 가지고 벡터로 나타낼 수 있다. 

예를 들어 한 단어의 문서1 tf-idf가 0.06이고, 문서2의 tf-idf가 0이면 (0.06,0) 으로 나타낸다. 

code

d1 = "The cat sat on my face I hate a cat"
d2 = "The dog sat on my bed I love a dog"
import numpy as np

def tf(t, d): # t가 d문서에서 등장한 횟수/d의 전체토큰수
    return d.count(t)/len(d) 

def idf(t, D): #전체 문서에서 t단어가 얼마나 많이 등장했는지
    N = len(D)
    n = len([True for d in D if t in d]) #전체 문서안에 개별 문서 탐색 그중에서 t가 d에 포함된것,
    return np.log(N/n)

def tokenizer(d): #토큰화는 공백으로 처리 
    return d.split()

def tfidf(t,d,D): #D는 전체문서수
     return tf(t,d)*idf(t, D)
          
def tfidf_scorer(D):
    docs = [tokenizer(d) for d in D]
    result = []      
    for d in docs:
        for t in d: 
          result.append([(t,tfidf(t,d,docs)) for t in d])
        #단어별 tfidf 스코어 계산/ 문서별 단어별 tfidf 계산
          
    return result
tfidf_scorer([d1,d2])
[[('The', 0.0),
  ('cat', 0.13862943611198905),
  ('sat', 0.0),
  ('on', 0.0),
  ('my', 0.0),
  ('face', 0.06931471805599453),
  ('I', 0.0),
  ('hate', 0.06931471805599453),
  ('a', 0.0),
  ('cat', 0.13862943611198905)],
  [('The', 0.0),
  ('dog', 0.13862943611198905),
  ('sat', 0.0),
  ('on', 0.0),
  ('my', 0.0),
  ('bed', 0.06931471805599453),
  ('I', 0.0),
  ('love', 0.06931471805599453),
  ('a', 0.0),
  ('dog', 0.13862943611198905)]]

sklearn 활용

from sklearn.feature_extraction.text import CountVectorizer
docs = [d1,d2]
count_vect = CountVectorizer()
countv = count_vect.fit_transform(docs)
#fit학습, trasform은 예측 fit_transform은 둘다함.
countv.toarray() #단어별로 카운팅
array([[0, 2, 0, 1, 1, 0, 1, 1, 1, 1],
       [1, 0, 2, 0, 0, 1, 1, 1, 1, 1]], dtype=int64)
count_vect.vocabulary_ #간 단어가 어떻게 INDEX표시 되었는지
{'the': 9,
 'cat': 1,
 'sat': 8,
 'on': 7,
 'my': 6,
 'face': 3,
 'hate': 4,
 'dog': 2,
 'bed': 0,
 'love': 5}
from sklearn.feature_extraction.text import TfidfVectorizer

tfidf_vect = TfidfVectorizer()
tfidfv = tfidf_vect.fit_transform(docs)
print(tfidfv.toarray())
print(tfidf_vect.vocabulary_)
[[0.         0.70600557 0.         0.35300279 0.35300279 0.
  0.25116439 0.25116439 0.25116439 0.25116439]
 [0.35300279 0.         0.70600557 0.         0.         0.35300279
  0.25116439 0.25116439 0.25116439 0.25116439]]
{'the': 9, 'cat': 1, 'sat': 8, 'on': 7, 'my': 6, 'face': 3, 'hate': 4, 'dog': 2, 'bed': 0, 'love': 5

 

 

N-gram

n개의 단어를 보느냐에 따라 unigram, bigram, trigram으로 구분

제한적으로 문맥을 표현 가능

unigrams : a, cute, little, boy

bigrams : a cute, a boy 

trigrams: a cute boy

 

n을 1보다 2로 설정하는 것이 모델 성능을 높일 수 있음, n을 크게 하면  n-gram이 unique할 확률이 높아 등장수가 낮을 확률이 높음.

n을 너무 작게하면 카운트는 잘되지만 정확도가 떨어질 수 있음.

n카운트가 0이 되는경우, 딕셔너리가 있을때 그 딕셔너리에 새로운 ngram이 포함될 확률이 낮을 수 있음

 

 

출처: https://github.com/insightcampus/sesac-nlp