DoITgrow

[자연어 처리] Bag of Word (BoW) - 파이썬(Python) 본문

딥러닝 & 머신러닝/자연어 처리 (Natural Language Processing)

[자연어 처리] Bag of Word (BoW) - 파이썬(Python)

김수성 (Kim SuSung) 2021. 9. 23. 12:23
반응형

뉴스, 논문, 특허 등의 텍스트로 이루어진 데이터를 분석하기 위해서 무엇을 해야 할까?

Tokenization(문장을 단어로 쪼개기), 불용어 제거, 단어 정규화(ex. apples → apple) 등의 전처리 작업과 더불어 컴퓨터가 이해할 수 있는 데이터로 변환해 주어야 한다. 즉, 문자를 숫자로 변환하는 작업을 수행해야 한다.

오늘은 문서(또는 문장)를 숫자로 변환하는 방법 중 가장 기본적인 BoW(Bag of Words) 방식을 활용하여 문서(또는 문장)를 컴퓨터가 이해할 수 있는 자료 형태로 가공하는 방법에 대해 포스팅하고자 한다.
본론에 들어가기 앞서 BoW는 자연어 처리에 많이 활용되는 기법이고, 주로 결과물로 활용하기 보다는 데이터를 전처리하는 중간 중간에 활용하는 데이터 형태로 이해하면 좋다.
(이유는 BoW로 언어를 모델링하기에는 많은 한계가 있고, 이에 대한 내용은 TF-IDF 관련 포스팅에서 다루고자 한다.)

2021.09.23 - [자연어 처리] TF-IDF (Term Frequency-Inverse Document Frequency)

본 포스팅에서는 BoW를 활용하여 문서(또는 문장)를 숫자로 표현하는 알고리즘을 알아 보고자 한다.
개념 설명 이후에 작성한 코드들은 알고리즘을 보다 잘 이해하기 위해 패키지 사용보다 하드코딩을 통해 진행하려고 한다.

BoW (Bag of Word)

직역하면 "단어의 가방"이란 뜻이다. 가방이란 우리가 분석할 문서(또는 문장)이며, 단어는 그 안에 들어있는 단어들로 이해하면 된다. 예를 들어 "I want to know what you know" 라는 문장을 BoW로 표현하기 위해 가방을 만들어 보자.

BoW를 만들기 앞서 간단한 규칙 2가지를 기억하자.

1. 가방에 넣는 물건의 자리는 정해져 있다. (단, 순서는 상관없음)
2. 같은 자리에 같은 물건을 무한대로 넣을 수 있다. (실제로 무한대는 아니고, 컴퓨터 메모리가 허용하는 범위일 것이다.)

그럼 위 문장을 구성하는 단어들의 자리를 정하기 위해 앞부터 읽어가며, 어떤 단어들이 있는지를 확인해 보자.

I, want, to, know, what, you 총 6개의 단어가 있는 것으로 확인했기에 해당 문장의 가방은 6칸짜리 가방으로 구성하면 된다. 그리고 각 단어의 개수를 숫자로 넣어주면 해당 문장의 BoW가 완성된다.
이렇게 얻은 BoW는 컴퓨터가 이해할 수 있는 문서(또는 문장) 벡터로 사용할 수 있다.

I want to know what you
1 1 1 2 1 1

여러 개의 문서(또는 문장)을 고려한 BoW 만들기

2개의 문서(또는 문장)를 고려한 BoW를 만들 때에는 모든 문서를 훑어보고 가방을 설계해야 한다.

  • I want to know what you know
  • I know who you are

만약 위 2개의 문장을 독립적으로 생각하여 BoW를 만든다면, 아래와 같이 2개의 데이터가 얻어지게 된다.

"I want to know what you know"의 BoW

I want to know what you
1 1 1 2 1 1

 

"I know who you are"의 BoW

I know who you are
1 1 1 1 1

 

여기서 컴퓨터는 두번째 행의 숫자 벡터만 이해할 수 있는데, 단어의 순서도 다르고 전체 길이도 다르기 때문에 컴퓨터는 많이 혼란스러워 할 것이다. 또한 2개의 문장 벡터를 어떻게 처리해야 유의미한 결과를 얻을 수 있을지 사람이 봐도 어려울 것 같다.

그래서 데이터 형태를 통일하기 위해 전체 문서(또는 문장)를 보고 단어 가방을 설계/제작해야 한다.
위 2개의 문장에서 어떠한 단어들이 고유하게 있는지를 확인한 후, 아래와 같이 데이터를 표현할 수 있다.
이제 문장 벡터들을 이용하여 유사성 비교 등의 작업을 수행할 수 있는 준비 단계를 마쳤다.

  I want to know what you who are
