본문 바로가기

NPL with ML

추천시스템 구축을 위한 텍스트 분석 이해 #2 - BOW

반응형

Bag of Words - BOW

Bag of Words 모델은 문서가 가지는 모든 단어(Words)를 문맥이나 순서를 무시하고 일괄적으로 단어에 대해 빈도 값을 부여해 피처 값을 추출하는 모델입니다. 

 

Bag of Words란 단어들의 순서는 전혀 고려하지 않고, 단어들의 출현 빈도(frequency)에만 집중하는 텍스트 데이터의 수치화 표현 방법입니다. Bag of Words를 직역하면 단어들의 가방이라는 의미입니다. 단어들이 들어있는 가방을 상상해봅시다. 갖고있는 어떤 텍스트 문서에 있는 단어들을 가방에다가 전부 넣습니다. 그러고나서 이 가방을 흔들어 단어들을 섞습니다. 만약, 해당 문서 내에서 특정 단어가 N번 등장했다면, 이 가방에는 그 특정 단어가 N개 있게됩니다. 또한 가방을 흔들어서 단어를 섞었기 때문에 더 이상 단어의 순서는 중요하지 않습니다.

 

BoW를 만드는 과정을 이렇게 두 가지 과정으로 생각해보겠습니다.
(1) 우선, 각 단어에 고유한 정수 인덱스를 부여합니다.
(2) 각 인덱스의 위치에 단어 토큰의 등장 횟수를 기록한 벡터를 만듭니다.

 

BoW는 각 단어가 등장한 횟수를 수치화하는 텍스트 표현 방법이기 때문에, 주로 어떤 단어가 얼마나 등장했는지를 기준으로 문서가 어떤 성격의 문서인지를 판단하는 작업에 쓰입니다. 즉, 분류 문제나 여러 문서 간의 유사도를 구하는 문제에 주로 쓰입니다. 가령, '달리기', '체력', '근력'과 같은 단어가 자주 등장하면 해당 문서를 체육 관련 문서로 분류할 수 있을 것이며, '미분', '방정식', '부등식'과 같은 단어가 자주 등장한다면 수학 관련 문서로 분류할 수 있습니다.

 

방법은 아래 Review와 같이 3가지 문장이 있다고 할 떄

3가지 문장에 있는 모든 단어에서 중복을 제거하고 각 단어(Feature 또는 Term)를 칼럼 형태로 나열합니다. 그러고 나서 각 던어에 고유의 인덱스를 부여합니다. 이 후 개별 문장에서 해당 단어가 나타나는 횟수(Occurrence)를 각 단어(단어 인덱스)에 기재합니다.

 

  • Review 1: This movie is very scary and long
  • Review 2: This movie is not scary and is slow
  • Review 3: This movie is spooky and good

  • Vector of Review 1: [1 1 1 1 1 1 1 0 0 0 0]
  • Vector of Review 2: [1 1 2 0 0 1 1 0 1 0 0]
  • Vector of Review 3: [1 1 1 0 0 0 1 0 0 1 1]

 

BOW의 장점은 쉽고 빠른 구축에 있습니다. 단순히 단어의 발생 횟수에 기반하고 있지만, 예상보다 문서의 특징을 잘 나타낼 수 있는 모델이어서 전통적으로 여러 분야에서 활용도가 높습니다.  반면 단점으로 

 

1. 문맥 의미(Semantic Context) 반영 부족: BOW는 단어의 순서를 고려하지 않기 때문에 문장 내에서 단어의 문맥적인 의미가 무시됩니다. 물론 이를 보완하기 위해 n_gram기법을 활용할 수 있지만, 제한적인 부분에 그치므로 언어의 많은 부분을 차지하는 문맥적인 해석을 처리하지 못하는 단점이 있습니다.

 

2. 희소 행렬 문제(회소성, 회소 행렬): BOW로 피처 벡터화를 수행하면 행렬 형태의 데이터 세트가 만들어지기 쉽습니다. 많은 문서에서 단어를 추출하면 매우 많은 단어가 칼럼으로 만들어집니다. 문서마다 서로 다른 단어로 구성되기에 단어가 문서마다 나타나지 않는 경우가 훨씬 많습니다. 즉, 매우 많은 문서에서 단어의 총 개수는 수십만 개가 될 수 있는데, 하나의 문서에 있는 단어는 이 중 극히 일부분이므로 대부분의 데이터는 0값으로 채워지는 희소 행렬(Sparse Matrix)이라고 합니다. 반대로 대부분의 값이 0이 아닌 의미 있는 값으로 채워져 있는 행렬을 밀집 행렬(Dense Matrix)이라고 합니다. 희소 행렬은 일반적으로 ML알고리즘의 수행 시간과 예측 성능을 떨어뜨리기 때문에 희소 행렬을 위한 특별한 기법이 마련돼 있습니다. 

 

