일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 딥러닝
- dtype
- type hints
- 표집분포
- Python
- Operation function
- 카테고리분포 MLE
- scatter
- 정규분포 MLE
- python 문법
- namedtuple
- Python 유래
- Array operations
- Python 특징
- Numpy data I/O
- pivot table
- unstack
- 부스트캠프 AI테크
- subplot
- Numpy
- seaborn
- VSCode
- linalg
- ndarray
- 최대가능도 추정법
- boolean & fancy index
- groupby
- Comparisons
- 가능도
- BOXPLOT
- Today
- Total
또르르's 개발 Story
[16-1] NaiveBayes Classifier Using Konlpy 본문
1️⃣ 설정
konlpy를 설치합니다.
konlpy는 다양한 한국어 형태소 분석기가 클래스로 구현되어 있습니다.
!pip install konlpy
설치한 konlpy와 나머지 필요한 모듈을 import 합니다.
from tqdm import tqdm
from konlpy import tag # 다양한 한국어 형태소 분석기가 클래스로 구현되어 있음
from collections import defaultdict
import math
2️⃣ 학습 및 테스트 데이터 전처리
NaiveBayes Classifier를 통해 해당 Sentence가 긍정인지 부정인지를 분류할 것입니다.
따라서 class는 긍정(1), 부정(0) 2가지 class로 구성되어 있습니다.
train_labels에서 1은 긍정, 0은 부정을 나타내고, test_data를 통해 긍정과 부정을 추론할 것입니다.
train_data = [
"정말 맛있습니다. 추천합니다.",
"기대했던 것보단 별로였네요.",
"다 좋은데 가격이 너무 비싸서 다시 가고 싶다는 생각이 안 드네요.",
"완전 최고입니다! 재방문 의사 있습니다.",
"음식도 서비스도 다 만족스러웠습니다.",
"위생 상태가 좀 별로였습니다. 좀 더 개선되기를 바랍니다.",
"맛도 좋았고 직원분들 서비스도 너무 친절했습니다.",
"기념일에 방문했는데 음식도 분위기도 서비스도 다 좋았습니다.",
"전반적으로 음식이 너무 짰습니다. 저는 별로였네요.",
"위생에 조금 더 신경 썼으면 좋겠습니다. 조금 불쾌했습니다."
]
train_labels = [1, 0, 0, 1, 1, 0, 1, 1, 0, 0]
test_data = [
"정말 좋았습니다. 또 가고 싶네요.",
"별로였습니다. 되도록 가지 마세요.",
"다른 분들께도 추천드릴 수 있을 만큼 만족했습니다.",
"서비스가 좀 더 개선되었으면 좋겠습니다. 기분이 좀 나빴습니다."
]
KoNLPy 패키지에서 제공하는 Twitter(Okt) tokenizer를 사용하여 tokenization 합니다.
tokenizer = tag.Okt()
def make_tokenized(data):
tokenized = [] # 단어 단위로 나뉜 리뷰 데이터.
for sent in tqdm(data):
tokens = tokenizer.morphs(sent)
tokenized.append(tokens)
return tokenized
train_tokenized = make_tokenized(train_data)
test_tokenized = make_tokenized(test_data)
>>> train_tokenized # token은 띄어쓰기 구분이 아닌 의미단위로 설정
[['정말', '맛있습니다', '.', '추천', '합니다', '.'],
['기대했던', '것', '보단', '별로', '였네요', '.'],
['다',
'좋은데',
'가격',
'이',
'너무',
'비싸서',
'다시',
'가고',
'싶다는',
'생각',
'이',
'안',
'드네',
'요',
'.'],
['완전', '최고', '입니다', '!', '재', '방문', '의사', '있습니다', '.'],
['음식', '도', '서비스', '도', '다', '만족스러웠습니다', '.'],
['위생',
'상태',
'가',
'좀',
'별로',
'였습니다',
'.',
'좀',
'더',
'개선',
'되',
'기를',
'바랍니다',
'.'],
['맛', '도', '좋았고', '직원', '분들', '서비스', '도', '너무', '친절했습니다', '.'],
['기념일',
'에',
'방문',
'했는데',
'음식',
'도',
'분위기',
'도',
'서비스',
'도',
'다',
'좋았습니다',
'.'],
['전반', '적', '으로', '음식', '이', '너무', '짰습니다', '.', '저', '는', '별로', '였네요', '.'],
['위생', '에', '조금', '더', '신경', '썼으면', '좋겠습니다', '.', '조금', '불쾌했습니다', '.']]
학습 데이터 기준으로 가장 많이 등장한 단어부터 순서대로 vocab에 추가합니다.
word_count = defaultdict(int) # Key: 단어, Value: 등장 횟수
for tokens in tqdm(train_tokenized):
for token in tokens:
word_count[token] += 1
word_count = sorted(word_count.items(), key=lambda x: x[1], reverse=True)
>>> print(len(word_count))
66
w2i는 key: 단어, value:단어 index로 구성된 vocabulary입니다.
w2i = {} # Key: 단어, Value: 단어의 index
for pair in tqdm(word_count):
if pair[0] not in w2i:
w2i[pair[0]] = len(w2i)
>>> w2i
{'!': 35,
'.': 0,
'가': 41,
'가격': 23,
'가고': 26,
'개선': 43,
'것': 20,
'기념일': 52,
'기대했던': 19,
'기를': 45,
...
'직원': 49,
'짰습니다': 59,
'최고': 33,
'추천': 17,
'친절했습니다': 51,
'합니다': 18,
'했는데': 53}
3️⃣ 모델 Class 구현
NaiveBayes Classifier 모델 클래스를 구현합니다.
- self.k: Smoothing을 위한 상수.
- self.w2i: 사전에 구한 vocab.
- self.priors: 각 class의 prior 확률.
- self.likelihoods: 각 token의 특정 class 조건 내에서의 likelihood.
여기서 self.k 즉, smoothing은 Laplace Smoothing을 뜻하며, $P(x|c)$에서 $x$가 vocab에는 존재하지만 $c$에는 존재하지 않는 경우 항상 $P(x|c)$는 0이 나오는 문제점을 해결합니다. 수식 $P(x_{i}|c)$가 0이 되지 않도록 일정한 상수 $k$를 더하는 방법을 사용합니다.
class NaiveBayesClassifier():
def __init__(self, w2i, k=0.1): # k는 적당한 값으로
self.k = k
self.w2i = w2i
self.priors = {}
self.likelihoods = {}
def train(self, train_tokenized, train_labels):
self.set_priors(train_labels) # Priors 계산.
self.set_likelihoods(train_tokenized, train_labels) # Likelihoods 계산.
def inference(self, tokens):
log_prob0 = 0.0 # 확률 값이기 때문에 0~1 사이만 가능한데 곱셈을 하게되면 0으로 수렴하는 현상 / 따라서 log를 사용해 곱셈을 덧셈으로 바꿔줘서 더해줌
log_prob1 = 0.0
for token in tokens:
if token in self.likelihoods: # 학습 당시 추가했던 단어에 대해서만 고려.
log_prob0 += math.log(self.likelihoods[token][0]) # log를 취해서 더해줌
log_prob1 += math.log(self.likelihoods[token][1])
# 마지막에 prior를 고려.
log_prob0 += math.log(self.priors[0])
log_prob1 += math.log(self.priors[1])
if log_prob0 >= log_prob1:
return 0
else:
return 1
# 문장 안에 단어가 얼마나 존재하는지의 확률
def set_priors(self, train_labels):
class_counts = defaultdict(int)
for label in tqdm(train_labels):
class_counts[label] += 1
for label, count in class_counts.items():
self.priors[label] = class_counts[label] / len(train_labels)
# likelihoods {key (token) : value{key (class : 0,1) : value (확률)}}
def set_likelihoods(self, train_tokenized, train_labels):
token_dists = {} # 각 단어의 특정 class 조건 하에서의 등장 횟수.
class_counts = defaultdict(int) # 특정 class에서 등장한 모든 단어의 등장 횟수.
for i, label in enumerate(tqdm(train_labels)):
count = 0
for token in train_tokenized[i]:
if token in self.w2i: # 학습 데이터로 구축한 vocab에 있는 token만 고려.
if token not in token_dists:
token_dists[token] = {0:0, 1:0}
token_dists[token][label] += 1
count += 1
class_counts[label] += count
for token, dist in tqdm(token_dists.items()):
if token not in self.likelihoods:
self.likelihoods[token] = {
0:(token_dists[token][0] + self.k) / (class_counts[0] + len(self.w2i)*self.k),
1:(token_dists[token][1] + self.k) / (class_counts[1] + len(self.w2i)*self.k),
# k는 Smoothing을 위한 상수.
# Laplace Smoothing
}
함수 set_priors는 문장 안에 단어가 얼마나 존재하는지의 확률을 의미합니다.
def set_priors(self, train_labels):
class_counts = defaultdict(int)
for label in tqdm(train_labels):
class_counts[label] += 1
for label, count in class_counts.items():
self.priors[label] = class_counts[label] / len(train_labels)
함수 set_likelihoods는
self.liklihoods = {key (token) : value {key (class : 0,1) : value (확률)}}
형태로 만들어집니다.
Liklihoods는 각 token에 대해 class (0,1)의 확률 값을 가지고 있는 vocabulary입니다.
Likelihoods $P(x_{i}|c)$는 아래 수식으로 계산됩니다.
$$P(x_{i}|c) = \frac {count(x_{i},c)+1} {\sum_{x \in V} (count(x,c)+1)} = \frac {count(x_{i},c)+k} {\sum_{x \in V} (count(x,c)) + k*V} $$
위 수식을 코드로 만들면 다음과 같습니다.
self.likelihoods[token] = {
0:(token_dists[token][0] + self.k) / (class_counts[0] + len(self.w2i)*self.k),
1:(token_dists[token][1] + self.k) / (class_counts[1] + len(self.w2i)*self.k),
# k는 Smoothing을 위한 상수.
# Laplace Smoothing
}
4️⃣ 모델 학습 및 테스트
모델 객체를 만들고 학습 데이터로 학습시킵니다.
classifier = NaiveBayesClassifier(w2i)
classifier.train(train_tokenized, train_labels)
Test sample에 대한 결과는 다음과 같습니다.
preds = []
for test_tokens in tqdm(test_tokenized):
pred = classifier.inference(test_tokens)
preds.append(pred)
>>> preds
[1, 0, 1, 0] 긍정, 부정, 긍정, 부정
test_data = [
"정말 좋았습니다. 또 가고 싶네요.", (긍정)
"별로였습니다. 되도록 가지 마세요.", (부정)
"다른 분들께도 추천드릴 수 있을 만큼 만족했습니다.",(긍정)
"서비스가 좀 더 개선되었으면 좋겠습니다. 기분이 좀 나빴습니다."(부정)
]
'부스트캠프 AI 테크 U stage > 실습' 카테고리의 다른 글
[16-3] Spacy를 이용한 영어 전처리 (0) | 2021.02.16 |
---|---|
[16-2] Word2Vec Using Konlpy (0) | 2021.02.16 |
[14-3] Scaled Dot-Product Attention (SDPA) using PyTorch (0) | 2021.02.04 |
[14-2] LSTM using PyTorch (0) | 2021.02.04 |
[13-3] Google Image Data 다운로드 (0) | 2021.02.04 |