본문 바로가기
자연어 처리

[딥러닝을 이용한 자연어 처리 입문] 카운트 기반의 언어 모델

by 나연하야 2023. 3. 20.

오늘은 텍스트 마이닝 분야에서 주로 사용되는 카운트 기반의 텍스트 표현 방법인 DTM(Document Term Matrix)과 TF-IDF(Term Frequency-Inverse Document Frequenct)에 대해 다루고자 함

 

단어의 표현 방법

  • 단어의 표현 방법은 크게 국소 표현과 분산 표현이 있으며, 국소 표현은 해당 단어만 보고 특정값을 맵핑하여 단어를 표현하는 것이며, 분산 표현은 그 단어를 표현하는 주변을 참고하여 단어를 표현하는 방법임
  • 분산 표현 방법의 경우 단어의 의미, 뉘앙스를 표현할 수 없게 됨
  • 예를 들어 puppy, cute, lovely라는 단어가 있을 때 각 단어를 1, 2, 3과 같은 숫자에 맵핑하는 것이 국소 표현이며, 분산 표현 방법은 puppy라는 단어는 cute, lovely한 느낌으로 정의한 것임
  • 국소 표현 방법은 이산 표현이라고 하며, 분산 표현 방법은 연속 표현이라고 함
  • 국소 표현 방법에는 Bag of Words, DTM(또는 TDM), TF-IDF가 있으며, 분산 표현 방법에는 Word2Vec, FastText, GloVe 등이 있음

 

Bag of Words

  • Bag of Words(BoW)는 단어들의 출현 빈도에 집중하여 텍스트 데이터를 수치화하는 방법임
  • CountVectorizer를 이용하여 BoW를 구할 수도 있지만 띄어쓰기만을 기준으로 단어를 자르는 낮은 수준의 토큰화를 진행하여 BoW를 계산함
  • 예를 들어 '정부가 발표하는 물가상승률과 소비자가 느끼는 물가상승률은 다르다'라는 문장에서 '물가상승률과', '물가상승률은'의 경우 조사 때문에 다른 단어로 인식됨
  • 아래는 특정 문장을 Okt를 이용하여 토큰화 한 후, Bag of Words vector를 산출하는 코드임
!pip install konlpy
from konlpy.tag import Okt

okt=Okt()

def build_bag_of_words(document):
  document=document.replace('.', '')
  tokenized_document=okt.morphs(document)

  word_to_index={}
  bow=[]

  for word in tokenized_document:
    if word not in word_to_index.keys():
      word_to_index[word]=len(word_to_index)
      bow.insert(len(word_to_index)-1, 1)
    else:
      index=word_to_index.get(word)
      bow[index]=bow[index]+1

  return word_to_index, bow
doc1 = "정부가 발표하는 물가상승률과 소비자가 느끼는 물가상승률은 다르다."
vocab, bow = build_bag_of_words(doc1)
print('vocabulary:', vocab)
print('bag of words vector:', bow)
vocabulary: {'정부': 0, '가': 1, '발표': 2, '하는': 3, '물가상승률': 4, '과': 5, '소비자': 6, '느끼는': 7, '은': 8, '다르다': 9}
bag of words vector: [1, 2, 1, 1, 2, 1, 1, 1, 1, 1]

 

  • 자연어 처리에서 큰 의미를 갖지 않는 단어인 불용어를 제거하여 BoW를 산출할 수도 있으며, 아래의 코드는 연구자가 the, a, an과 같은 불용어를 지정한 후 BoW를 산출한 과정임
from sklearn.feature_extraction.text import CountVectorizer
from nltk.corpus import stopwords

text = ["Family is not an important thing. It's everything."]
vect=CountVectorizer(stop_words=["the", "a", "an", "is", "not"])
print('bag of words vector:', vect.fit_transform(text).toarray())
print('vocabulary: ', vect.vocabulary_)
bag of words vector: [[1 1 1 1 1]]
vocabulary:  {'family': 1, 'important': 2, 'thing': 4, 'it': 3, 'everything': 0}

 

