나이브 베이즈(Naive Bayes)를 활용한 문서 분류 – 파이썬 코드 예제

이전 포스팅에서 나이브 베이즈(Naive Bayes)를 사용해서 텍스트를 어떻게 분류할 수 있는지 정말 간단한 예제를 통해 살펴보았다.

이번에는 파이썬 머신러닝 라이브러리 scikit-learn에서 실제로 어떻게 구현하고 동작하는지 코드를 알아볼 차례.

scikit-learn 사용법

1. CounterVectorizer

scikit-learn에서 Naive Bayes 분류기를 사용하기 전에 일단 자연어(텍스트)로 이루어진 문서들을 1과 0 밖에 모르는 컴퓨터가 이해할 수 있는 형식으로 변환해야 할 거다. feature extraction, 어휘(특성) 추출 과정이라 볼 수 있다.

.fit()

일단 CountVectorizer라는 객체를 만든 후, fit() 메소드를 호출해서 학습 데이터 세트에 등장하는 어휘를 가르쳐놓아야 한다.

예를 들어 아래와 같이 코드를 작성하면

from sklearn.feature_extraction.text import CountVectorizer

vectorizer = CountVectorizer()
vectorizer.fit(["첫번째 문서 테스트", "두번째 문서 테스트"])

“첫번째”, “문서”, “테스트”, “두번째” 이렇게 총 4개의 어휘를 학습한 CountVectorizer를 만들게 된다.

못 믿겠다면 vectorizer.vocabulary_를 출력해보면 된다. 이렇게 뜰 거다.

{'첫번째': 2, '문서': 1, '테스트': 3, '두번째': 0}

고유한 어휘가 딕셔너리처럼 각각의 인덱스를 가지고 있다.

.transform()

어휘 사전을 만들었으니 이제 .transform() 메서드를 호출할 수 있다. .transform()는 문자열 목록을 가져와 미리 학습해놓은 사전을 기반으로 어휘의 빈도를 세주는 거다.

아래와 같이 공백으로 구분되는 문자열로 가져와서 넣어주면

counts = vectorizer.transform (["직접 첫번째 테스트 두번째 테스트"])

counts에는 각 어휘가 등장한 빈도수가 저장되는 거다. counts.toarray()를 출력해보면 [[1 0 1 2]]와 같은 array를 볼 수 있다. 위에서 학습된 인덱스에 따라 개수를 세주었다. 그리고 “직접”이라는 단어는 애초에 사전에 등록된 단어가 아니기 때문에 고려하지 않는다.

이렇게 텍스트를 컴퓨터가 이해할 수 있는 숫자들로 변환시켜놓은 셈이니 이제 나이브 베이즈를 본격적으로 사용해볼 수 있다.

2. MultinomialNB

나이브 베이즈 분류기를 학습시킬 때는 당연히 2개의 파라미터가 필요하다. 위에서 여러 문서들을 .transform() 해놓은 문서-단어 행렬과 그 문서들이 어떤 분류에 속하는지 레이블을 준비해서 넣어주면 된다.

from sklearn.naive_bayes import MultinomialNB

classifier = MultinomialNB()
classifier.fit(counts, labels)

이제 새로운 문서가 등장했을 때 어떤 분류로 속할지 예측을 할 수 있다.

단…! 텍스트를 바로 넣는다고 되는 게 아니다. 위에서 모델을 학습시키기 위해 학습 데이터를 문서-단어 행렬로 tranform 한 것처럼 새로운 텍스트도 tranform해서 넣어줘야 한다.

new_text= "이건 새로운 문서"
new_text_counts = counter.transform([new_text])

print(classifier.predict(new_text_counts))
print(classifier.predict_proba(new_text_counts))

.predict_proba()는 각 레이블로 분류될 확률을 돌려준다. 베이즈 정리로 해당 문서가 어떤 분류에 속할 확률을 각각 다 계산할 수 있기 때문이다.


나이브 베이즈 분류기를 구현하는 방법은 일단 여기까지다.

주요 내용을 다시 간단히 요약해보자.

요약

  • 베이즈 정리에서 사용되는 확률을 계산하려면 레이블이 지정된 데이터 세트가 필요하다.
  • 데이터 세트에 포함된 문서에서 등장하는 어휘들이 곧 특성(feature)이다. 그리고 베이즈 정리를 적용하기 위해 이 특성(어휘)들은 모두 독립적인 것으로 가정한다.
  • 베이즈 정리를 사용하면 어떤 문서가 있을 때 그것이 특정 레이블에 속할 확률, P(레이블 | 문서)를 계산할 수 있으며, 이 중 확률이 가장 높은 레이블로 예측을 하게 된다.

