Sentence Classifcation
import pandas as pd
import numpy as np
loading data
data = pd.read_csv('Sheet_1.csv')
data.head()
response_id | class | response_text | |
---|---|---|---|
0 | response_1 | not_flagged | I try and avoid this sort of conflict |
1 | response_2 | flagged | Had a friend open up to me about his mental ad... |
2 | response_3 | flagged | I saved a girl from suicide once. She was goin... |
3 | response_4 | not_flagged | i cant think of one really...i think i may hav... |
4 | response_5 | not_flagged | Only really one friend who doesn't fit into th... |
data 설명
data는 캐글의 여기에서 가져왔다.
치료 챗봇과 사람이 대화를 하는데 사람이 어떤 반응을 했냐에 따라 flagged가 될 수도 있고 not flagged가 될 수도 있다.
자세히는 모르겠지만, flagged가 되면 도움을 받으라고 챗봇이 메세지를 보낼 것이다.
우리가 풀어야 할 것은 test response가 주어졌을 때, flagged에 해당하는지 not flagged에 해당하는지 분류하는 것이다.
deleting needless colunms
data = data.drop(['Unnamed: 3', 'Unnamed: 4', 'Unnamed: 5', 'Unnamed: 6', 'Unnamed: 7'], axis=1)
x = data['response_text']
y = data['class']
term-existence를 이용한 문서간 유사도 구하기.
text class
# series를 list로 만든다.
x_list = x.tolist()
import string
# 단어들을 담을 set을 만들고 text를 공백기준으로 분리한 뒤, 해당 단어들의 punctuation을 없애고 소문자로 만든 뒤 words_set에 더해준다.
# set을 이용하는 이유는 중복을 제거해주기 위해서이다.
words_set = set()
for text in x_list:
words = text.split()
for word in words:
words_set.add(word.strip(string.punctuation).lower())
# 단어를 인덱싱한다.
words_dic = {w: i for i, w in enumerate(words_set)}
# ''는 아무의미가 없으므로 제거해준다.
del words_dic['']
# sklearn의 DictVectorizer를 써서 one hot vector를 만들기 위해서는 딕셔너리의 리스트를 만들 필요가 있다.
one_hot_dicts = []
for text in x_list:
words = text.split()
one_hot_dic = {}
for word in words:
word = word.strip(string.punctuation).lower()
if word in words_dic.keys():
one_hot_dic[word] = 1
one_hot_dicts.append(one_hot_dic)
x_list[0]
'I try and avoid this sort of conflict'
one_hot_dicts[0]
{'and': 1,
'avoid': 1,
'conflict': 1,
'i': 1,
'of': 1,
'sort': 1,
'this': 1,
'try': 1}
from sklearn.feature_extraction import DictVectorizer
# DictVectorizer를 써서 one hot vector를 만들어준다.
vec = DictVectorizer()
one_hot = vec.fit_transform(one_hot_dicts).toarray()
print('words dictionary length:', len(words_dic))
print('one_hot length: ', len(one_hot[0]))
print(one_hot[0])
words dictionary length: 675
one_hot length: 675
[ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0.]
train test split
sklearn의 train_test_split을 더 권장한다…
len(one_hot)
80
# 총 개수가 80개이므로 적당히 75개는 train으로 5개는 test로 한다.
x_train = one_hot[:75]
y_train = y[:75]
x_test = one_hot[75:]
y_test = y[75:]
밑의 과정은 x_train의 one_hot이 잘 됐는지 확인하는 것이다. (필요 없다고 생각하면 생략 가능)
index = []
for i in range(len(x_train[0])):
if x_train[0][i] == 1.:
index.append(i)
index
[34, 55, 120, 290, 409, 529, 594, 619]
for idx in index:
print(vec.get_feature_names()[idx], end= ' ')
and avoid conflict i of sort this try
x_list[0]
'I try and avoid this sort of conflict'
잘 된 걸 확인할 수 있다!
doc product?
- 두 행렬을 곱할 때, 같은 행과 같은 열끼리 곱해주는 연산이다.
- 예를 들면 행렬 A와 B가 있을 때, A의 1행1열과 B의 1행 1열을 곱해주는 식이다.
# one_hot 시킨 test response와 train response를 dot product한다.
# 비슷한 단어가 많을 수록 dot product 값이 클 것이다.
# 이전의 결과보다 크다면 현재 계산한 doc product의 결과를 추가해준다.
sim_test = []
for i in range(len(x_test)):
max_sum = 0.
max_idx = 0
for j in range(len(x_train)):
result = np.dot(x_test[i], x_train[j])
if result > max_sum:
max_sum = result
max_idx = j
sim_test.append('test{} -> train{}'.format(i+len(x_train), max_idx))
# 계속 비교해서 큰 걸 추가시켰으니, 맨 마지막에 있는 게 가장 큰 것이다.
# 가장 큰 거만 출력하고 싶은데 못하겠다 ㅠㅠ...
sim_test
['test75 -> train0',
'test75 -> train1',
'test75 -> train4',
'test75 -> train22',
'test75 -> train48',
'test76 -> train1',
'test76 -> train2',
'test76 -> train9',
'test76 -> train33',
'test76 -> train38',
'test77 -> train0',
'test77 -> train2',
'test77 -> train22',
'test77 -> train41',
'test78 -> train0',
'test78 -> train1',
'test78 -> train2',
'test78 -> train4',
'test78 -> train36',
'test78 -> train48',
'test79 -> train0',
'test79 -> train1',
'test79 -> train2',
'test79 -> train36',
'test79 -> train48']
# test와 가장 비슷하다고 나온 train의 결과값을 예측 list에 차례대로 추가해준다.
predicted_list = []
predicted_list.append(y_train[48])
predicted_list.append(y_train[38])
predicted_list.append(y_train[41])
predicted_list.append(y_train[48])
predicted_list.append(y_train[48])
과연…?
correct = 0
for i in range(len(y_test)):
if y_test[i+len(x_train)]==predicted_list[i]:
correct += 1
print(len(y_test), "개 중에", correct,"개 맞췄음.")
5 개 중에 3 개 맞췄음.
크~구린(?) 방법임에도 반 이상은 맞았다.
데이터 자체가 워낙 작으니 이런 방법을 쓸 수 있던 것이지 데이터가 크다면 시도하면 안 된다…!
모든 단어들의 크기가 one hot vector의 크기가 되기 때문이다.
TF-IDF를 이용한 문서간 유사도 구하기
TF-IDF ?
참고: 위키백과
- term frequency - inverse document frequency의 약자이다.
- 어떤 단어가 문서에서 얼마나 중요한지를 나타내는 통계적 수치.
- 문서의 핵심어, 문서 간의 유사도등을 구할 때 이용할 수 있다.
- 단어 빈도인 TF는 문서에서 단어가 얼마나 등장하는 지를 나타내는 값이다.
- 역문서 빈도인 IDF는 단어가 여러 문서에서 잘 나오지 않는 지를 나타내는 값이다. 만약 여러 문서에서 잘 나타나지 않는 단어라면 IDF 값은 높아질 것이다.
- 그리고 TF-IDF값은 이 두 값을 곱한 결과이다.
- 예를 들어 보자. A,B,C,D 문서가 있을 때 A 문서내에 있는 ybigta라는 단어의 TF-IDF 값이 크다면 ybigta라는 단어는 A문서 내에서 많이 등장하고 다른 문서에서는 잘 등장하지 않는 단어이다. 따라서 A문서의 고유한 특성이 될 수 있다…! 즉, A문서를 대표하는 값으로 쓸 수 있다는 말이다.
소스 코드 출처:
https://stackoverflow.com/questions/12118720/python-tf-idf-cosine-to-find-document-similarity
skleran
from sklearn.feature_extraction.text import TfidfVectorizer
vec = TfidfVectorizer()
x = vec.fit_transform(x_list)
x
<80x660 sparse matrix of type '<class 'numpy.float64'>'
with 1863 stored elements in Compressed Sparse Row format>
cosine similarity ?
- 간단하게 말하면 두 비교 대상이 얼마나 가까운지를 측정하는 방법이다.
- 자세하게 알고 싶다면 코사인 유사도를 참고하기 바란다.
from sklearn.metrics.pairwise import linear_kernel
# linear_kernel은 dot product이다.
# doc product를 통해 cosine similarity를 구한다.
cos_sim_list = []
for i in range(75,80):
cosine_sim = linear_kernel(x[i], x).flatten()
cos_sim_list.append(cosine_sim)
# test 문장과 가장 가까운 문장을 2개 뽑는다.
# argsort()는 가장 큰 값을 1으로 indexing하고 그 다음 큰 값을 2로 indexing하고 이를
# 이를 반복하는 메서드이다.
rel_doc_list = []
for i in range(len(cos_sim_list)):
related_doc_idx = cos_sim_list[i].argsort()[:-3:-1]
rel_doc_list.append(related_doc_idx)
# 2개를 뽑은 이유는 test역시 cosine similarity를 구할 때 고려됐으므로, 똑같은 문장
# 다음에 큰 값이 필요하기 때문이다.
rel_doc_list
[array([75, 48], dtype=int64),
array([76, 38], dtype=int64),
array([77, 32], dtype=int64),
array([78, 57], dtype=int64),
array([79, 63], dtype=int64)]
# 가장 유사한 문장을 추가해준다.
most_similar_doc = []
most_similar_doc.append(y_train[48])
most_similar_doc.append(y_train[38])
most_similar_doc.append(y_train[32])
most_similar_doc.append(y_train[57])
most_similar_doc.append(y_train[63])
correct = 0
for i in range(len(y_test)):
if y_test[i+len(x_train)]==most_similar_doc[i]:
correct += 1
print(len(y_test), "개 중에", correct,"개 맞췄음.")
5 개 중에 4 개 맞췄음.
확실히 단순하게 one-hot vector를 이용하는 것보다 좋은 결과가 나왔다.
그리고 문서내에서 키워드를 추출하거나 비교를 할 때 tf-idf를 많이 이용하는 것 같다!
Conclusion
- term-existence보다는 tf-idf를 이용하자~
Reference
- https://www.kaggle.com/samdeeplearning/deepnlp
- https://www.lucypark.kr/slides/2015-pyconkr/#13
- https://ko.wikipedia.org/wiki/TF-IDF
- https://stackoverflow.com/questions/12118720/python-tf-idf-cosine-to-find-document-similarity
Leave a comment