[텍스트 마이닝] 카운트 기반의 문서 표현이란?

2024. 2. 3. 20:37·AI & 빅데이터
728x90

"파이썬 텍스트 마이닝 완벽 가이드" 도서를 읽고 정리한 글입니다.

 

이 글을 통해 얻을 수 있는 내용

- 카운트 기반 문서 표현이란 무엇인가

- 카운트 벡터를 생성하는 방법과 코드 이해

- 카운트 벡터와 TF-IDF 방법의 차이


4장. 카운트 기반의 문서 표현

4-1. 카운트 기반 문서 표현의 개념

카운트 기반 문서 표현이란?

단어의 통계를 이용해 문서의 내용을 이해하고자 하는 시도이다.

카운트 기반 문서 표현은 단어의 빈도를 세어 벡터로 표현하는 방법이다.

 

텍스트의 특성을 무엇으로 정의할까?

텍스트의 특성을 단어로 표현하고, 특성이 갖는 값을 그 단어가 텍스트에서 나타나는 횟수로 표현한다.

ex) 정치(특성) : 4(특성이 갖는 값, 빈도)

 

문서마다 특성이 제각각인데, 어떻게 비교하지?

전체 문서에서 한 번이라도 사용된 단어는 문서에 없더라도 특성에 포함하고, 빈도를 0으로 준다.

→ 전체 문서에서 나타나는 단어는 매우 많기 때문에, 대부분의 값이 0인 특성 벡터를 가지게 된다. (희소 벡터, sparse vector)

 


4-2. BOW 기반의 카운트 벡터 생성

단어별 카운트를 기반으로 문서로부터 특성을 추출하고 표현하는 방식을 BOW(Bag of Words) 라고 한다.

NLTK가 제공하는 영화 리뷰를 사용하여 카운트 벡터를 생성해보자.

 

nltk에서 제공하는 movie_reviews 데이터를 불러올 수 있다.

리뷰 수는 총 2,000개이다.

import nltk
nltk.download('movie_reviews')

from nltk.corpus import movie_reviews

print('review count :', len(movie_reviews.fileids()))
# 출력
review count : 2000

 

 

첫 번째 리뷰의 일부분을 살펴보자.

first_file_id = movie_reviews.fileids()[0]
print('first review content:\n', movie_reviews.raw(first_file_id)[:200])
# 출력
first review content:
 plot : two teen couples go to a church party , drink and then drive . 
they get into an accident . 
one of the guys dies , but his girlfriend continues to see him in her life , and has nightmares . 
w

 

각 리뷰의 단어를 모두 추출하여 리스트 형태로 저장한다.

첫 번째 리뷰의 단어들을 살펴본다.

documents = [list(movie_reviews.words(fileid)) for fileid in movie_reviews.fileids()]

# 첫 번째 문서의 앞 50개 단어를 출력
print(documents[0][:50])
# 출력
['plot', ':', 'two', 'teen', 'couples', 'go', 'to', 'a', 'church', 'party', ',', 'drink', 'and', 'then', 'drive', '.', 'they', 'get', 'into', 'an', 'accident', '.', 'one', 'of', 'the', 'guys', 'dies', ',', 'but', 'his', 'girlfriend', 'continues', 'to', 'see', 'him', 'in', 'her', 'life', ',', 'and', 'has', 'nightmares', '.', 'what', "'", 's', 'the', 'deal', '?', 'watch']

 

단어별 빈도수를 계산하고, 가장 많이 나타나는 단어 10개를 출력한다.

결과를 보니, 의미적으로 쓸모없는 단어가 많이 존재한다.

word_count = {}
for text in documents:
    for word in text:
        word_count[word] = word_count.get(word, 0) + 1

sorted_features = sorted(word_count, key=word_count.get, reverse=True)
for word in sorted_features[:10]:  # 빈도수가 높은 단어 10개 출력
    print(f"count of '{word}': {word_count[word]}", end=', ')
