본문 바로가기
Python과 확률

조건부 확률부터 마르코프까지 - 3) 나이브 베이즈 분류 (근데 간단한)

by Yoo Sung Hyun 2021. 12. 20.
728x90

2021.12.19 - [Python과 확률] - 자연어 처리를 위한 TF-IDF

 

자연어 처리를 위한 TF-IDF

막연하게 나이브 베이즈 분류로 무언가 주제를 잡으려고 하던 와중에, 조금 생각해보니 기왕이면 Counter 기반 말고, 문장과 단어를 더 잘 표현할 수 있는 방법이 무엇이 있을까? 하다가 잠깐 들르

shyu0522.tistory.com

여기서 이어집니다.

 

tf-idf는 간단하게 위키에서 이전에 짰던 소스를 참고하여, 나이브 베이즈 분류기를 만들어 보도록 하겠다.

이 조건부 확률을 이용한 베이즈 정리의 파생은, 연속형 변수에도 가능하고, 지금 예제와는 다른 다항 출력에서도 적용 가능하다.

 

정말 귀인으로 예상되는 분의 사이트를 하나 발견했는데, 정리가 무척 잘 되어있다.

(다만 수학식은 조건부 수식 P(A|B)에 익숙해져있다면 눈에 조금 안들어 올 수도 있다.)

https://zephyrus1111.tistory.com/80?category=858748 

 

[머신 러닝] 4. 나이브 베이즈 분류기(Naive Bayes Classifier) with Python

본 포스팅에서는 수식을 포함하고 있습니다. 티스토리 피드에서는 수식이 제대로 표시되지 않을 수 있으니 PC 웹 브라우저 또는 모바일 웹 브라우저에서 보시기 바랍니다. 이번 포스팅에서는 나

zephyrus1111.tistory.com

사실 여기 파이썬으로 전부 구현된 케이스가 존재하긴 하나, 나는 일단 Native로 작성하는 것이 목적이고, 스스로 학습의 의미도 있으니, 아이디어는 좀 차용할 수 있어도 기본적인 소스는 모두 내가 손수 짜려고 한다.

 

때문에 위의 Article 부터 들어간다면, 치트를 사용하는 느낌이 들 수도 있고, 이제 막 재미를 붙히기 시작했다면 너무 진도가 빠를 수 있으니, 우리는 이전 베이즈 정리(조건부확률) -> tf-idf로 이어지는 위키독스를 참고해서 예제를 짜보도록 하겠다.

 

https://wikidocs.net/22892

 

5) 나이브 베이즈 분류기(Naive Bayes Classifier)

텍스트 분류를 위해 전통적으로 사용되는 분류기로 나이브 베이즈 분류기가 있습니다. 나이브 베이즈 분류기는 인공 신경망 알고리즘에는 속하지 않지만, 머신 러닝의 주요 알고리즘 ...

wikidocs.net

마침 파이썬으로 작성된 소스도 없다! (물론 확률 계산된 정답은 있다....;;ㅋㅋ)

아마 간단한 식의 코테라면 설명을 좀 더 간소화해서 던져주지 않을까 싶다... 1~2번문제로 적당하려나?

직접 차근차근 조건부 확률을 이해하고, 주어진 서술형 문장에서 파이썬 소스로 구현 가능한지 한번 보도록 하자!

 

베이즈 정리는 이미 이전시간에 다뤘으므로, 간단하게 보고 지나가면 될 것 같고, 만약 까먹었다면

P(you|정상메일)의 확률은 '총 정상 메일의 전제하에 you가 들어간 비율' 이라는 점만 유의한다면, 소스를 작성하는데 어렵지 않다.

 

https://github.com/YooSungHyun/probability/blob/main/tfidf_naive_base_spam_classi.ipynb

 

GitHub - YooSungHyun/probability: Independent, Markov Property, Chain, HMM and BEYOND!🚀

Independent, Markov Property, Chain, HMM and BEYOND!🚀 - GitHub - YooSungHyun/probability: Independent, Markov Property, Chain, HMM and BEYOND!🚀

github.com

docs = ['me free lottery','free get free you','you free scholarship','free to contact me','you won award','you ticket lottery']
label = ['Y','Y','N','N','N','Y']
vocab = list(set(w for doc in docs for w in doc.split()))
vocab.sort()

N = len(docs) # 총 문서의 수

from math import log

def tf(t, d):
    return d.count(t)

def idf(t):
    df = 0
    for doc in docs:
        df += t in doc
    return log(N/(df + 1))

def tfidf(t, d):
    return tf(t,d)* idf(t)

