관리 메뉴

귀퉁이 서재

NLP - 6. 카운트 기반 벡터화(CountVectorizer)와 TF-IDF 벡터화 본문

자연어 처리 (NLP)

NLP - 6. 카운트 기반 벡터화(CountVectorizer)와 TF-IDF 벡터화

데이터 파수꾼 Baek Kyun Shin 2020. 2. 15. 12:04

이전 장에서 BOW에 대해 알아봤고, BOW의 피처 벡터화는 카운트 기반 벡터화(CountVectorizer)와 TF-IDF(Term Frequency - Inverse Document Frequency) 기반 벡터화가 있다고 했습니다. 이번장에서는 CountVectorizer와 TF-IDF에 대해 알아보겠습니다. 이번 장 역시 파이썬 머신러닝 완벽 가이드 (권철민 저), 딥 러닝을 이용한 자연어 처리 입문(유원주 저)을 요약정리했습니다.

카운트 기반 벡터화

이전 장에서 BOW(Bag of Words) 모델에서의 피처 벡터화 수행 방법에 대해 설명했습니다. 그와 마찬가지로, 단어 피처에 값을 부여할 때, 각 문서에서 해당 단어가 나타나는 횟수, 즉 Count를 부여하는 경우를 카운트 벡터화라고 합니다. (의미를 잘 모르겠다면, 이전 장은 NLP - 5. Bag of Words (BOW)를 참고해주시기 바랍니다.) 카운트 벡터화에서는 값이 높을수록 중요한 단어로 인식됩니다.

CountVectorizer 클래스로 BOW 만들기

카운트 기반 벡터화는 사이킷런의 CountVectorizer 클래스를 활용하여 적용할 수 있습니다. 아래는 CountVectorizer를 활용하여 코퍼스 한 문장을 BOW로 만드는 예제 코드입니다.

from sklearn.feature_extraction.text import CountVectorizer
corpus = ['you know I want your love. because I love you.']
vector = CountVectorizer()
print(vector.fit_transform(corpus).toarray()) # 코퍼스로부터 각 단어의 빈도 수를 기록한다.
print(vector.vocabulary_) # 각 단어의 인덱스가 어떻게 부여되었는지를 보여준다.
[[1 1 2 1 2 1]]
{'you': 4, 'know': 1, 'want': 3, 'your': 5, 'love': 2, 'because': 0}

예제 문장에서 you와 love는 두 번씩 있으므로 인덱스 2와 인덱스 4는 2의 값을 가집니다. you와 love를 중요한 단어로 인식한다는 뜻입니다. CountVectorizer는 기본적으로 2자리 이상의 문자에 대해서만 토큰으로 인식하기 때문에 I는 없어졌습니다.

불용어(Stop words)를 제거한 BOW 만들기

아래와 같은 코드를 통해 사용자가 직접 정의한 불용어를 제거할 수 있습니다.

from sklearn.feature_extraction.text import CountVectorizer

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

the, a, an, is, not을 불용어로 정의했기 때문에, 출력된 토큰에 the, a, an, is, not이 빠져있습니다.

CountVectorizer에서 제공하는 불용어를 사용할 수도 있습니다.

from sklearn.feature_extraction.text import CountVectorizer

text=["Family is not an important thing. It's everything."]
vect = CountVectorizer(stop_words="english")
print(vect.fit_transform(text).toarray())
print(vect.vocabulary_)
[[1 1 1]]
{'family': 0, 'important': 1, 'thing': 2}

TF-IDF(Term Frequency - Inverse Document Frequency)

위에서 설명한 카운트 기반 벡터화는 카운트 값이 높을수록 중요한 단어로 인식한다고 했습니다. 하지만 단순히 단어의 빈도만 고려한다면 모든 문서에서 자주 쓰일 수밖에 없는 단어들이 (불용어 등) 중요하다고 인식될 수 있습니다. 가령 A문서에서도 'The'가 가장 많이 등장하고, B문서에서도 'The'가 가장 많이 등장한다고 해서 두 문서가 유사한 문서라고 판단할 수는 없습니다. 이런 문제를 보완하기 위해 TF-IDF(Term Frequency - Inverse Document Frequency) 벡터화를 사용합니다.

