minhui study

베이스 정리로 텍스트 분류하기 본문

Python/챗봇

베이스 정리로 텍스트 분류하기

minhui 2020. 7. 21. 02:22

텍스트 분류

 

텍스트 분류에는 여러 가지 방법이 사용되는데 자주 사용되는 방법으로는 베이즈 정리를 이용한 "베이지안 필터(Bayesian filter)"가 있다. 학습을 많이 시킬수록 필터의 분류 능력이 오른다는 특징이 있어 스팸 글을 구분할 때 많이 사용되고 문자의 카테고리 분류에도 많이 사용된다. 머신러닝은 "교사 학습", "비교사 학습", "강화 학습"이라는 3가지 종류가 있는데 베이지안 필터는 이 중에서 "교사 학습"에 해당한다. 

 

*교사 학습(Supervised learning)이란? 

훈련 데이터로부터 하나의 함수를 유추해내기 위해 기계 학습의 한 방법으로 데이터와 답을 함께 줘서 학습시켜 머신러닝 모델을 구축하면 새로운 데이터를 줬을 때 답을 예측할 수 있다.

종류 설명
교사 학습 데이터와 함께 답을 입력
다른 데이터의 답을 예측
비교사 학습 데이터는 입력하지만 답은 입력하지 않는다.
다른 데이터의 규칙성을 찾는다.
강화 학습 부분적으로 답을 입력
데이터를 기반으로 최적의 답을 찾는다.

 

