
모두의 인공지능 기초 수학 Chapter 14 복습
1. # 베이지안 확률
B일 때 A일 확률
P(A|B)=A(B|A)P(A)P(B)
P(B)=P(B∩A)+P(B∩A′)
P(A|B)=P(B|A)P(A)P(B|A)P(A)+P(B|A′)P(A′)
# 베이지안 이론
- 스팸메일 필터
- 메일본문(입력 테스트)을 이용하여 메일이 스팸인지 구분하는 예시를 살펴보자
- 메일의 스팸 유무
P(정상메일|메일본문)=메일본문이정상일확률
P(스팸메일|메일본문)=메일본문이스팸일확률
* 나이브베이즈
베이즈 모델 :
P(A|B)=P(B|A)P(A)P(B)
이 조건부 확률을 이용 (B가 있을 때 A일 확률)
A를 문서 카테고리, B를 문서라고 가정했을 때, 해당 문서가 있을 때, 해당 카테고리일 확률
P(A1|B)와 P(A2|B)를 비교해서 확률이 더 높은 값을 분류하기 위함
이런 비교 계산을 하기 위해 공통되는 P(B) 제거하면
P(A|B)=P(B|A)P(A)
A 카테고리일 때, W1, W2, ..., Wn 단어가 있다고 가정하면
P(A|B)=P(W1,W2,...,Wn|C)P(C)
>> Naive Bayes : 각각의 단어가 A 카테고리에 독립적이라는 나이브한 가정을 둠으로써 계산
P(C|D)=P(W1|C)P(W2|C)...P(Wn|c)P(C)=∏P(W|C)P(C)
- 나이브 베이즈를 이용하여 정리하면
P(정상메일|메일본문)=P(메일본문|정상메일)XP(정상메일)P(메일본문)
P(스팸메일|메일본문)=P(스팸본문|정상메일)XP(정상메일)P(스팸본문)
입력 텍스트가 주어졌을 때, P(정상메일|메일본문)이 P(스팸메일|메일본문)보다 크면 정상, 그렇지 않으면 스팸
본문에 단어가 두 개 있다고 가정했을 때 단어를 W1, W2라고 하고,
이 식을 간단히 하면
P(정상메일|메일본문)=P(W1|정상메일)XP(W2|정상메일)XP(정상메일)
P(스팸메일|메일본문)=P(W1|스팸메일)XP(W2|스팸메일)XP(스팸메일)

- 입력 테스트 "my free lottery" 있다고 하면
P(정상메일|메일본문)=P(my|정상메일)XP(free|정상메일)XP(lottery|정상메일)XP(정상메일)
P(스팸메일|메일본문)=P(my|스팸메일)XP(free|스팸메일)XP(lottery|스팸메일)XP(스팸메일)
- P(정상메일) = P(스팸메일) = 총 메일여섯 개 중 세 개 = 0.5
>> 두 식 확률 같으므로 생략 가능
$$P(정상메일 | 메일본문) = P(my | 정상메일) X P(free | 정상메일) X P(lottery | 정상메일) $$
P(스팸메일|메일본문)=P(my|스팸메일)XP(free|스팸메일)XP(lottery|스팸메일)
- P(my | 정상메일)을 구하는 방법
정상메일에서my가등장한총빈도수정상메일에등장한모든단어의빈도수총합
- 이런 원리로 "my free lottery"를 분류해보면
P(정상메일|메일본문)=010×210×010=0
$$P(스팸메일 | 메일본문) = \frac{1}{10} \times \frac{3}{10} \times \frac{3}{10} = 0.009$$
>> "my free lottery" 는 스팸메일
# 라이브러리 호출
import numpy as np
import pandas as pd
from nltk.corpus import stopwords
import string
import nltk
# corpus : 말뭉치
# stopwords : 불용어
df = pd.read_csv('spam.csv')