TF-IDF는 개별 문서에서 자주 등장하는 단어에 높은 가중치를 주되, 모든 문서에서 전반적으로 자주 등장하는 단어에 대해서는 패널티를페널티를 주는 방식으로 값을 부여합니다. 모든 문서에서 자주 등장하는 단어에는 페널티를 주고, 해당 문서에서만 자주 등장하는 단어에 높은 가중치를 주는 방식입니다. 그렇게 함으로써 해당 단어가 실질적으로 중요한 단어인지 검사하는 것입니다. 문서의 양이 많을 경우에는 일반적으로 카운트 기반의 벡터화보다 TF-IDF 방식의 벡터화를 사용합니다.

TF-IDF 공식

출처: woongheelee.com

첫번째 termd니 tf_(i,j)만 있다면 이는 카운트 기반 벡터화의 식이라고 볼 수 있습니다. 어떤 문서에 특정 단어가 몇 번 나타났는지 count를 뜻하기 때문입니다. 하지만 여기에 log(N/df_i)를 곱해주면 TF-IDF 공식이 됩니다. N은 고정된 값이기 때문에, df_i가 증가할수록 log(N/df_i)는 감소합니다. df_i는 특정 단어 i를 포함하는 문서의 개수입니다. 특정 단어 i를 포함하는 문서가 많다는 것은 i가 보편적으로 사용되는 단어라는 뜻이고, 이는 i가 실질적으로 중요한 단어가 아니라는 뜻입니다. 따라서 log(N/df_i) 값이 작아지며 페널티가 적용되는 것입니다.

TF-IDF 클래스로 BOW 만들기

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}

비교를 위해 CountVectorizer로 동일한 코퍼스를 벡터화해보겠습니다.

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}

카운트 기반 벡터화인 CountVectorizer는 단순히 빈도를 기반으로 표현을 해주지만, TF-IDF 벡터화는 다른 문장에서의 단어 빈도도 고려하여 해당 단어의 중요도를 표현해주고 있습니다. 따라서 CountVectorizer보다는 TF-IDF를 일반적으로 더 많이 씁니다. 지금까지 CountVectorizer, TfidfVectorizer를 활용하여 피처 벡터화를 하는 방법에 대해 알아봤습니다.

References

Reference1: 파이썬 머신러닝 완벽가이드 (권철민 저)

Reference2: 딥 러닝을 활용한 자연어 처리 입문 (Bag of Words)

Reference3: 딥 러닝을 활용한 자연어 처리 입문 (TF-IDF)

 

2 Comments
  • 프로필사진 길정준 2021.02.03 12:34 1년 전 내용이라서 답변을 해주실지 작은 기대를 하면서 질문을 드립니다. 내용에서 CountVectorizer는 1음절 글자는 포함시키지 않는다고 했는데 분석 목적에 따라서 1음절 단어가 필요할 경우 하이퍼파라미터를 조정해서 계산하려면 어떻게 하면 되나요?
  • 프로필사진 데이터 파수꾼 Baek Kyun Shin 2021.02.03 15:04 신고 안녕하세요! 질문해주셔서 감사합니다 ^^

    token_pattern 파라미터를 조정하면 1음절 단어도 추출할 수 있습니다.
    token_pattern에 정규표현식을 전달하면 되는데 기본값은 r”(?u)\b\w\w+\b”입니다.
    이걸 token_pattern = r"(?u)\b\w+\b"로 수정하면 1음절 단어도 추출할 수 있습니다. :)

    참고링크: ps://stackoverflow.com/questions/43601358/empty-vocabulary-for-single-letter-by-countvectorizer
댓글쓰기 폼