NLP | Bag of Words, scikit-learn의 CounterVectorizer 한국어

BoW란 무엇인가

Bag of Words (BoW)

단어들의 순서는 전혀 고려하지 않고, 단어들의 출현 빈도(frequency)에만 집중하는 텍스트 데이터의 수치화 표현 방법

BoW 특징

  • 자주 발생하는 단어가 문서의 특징을 나타낸다는 것을 가정
  • 문서 벡터의 차원은 데이터 내 발생하는 모든 단어의 개수와 동일
  • 합성어를 독립적인 단어로 개별처리 (ex. log off)

BoW를 만드는 방법

  1. 각 단어에 고유한 정수 인덱스를 부여한다.
  2. 각 인덱스의 위치에 단어의 등장 횟수를 기록한 벡터를 만든다.

사이킷런 CounterVectorizer

각 단어에 정수 인덱스를 부여하고, for문을 돌면서 카운트되도록 직접 구현할 수도 있지만, scikit-learn에 이미 구현되어 있다.

from sklearn.feature_extraction.text import CountVectorizer

text = ["가을 아침 내겐 정말 커다란 기쁨이야 가을 아침 내겐 정말 커다란 행복이야 응석만 부렸던 내겐",
        "이른 아침 작은 새들 노랫소리 들려오면 언제나 그랬듯 아쉽게 잠을 깬다"]

cv = CounterVectorizer() # vectorizer 개체 생성
X = cv.fit_transform(text) # 리스트 안의 문자열이 각각 문서로 취급된다

print(X.shape) # (문서 개수, 전체 문서에서 등장하는 단어 개수)
### (2, 19)

print(X) # (문서 인덱스, 단어 인덱스) count
'''
(0, 0)	2
(0, 10)	2
(0, 4)	3
(0, 16)	2
(0, 17)	2
(0, 2)	1
(0, 18)	1
(0, 12)	1
(0, 7)	1
(1, 10)	1
(1, 13)	1
(1, 14)	1
(1, 8)	1
(1, 5)	1
(1, 6)	1
(1, 11)	1
(1, 1)	1
(1, 9)	1
(1, 15)	1
(1, 3)	1
'''
print(X.toarray())
'''
[[2 0 1 0 3 0 0 1 0 0 2 0 1 0 0 0 2 2 1]
 [0 1 0 1 0 1 1 0 1 1 1 1 0 1 1 1 0 0 0]]
'''
print(cv.vocabulary_) # 단어-인덱스 정보
'''
{'가을': 0, '아침': 10, '내겐': 4, '정말': 16, '커다란': 17, '기쁨이야': 2, '행복이야': 18, '응석만': 12, '부렸던': 7, '이른': 13, '작은': 14, '새들': 8, '노랫소리': 5, '들려오면': 6, '언제나': 11, '그랬듯': 1, '아쉽게': 9, '잠을': 15, '깬다': 3}
'''
  • X.toarray()의 [0][0]은 첫번째 문장(문서)에서 단어 인덱스가 0인 단어의 등장 빈도수
  • 인덱스가 0인 단어는 ‘가을’
  • 첫번째 문서에 ‘가을’ 단어가 두 개있으므로 제대로 count됨을 알 수 있다.

CountVectorizer는 토큰화 + 벡터화 동시에 해준다.

  • tokenizing(토큰화): 문장을 단어 단위로 끊어줌 (띄어쓰기 기준 split)
  • vectorizing(벡터화): 단어들을 수치형 데이터(vector)로 바꿔줌

보통 띄어쓰기 기준으로 단어를 끊어 토큰화하고 벡터화하기 때문에, 띄어쓰기로 단어가 나뉘지 않는 한국어에는 적용하기 어렵다.

ex. 사랑을, 사랑이구나, 사랑 -> 모두 다른 단어로 처리됨

한국어 BoW를 만들고 싶을 때

아래 두 방법이 사실 똑같긴 한데,

1) CountVecetorizer 객체에 tokenizer 옵션을 konlpy로 설정하기

from konlpy.tag import Okt
from sklearn.feature_extraction.text import CountVecetorizer

okt = Okt()
cv = CountVecetorizer(tokenizer=lambda x: okt.nouns(x))

2) konlpy등으로 tokenizing을 먼저 하고, CountVectorizer의 옵션을 조절하여 vectorizing만 수행

nouns = []
for doc in documents:
  nouns.append(okt.nouns(doc))

cv = CountVectorizer(tokenizer=lambda x: x, lowercase=False) # lowercase 인자를 꼭 False로 해야 에러가 안난다.
X = cv.fit_transform(nouns)

2번 방법으로 하면 토큰화된 결과를 한 번 확인할 수 있음

N-grams

연속된 N개의 단어를 기준으로 텍스트 분석을 수행

TF-IDF

상대적으로 자주 발생하는 단어가 더 중요하다는 점을 반영

  • 단순한 빈도수가 아니라 상대적 빈도수가 중요하다
  • (특정 단어 빈도수) / (전체 단어 빈도수) * log (데이터 내 총 문서의 개수/데이터 내 특정 단어가 들어간 문서의 개수)
  • 모든 문서에서 전반적으로 나타나는 단어의 점수를 줄여줌 (ex. 그러나, 그리고 등)
from sklearn.feature_extraction.text import TfidfVectorizer

# TfidfVectorizer을 불러옵니다. (stop_words 는 영어로 설정)
vectorizer = TfidfVectorizer(stop_words = 'english')

X = vectorizer.fit_transform(df_clean['Review Text'].str.lower())

Reference