베이즈 정리(Bayes' theorem)

베이즈 정리는 조건부 확률과 관련된 이론으로 토머스 베이즈에 의해 정립된 이론이다. 베이즈 정리는 다음과 같다.

 

                                P(B|A) = P(A|B)P(B)/P(A)

 

P(A) : A가 일어날 확률

P(B) : B가 일어날 확률(사전 확률)

P(A|B) : B가 일어난 후에 A가 일어날 확률(조건부 확률, 사후 확률)

P(B|A) : A가 일어난 후에 B가 일어날 확률(조건부 확률, 사전 확률)

 

 

 

조건부 확률

    조건부 확률이란?

      <어떤 A라는 사건이 일어났다는 조건>에서 <다른 사건 B가 일어날 확률>을 나타낸다. 기호로는 P(B|A)

      예를 들어 비가 내리는 날에 교통 사고가 발생할 확률은 P(<교통 사고>|<비>)로 나타낸다.

 

 

결합 확률과 곱셈 법칙

*결합 확률 : 동시에 일어날 확률

 예를 들어 주사위를 던져서 첫 번째가 5 그리고 두 번째가 짝수가 나올 확률을 구해보자

 주사위는 1부터 6까지 있으므로 특성 숫자인 5가 나올 확율은 1/6이다. 그리고 홀수 또는 짝수가 나온 확률은 3/6=1/2   이다. 따라서 동시에 2가지 사건이 발생할 때의 확률은 두 사건의 확률을 곱하면 된다. 즉, 1/6 * 1/2 = 1/12이다.

 

 

그럼 이제 한 카페의 매출을 예측하는 경우를 생각해보자. 제일 잘 팔리는 음료는 아메리카노와 카페 라떼이다. 특정 주의 "전체 손님", "아메리카노를 산 손님", "카페 라떼를 산 손님", "두 가지를 모두 산 손님"을 세어보니 다음과 같았다.

전체 손님 수 : 100
아메리카노를 산 손님 수 : 50
카페 라떼를 산 손님 수 : 20
두 가지를 모두 산 손님 : 10

(A)아메리카노를 산 사람(50명) 중에서 이어서 (B)카페 라떼를 산 손님(10명)의 결합 확률은 다음과 같다.

 

50/100 * 10/50 = 1/10

P(A) : 아메리카노를 살 확률

P(B) : 카페 라떼를 살 확률

 

B와 A의 결합 확률 = P(A|B) X P(B) 

 

반대로 (B)카페 라떼를 산 사람 중에서 (A)카페 라떼를 살 확률은 다음과 같다.

 

20/100 * 10/20 = 1/10

 

따라서 "B와 A의 결합 확률"과 "A와 B의 결합 확률"은 같다는 것을 알 수 있다. 이를 변형하면 베이즈 정리가 다음과 같이 나오게 된다.

 

P(B|A) X P(A) = P(A|B) X P(B)

 

 

 

 

나이브 베이즈 분류

베이지안 필터는 나이브 베이즈 분류라는 알고리즘을 사용한다. 나이브 베이즈 분류는 베이즈 정리를 사용한 분류 방법이라고 할 수 있다. 간단하게 살펴보면 베이즈 정리는 A라는 사건이 B에 속하는지 판단할 때 사용한다. 

어떤 문장을 카테고리 분류할 때 나이브 베이즈 분류는 텍스트 내부에서의 단어 출현 비율으 조사한다. 이를 기반으로 해당 텍스트를 어떤 카테고리로 분류하는 것이 적합한지 알아본다.

 

실제 판정을 할 때 P(B|A)는 1개의 확률이 아니라 여러 개의 카테고리 중에 어떤 카테고리에 속할 확률이 가장 큰지를 타나해는 정보이다. 따라서 베이즈 정리의 분모에 있는 P(A)는 입력 텍스트가 주어질 확률이다. 다만 어떤 카테고리를 판정하든 같은 입력 텍스트가 주어지므로 같은 값으로 생각하면 되기 때문에 고려하지 않아도 된다. 따라서 나이브 베이즈 분류 공식은 다음과 같이 단순하게 나타낼 수 있다.

 

P(B|A) = P(B) X P(A|B)

 

P(A) : 입력 테스트가 주어질 확률

P(B) : 각 카테고리로 분류될 확률(전체 문서에서 해당 카테고리의 문서가 나올 확률)

 

이어서 P(A|B)를 생각해보자. 입력 테스트 A는 단어들의 집합이므로 텍스트를 단어들로 분리한다. 단어가 문서의 어떤 위치에 있다는 정보 등을 고려하지 않아도 되는데 이를 가방 안에 단어들을 그냥 넣어서 사용하는 것과 같다고 하여 이러한 텍스트 표현 즉, 순서를 고려하지 않고 단어로만 표현하는 것을 "BoW = bag-of-word(단어가 담긴 가방)" 이라고 부른다. A를 각 단어(aN)의 집합이라고 할 때, P(A|B)는 다음과 같이 나타낼 수 있다.

 

P(A|B) = P(a1|B)P(a2|B)P(a3|B)....P(aN|B)

 

P(aN|B)은 단어가 카테고리에 속할 확률이다. 어떤 카테고리에 해당 단어가 출현할 확률을 구하면 되는데 이는 다음과 같이 구할 수 있다.

 

< 단순한 출현율 > = <단어의 출현 횟수> / <카테고리 전테 단어 수>

 

입력 텍스트를 학습시킬 때에 출현한 단어가 카테고리에 분류된 횟수를 기억하면 좋다. 그리고 카테고리 분류를 실제로 할 때는 P(B)*P(A|B)를 카테고리별로 계산하면 된다.

 

 

 

 

베이지안 필터 사용해보기

 

메일의 제목으로 스팸을 구분하는 상황을 가정해서 메일의 제목을 기반으로 "광고", "중요" 메일을 구분하는 예제이다. 몇 가지 메일을 학습시킨 이후에 새로운 메일이 왔을 때 제대로 분류를 할 수 있는지 테스트해보자.

 

<bayes.py>

import math, sys
from konlpy.tag import Okt

class BayesianFilter:
    def __init__(self):
        self.words = set() # 출현한 단어 기록
        self.word_dict = {} # 카테고리마다의 출현 횟수 기록
        self.category_dict = {} # 카테고리 출현 횟수 기록
    # 형태소 분석하기 -- (1)
    def split(self, text):
        results = []
        okt = Okt()

        # 단어의 기본형 사용
        malist = okt.pos(text, norm=True, stem=True )
        for word in malist:
            # 어미/조사/구두점 등은 대상에서 제외
            if not word[1] in ["Josa", "Eomi", "Punctuaion"]:
                results.append(word[0])
        return results

    #단어와 카테고리의 출현 횟수 세기 -- (2)
    def inc_word(self, word, category):
        # 단어를 카테고리에 추가하기
        if not category in self.word_dict:
            self.word_dict[category] = {}
        if not word in self.word_dict[category]:
            self.word_dict[category][word] = 0
        self.word_dict[category][word] += 1
        self.words.add(word)

    def inc_category(self, category):
        #카테고리 계산하기
        if not category in self.category_dict:
            self.category_dict[category] = 0
        self.category_dict[category] += 1

    # 텍스트 학습하기 -- (3)
    def fit(self, text, category):
        """ 텍스트 학습 """
        word_list = self.split(text)
        for word in word_list:
            self.inc_word(word, category)
        self.inc_category(category)

    # 단어 리스트에 점수 매기기-- (4)
    def score(self, words, category):
        score = math.log(self.category_prob(category))
        for word in words :
            score += math.log(self.word_prob(word, category))
        return score

    #예측하기 -- (5)
    def predict(self, text):
        best_category = None
        max_score = -sys.maxsize
        words = self.split(text)
        score_list = []
        for category in self.category_dict.keys():
            score = self.score(words, category)
            score_list.append((category, score))
            if score > max_score:
                max_score = score
                best_category = category
        return best_category, score_list

    # 카테고리 내부의 단어 출현 횟수 구하기
    def get_word_count(self, word, category):
        if word in self.word_dict[category]:
            return self.word_dict[category][word]
        else:
            return 0
    
    # 카테고리 계산
    def category_prob(self, category):
        sum_categories = sum(self.category_dict.values())
        category_v = self.category_dict[category]
        return category_v / sum_categories
    
    #카테소리 내부의 단어 출현 비율 계산 -- (6)
    def word_prob(self, word, category):
        n = self.get_word_count(word, category) + 1 # -- (6a)
        d = sum(self.word_dict[category].values()) + len(self.words)
        return n / d

 

1 : konlpy의 Okt을 이용하여 형태소 분석을 한다.

 

2 : 단어를 카테고리에 추가하고, 카테고리 내부의 단어 출현 빈도를 구할 수 있게 출현 횟수를 센다. 

inc_word() 메서드에서는 word_dict를 사용해 단어 출현 횟수를 세고, inc_category() 메서드에서는 category_dict를 사용해 카테고리의 출현 횟수를 센다.

 

3 : 텍스트를 학습하는 fit() 메서드를 정의한다. 이 메서드에서는 텍스트를 형태소로 분할하고, 카테고리와 단어를 연결한다.

 

4 : 단어 리스트를 주면 점수를 계산해주는 메서드이다. 

 

5 : 테스트의 카테고리를 구분하는 메서드이다. 텍스트가 주어졌을 때 카테고리 점수를 계산하고, 가장 점수가 높은 카테고리를 결과로 리턴하는 것이다. 

 

6 : 단어 출현률을 계산할 때 학습 사전(word_dict)에 없는 단어가 나오면 카테고리의 확률이 0이 되어버린다. 그래서 1을 더해서 활용하는 것이다. 왜냐면어떤 값에 0을 곱하면 무조건 0이 되지만 어떤 값에 1을 곱하면 무조건 같은 값이 되기 때문이다. 

 

 

<bayes_test.py>

from bayes import BayesianFilter
bf = BayesianFilter()

#텍스트 학습
bf.fit("파격 세일 - 오늘까지만 30% 할인", "광고")
bf.fit("쿠폰 선물 & 무료 배송", "광고")
bf.fit("현데계 백화점 세일", "광고")
bf.fit("봄과 함께 찾아온 따뜻한 신제품 소식", "광고")
bf.fit("인기 제품 기간 한정 세일", "광고")
bf.fit("오늘 일정 확인", "중요")
bf.fit("프로젝트 진행 상황 보고", "중요")
bf.fit("계약 잘 부탁드립니다", "중요")
bf.fit("회의 일정이 등록되었습니다.", "중요")
bf.fit("오늘 일정이 없습니다.", "중요")

# 예측
pre, scorelist = bf.predict("재고 정리 할인, 무료 배송")
print("결과 =", pre)
print(scorelist)

 

실행 결과는 다음과 같다.

결과를 보면 "재고 정리 할인, 무료 배송"을 제대로 "광고"로 분류를 한 것을 확인할 수 있다. 



< 출처 및 참고 자료 >

파이썬을 이용한 머신러닝, 딥러닝 실전 개발 입문

 

Comments