Juhans
article thumbnail

이번에는 기계학습을 통한 자연어처리를 배워보자.

 

기계학습을 사용해 자연어처리를 하는 방법은 크게 두가지가 존재한다.

당연하지만 지도학습(Supervised-learning)과 비지도학습(Unsupervised-learning)이다.

 

자연어처리 대부분은 분류를 통해 처리가 가능하기 때문에 지도학습을 주로 이용할 수도 있지만 군집화와 같은 비지도학습을 통해 비슷한 경향의 뉴스를 구분한다던가 할 수 있다.

 

자연어처리를 어떤 방식으로 처리할 것이냐를 논하기 전에 먼저 해야할 것이 존재한다.

바로 전처리이다.

 

문서 벡터화와 문서 유사성

어떤 문서가 있을 때 그 문서를 그대로 기계학습 모델의 input으로 넣어줄 수 없기 때문에 전처리를 통해 문서를 숫자화하고 형식에 맞는 Input으로 변화시켜줘야 한다. 이 과정이 문서를 벡터화하는 과정이다.

 

또, 문서를 벡터화했다면 벡터는 서로의 유사성을 확인할 수 있기 때문에 문서 사이의 관계를 확인할 수 있다. 

 

문서 벡터화

Bag of Words

Bag of words (BOW)는 문서 내부의 단어 순서를 고려하지 않고 단어들의 출현 빈도만을 고려하여 단어들을 수치화한 것이다. 

BOW를 만드는 과정은 아주 단순하다. 과정은 아래와 같다.

  • 문서 내부의 각 단어에 고유한 Index를 부여한다.
  • 단어에 부여한 Index들만큼 길이의 벡터를 만들고 해당 단어 Index에 해당 단어가 등장한 횟수를 입력한다.

→ 벡터화 된 문서 완성!

 

문서 벡터화 코드

from sklearn.feature_extraction.text import CountVectorizer


# max_features : 전체 문서에서 빈도수 높은 순으로 top max_features 갯수의 단어만 포함하여
# fit : vocabulary를 생성한다
vec = CountVectorizer(max_features = 1000).fit(train_docs_X)

# 학습데이터(train_docs_X), 테스트데이터(test_docs_X)를 BoW 벡터로 변환
train_X = vec.transform(train_docs_X).toarray()
test_X = vec.transform(test_docs_X).toarray()

위 코드와 같이 사이킷 런의 feature_extraction 패키지 안에 있는 CountVectorizer class를 사용하여 fit함수의 입력 문서에서 단어들을 가져와 벡터화 시킴과 동시에 각 단어가 출현한 빈도를 벡터에 넣는다.

 

BOW 과정

다만 BOW 과정은 영어의 경우에는 문서로부터 바로 진행될 수 있지만 한국어의 경우에는 쓸모없는 형태소를 제거하고 의미 있는 형태소만을 추출하여 진행된다.

 

그렇다면 BOW 과정에서 벡터에 들어가는 단어의 출현 횟수는 가중치로서 적합할까?

 

결과부터 말하면 아니다!

Documnet Weigting

BOW 과정에서 출현 횟수로 가중치를 정하게 된다면 쓸모없지만 가장 많이 출현한 단어가 큰 가중치를 갖게 될 수도 있으며 문서의 길이가 긴 문서는 짧은 문서에 비해 전체적으로 대부분의 단어에서 더 많은 가중치를 갖게 될 수도 있다.

 

이를 방지하기 위해 새롭게 가중치를 부여하는 방식이 필요했다.

 

Weighting 기법

  • TF
  • TF x IDF
  • Probability

먼저 TF는 앞에서 BOW를 설명할 때 적용되었던 weighting 기법이다. 단순하게 문서에서 단어의 출현 빈도를 가중치화한 것이다.

TF x IDF는 새로운 척도인 IDF를 추가하여 좀 더 합리적인 weighting을 가능하게 한다.

마지막으로 Probability는 잠시 넘어가겠다.

 

TF-IDF

TF-IDF는 TF에 IDF 척도를 곱해 새롭게 부여하는 가중치 기법이다. IDF는 Inverse Document Frequency의 약자로 문서 빈도라는 새로운 개념을 추가하여 전체 문서에 대해서의 희귀도를 수치화할 수 있다.

 

IDF는 아래의 식과 같다.

IDF 식

 

IDF를 이해하기 위해서는 아래의 질문을 답할 수 있어야 한다.

모든 문서들의 집합에서 항상 나타나는 단어는 중요도가 높은 단어일까?

 

그렇지 않다. 모든 문서들의 집합에서 나타나는 단어는 중요도가 높지 않다. 각각의 문서들은 모두 각각의 글의 카테고리, 방식, 시간, 글쓴이가 다르기 때문에 중요하게 다뤄지는 단어는 다른 문서들과 겹치기 어렵다. 물론 같은 카테고리의 글에서는 같은 단어가 나타날 수 있다. 이는 이후 문서의 유사성에 다루자.

 