BOW피처 벡터화

머신러닝 알고리즘은 일반적으로 숫자형 피처를 데이터로 입력받아 동작하기 때문에 텍스트와 같은 데이터는 머신러닝 알고리즘에 바로 입력할 수가 없습니다. 따라서 텍스트는 특정 의미를 가지는 숫자형 값인 벡터 값으로 변환해야 하는데, 이러한 변환을 피처 벡터화라고 합니다.

 

예를 들어 피처벡터화는 각 문서의 텍스트를 단어로 추출해 피처로 할당하고, 각 단어의 발생 빈도와 같은 값을 이 피처에 값으로 부여해 각 문서를 이 단어 피처의 발생 빈도 값으로 구성된 벡터로 만드는 기법입니다.

 

BOW모델에서 피처 벡터화를 수행한다는 것은 모든 문서에서 모든 단어를 칼럼 형태로 나열하고 각 문서에서 해당 단어의 횟수나 정규화된 빈도를 값으로 부여하는 데이터 세트 모델로 변경하는 것입니다.

 

BOW의 피처 벡터화는 크게 카운트 기반의 벡터화와 TF-IDF(Term Frequency - Inverse Document Frequency)기반의 벡터화가 있습니다. 일반적으로 TF-IDF방법을 이용하며, TF-IDF는 개별 문서에서 자주 나타나는 단어에 높은 가중치를 주되, 모든 문서에서 전반적으로 자주 나타나는 단어에 대해서는 페널티를 주는 방식으로 값을 부여합니다.

 

사이킷런에서는 피처 벡터화를 지원하기 위해 CountVectorizer와 TfidfVectorizer 클래스를 지원하며 피처 벡터화를 위해 하기와 같은 순서로 진행을 합니다.

 

1. 사전 데이터 가공: 모든 문자를 소문자로 변환하는 등의 사전 작업 수행

2. 토큰화

3. 텍스트 정규화

4. 피처 벡터화

 

CountVectorizer와 TfidfVectorizer기본 예제

from sklearn.feature_extraction.text import CountVectorizer
corpus = [
    '신종 코로나바이러스 감염증(코로나19) 일일 확진자가 8일 0시 기준 143명 발생했다. 전날인 7일 0시 기준 확진자가 나흘만에 두자릿수를 기록했으나, 해외유입을 제외한 지역발생 확진자가 다시 100명을 넘게 나타난 영향이다.',
]
vector = CountVectorizer()
print(vector.fit_transform(corpus).toarray()) # 코퍼스로부터 각 단어의 빈도 수를 기록한다.
print(vector.vocabulary_) # 각 단어의 인덱스가 어떻게 부여되었는지를 보여준다.


from sklearn.feature_extraction.text import TfidfVectorizer
corpus = [
    '신종 코로나바이러스 감염증(코로나19) 일일 확진자가 8일 0시 기준 143명 발생했다. 전날인 7일 0시 기준 확진자가 나흘만에 두자릿수를 기록했으나, 해외유입을 제외한 지역발생 확진자가 다시 100명을 넘게 나타난 영향이다.',
]
tfidfv = TfidfVectorizer().fit(corpus)
print(tfidfv.transform(corpus).toarray())
print(tfidfv.vocabulary_)

 

TfidfVectorizer / CountVectorizer

두 클래스의 입력 파라미터는 유사하며 하기와 같다.

1.max_df: 전체 문서에 걸쳐서 너무 높은 빈도수를 가지는 단어 피처를 제외하기 위한 파라미터로 너무 높은 빈도수를 가지는 단어는 스톱 워드와 비슷한 문법적인 특성으로 반복적인 단어일 가능성이 높기에 이를 제거하기 위해 사용됩니다.