>> 라이브러리 호출하고 csv 파일을 불러온다
>> from nltk.corpus import stopwords :
데이터에서 유의미한 단어 토큰만 선별하기 위해서는 큰 의미가 없는 토큰을 제거하는 작업이 필요.
' I, my, me, over, 조사, 접미사' 같은 단어들은 실제 의미 분석하는데 거의 기여X
>> 이런 단어들을 불용어(stopword)
def process_text(text):
# text에서 구두점(punctuation) 삭제
nopunc = [char for char in text if char not in string.punctuation]
nopunc = ''.join(nopunc)
# text에서 불용어(접미사, 조사 등) 삭제
cleaned_words = [word for word in nopunc.split()
if word.lower() not in stopwords.words('english')]
return cleaned_words
>> process_text를 정의하여 데이터 프레임 'df'에 있는 'text'를 토큰화 시키고자 함
>> 1. text에서 구두점(punctuation) 삭제
string.punctuaion : 영어로 구두점으로 간주되는 모든 문자를 포함
이 목록을 제외함으로써 문자열에서 모든 구두점을 제외 할 수 있음
text에서 string.punctuation이 아닌 char만 char에 넣고 nopunc라고 해줘
join() 함수를 이용해서 nopunc를 정제해서 nopuc에 저장해줘
>> 2. text에서 불용어 삭제
stopwords.words('english') : 영어사전에서 제공하는 영어의 불용어 리스트
이 리스트를 제외하고 소문자로 만들어 리스트에 넣음
punctuation이 없는 정제된 nopunc에서 word를 불러오는데 불용어(stopwords)가 없는 것만 뽑아서 word에 넣어줘 대신
lower(소문자)로 만들어서 리스트에 저장해줘.
process_text에 text에 넣고 반환
df['text'].apply(process_text)

>> 정의한 process_text의 text 결과
punctuation과 불용어가 삭제됨
# text를 토큰화한 뒤, 토큰 수의 행렬 변환
from sklearn.feature_extraction.text import CountVectorizer
messages_bow = CountVectorizer(analyzer = process_text).fit_transform(df['text'])
>> Scikit-Learn의 서브 패키지 feature_extraction에서 CountVectorizer 클래스 호출
문서 집합에서 단어 토큰을 생성하고 각 단어의 수를 세어 BOW 인코딩 벡터를 만드는 클래스
(BOW 인코딩 : 문서를 숫자 벡터로 변환하는 방법)
1. 문서를 토큰 리스트로 변환
2. 각 문서에서 토큰의 출현 빈도를 셈
3. 각 문서를 BOW 인코딩 벡터로 변환
analyzer 인수로 사용할 토큰 생성기를 'process_text'로 함
fit_transform() 은 fit()과 transform()을 한번에 처리할 수 있게 하는 메서드
fit()은 데이터를 학습시키는 메서드이고, transform()은 실제로 학습시킨 것을 적용하는 메서드
CountVectorizer 클래스를 이용해 process_text로 단어 토큰을 생성하고 각 단어의 수를 셈
이때, 학습시키고 적용할 문서는 df['text']
단어의 수를 행렬로 변환시켜서 messages_bow라고 함
# train data, test data 8:2 비율로 구분
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(messages_bow, df['label_num'], test_size = 0.2, random_state = 42)
# test_size는 0.2 train size는 자동적으로 0.8
# 랜덤하게 섞은것 중에 여러 세트가 있는데 42번으로 했을 떄 제일 잘 나옴
# 랜덤하게 섞은 데이터로 다른 알고리즘으로 적용해보고 싶을 때 random_state를 꼭 해줘야함
>> Scikit-Learn의 서브 패키지 model_selection에서 train_test_split 클래스 호출
>> train / test를 분리하는 이유는 머신러닝 모델에 train 데이터를 100% 학습시킨 후 test데이터에 모델을 적용했을 떄 성능이 생각보다 않나오는 경우가 많음 >> overfitting
모델이 내가 가진 학습 데이터에 너무 과적합되도록 학습한 나머지, 이를 조금이라도 벗어난 케이스는 예측률이 현저히 떨어지기 때문
>> overfitting을 방지하기 위해 validation set으로 모델 평가
>> test_size를 0.2, train_size를 0.8로 지정
random_state : 세트를 섞을 때 해당 int 값을 보고 섞는데, 이 값을 고정해야 매번 데이터 셋이 변경되지 않음
>> x_train : train data set의 feature
x_test : test data set의 feature
y_train : train data set의 label
y_test : test data set의 label
messages_bow를 feature 부분으로, df['label_num']을 label 부분으로 하여 test와 train 비율을 2:8로 하여 모델 학습
(label_num = spam mail 이면 1, 정상메일이면 0)
# 나이브 베이즈 모델 적용, 훈련
# 훈련(train)>> 모델 생성
from sklearn.naive_bayes import MultinomialNB
classifier = MultinomialNB()
classifier.fit(X_train, y_train) # 훈련시, 반드시 train data 사용
>> Scikit-Learn의 서브 패키지 naive_bayes에서 MultinomialNB 클래스 호출
>> MultinomialNB 모델을 classifier에 저장하고 X_train, y_train 데이터를 학습
# 훈련용 데이터로 예측해서 비교해 봄
classifier.predict(X_train) # 예측값
y_train.values # 실제값