어떤 단어가 모든 문서들의 집합에서 항상 나타나지 않고 일부 문서에서만 나타날 수록 그 단어의 IDF는 증가한다. 

그리고 그 단어가 어떤 문서에서 많이 나타난다면 TF는 증가할 것이다. 즉, 일부의 문서에서만 그 단어가 나타나고 그 문서들 중 그 단어가 많이 나타나는 단서가 있다면 그 단서에서의 그 단어는 가중치가 매우 클 것이다.

 

말로 풀어쓰니 조금 이해하기 어려울 수 있지만 정리하자면 아래와 같다.

TF-IDF 가중치 기법

문서 유사성

이제 다양한 기법으로 BOW를 만들었다. 각각의 문서는 이런 BOW 벡터를 가질 것이다. 

이 때 벡터의 유사성으로 문서간의 유사도를 확인할 수 있다.

 

벡터들은 단어들로 이루어진 벡터 공간에 직선으로 표시될 수 있다. 이 직선들의 유사도를 구하면 문서의 유사도를 구하는 것과 마찬가지이다.

BOW 벡터

앞서 말했듯 문서의 유사도를 구하기 위해서는 벡터의 유사도를 구하면 되는데 이때 가장 쉬운 방법이 직선들의 각도차이를 확인하여 구할 수 있는 코사인 유사도 방법이다. 

코사인 유사도 그림
코사인 유사도 식

문서 분류

이번에는 기계학습을 이용하여 문서들을 구분해보자.

문서들을 분류할 때는 이진 분류, 다중 분류로 구분할 수 있다.

 

문서를 분류할 때 사용할 수 있는 알고리즘들은 아래와 같다.

  • K-Nearest Neighbors (KNN)
  • Naive Bayes Classifier
  • Support Vector Machine
  • 딥러닝 (CNN, RNN, BERT 등)

KNN과 SVM은 이미 알고 있는 알고리즘으로 일단 넘어가기로 하고 먼저 Naive Bayes 부터 알아보자.

Naive Bayes Classification

Naive Bayes 식

Naive Bayes 알고리즘의 기본적인 형태는 위의 식과 같다.

x가 분류될 문장, c는 분류될 클래스라고 했을 때, 결과 p(c|x)의 의미는 분류될 문장이 c 클래스일 확률인 것이다. 클래스마다 p(c|x)를 구해 argmax, 즉, 가장 확률이 큰 p를 찾았을 때의 c(클래스)가 x 문장이 분류될 클래스라는 의미이다.

 

p(c|x)를 구하기 위해서는 p(x|c)를 구해야 하는데 이는 이미 우리가 가지고 있는 c 클래스에 해당하는 문서들에서 확률을 추출한 값이다. 즉, c가 '긍정'이라는 클래스라면 '긍정'에 해당하는 문서들 전체에서 x가 출현한 비율을 구하면 된다.

 

다만 p(x|c)에서 x는 문장을 의미하기 때문에 단어로 분할 하면 식은 아래와 같아진다.

Naive Bayes 이해과정

 

우리는 어떤 문장에 대해서 클래스를 분류하는 문제를 하고 있기 때문에 Naive Bayes 기본식에서 우항의 분모는 모든 클래스 확률마다 등장한다. 때문에 이를 제거하여 식들을 간략화하기도 한다.

 

여기서 또 하나의 문제가 발생한다.

아래와 같은 예시에서 어떤 문제가 발생할 수 있을지 생각해보자.

 

사후확률인 p(c|x)를 구하기 위해 우리는 우항에서 해당 클래스의 문서들 중에서 x에 포함되어 있는 단어를 확률로 만들어낸다. 하지만 위 표의 경우에는 0이 존재하는 경우도 발생한다. 즉, 어떤 클래스 문서에 대해서 다른 클래스 문서에는 있는 단어가 그 클래스 문서에는 없을 수도 있는 것이다. 이럴 때는 Naive Bayes 계산 우항에서 0에 의해 모든 항들의 곱이 0이 되는 문제가 생긴다.

 

이를 방지하기 위해 모든 사전확률값에 새로운 숫자를 추가한다.

 

Smoothing

스무딩 이후

스무딩을 통해 Naive Bayes의 값이 의미없이 0으로 가는 것을 막는다.

 

%%time

# sklearn 팩키지에서 GaussianNB 모델을 import
from sklearn.naive_bayes import GaussianNB

# GaussianNB 객체를 생성
gnb = GaussianNB()

# train_X: 학습데이터, train_Y: 정답레이블을 받아서 학습
gnb.fit(train_X, train_Y)

# score(test_data, true_labels) returns the mean accuracy on the given test data and labels.
print("Accuracy on training set: {:.3f}".format(gnb.score(train_X, train_Y)))
print("Accuracy on test set: {:.3f}".format(gnb.score(test_X, test_Y)))