오늘은 텍스트 마이닝 분야에서 주로 사용되는 카운트 기반의 텍스트 표현 방법인 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.
'자연어 처리' 카테고리의 다른 글
[딥러닝을 이용한 자연어 처리 입문] 머신 러닝 (0) | 2023.04.06 |
---|---|
[딥러닝을 이용한 자연어 처리 입문] 벡터의 유사도 (0) | 2023.04.06 |
[딥러닝을 이용한 자연어 처리 입문] 언어모델 (0) | 2023.03.04 |
[딥러닝을 이용한 자연어 처리 입문] 패딩과 원-핫인코딩 (0) | 2023.02.27 |
[딥러닝을 이용한 자연어 처리 입문] 정규 표현식 (0) | 2023.02.25 |