뭐 이렇게 간단해보이긴 하지만 사실 텍스트(문서)를 나이브 베이즈로 제대로 분류하려면 그 전에 꽤 복잡한 과정을 거쳐 데이터를 잘 가공하는 게 정말 중요하다. garbage in, garbage out이라는 거…

뭐 일단 간단하고 일반적인 작업으로는 이런 게 있겠다.

  • 문장 부호, 불용어 등을 제거한다.
  • 모든 단어를 소문자로 통일한다. (한국어는 해당사항 없지만)
  • ngram모델을 사용한다. 예를 들어 “이 음식은 너무 맛있다”는 문서가 있다면 여기서 feature는 “이”, “음식은”, “너무”, “맛있다”가 될 거다. 그러나 만약 bigram 모델을 사용하면 “이 음식은”, “음식은 너무”, “너무 맛있다”가 된다. 나이브 베이즈에서는 애초에 각 어휘들이 독립적이라고 가정하는데 이렇게 bigram 모델을 사용하면 연달아 등장하는 어휘를 고려하는 셈이니 조금 나아지는 편이다.
  • 형태소 단위로 쪼개서 집어 넣는다. 예를 들면 “공부하다”, “공부했다”, “공부하고”, “공부하는” 등의 다양한 어휘가 등장할 때 이것들 각각을 어휘로 보지 않고 “공부하-“라는 대표형으로 묶어서 보는 게 효과적이기 때문이다.
  • 특정 품사들만 선택해서 사용한다. 형태소 분석을 하면 각 어휘가 어떤 품사를 지니는지(태깅) 알 수 있기 때문에 이걸 바탕으로 일반명사, 고유명사, 형용사, 동사, 일반부사 위주로 사용하면 결과가 더 좋을 수도 있다. 물론 케바케다. 게다가 형태소를 나누는 기준이나 체계도 딱 정해져 있는 건 아니라…

사실 텍스트 분석은 생각보다 어렵다. 늪이다.

추천 글

“나이브 베이즈(Naive Bayes)를 활용한 문서 분류 – 파이썬 코드 예제”의 4개의 댓글

  1. 안녕하세요. 글 정말 도움 많이 되었습니다.
    혹시 Label 부분을 어떻게 만들어야하는지 이해가 안가서 질문 드립니다.

    Label 부분을 list 형태로 1개만 넣으면 되는것인지 아니면 문서-단어 행렬 만큼 넣어주어야 하는지 궁금합니다.

    positive 텍스트만 넣어 positive 카테고리를 1개만 넣어 test 할 text가 들어올 경우 분류가 된 positive로 분류를 하는지 연습중에 있습니다.

    감사합니다.

    1. 아, 제가 질문을 정확히 이해한 건지 잘 모르겠지만 일단 답을 드려보자면…
      positive인지 negative인지 2진 분류를 하신다는 말씀인 것으로 보입니다.
      (positive 문장만 다 넣고, 레이블도 다 positive라는 건 좀 이상한 것 같아요.)

      label은 당연히 문서 개수만큼 넣어주어야 합니다. (예를 들어 positive면 1, negative면 0으로)
      그리고 이 때 데이터 형식은 list라기보다는 numpy의 array 형식이라고 보는 게 맞겠네요.

    2. 감사합니다~ 행렬로 들어가는 거였군요

      혹시 하나 더 여쭈어 봐도 될까요?

      next_test 부분에서 어떤 문장을 test를 할 때 token를 해서 들어갈 것 같았는데
      긴 문장 그대로 들고 가던데 이 부분 해결 방법 있나요? 아니면 원래 그런가요?

    3. 이건 때에 따라 다른데 저는 한국어 분석 할 때 토크나이주 한 후에 원하는 품사를 선별해서 넣곤 합니다. 성능을 위해…?

      그런데 예를 들어 인터넷에 올라온 리뷰 같은 걸 분류할 때는 특수기호 같은 것도 의미가 있을 수 있을 거예요. 그냥 나이브하게(?) 예상해보자면 긍정적 리뷰에는 느낌표, 부정적 리뷰에는 물음표가 더 많이 등장하는 상황이 있을 수 있겠죠.

      분석의 목적이나 맥락에 따라, 데이터 속성에 따라 품사를 선별하는 게 도움이 되긴 합니다.

댓글 남기기