문서 단어 행렬(Document-Term Matrix, DTM)

  • DTM은 다수의 문서에서 등장하는 각 단어들의 빈도를 행렬로 표현한 것을 말함
  • 예를 들어, 문서1: 먹고 싶은 사과, 문서2: 먹고 싶은 바나나, 문서3: 길고 노란 바나나 바나나, 문서4: 저는 과일이 좋아요라고 하면 DTM은 아래와 같이 표현됨
  과일이 길고 노란 먹고 바나나 사과 싶은 저는  좋아요
문서1 0 0 0 1 0 1 1 0 0
문서2 0 0 0 1 1 0 1 0 0
문서3 0 1 1 0 2 0 0 0 0
문서4 1 0 0 0 0 0 0 1 1
  • DTM은 간단하고 구현이 쉽지만 희소 행렬이라는 한계를 가지고 있음.
  • 희소 행렬은 대부분의 행렬값이 0인 것으로 많은 양의 저장공간과 계산이 복잡해지기 때문에 불용어를 제거하는 방법 등으로 이를 해결해야 함
  • 이외에도 DTM은 단순 빈도 수를 기반으로 하기 때문에 the와 같은 단어는 여러 문서에 등장하지만 이 문서들이 유사하다고 판단해서는 안됨
  • 이와 같은 문제를 해결하기 위해 TF-IDF라는 개념이 나옴
  • 아래의 코드는 단수 빈도 수에 기반하여 DTM을 산출한 예시임
from sklearn.feature_extraction.text import CountVectorizer

corpus=[
    'you know I want your love',
    'I like you',
    'what should I do'
]

vector=CountVectorizer()
print(vector.fit_transform(corpus).toarray())
print(vector.vocabulary_)
[[0 1 0 1 0 1 0 1 1]
 [0 0 1 0 0 0 0 1 0]
 [1 0 0 0 1 0 1 0 0]]
{'you': 7, 'know': 1, 'want': 5, 'your': 8, 'love': 3, 'like': 2, 'what': 6, 'should': 4, 'do': 0}

 

단어 빈도-역 문서 빈도(Term Frequency-Inverse Document Frequency, TF-IDF)

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

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

(3) idf(d, t): df(t)에 반비례하는 수 (=ln(n/(1+df(t)))  -> 총 문서 수가 많을 경우 idf(d, t)가 기하급수적으로 커지는 것을 방지하기 위해 ln을 활용함

 

  • 위의 예에서 idf를 계산하면 과일이, 길고, 노란, 사과, 저는, 좋아요는 하나의 문서에 나타났기 때문에 ln(4/(1+1))=0.693147이며, 먹고, 바나나, 싶은은 두 개의 문서에 나타났기 때문에 ln(4/(2+1))=0.287682임
  • 이를 통해 tf-idf를 계산하면 다음과 같으며, 노란의 경우 문서3, 먹고의 경우 문서1에서 한번씩 언급되었지만 노란의 경우 문서3에서만 언급되었으므로 해당 문서에서 가중치가 크게 나타남
  과일이 길고 노란 먹고 바나나 사과 싶은 저는  좋아요
문서1 0 0 0 0.287682 0 0.693147 0.287682 0 0
문서2 0 0 0 0.287682 0.287682 0 0.287682 0 0
문서3 0 0.693147 0.693147 0 0.575364 0 0 0 0
문서4 0.693149 0 0 0 0 0 0 0.693147 0.693147
from sklearn.feature_extraction.text import TfidfVectorizer

corpus=[
    'you know I want your love',
    'I like you',
    'what should I do'
]

tfidfv=TfidfVectorizer().fit(corpus)
print(tfidfv.transform(corpus).toarray())
print(tfidfv.vocabulary_)
[[0.         0.46735098 0.         0.46735098 0.         0.46735098
  0.         0.35543247 0.46735098]
 [0.         0.         0.79596054 0.         0.         0.
  0.         0.60534851 0.        ]
 [0.57735027 0.         0.         0.         0.57735027 0.
  0.57735027 0.         0.        ]]
{'you': 7, 'know': 1, 'want': 5, 'your': 8, 'love': 3, 'like': 2, 'what': 6, 'should': 4, 'do': 0}

출처: 유원준/안상준, 딥러닝을 이용한 자연어 처리 입문-1권, p139-p157.