# 출력
count of ',': 77717, count of 'the': 76529, count of '.': 65876, count of 'a': 38106, count of 'and': 35576, count of 'of': 34123, count of 'to': 31937, count of ''': 30585, count of 'is': 25195, count of 'in': 21822,

 

nltk 라이브러리에서 제공하는 stopwords에서 일반적으로 분석 대상이 아닌 영어 단어들을 제공하고 있다.

정규 표현식을 사용하여 문자가 3개인 경우만 추출하고, stopword인 경우는 모두 제외하도록 하자.

',' 'the' 'a' 와 같은 단어들이 제거된 것을 확인할 수 있다.

from nltk.tokenize import RegexpTokenizer
from nltk.corpus import stopwords  # 일반적으로 분석대상이 아닌 단어들

tokenizer = RegexpTokenizer("[\w']{3,}")  # 정규포현식으로 토크나이저를 정의 -> 문자가 3개 이상인 경우만 추출
english_stops = set(stopwords.words('english'))  # 영어 불용어를 가져옴

# words() 대신 raw()를 이용해 원문을 가져옴
documents = [movie_reviews.raw(fileid) for fileid in movie_reviews.fileids()] 

# stopwords의 적용과 토큰화를 동시에 수행.
tokens = [[token for token in tokenizer.tokenize(doc) if token not in english_stops] for doc in documents]

word_count = {}
for text in tokens:
    for word in text:
        word_count[word] = word_count.get(word, 0) + 1

sorted_features = sorted(word_count, key=word_count.get, reverse=True)

print('num of features:', len(sorted_features))
for word in sorted_features[:10]:  # 빈도수가 높은 단어 10개 출력
    print(f"count of '{word}': {word_count[word]}", end=', ')
# 출력
num of features: 43030
count of 'film': 8935, count of 'one': 5791, count of 'movie': 5538, count of 'like': 3690, count of 'even': 2564, count of 'time': 2409, count of 'good': 2407, count of 'story': 2136, count of 'would': 2084, count of 'much': 2049,

 

특성을 모두 사용해도 되지만, 상위 빈도수를 가지는 단어 1,000개만 추출하여 최종적으로 문서를 표현할 특성으로 사용한다.

첫 번째 문서의 특성이 표현된 것을 볼 수 있다.

word_features = sorted_features[:1000]  # 빈도가 높은 상위 1000개의 단어만 추출하여 features를 구성

def document_features(document, word_features):
    word_count = {}
    for word in document:  # document에 있는 단어들에 대해 빈도수를 먼저 계산
        word_count[word] = word_count.get(word, 0) + 1
        
    features = []
    for word in word_features:  # word_features의 단어에 대해 계산된 빈도수를 feature에 추가
        features.append(word_count.get(word, 0))  # 빈도가 없는 단어는 0을 입력
    return features

feature_sets = [document_features(d, word_features) for d in tokens]

# 첫째 feature set의 내용을 앞 20개만 word_features의 단어와 함께 출력
for i in range(20):
    print(f'({word_features[i]}, {feature_sets[0][i]})', end=', ')
    
# 첫 번째 문서의 벡터를 출력
print(feature_sets[0])
# 출력
(film, 5), (one, 3), (movie, 6), (like, 3), (even, 3), (time, 0), (good, 2), (story, 0), (would, 1), (much, 0), (also, 1), (get, 3), (character, 1), (two, 2), (well, 1), (first, 0), (characters, 1), (see, 2), (way, 3), (make, 5),

[5, 3, 6, 3, 3, 0, 2, 0, 1, 0, 1, 3, 1, 2, 1, 0, 1, 2, 3, 5, 1, 2, 2, 1, 2, 1, 0, 2, 0, 0, 0, 0, 1, 2, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 2, 1, 0, 1, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 2, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 5, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 1, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

 


4-3. 사이킷런을 이용한 카운트 벡터 생성

위 내용처럼 직접 내용을 구현할 수도 있지만, 사이킷런의 텍스트 관련 라이브러리를 사용하면 훨씬 편리하다.

이번에는 위에서 사용한 movie_reviews 데이터에 대해, 사이킷런의 CountVectorizer 클래스를 이용해 카운트 벡터를 생성해 보자.

 

위와 동일하게, 상위 단어 1,000개만 가지고 벡터를 표현할 것이다.

# data 준비, movie_reviews.raw()를 사용하여 raw text를 추출
reviews = [movie_reviews.raw(fileid) for fileid in movie_reviews.fileids()]

from sklearn.feature_extraction.text import CountVectorizer

# 앞에서 생성한 word_features를 이용하여 특성 집합을 지정하는 경우
# word_features에 해당하는 단어만 표현한다.
cv = CountVectorizer(vocabulary=word_features) 

print(cv) #객체에 사용된 인수들을 확인
# 출력
CountVectorizer(vocabulary=['film', 'one', 'movie', 'like', 'even', 'time',
                            'good', 'story', 'would', 'much', 'also', 'get',
                            'character', 'two', 'well', 'first', 'characters',
                            'see', 'way', 'make', 'life', 'really', 'films',
                            'plot', 'little', 'people', 'could', 'bad', 'scene',
                            'never', ...])

 

CountVectorizer에 사용된 단어와, word_features에 저장한 단어가 동일한 모습을 볼 수 있다.

# reviews를 이용하여 count vector를 학습하고, 변환
reviews_cv = cv.fit_transform(reviews)

# count vector에 사용된 feature 이름을 반환
print(cv.get_feature_names_out()[:20])  

# 비교를 위해 출력
print(word_features[:20])
# 출력
['film' 'one' 'movie' 'like' 'even' 'time' 'good' 'story' 'would' 'much'
 'also' 'get' 'character' 'two' 'well' 'first' 'characters' 'see' 'way'
 'make']
['film', 'one', 'movie', 'like', 'even', 'time', 'good', 'story', 'would', 'much', 'also', 'get', 'character', 'two', 'well', 'first', 'characters', 'see', 'way', 'make']

 

카운트 벡터가 매우 희소하기 때문에, 모든 벡터를 저장하고 있는 것이 아닌 값이 있는 위치만 저장하고 있다.

print('#type of count vectors:', type(reviews_cv))
print('#shape of count vectors:', reviews_cv.shape)
print('#sample of count vector:')
print(reviews_cv[0, :10])
# 출력
#type of count vectors: <class 'scipy.sparse._csr.csr_matrix'>
#shape of count vectors: (2000, 1000)
#sample of count vector:
  (0, 0)	6
  (0, 1)	3
  (0, 2)	6
  (0, 3)	3
  (0, 4)	3
  (0, 6)	2
  (0, 8)	1

 

첫 번째 문서의 벡터 앞부분을 비교해 보자.

값 2개 정도가 다른 것을 볼 수 있는데, 이는 CountVectorizer와 우리가 정규표현식으로 만든 토크나이저에 차이가 있기 때문이다.

print(feature_sets[0][:20])  # 절 앞에서 직접 계산한 카운트 벡터
print(reviews_cv.toarray()[0, :20])  # 변환된 결과의 첫째 feature set 중에서 앞 20개를 출력
# 출력
[5, 3, 6, 3, 3, 0, 2, 0, 1, 0, 1, 3, 1, 2, 1, 0, 1, 2, 3, 5]
[6 3 6 3 3 0 2 0 1 0 1 3 2 2 1 0 1 2 3 5]

 


4-4. 한국어 텍스트의 카운트 벡터 변환

한국어 텍스트로부터 카운트 벡터를 생성하는 단계도 기본적으로 영어와 동일하다.

단, 형태소 분석기가 필요하다. 한글 분석에는 CountVectorizer가 제공하는 토크나이저를 쓸 수 없다.

 

여기서는 한국어 형태소 분석기를 사용할 수 있는 NoNLPy 라이브러리를 사용한다.

다음(Daum) 영화 리뷰를 크롤링해 만든 데이터를 사용한다.

 

리뷰 데이터는 총 14,725개이며, ['review', 'rating', 'date', 'title'] 컬럼으로 이루어져 있다.

import pandas as pd
df = pd.read_csv('./data/daum_movie_review.csv')

print("review count :", len(df))		# 문서 수 출력
df.head(10)  					# 상위 10개 문서 출력

 

영어와 동일하게 한국어 텍스트에 CountVectorizer를 사용하게 되면,

예를 들어 'cg'가 들어간 단어들 ('cg' 'cg가' 'cg는' 'cg도' 'cg만')이 전부 별도의 단어로 분류된다.

따라서 형태소 분석기를 사용하여 토큰화하는 것이 필수적이다.

from sklearn.feature_extraction.text import CountVectorizer
daum_cv = CountVectorizer(max_features=1000)

daum_DTM = daum_cv.fit_transform(df.review)	# review를 이용하여 count vector를 학습하고, 변환
print(daum_cv.get_feature_names_out()[:100])	# count vector에 사용된 feature 이름을 반환

 

KoNLPy의 Okt 형태소 분석기를 사용한다.

from konlpy.tag import Okt  # konlpy에서 Twitter 형태소 분석기를 import
twitter_tag = Okt()

print('#전체 형태소 결과:', twitter_tag.morphs(df.review[1]))
print('#명사만 추출:', twitter_tag.nouns(df.review[1]))
print('#품사 태깅 결과', twitter_tag.pos(df.review[1]))

 

명사, 동사, 형용사만 추출할 수 있도록 함수를 정의한다. 이와 같이 함수를 직접 정의하여 원하는 품사만을 추출할 수도 있다.

def my_tokenizer(doc):
    return [token for token, pos in twitter_tag.pos(doc) if pos in ['Noun', 'Verb', 'Adjective']]

print("나만의 토크나이저 결과:", my_tokenizer(df.review[1]))

 

CountVectorizer를 사용한다. 특성 수(max_features)는 1,000개로 지정하고, 토크나이져로 my_tokenizer를 사용한다.

리뷰 데이터를 학습한 후 count vector에 사용된 feature를 확인해 보자.

from sklearn.feature_extraction.text import CountVectorizer

#토크나이저와 특성의 최대개수를 지정
daum_cv = CountVectorizer(max_features=1000, tokenizer=my_tokenizer)
#명사만 추출하고 싶은 경우에는 tokenizer에 'twitter_tag.nouns'를 바로 지정해도 됨

daum_DTM = daum_cv.fit_transform(df.review) #review를 이용하여 count vector를 학습하고, 변환
print(daum_cv.get_feature_names_out()[:100]) # count vector에 사용된 feature 이름을 반환
# 출력
['가' '가는' '가는줄' '가면' '가서' '가슴' '가장' '가족' '가족영화' '가지' '가치' '각색' '간' '간다'
 '간만' '갈' '갈수록' '감' '감독' '감동' '감사' '감사합니다' '감상' '감성' '감정' '감탄' '갑자기' '갔는데'
 '갔다' '갔다가' '강' '강철' '강추' '같고' '같네요' '같다' '같습니다' '같아' '같아요' '같은' '같은데'
 '같음' '개' '개그' '개봉' '개연' '개인' '거' '거기' '거리' '거의' '걱정' '건' '건가' '건지' '걸'
 '겁니다' '것' '게' '겨울왕국' '결론' '결말' '경찰' '경험' '계속' '고' '고맙습니다' '고민' '고생' '곤지암'
 '곳' '공감' '공포' '공포영화' '과' '과거' '관' '관객' '관객수' '관람' '광주' '괜찮은' '교훈' '구성'
 '국내' '국민' '군인' '군함도' '굿' '권선' '귀신' '그' '그것' '그게' '그날' '그냥' '그닥' '그대로'
 '그때' '그래픽']

 


4-5. 카운트 벡터의 활용

카운트 벡터는 문서로부터 특성을 추출하는 하나의 방법이다.

이렇게 추출한 벡터는 문서 분류부터 시작해 다양한 분야에 활용될 수 있다.

 

벡터는 문서의 특성을 표현하고 있어 문서 간의 유사도를 측정하는 데 사용될 수 있다.

유사도 계산에 가장 많이 사용되는 척도는 코사인 유사도(cosine similarity)다.

 

벡터의 각 원소는 항상 양수이므로, 코사인 유사도의 값은 0~1 사이의 값이 된다.

두 벡터가 가장 가까우면 유사도는 1, 가장 먼 경우에는 유사도가 0이 된다.

 

문서를 비교하기 위해, 첫 번째 리뷰를 사용해 보자.

첫 번째 리뷰의 절반만 가져와, 이를 카운트 벡터로 변환하고 가장 유사한 문서를 찾는다.

아마도 가장 유사한 문서는 첫 번째 리뷰일 것이다.

유사도를 기준으로 정렬하니 약 0.836.. 정도 되는 문서가 존재한다.

from sklearn.metrics.pairwise import cosine_similarity

start = len(reviews[0]) // 2 #첫째 리뷰의 문자수를 확인하고 뒤 절반을 가져오기 위해 중심점을 찾음
source = reviews[0][-start:] #중심점으로부터 뒤 절반을 가져와서 비교할 문서를 생성

source_cv = cv.transform([source]) #코사인 유사도는 카운트 벡터에 대해 계산하므로 벡터로 변환
#transform은 반드시 리스트나 행렬 형태의 입력을 요구하므로 리스트로 만들어서 입력

print("#대상 특성 행렬의 크기:", source_cv.shape) #행렬의 크기를 확인, 문서가 하나이므로 (1, 1000)

sim_result = cosine_similarity(source_cv, reviews_cv) #변환된 count vector와 기존 값들과의 similarity 계산

print("#유사도 계산 행렬의 크기:", sim_result.shape)
print("#유사도 계산결과를 역순으로 정렬:", sorted(sim_result[0], reverse=True)[:10])
# 출력
#대상 특성 행렬의 크기: (1, 1000)
#유사도 계산 행렬의 크기: (1, 2000)
#유사도 계산결과를 역순으로 정렬: [0.8367205630128807, 0.43817531290756406, 0.4080451370075411, 0.40727044884302327, 0.4060219836225451, 0.3999621981759778, 0.39965783997760135, 0.39566661804603703, 0.3945302295079114, 0.3911637170821695]

 

가장 유사한 리뷰의 인덱스를 확인해 보니, 예상한 대로 첫 번째 리뷰이다.

import numpy as np
print('#가장 유사한 리뷰의 인덱스:', np.argmax(sim_result[0]))
# 출력
#가장 유사한 리뷰의 인덱스: 0

 


4-6. TF-IDF로 성능을 높여보자.

카운트 벡터에서는 빈도가 높을수록 중요한 단어로 취급되는 경향이 있다.

그런데, 어떤 단어가 모든 문서에서 나타난다면 그 단어는 중요한 단어일까?

특정 단어가 많은 문서에서 나타난다면, 해당 단어로 문서를 구분할 수 없을 것이다.

다시 말해서, 단어가 더 많은 문서에서 나타날수록 그 단어는 점점 중요하지 않게 된다. 그러한 의미를 반영한 것이 TF-IDF이다.

 

TF-IDF (Term Frequency - Inverse Document Frequency)

용어를 그대로 해석하자면 단어빈도-역문서빈도 이다.

한 문서에서 나타난 단어의 빈도에 그 단어가 나타난 문서 수의 역수를 곱한다는 의미이다.

 

사이킷런에서 tf-idf를 계산하는 클래스를 제공한다.

카운트 벡터와 tf-idf를 적용한 벡터를 비교해 보자.

from sklearn.feature_extraction.text import TfidfTransformer
transformer = TfidfTransformer()
transformer

reviews_tfidf = transformer.fit_transform(reviews_cv)
print('#shape of tfidf matrix:', reviews_tfidf.shape) #TF-IDF 행렬의 모양과 카운트 행렬의 모양이 일치하는 것을 확인

#첫 리뷰의 카운트 벡터 중 앞 20개 값 출력
print('#20 count score of the first review:', reviews_cv[0].toarray()[0][:20]) 
#첫 리뷰의 TF-IDF 벡터 중 앞 20개 값 출력
print('#20 tfidf score of the first review:', reviews_tfidf[0].toarray()[0][:20])
# 출력
#shape of tfidf matrix: (2000, 1000)
#20 count score of the first review: [6 3 6 3 3 0 2 0 1 0 1 3 2 2 1 0 1 2 3 5]
#20 tfidf score of the first review: [0.13556199 0.06700076 0.14998642 0.0772298  0.08608998 0.
 0.0609124  0.         0.03126552 0.         0.03242315 0.09567082
 0.06575035 0.06518293 0.03225625 0.         0.0345017  0.06863314
 0.10042383 0.16727495]

 

아까와 동일하게 첫 번째 리뷰의 절반을 가져와 벡터를 만들고, 코사인 유사도를 기준으로 가장 유사한 문서를 추출한다.

이번에도 첫 번째 문서인 것을 확인할 수 있다.

from sklearn.feature_extraction.text import TfidfVectorizer

tf = TfidfVectorizer(vocabulary=word_features)
reviews_tf = tf.fit_transform(reviews)

source_tf = tf.transform([source]) #코사인 유사도는 카운트 벡터에 대해 계산하므로 벡터로 변환
#transform은 반드시 리스트나 행렬 형태의 입력을 요구하므로 리스트로 만들어서 입력

sim_result_tf = cosine_similarity(source_tf, reviews_tf) #변환된 count vector와 기존 값들과의 similarity 계산

print('#가장 유사한 리뷰의 인덱스:', np.argmax(sim_result_tf[0]))
# 출력
#가장 유사한 리뷰의 인덱스: 0

 

첫 번째 문서의 일부분을 대상으로 비교했으니 당연히 첫 번째 문서가 가장 유사한 결과를 얻었다.

카운트 벡터, tf-idf 2가지 방법에서 유사한 문서의 순서가 모두 동일할까? 결과를 비교해 보자.

1, 2번째로 유사한 문서는 동일하지만, 그 이후부터는 순서가 달라지는 것을 볼 수 있다.

print('#카운트 벡터에 대해 가장 유사한 리뷰부터 정렬한 인덱스:', (-sim_result[0]).argsort()[:10])
print('#TF-IDF 벡터에 대해 가장 유사한 리뷰부터 정렬한 인덱스:', (-sim_result_tf[0]).argsort()[:10])
#카운트 벡터에 대해 가장 유사한 리뷰부터 정렬한 인덱스: [   0 1110 1570  687  628  112 1712 1393  524 1740]
#TF-IDF 벡터에 대해 가장 유사한 리뷰부터 정렬한 인덱스: [   0 1110 1393 1570  645  323 1143  628 1676 1391]

 

이렇게 벡터를 생성하는 방법에 따라 다른 결과를 얻을 수 있으며, 이후에 문서 분류와 같은 머신러닝 작업 시 성능의 차이를 만들어낸다.

 

728x90

'AI & 빅데이터' 카테고리의 다른 글

[빅데이터 시스템 구축] HDFS란?  (0) 2022.06.17
[빅데이터 시스템 구축] Hadoop, Hadoop Ecosystem  (0) 2022.06.16
Anaconda 명령어  (0) 2022.04.06
Visual Studio에 Docker 연동하기  (0) 2022.03.27
Docker 명령어 정리  (0) 2022.03.27
'AI & 빅데이터' 카테고리의 다른 글
  • [빅데이터 시스템 구축] HDFS란?
  • [빅데이터 시스템 구축] Hadoop, Hadoop Ecosystem
  • Anaconda 명령어
  • Visual Studio에 Docker 연동하기
kiminae
kiminae
공부한 내용을 정리합니다.
  • kiminae
    데이터 다루는 사람
    kiminae
  • 전체
    오늘
    어제
    • 분류 전체보기 (67)
      • AI & 빅데이터 (6)
        • kafka (10)
        • [Book] 빅데이터를 지탱하는 기술 (12)
      • 알고리즘 (19)
      • 알고리즘 문제풀이 (13)
        • programmers (0)
        • 백준 (1)
        • LeetCode (12)
      • Android (3)
      • Book&Lesson (13)
        • [Lesson] 프로그래머스 커뮤러닝 (Pyth.. (1)
      • 참고한 글들 (1)
      • 컨퍼런스 정리 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    BI도구
    DP문제
    leetcode
    hadoop
    MPP데이터베이스
    정렬
    Kafka
    알고리즘풀이
    릿코드
    데이터엔지니어
    머신러닝
    리트코드
    파이프라인구축
    개인화추천
    카프카
    빅데이터
    추천알고리즘
    sort
    알고리즘문제
    트리
    Algorithm
    버블정렬
    카프카클라이언트
    데이터시각화
    빅데이터를지탱하는기술
    시간복잡도
    mvvm
    ViewModel
    정렬알고리즘
    알고리즘
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
kiminae
[텍스트 마이닝] 카운트 기반의 문서 표현이란?
상단으로

티스토리툴바