딥러닝을 활용한 NLP Task 이전에 사용되던 단어 및 문장을 숫자로 나타내는 Word Vectorizing 방법
Step 1. Text Dataset에서 Unique한 Word를 모아서 하나의 Vocabulary를 생성한다. 이 때, 중복은 허용하지 않는다.
ex) “John really really loves this movie.”, “Jane really likes this song” 두개의 Text Dataset에서
{”John”, “really”, “loves”, “this”, “movie”, “Jane”, “likes”, “song”}의 Vocabulary 생성
Step 2. 각각의 단어를 하나의 Categorical Variable로 취급할 수 있고 이를 One-hot Vector로 변경
NaiveBayes Classifier는 Bag of Words로 나타내진 문장을 특정 Class(Label)로 분류할 수 있는 대표적인 분류기
문서의 총 클래스 C가 존재할 때, 각각의 클래스 c에 해당 문서 d가 속할 조건부 확률로 문서를 분류
# 한국어의 형태소 분석 등 복잡한 한글 구조를 간결한 정보로 처리할 수 있는 Module
$ pip install konlpy
from tqdm import tqdm
from collections import defaultdict
import math
# 다양한 한국어 형태소 분석기가 클래스로 구현되어 있음
from konlpy import tag
Class TrainDataset:
train_data = [
"정말 맛있습니다. 추천합니다.",
"기대했던 것보단 별로였네요.",
"다 좋은데 가격이 너무 비싸서 다시 가고 싶다는 생각이 안 드네요.",
"완전 최고입니다! 재방문 의사 있습니다.",
"음식도 서비스도 다 만족스러웠습니다.",
"위생 상태가 좀 별로였습니다. 좀 더 개선되기를 바랍니다.",
"맛도 좋았고 직원분들 서비스도 너무 친절했습니다.",
"기념일에 방문했는데 음식도 분위기도 서비스도 다 좋았습니다.",
"전반적으로 음식이 너무 짰습니다. 저는 별로였네요.",
"위생에 조금 더 신경 썼으면 좋겠습니다. 조금 불쾌했습니다."
]
train_labels = [1, 0, 0, 1, 1, 0, 1, 1, 0, 0]
Class TestDataset:
test_data = [
"정말 좋았습니다. 또 가고 싶네요.",
"별로였습니다. 되도록 가지 마세요.",
"다른 분들께도 추천드릴 수 있을 만큼 만족했습니다.",
"서비스가 좀 더 개선되었으면 좋겠습니다. 기분이 좀 나빴습니다."
]
def make_tokenized(data):
# KoNLPy Package에 있는 Okt Tokenizer를 사용하여 Word tokenization
tokenizer = tag.Okt()
# Sentence를 단어 단위로 나눈 List들을 저장하는 List
tokenized = []
for sentence in tqdm(data):
# Sentence를 Tokenization하여 Word의 List로 반환
tokens = tokenizer.morphs(sentence)
tokenized.append(tokens)
print(train_tokenized[:2}
# ---print---
# [['정말', '맛있습니다', '.', '추천', '합니다', '.'], ['기대했던', '것', '보단', '별로', '였네요', '.']]
def wordToIndex():
word_count = defaultdict(int) # Key: 단어, Value: 등장 횟수
# List로 구성된 Tokenized Sentence들 ex) tokens = ['정말', '맛있습니다', '.', '추천', '합니다', '.']
for tokens in tqdm(train_tokenized):
# Tokenized Sentence에서 하나의 word들 ex) token = '정말'
for token in tokens:
# Key가 없으면 Key를 추가해주고 대응하는 Value는 1, Key가 있으면 Value에 +1을 해주면서 word frequency counting
word_count[token] += 1
# Dictionary의 value값이 큰(가장 빈도수가 높은 단어) key를 순서대로 vocabulary에 정렬
word_count = sorted(word_count.items(), key=lambda x: x[1], reverse=True)
w2i = {} # dict type(key: word, value: index)
for pair in tpdm(word_count)
# 만약 w2i dictionary에 pair에 해당하는 word가 없을 경우
if pair[0] not in w2i:
# len이 0부터 word가 하나씩 추가될 때마다 늘어나기 때문에 index로 사용
w2i[pair[0]] = len(w2i)
class NaiveBayesClassifier():
def __init__(self, w2i, k=0.1):
"""
self.k: Smoothing을 위한 상수.
self.w2i: 사전에 구한 vocab.
self.priors: 각 class의 prior 확률.
self.likelihoods: 각 token의 특정 class 조건 내에서의 likelihood.
"""
self.k = k
self.w2i = w2i
self.priors = {}
self.likelihoods = {}
def train(self, train_tokenized, train_labels):
# Tokenized train dataset을 사용하여 priors를 계산
self.set_priors(train_tokenized)
self.set_likelihoods(train_tokenized, train_labels)
def inference(self, tokens):
# Token이 0(부정)을 의미할 확률
log_prob0 = 0.0
# Token이 1(긍정)을 의미할 확률
log_prob1 = 0.0
for token in tokens:
# 학습 당시에 추가했던 단어들만 likelyhoods를 고려한다.
if token in self.likelyhoods:
log_prob0 += math.log(self.likelihoods[token][0])
log_prob1 += math.log(self.likelihoods[token][1])
# Priority를 고려하여 더해준다.
log_prob0 += math.log(self.priors[0])
log_prob1 += math.log(self.priors[1])
if log_prob0 >= log_prob1:
return 0
else:
return 1
# label의 분포에 따라 부여되는 가중치
def set_priors(self, train_labels):
class_counts = defaultdict(int)
for label in tqdm(train_labels):
class_count[label] += 1
for label, count in class_counts.items():
self.priors[label] = class_counts[label] / len(train_labels)
def set_likelihoods(self, train_tokenized, train_labels):
token_dists = {} # 각 word의 특정 class(여기서는 0 or 1) 조건 하에서의 등장 횟수
class_counts = defaultdict(int) # 특정 class에서 등장한 모든 단어의 등장 횟수
# Train label에 index를 주어 반복문 진행
for i, label in enumerate(tqdm(train_labels)):
count = 0
for token in train_tokenized[i]:
if token in self.w2i: # 학습 데이터로 구축한 vocab에 있는 token만 고려, 모든 단어가 vocab을 만들 때 포함되는 건 아님
if token not in token_dists:
token_dists[token] = {0:0, 1:0}
# Token이 등장한 문장의 label에 등장 횟수를 1 더해준다.
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),
}
classifier = NaiveBayesClassifier(w2i)
classifier.train(train_tokenized, train_labels)
preds = []
for test_tokens in tqdm(test_tokenized):
pred = classifier.inference(test_tokens)
preds.append(pred)
for i in range(len(preds)):
print(test_data[i], preds[i], sep=' Predicted Class:')
정말 좋았습니다. 또 가고 싶네요. Predicted Class:1
별로였습니다. 되도록 가지 마세요. Predicted Class:0
다른 분들께도 추천드릴 수 있을 만큼 만족했습니다. Predicted Class:1
서비스가 좀 더 개선되었으면 좋겠습니다. 기분이 좀 나빴습니다. Predicted Class:0
test_sentence = [
"대체로 좋았지만, 서비스가 별로였습니다. 추천하지 않습니다.",
"맛은 있지만 가격이 비쌌습니다. 가성비가 좋지 않습니다.",
"비싼 느낌이 있지만 좋았습니다."
]
test_token = make_tokenized(test_sentence)
preds = []
for test in tqdm(test_token):
pred = classifier.inference(test)
preds.append(pred)
for i in range(len(preds)):
print(test_sentence[i], preds[i], sep=' Predicted Class:')