from nltk.tokenize import word_tokenize
import nltk
train = [('i like you', 'pos'), ('i hate you', 'neg'),
('you like me', 'neg'), ('i like her', 'pos')]
# 말 뭉치 만들기
all_words = set(word.lower() for sentence in train
for word in word_tokenize(sentence[0]))
all_words
>>>
{'hate', 'her', 'i', 'like', 'me', 'you'}
앞에 나오는 for문부터 실행된다.
# 각 문장 속에 말 뭉치의 단어가 있는지 확인
t = [({word: (word in word_tokenize(x[0])) for word in all_words}, x[1])
for x in train]
t
>>>
[({'her': False,
'i': True,
'like': True,
'you': True,
'me': False,
'hate': False},
'pos'),
({'her': False,
'i': True, ...
clf = nltk.NaiveBayesClassifier.train(t)
clf.show_most_informative_features()
>>>
Most Informative Features
hate = False pos : neg = 1.7 : 1.0
her = False neg : pos = 1.7 : 1.0
i = True pos : neg = 1.7 : 1.0
like = True pos : neg = 1.7 : 1.0
me = False pos : neg = 1.7 : 1.0
you = True neg : pos = 1.7 : 1.0
임의의 문장으로 테스트해보면,
test_sentence = 'i like Mery'
test_sent_features = {
word.lower(): (word in word_tokenize(test_sentence.lower())) for word in all_words
}
clf.classify(test_sent_features)
>>>
'pos'
한글은 형태소 분석을 거쳐야 제대로 된 결과를 얻을 수 있다.
from konlpy.tag import Okt
train = [('메리가 좋아', 'pos'),
('고양이도 좋아', 'pos'),
('난 수업이 지루해', 'neg'),
('메리는 예쁜 고양이야', 'pos'),
('난 마치고 메리랑 놀거야', 'pos')]
# 형태소 분석하여 단어 뒤에 품사 붙이기
tagger = Okt()
def tokenize(doc):
# norm : 문장 정규화 여부
# stem : 단어에서 어간 추출 여부
# join() : 리스트나 튜플 내의 문자열들을 이어 붙일 때
return ['/'.join(t) for t in tagger.pos(doc, norm=True, stem=True)]
train_pos = [(tokenize(row[0]), row[1]) for row in train]
train_pos
>>>
[(['메리/Noun', '가/Josa', '좋다/Adjective'], 'pos'),
(['고양이/Noun', '도/Josa', '좋다/Adjective'], 'pos'), ...
🍳 어떤 과정으로?
[t for t in tagger.pos(train[0][0], norm=True, stem=True)]
[('메리', 'Noun'), ('가', 'Josa'), ('좋다', 'Adjective')]
➡
['/'.join(t) for t in tagger.pos(train[0][0], norm=True, stem=True)]
['메리/Noun', '가/Josa', '좋다/Adjective']
➡
[(tokenize(train[0][0]), train[0][1])]
[(['메리/Noun', '가/Josa', '좋다/Adjective'], 'pos')]
코드를 분해해서 실행해보니 이해가 된다.
형태소 분석까지 마쳤으니 영문 분석과 동일하게 말 뭉치를 만든다.
# 말 뭉치 만들기
all_words = [t for d in train_pos for t in d[0]]
all_words
# 각 문장 속에 말 뭉치의 단어가 있는지 확인
def term_exists(doc):
return {word: (word in set(doc)) for word in all_words}
train_xy = [(term_exists(d), c) for d, c in train_pos]
train_xy
>>>
[({'메리/Noun': True,
'가/Josa': True, ...
'놀다/Verb': False},
'pos'),
({'메리/Noun': False, ...
clf = nltk.NaiveBayesClassifier.train(train_xy)
clf.show_most_informative_features()
>>>
Most Informative Features
난/Noun = True neg : pos = 2.5 : 1.0
메리/Noun = False neg : pos = 2.5 : 1.0
고양이/Noun = False neg : pos = 1.5 : 1.0
좋다/Adjective = False neg : pos = 1.5 : 1.0
가/Josa = False neg : pos = 1.1 : 1.0
고/Josa = False neg : pos = 1.1 : 1.0
놀다/Verb = False neg : pos = 1.1 : 1.0
는/Josa = False neg : pos = 1.1 : 1.0
도/Josa = False neg : pos = 1.1 : 1.0
랑/Josa = False neg : pos = 1.1 : 1.0
임의의 문장으로 테스트해보면,
test_sentence = [('난 수업이 마치면 메리랑 놀거야')]
test_pos = tagger.pos(test_sentence[0])
test_pos
>>>
[('난', 'Noun'),
('수업', 'Noun'),
('이', 'Josa'),
('마치', 'Noun'),
('면', 'Josa'),
('메리', 'Noun'),
('랑', 'Josa'),
('놀거야', 'Verb')]
test_sent_features = term_exists(test_docs)
clf.classify(test_sent_features)
>>>
'pos'
문장을 벡터로 변환할 수 있다면 두 문장 사이의 거리를 통해 유사한 문장을 찾을 수 있다.
from sklearn.feature_extraction.text import CountVectorizer
from konlpy.tag import Okt
# 문장을 벡터로 변환하는 기능
vectorizer = CountVectorizer(min_df=1)
t = Okt()
contents = ['상처받은 아이들은 너무 일찍 커버려',
'내가 상처받은 거 아는 사람 불편해',
'잘 사는 사람들은 좋은 사람 되기 쉬워',
'아무 일도 아니야 괜찮아']
contents_tokens = [t.morphs(row) for row in contents]
contents_tokens
>>>
[['상처', '받은', '아이', '들', '은', '너무', '일찍', '커버', '려'], ...
CountVectorize는 띄어쓰기로 구분하기 때문에(영어 기준) 리스트(contents_tokens)를 다시 합쳐서 문장으로 만들어준다. 형태소로 구분된 문장을 만드는 것!
contents_for_vectorize = []
for content in contents_tokens:
sentence = ''
for word in content:
sentence = sentence + ' ' + word
contents_for_vectorize.append(sentence)
contents_for_vectorize
>>>
[' 상처 받은 아이 들 은 너무 일찍 커버 려', ...
X = vectorizer.fit_transform(contents_for_vectorize)
num_samples, num_features = X.shape
num_samples, num_features
>>>
(4, 17)
vectorizer.get_feature_names_out()
X.toarray()
test_sentence = ['상처받기 싫어 괜찮아']
test_sent_tokens = [t.morphs(row) for row in test_sentence]
test_sent_for_vectorize = []
for content in test_sent_tokens:
sentence = ''
for word in content:
sentence = sentence + ' ' + word
test_sent_for_vectorize.append(sentence)
test_sent_for_vectorize
>>>
[' 상처 받기 싫어 괜찮아']
test_X = vectorizer.transform(test_sent_for_vectorize)
test_X.toarray()
>>>
array([[1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int64)
앞서 만든 말 뭉치에 없는 단어는 포함되지 않는다.
import scipy as sp
# 기하학적 거리 구하기
def dist_raw(v1, v2):
delta = v1 - v2
return sp.linalg.norm(delta.toarray())
dist = [dist_raw(each, test_X) for each in X]
dist
>>>
[2.449489742783178, 2.23606797749979, 3.1622776601683795, 2.0]
print('Best is', dist.index(min(dist)), 'dist =', min(dist))
print('Test sentence is', test_sentence)
print('Best sentence is', contents[dist.index(min(dist))])
>>>
Best is 3 dist = 2.0
Test sentence is ['상처받기 싫어 괜찮아']
Best sentence is 아무 일도 아니야 괜찮아
for i in range(0, len(contents)):
print(X.getrow(i).toarray())
print('-' * 37)
print(test_X.toarray())
>>>
[[0 1 0 1 0 0 0 1 0 0 0 0 1 0 1 0 1]]
[[0 0 0 1 1 0 1 1 0 1 0 0 0 0 0 0 0]]
[[0 0 1 0 0 1 2 0 1 0 0 0 0 0 0 1 0]]
[[1 0 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0]]
-------------------------------------
[[1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0]]
벡터의 크기를 맞춰주기 위해서도 말 뭉치가 필요하다.
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer(min_df=1, decode_error='ignore')
X = vectorizer.fit_transform(contents_for_vectorize)
X.toarray().transpose()
>>>
array([[0. , 0. , 0. , 0.5 ],
[0.43671931, 0. , 0. , 0. ],
[0. , 0. , 0.39264414, 0. ],
[0.34431452, 0.40104275, 0. , 0. ], ...
test_X = vectorizer.transform(test_sent_for_vectorize)
# 정규화시켜서 기하학적 거리 구하기
def dist_norm(v1, v2):
v1_normalized = v1 / sp.linalg.norm(v1.toarray())
v2_normalized = v2 / sp.linalg.norm(v1.toarray())
delta = v1_normalized - v2_normalized
return sp.linalg.norm(delta.toarray())
dist
>>>
[1.254451632446019, 1.2261339938790283, 1.414213562373095, 1.1021396119773588]
두 벡터의 크기를 1로 변경한 후 거리를 측정하는 것이다.
이렇게 하면 한쪽 특성이 과하게 두드러지는 것을 방지할 수 있다.
print('Best is', dist.index(min(dist)), 'dist =', min(dist))
print('Test sentence is', test_sentence)
print('Best sentence is', contents[dist.index(min(dist))])
>>>
Best is 3 dist = 1.1021396119773588
Test sentence is ['상처받기 싫어 괜찮아']
Best sentence is 아무 일도 아니야 괜찮아
import urllib.request
import json
import datetime
def gen_search_url(api_node, search_text, start_num, disp_num):
base = "https://openapi.naver.com/v1/search"
node = "/" + api_node + ".json"
param_query = "?query=" + urllib.parse.quote(search_text)
param_start = "&start=" + str(start_num)
param_disp = "&display=" + str(disp_num)
return base + node + param_query + param_start + param_disp
def get_result_onpage(url):
request = urllib.request.Request(url)
request.add_header("X-Naver-Client-Id",client_id)
request.add_header("X-Naver-Client-Secret",client_secret)
response = urllib.request.urlopen(request)
print("[%s] Url Request Success." % datetime.datetime.now())
return json.loads(response.read().decode("utf-8"))
url = gen_search_url('kin', '파이썬', 1, 100)
one_result = get_result_onpage(url)
one_result['items'][0]['description']
>>>
'각각 인원을 분배해 <b>파이썬</b> ...
def delete_tag(input_str):
input_str = input_str.replace('<b>', '')
input_str = input_str.replace('</b>', '')
return input_str
def get_description(pages):
contents = []
for sentences in pages['items']:
contents.append(delete_tag(sentences['description']))
return contents
from sklearn.feature_extraction.text import CountVectorizer
from konlpy.tag import Okt
t = Okt()
vectorizer = CountVectorizer(min_df=1)
contents = get_description(one_result)
contents_tokens = [t.morphs(row) for row in contents]
contents_for_vectorize = []
for content in contents_tokens:
sentence = ''
for word in content:
sentence = sentence + ' ' + word
contents_for_vectorize.append(sentence)
X = vectorizer.fit_transform(contents_for_vectorize)
test_sentence = ['파이썬 독학하는 방법 알려주세요']
test_sent_tokens = [t.morphs(row) for row in test_sentence]
test_sent_for_vectorize = []
for content in test_sent_tokens:
sentence = ''
for word in content:
sentence = sentence + ' ' + word
test_sent_for_vectorize.append(sentence)
test_X = vectorizer.transform(test_sent_for_vectorize)
dist = [dist_raw(each, test_X) for each in X]
print('Best is', dist.index(min(dist)), 'dist =', min(dist))
print('Test sentence is', test_sentence)
print('Best sentence is', contents[dist.index(min(dist))])
테스트 문장을 바꿔도 계속 같은 best sentence가 나온다.
수치가 달라지긴 하는데... 다시 확인해볼 필요가 있겠다!