tf_result = []
for i in range(N): # 각 문서에 대해서 아래 명령을 수행
    tf_result.append([])
    d = docs[i]
    for j in range(len(vocab)):
        t = vocab[j]        
        tf_result[-1].append(tf(t, d))
print(vocab)
print(tf_result)

idf_result = []
for j in range(len(vocab)):
    t = vocab[j]
    idf_result.append(idf(t))
    
print(vocab)
print(idf_result)

tfidf_result = []
for i in range(N):
    tfidf_result.append([])
    d = docs[i]
    for j in range(len(vocab)):
        t = vocab[j]

        tfidf_result[-1].append(tfidf(t,d))

print(vocab)
print(tfidf_result)

첫번째 코드블럭은 tfidf를 구하는 방식으로, idf의 방식은 평이하게 log(N/df+1)이다.

(이전에 tf-idf 장에서 이야기 했듯, idf의 기준은 다른 공식으로도 사용 가능하니, 필요하다면 수정해서 쓸 수도 있을 것이다.)

 

import collections
label_cnt = collections.Counter(label)

p_label = collections.defaultdict(float)
for key in label_cnt.keys():
    p_label[key] = label_cnt[key]/len(label)

어짜피 우리는 출력이 2개짜리인 베르누이 나이브 베이즈 분류 모형이긴 하나, 추후에 다항으로 확장시킬 것도 혹시나 고려해서, p_label dict를 통해, 들어온 label의 종류만큼 확률을 구할 수 있도록 처리하였다.

p_label[a 레이블] = a 레이블 개수 / 전체 레이블 개수

input_text = ['you','free','lottery']
output_dict = collections.defaultdict(float)

for idx, item in enumerate(label):
    for label_word in p_label.keys():
        for i in range(len(input_text)):
            if item==label_word:
                output_dict[label_word] = sum(tfidf_result[idx]) + output_dict[label_word]
                output_dict[str(i)+'_'+label_word] = tfidf_result[idx][vocab.index(input_text[i])] + output_dict[str(i)+'_'+label_word]

실제로 Input이 주어졌을때, 실제 문장 테이블에서 label 조건에 맞는 전체 수와 각 label의 수를 구하게 된다.

이 부분에서, '정상/스팸 메일 중 특정 단어 비율' 을 구하기위한 선작업을 한다고 생각하면 된다.

output_dict[label_word] = 스팸 혹은 정상이었던 메일의 총합

output_dict[str(i)+'_'+label_word] = 'n번째_spam/정상' case에 따른 총 word 합

이 둘을 구하면 각 case(정상/스팸)별로 output_dict[str(i)+'_'+label_word] / output_dict[label_word] 을 구해주면, 조건부확률이 완성되게 된다.

Pandas 등을 쓰면 index나 column명을 활용해서 좀 더 직관적으로 소스를 짤 수 있겠으나, 딕셔너리로 구현하자니 tf-idf 쪽 수정이 커져서 리스트로 활용하려면 위와같이 소스가 좀 더러워진다.

 

output = collections.defaultdict(lambda : 1.0)
for label_word in p_label.keys():
    for _, value in enumerate(output_dict):
        if value in p_label.keys():
            continue
        if value.split('_')[1] == label_word:
            output[label_word] = (output_dict[value]+1/output_dict[label_word]+1) * output[label_word]
    output[label_word] = p_label[label_word]* output[label_word]

각 case(정상/스팸)별로 전체 for문을 돌려서 최종 확률을 구하게 된다.

if max(output):
    print('Spam!!!')

결과는 max 해주면된다.

 

결과는?

 

 

------------------------------------------------------------------

 귀인분의 소스를 보다보니, 연속형 나이브 베이즈 분류나, 혼합형 소스, 다항 베이즈에도 관심이 좀 생겨서, 마르코프 체인으로 넘어가기 전에 이번주에는 이 부분들을 좀 짚어보고 넘어가보자 한다. (핵심으로 빠르게 치고나가야하는데 자꾸 딴 곳에 한 눈 파는 것 같네...ㅠㅠ)

 소스를 보아하니 Arima를 다루듯이 재활용 가능한 값들은 뭔가 Train으로 저장까지 해놓고 사용하고자 하셨던데, 아직 소스를 구체적으로 보진 않았다. 또한 소스를 구체적으로 보기 시작하면 내 사상이 그 쪽 방향으로 치우칠까 우려되어 선뜻 보기도 두렵다.

 지금은 간단한 예제를 보고 넘어가지만 좀 더 구체적으로 보고 이후 시간들에 추가적으로 정리해보고자 하겠다.

728x90

댓글