>> predict를 사용하면 0과 1만 반환하는데 0은 정상메일 1은 스팸메일이다.
- 학습 데이터셋에서 모델의 정확도를 표현해보자
# 학습 데이터셋에서 모델의 정확도 표현하기
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix, accuracy_score
pred = classifier.predict(X_train)
# metrics pkg >> prescision(정밀도), recall(재현율), f1 score(f1 스코어) 점수 구하기
print(classification_report(y_train, pred))

>> Scikit-Learn의 matrics 패키지에서 classification_report를 보면
y_train(실제값)과 classifier.predict(X_train) (예측값)을 비교했을 때 정밀도, 재현율, f1 스코어 모두 1로 높은 값으로 나왔다.
# 혼동행렬(confusion matrix)
print('confusion Matrix: \n', confusion_matrix(y_train, pred))
print()
print('Accuracy: ', accuracy_score(y_train, pred))

>> 혼동행렬
실제\예측 N P
N TN FN
P FN TP
* 정상메일 = 0, 스팸메일 = 1
TN = True Negative, 정상메일을 정상메일으로 예측 (정답) 3
FP = False Positive, 정상메일이 아닌 것을 정상메일이라고 예측 0
FN = False Negative, 정상메일을 정상메일이 아니라고 예측 0
TP = True Positive, 정상메일이 아닌 것을 정상메일이 아니라고 예측(정답) 1
>> 1) precision = 정밀도
Positive 라고 예측한 샘플(TP + FP) 중 실제로 Positive인 샘플(TP) 비율
'예측값' 기준 '정답인 예측값'
precision=TPTP+FP
>> 2) Recall = 재현율
실제 Positive인 샘플(TP+FN) 중 Positive라고 예측한 샘플(TP) 비율
'실제값' 기준 '정답인 예측값'
Recall=TPTP+FN
>> 3) Accuracy = 정확도
전체 샘플 중 맞게 예측한 샘플의 비율
Accuracy=TP+TNTN+TP+FN+FP
>> 4) F1-score
F-score 란 precision과 recall의 가중 조화평균
Fβ=(1+β2)(precision×recall)β2precision+recall
이때 가중치를 1인 경우를 F1-score
F1=(1+12)(precision×recall)12precision+recall
>>
F1=(2)(precision×recall)precision+recall
>> 5) support = 각 라벨의 실제 샘플 개수
4개의 샘플 중 라벨이 0 (정상메일)인 샘플은 3개
+ 민감도(Sensitivity)
Sensitivity=TruePositiveTruePositives+FalseNegatives
+ 특이도 (Specificity)
Specificity=TrueNegativesTrueNegatives+FalsePositives
>> 6) macro avg : 단순 평균
>> 7) weight avg : 가중 평균
- test data에 적용
# test data에 적용 (모델의 성능)
classifier.predict(X_test) # 예측값
print('Actual value: ',y_test.values) # 실제값

>> train 데이터와 달라짐
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix, accuracy_score
pred = classifier.predict(X_test)
print(classification_report(y_test, pred))
print(confusion_matrix(y_test,pred))
accuracy_score(y_test, pred)


>> 실제 test 데이터에 적용했을 때는 precision, recall, f1-score 모두 0으로 나옴