max_df=100과 같이 정수 값을 가지면 전체 문서에 거쳐 100개 이하로 나타나는 단어만 피처로 추출합니다. max_df=0.95와 같은 부동소수점 값을 가지면 전체 문서에 걸쳐 빈도수 0~95%까지의 단어만 피처로 추출하고 나머지 상위 5%는 피처로 추출하지 않습니다.

 

2.min_df: 전체 문서에 걸쳐서 너무 낮은 빈도수를 가지는 단어 피처를 제외하기 위한 파라미터로 수백~수천 개의 전체 문서에서 특정 단어가 min_df에 설정된 값보다 적은 빈도수를 가진다면 이 단어는 크게 중요하지 않거나 가비지서 단어일 확률이 높습니다.

 

3. max_features: 추출하는 피처의 개수를 제한하며 정수로 값을 지정합니다. 가장 높은 빈도를 가지는 단어 순으로 정렬해 2000개까지만 피처로 추출합니다.

 

4.stop_words: 'english'로 지정하면 영어의 스톱 워드로 지정된 단어는 추출에서 제외합니다.

 

5.n_gram_range: Bag of Words모델의 단어 순서를 어느 정도 보강하기 위한 n_gram범위를 설정합니다. 튜플 형태로 (범위 최소값, 범위 최댓값)을 지정합니다.

 

* n-gram? 문장을 단어별로 하나씩 토큰화 할 경우 문맥적인 의미는 무시될 수밖에 없습니다. 이러한 문제를 조금이라도 해결해 보고자 도입 된 것이 n-gram입니다. n-gram은 연속된 n개의 단어를 하나의 토큰화 단위로 분리해 내는 것입니다. n개 단어 크기 윈도우를 만들어 문장의 처음부터 오른쪽으로 움직이면서 토큰화를 수행합니다. "Agent Smith knocks door"를 2-gram으로 만들면 (Agent, Smith), (Smith, Knocks), (Knocks, door)와 같이 연속적으로 2개의 단어들을 순차적으로 이동하면서 단어들을 토큰화 합니다.

 

6.analyzer: 피처 추출을 수행한 단위를 지정합니다. 기본값은 'word'입니다.

 

7.token_pattern: 토큰화를 수행하는 정규 표현식 패턴을 지정합니다. 디폴트 값은 '\b\w\w+\b로, 공백 또는 개행 문자 등으로 구분된 단어 분리자(\b)사이의 2문자 이상의 단어를 토큰으로 분리합니다. analyzer='word'로 설정했을 때만 변경 가능하나 디폴트 값을 변경할 경우는 거의 발생하지 않습니다.

 

8.tokenizer: 토큰화를 별도의 커스텀 함수로 이용시 적용합니다. 일반적으로 CountTokenizer클래스에서 어근 변환 시 이를 수행하는 별도의 함수를 tokenizer 파라미터에 적용하면 됩니다.

 

BOW벡터화를 위한 희소 행렬

모든 문서에 있는 단어를 추출해 이를 피처로 벡터화하는 방법은 필연적으로 많은 피처 칼럼을 만들 수 밖에 없습니다. 모든 문서에 있는 단어를 중복을 제거하고 피처로 만들면 일반적으로 수만 개에서 수십만 개의 단어가 만들어집니다. 대규모의 행렬이 생성되더라도 레코드의 각 문서가 가지는 단어의 수는 제한적이기 때문에 이 행렬의 값은 대부분 0이 차지할 수밖에 없습니다.

 

이처럼 대규모 행렬의 대부분의 값을 0이 차지하는 행렬을 가리켜 희소 행렬이라고 합니다. BOW형태를 가진 언어 모델의 피처 벡터화는 대부분 희소 행렬입니다. 이 희소 행렬은 너무 많은 불필요한 0값이 메모리 공간에 할당되어 메모리 공간이 많이 필요하며, 행렬의 크기가 커서 연산 시에도 데이터 액세스를 위한 시간이 많이 소모됩니다.

 

 

따라서 이러한 회소 행렬을 물리적으로 적은 메모리 공간을 차지할 수 있도록 변환해야 하는데, 대표적인 방법으로 COO형식과 CSR형식이 있습니다. 일반적으로 큰 희소 행렬을 저장하고 계산을 수행하는 능력이 CSR형식이 더 뛰어나기 때문에 CSR을 많이 사용합니다. 

 

 

반응형