문장 1 1 1 1 2 1 1 0 0
문장 2 1 0 0 1 0 1 1 1

코드 구현

실제 우리가 가지고 있는 데이터는 아래와 같이 임의로 생성한 문장처럼 깔끔하게 존재하지 않는다. 허나 본 포스팅에서는 BoW 개념과 방법에 대해 중점적으로 다루므로 앞 단의 전처리에 대해서는 생략하고 진행한다.
이후 전처리 방법에 대해서도 포스팅할 예정이다.

### BoW.py

sentences = ["I want to know what you know", "I know who you are"]

# 문장에서 단어 토큰을 추출하여 고유한 vocab으로 만드는 함수 
def sentences_to_vocabs(sentences):
    vocabs = []
    for sentence in sentences:
        tokens = sentence.split()
        for token in tokens:
            if not token in vocabs:
                vocabs.append(token)
    return vocabs

vocabs = sentences_to_vocabs(sentences)
>>> print(vocabs)
>>> ['I', 'want', 'to', 'know', 'what', 'you', 'who', 'are']
>>> print("전체 vocab의 수: {}개".format(len(vocabs)))
>>> 전체 vocab의 수: 8개

결과를 보니 2개의 문장을 통해 8개의 고유 단어들이 있는 것으로 확인하였다.
이로써 각 문장들을 8차원의 벡터로 만들 수 있다.

그러면 얻은 vocab 을 이용하여 각 문장들을 BoW으로 변환하는 코드를 살펴보자.

### BoW.py

# 생성한 vocabs을 이용하여 문장을 벡터로 변환하는 함수
def senteces_to_vector(vocabs, sentence):
    sentence_tokens = sentence.split()

    vector = []
    for vocab in vocabs:
        if vocab in sentence_tokens:
            count = sentence_tokens.count(vocab) 
            vector.append(count)
        else: 
            vector.append(0)

    return vector

vectors = []
for sentence in sentences:
    vector = senteces_to_vector(vocabs, sentence)
    vectors.append(vector)

>>> print(vectors)
>>> [[1, 1, 1, 2, 1, 1, 0, 0], 
     [1, 0, 0, 1, 0, 1, 1, 1]]

위 코드를 실행하여 얻은 vectors 결과를 출력해보면 위의 테이블에서 수기로 확인했던 것과 동일한 결과를 얻을 수 있음을 확인했다. 이로써 BoW를 구현하는 코드를 살펴보았다.

그러나 실제 현업에서 사용할 때는 하드코딩은 잘 하지 않고, 잘 구현된 패키지를 이용하는 편이다.
BoW를 만드는 패키지는 파이썬의 scikit-learn 을 활용하면 된다.

### BoW_by_sklearn.py

# 패키지 불러오기
from sklearn.feature_extraction.text import CountVectorizer

sentences = ["I want to know what you know", "I know who you are"]

cv = CountVectorizer()
cv.fit(sentences) # 가지고 있는 문장들로 가방을 설계
vectors = cv.transform(sentences).toarray() # 단어들을 가방에 정리하여 넣음

>>> print(vectors)
>>> [[0 2 1 1 1 0 1]
     [1 1 0 0 0 1 1]]

>>> print(cv.vocabulary_)
>>> {'want': 3, 'to': 2, 'know': 1, 'what': 4, 'you': 6, 'who': 5, 'are': 0}

간단한 코드 몇 줄로 알아보았던 BoW를 쉽게 구현할 수 있다.
여기서 하드코딩으로 직접 구현한 결과와 비교하면 벡터를 구성하는 숫자들의 순서가 조금 다른 것을 볼 수 있다.
이것은 가방을 어떻게 설계하느냐의 차이이기에 다를 수 있고, 순서는 중요하지 않으므로 결과는 같다고 생각하면 된다.

즉, sklearn 패키지에서 만든 가방의 정보는 print(cv.vocabulary_) 코드를 출력하여 확인해 볼 수 있다.

하드코딩으로 만든 가방에서 단어의 순서는 ['I', 'want', 'to', 'know', 'what', 'you', 'who', 'are'] 인데, sklearn 패키지를 통해 만들어진 단어의 순서는 0~6까지 ['are', 'know', 'to', 'want', 'what', 'who', 'you'] 인 것을 볼 수 있다.

BoW에 관한 포스팅은 여기서 마무리하고, 이후 BoW의 한계점과 이를 보완하기 위한 방법인 TF-IDF에 대해 알아볼 예정이다.

궁금하신 점이나 잘못 전달된 부분이 있으면 언제든지 지적 부탁드립니다~ ^^

반응형
Comments