일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 기술통계학
- 결정계수
- 군집화
- 밑바닥부터 시작하는 딥러닝2
- 오래간만에 글쓰네
- 텍스트 분류
- 다층 퍼셉트론
- 머신러닝
- student t분포
- 최소자승법
- 자연어 처리
- rnn
- Django
- 회귀분석
- word2vec
- 코사인 유사도
- 차원축소
- Pandas
- 파이썬 pandas
- 텐서플로2와 머신러닝으로 시작하는 자연어처리
- 가설검정
- 감성분석
- 모두의 딥러닝
- numpy
- 구글 BERT의 정석
- F분포
- 기초통계
- 히스토그램
- 밑바닥부터 시작하는 딥러닝
- 은준아 화이팅
- Today
- Total
데이터 한 그릇
텍스트 유사도 본문
텍스트 유사도
- 쿼라 데이터 사용(질의 응답 데이터)
- 비슷한 질문끼리 분류할 수 있다면, 미리 잘 작성된 답변을 중복 사용할 수 있게 된다.
- 질문들이 서로 유사한지 유사하지 않은지 유사도를 판별해야 한다.
데이터 EDA
데이터 불러오기
!kaggle competitions download -c quora-question-pairs
import numpy as np
import pandas as pd
import os
import matplotlib.pyplot as plt
import seaborn as sns
from pathlib import Path
%matplotlib inline
DATA_IN_PATH = './data_in/'
train_data = pd.read_csv(DATA_IN_PATH + 'train.csv')
train_data.head()
캐글 API 이용해서 데이터 불러오기
다른 데이터 유형과 달리 평가 데이터가 훈련 데이터보다 더 크다.
이는 평가 데이터에 컴퓨터가 임의로 만든 데이터를 집어 넣었기 떄문이다.
데이터 전체 길이 및 중복 데이터 개수
train_set = pd.Series(train_data['question1'].tolist() + train_data['question2'].tolist()).astype(str)
train_set.head()
데이터는 두 질문이 유사한지 판별하기 위해서 질문에 대한 데이터를 question1, question2 칼럼으로 보관하고 있다.
이 두 데이터를 합쳐서 전체 데이터의 길이 및 중복된 데이터가 몇 개 있는지 살펴보자.
print('교육 데이터의 총 질문 수 : {}'.format(len(np.unique(train_set))))
print('반복해서 나타나는 질문의 수 : {}'.format(np.sum(train_set.value_counts() > 1)))
unique 를 통해서 개별적인 질문들이 몇 개 있는지 파악하고,
value_counts를 사용해서 1개 이상이면 중복된걸로 간주하고 그 개수를 np.sum으로 합쳐서 중복된 개수를 가져온다.
교육 데이터의 총 질문 수 : 537361
반복해서 나타나는 질문의 수 : 111873
plt.figure(figsize= (12,5))
plt.hist(train_set.value_counts(), bins = 50, alpha = 0.5, color = 'r', label = 'word')
plt.yscale('log', nonposy = 'clip')
plt.title('Log-Histogram of question appearance counts')
plt.xlabel('Numbeer of occurrences of question')
plt.ylabel('Number of questions')
중복 질문의 빈도수를 히스토그램으로 그려서 확인하기
print('중복 최대 개수 : {}'.format(np.max(train_set.value_counts())))
print('중복 최소 개수 : {}'.format(np.min(train_set.value_counts())))
print('중복 평균 개수 : {:.2f}'.format(np.mean(train_set.value_counts())))
print('중복 표준편차 : {:.2f}'.format(np.std(train_set.value_counts())))
print('중복 중간길이 : {}'.format(np.median(train_set.value_counts())))
print('제1사분위 중복 : {}'.format(np.percentile(train_set.value_counts(),25)))
print('제 3사분위 중복 : {}'.format(np.percentile(train_set.value_counts(),75)))
기초 통계량 얻기
plt.figure(figsize = (12,5))
plt.boxplot([train_set.value_counts()],
labels = ['counts'],
showmeans = True)
박스플롯을 그려서 기초 통계량 시각화.
이상치가 많이 등장한다.
데이터에 많이 등장한 단어
from wordcloud import WordCloud
cloud = WordCloud(width = 800, height = 600).generate(" ".join(train_set.astype(str)))
plt.figure(figsize = (15,10))
plt.imshow(cloud)
plt.axis('off')
wordcloud 를 이용해서 확인한다.
fig, axs = plt.subplots(ncols = 1
)
fig.set_size_inches(6,3)
sns.countplot(train_data['is_duplicate'])
train_data['is_duplicate'].value_counts()
is_duplicate 칼럼은 question1 과 question2 문장이 서로 중복된 문장인지 아닌지에 대한 정답 레이블이다.
counplot 을 통해서 레이블의 숫자를 시각화한다.
중복이 아닌 데이터가 255027 개
중복인 데이터가 149263 개이다.
레이블 불균형의 문제를 가지고 있는데, 최대한 라벨의 개수를 맞추는 게 모델의 성능에 좋다.
1. 데이터를 줄여서 라벨의 개수를 맞출 수 있고
2. 데이터를 늘려서 라벨의 개수를 맞출 수 있다.
텍스트 데이터의 길이
train_length = train_set.apply(len)
train_length
각 행마다의 텍스트 길이를 train_length 함수에 저장한다.
plt.figure(figsize = (15,10))
plt.hist(train_length, bins = 200, range = [0,200], facecolor = 'r',label = 'train')
plt.title('Normalised histogram of character count in questions', fontsize = 15)
plt.legend()
plt.xlabel('Number of charecters', fontsize = 15)
plt.ylabel('Probability', fontsize = 15)
이를 히스토그램으로 시각화한다.
plt.figure(figsize = (12,5))
plt.boxplot(train_length,
labels = ['char counts'])
showmeans = True
박스플롯.
다음으로 단어의 개수를 확인해보자.
train_words_counts = train_set.apply(lambda x : len(x.split(' ')))
split함수로 각 행마다 단어별로 구분하고 그 개수를 세어줘서 train_words_counts 변수에 저장해준다.
plt.figure(figsize = (15,10))
plt.hist(train_words_counts, bins = 50, range = [0,50], facecolor = 'r', label = 'train')
plt.title('Normalised histogram of word count in questions', fontsize = 15)
plt.legend()
plt.xlabel('Number of words', fontsize = 15)
plt.ylabel('Probability', fontsize = 15)
train_words_counts 를 히스토그램으로 시각화한다.
plt.figure(figsize = (12,5))
plt.boxplot(train_words_counts,
labels = ['word counts'],
showmeans = True)
박스플롯.
특수문자 중 구두점, 물음표, 마침표가 사용된 비율과 수학 기호가 사용된 비율, 대/소문자 비율 확인
qmarks = np.mean(train_set.apply(lambda x : '?' in x)) #물음표가 구두점으로 쓰임
math = np.mean(train_set.apply(lambda x : '[math]' in x)) #[]
fullstop = np.mean(train_set.apply(lambda x : '.' in x)) #마침표
capital_first = np.mean(train_set.apply(lambda x: x[0].isupper())) #첫 대문자
capitals = np.mean(train_set.apply(lambda x :max([y.isupper() for y in x]))) #대문자가 몇 개?
print('물음표가 있는 질문 : {:.2f}%'.format(qmarks * 100))
print('수학수식이 있는 질문 : {:.2f}%'.format(math * 100))
print('마침표가 있는 질문 : {:.2f}%'.format(fullstop * 100))
print('첫 대문자인 질문 : {:.2f}%'.format(capital_first * 100))
print('대문자의 개수 : {:.2f}%'.format(capitals * 100))
train_set 각각에 apply lambda 함수를 사용해서 구하고 싶은 조건에 부합한지 부합하지 않은지를 판별해서 평균들을 구한다.
데이터 전처리
import pandas as npd
import numpy as np
import re
import json
DATA_IN_PATH = './data_in/'
train_data = pd.read_csv(DATA_IN_PATH + 'train.csv', encoding = 'utf-8')
데이터를 불러온다.
클래스 불균형 해소
1. 중복인 데이터와 중복이 아닌 데이터로 나누기
2. 중복이 아닌 개수가 비슷하도록 데이터의 일부를 다시 뽑는다.
train_pos_data = train_data.loc[train_data['is_duplicate'] == 1] #중복인 데이터
train_neg_data = train_data.loc[train_data['is_duplicate'] == 0] #중복이 아닌 데이터
class_difference = len(train_neg_data) - len(train_pos_data) #두 변수의 길이의 차
sample_frac = 1 - (class_difference / len(train_neg_data)) #적은 데이터의 개수와 많은 데이터의 개수의 비율을 구한다.
#적은 데이터의 개수가 많은 데이터에 대한 비율을 계산.
train_neg_data = train_neg_data.sample(frac = sample_frac) #더 많은 데이터에서 더 적은 데이터 비율만큼만 뽑기
print('중복 질문 개수 : ',len(train_pos_data))
print('중복이 아닌 질문 개수 : ',len(train_neg_data))
앞에서 is_duplicate 칼럼이 중복인지 중복이 아닌지에 관한 칼럼임을 살펴봤다.
중복인 데이터와 중복이 아닌 데이터들을 따로 구분해서 train_pos_data와 train_neg_data 에 따로 저장해둔다.
그리고 적은 데이터의 개수와 많은 데이터의 개수의 비율을 구하고 sample_frac 변수에 저장한다.
이 비율을 이용해서 더 개수가 많은 데이터에서 개수가 적은 데이터의 비율만큼 샘플을 뽑는다.
그리고 중복인 데이터의 개수와 중복이 아닌 데이터의 개수를 살펴본다.
특수문자 제거 및 소문자 변환
# change_filter = re.compile(FILTERS)
question1 = [str(s) for s in train_data['question1']]
question2 = [str(s) for s in train_data['question2']]
filtered_question1 = list()
filtered_question2 = list()
for q in question1:
filtered_question1.append(re.sub('[^a-zA-Z]'," ",q).lower())
for q in question2:
filtered_question2.append(re.sub('[^a-zA-Z]'," ",q).lower())
정규화 식을 이용해서 question1 과 question2 데이터에서 특수문자들을 제거한다.
단어 token 화
from tensorflow.keras.preprocessing.text import Tokenizer
tokenizer = Tokenizer()
tokenizer.fit_on_texts(filtered_question1 + filtered_question2) #전체 질문의 단어들에 대한 단어id 사전 만들기
question1_sequence = tokenizer.texts_to_sequences(filtered_question1) #문장을 단어id 시퀀스 데이터로 만듬
question2_sequence = tokenizer.texts_to_sequences(filtered_question2)
tensorflow Tokenizer 를 이용해서 텍스트 단어들에 id를 붙이고 단어 id 시퀀스들을 만든다.
입력 데이터 길이 통일
from tensorflow.keras.preprocessing.sequence import pad_sequences
MAX_SEQUENCE_LENGTH = 31
q1_data = pad_sequences(question1_sequence, maxlen = MAX_SEQUENCE_LENGTH, padding = 'post')
q2_data = pad_sequences(question2_sequence, maxlen = MAX_SEQUENCE_LENGTH, padding = 'post')
입력 데이터가 모델에 들어가려면 크기가 통일되어야 하기 때문에 pad_sequences 로 길이를 맞춘다.
단어 id 사전
word_vocab = {}
word_vocab = tokenizer.word_index
word_vocab["<PAD>"] = 0
labels = np.array(train_data['is_duplicate'], dtype = int)
print('Shape of question1 data : {}'.format(q1_data.shape))
print('Shape of question2 data : {}'.format(q2_data.shape))
print('Shape of label : {}'.format(labels.shape))
print('Words in index : {}'.format(len(word_vocab)))
tokenizer의 word_index 함수를 이용해서 단어 id 사전을 저장해둔다.
전처리된 데이터들 저장(label, 단어 id사전 포함)
TRAIN_01_DATA = 'q1_train.npy'
TRAIN_02_DATA = 'q2_train.npy'
TRAIN_LABEL_DATA = 'label_train.npy'
DATA_CONFIGS = 'data_configs.npy'
np.save(open(DATA_IN_PATH + TRAIN_01_DATA, 'wb'), q1_data) #q1데이터 저장
np.save(open(DATA_IN_PATH + TRAIN_02_DATA,'wb'),q2_data) #q2 데이터 저장
np.save(open(DATA_IN_PATH + TRAIN_LABEL_DATA,'wb'), labels) #라벨 데이터 저장
json.dump(data_configs, open(DATA_IN_PATH + DATA_CONFIGS,'w')) #단어 id 사전 저장
test 데이터 전처리
test_data = pd.read_csv(DATA_IN_PATH + 'test.csv', encoding = 'utf-8')
valid_ids = [type(x) == int for x in test_data.test_id]
test_data = test_data[valid_ids].drop_duplicates()
test_question1 = [str(s) for s in test_data['question1']]
test_question2 = [str(s) for s in test_data['question2']]
filtered_test_question1 = list()
filtered_test_question2 = list()
for q in test_question1:
filtered_test_question1.append(re.sub("[^a-zA-Z]"," ", q).lower())
for q in test_question2:
filtered_test_question2.append(re.sub("[^a-zA-Z]"," ",q).lower())
#train 에서 만들었던 단어 인덱스를 그대로 사용해야 한다. (tokenizer 객체 그대로 사용)
test_question1_sequence = tokenizer.texts_to_sequences(filtered_test_question1)
test_question2_sequence = tokenizer.texts_to_sequences(filtered_test_question2)
test_q1_data = pad_sequences(test_question1_sequence, maxlen = MAX_SEQUENCE_LENGTH,
padding = "post")
test_q2_data = pad_sequences(test_question2_sequence, maxlen = MAX_SEQUENCE_LENGTH,
padding = "post")
train 데이터와 마찬가지로 전처리 진행
test 데이터 저장
TEST_Q1_DATA = 'test_q1.npy'
TEST_Q2_DATA = 'test_q2.npy'
TEST_ID_DATA = 'test_id.npy'
np.save(open(DATA_IN_PATH + TEST_Q1_DATA,'wb'), test_q1_data)
np.save(open(DATA_IN_PATH + TEST_Q2_DATA, 'wb'), test_q2_data)
np.save(open(DATA_IN_PATH + TEST_ID_DATA,'wb'), test_id)
모델링
XG부스트 모델
대략적으로 앙상블의 정의와 종류에 대해서 살펴보자.
앙상블은 여러 모델들을 합친 모델을 의미한다.
- 배깅 : 같은 알고리즘의 모델들 여러 개를 합치고 각각의 결과들을 취합하여 최종 결론을 내는 유형(하드 보팅, 소프트 보팅)
- 부스팅 : 배깅과 똑같이 여러 알고리즘의 결과를 취합하지만 순차적으로 취합하면서, 이전 모델이 잘못 예측한 부분에 대해서 가중치를 줘서 다시 모델로 가서 학습하는 방식.
- XG부스팅 : 부스팅 기법 중, 트리 기반 부스팅 기법. 여러 개의 트리 알고리즘을 사용하지만 단순히 결과를 평균 내는 게 아니라, 결과를 보고 오답에 대해서 가충치를 부여하는 방식으로 학습. 즉, xg부스팅은 트리 부스팅 방식에 경사 하강법을 통해서 최적화하는 방식
전처리된 데이터 불러오기
import numpy as np
DATA_IN_PATH = './data_in/'
TRAIN_Q1_DATA_FILE = 'q1_train.npy'
TRAIN_Q2_DATA_FILE = 'q2_train.npy'
TRAIN_LABEL_DATA_FILE = 'label_train.npy'
#훈련 데이터를 가져온다.
train_q1_data = np.load(open(DATA_IN_PATH + TRAIN_Q1_DATA_FILE, 'rb'))
train_q2_data = np.load(open(DATA_IN_PATH + TRAIN_Q2_DATA_FILE, 'rb'))
train_labels = np.load(open(DATA_IN_PATH + TRAIN_LABEL_DATA_FILE, 'rb'))
train_input = np.stack([train_q1_data, train_q2_data], axis = 1)
train_input.shape
데이터 분리
from sklearn.model_selection import train_test_split
train_input, eval_input, train_label, eval_label = train_test_split(train_input, train_labels, test_size = 0.2, random_state = 4242)
학습 데이터와 검증 데이터 DMatrix 화 하기
import xgboost as xgb
#xgboost 를 사용하려면 입력값을 DMatrix 형태로 변환해줘야 한다.
#학습 데이터와 검증 데이터 모두 적용한다.
#sum 을 해주는 이유는 각 데이터에 대해 두 질문의 값을 하나의 값으로 만들어 주기 위함임 (array 두 개가 31 차원을 공유하고 있다.)
train_data = xgb.DMatrix(train_input.sum(axis = 1), label = train_label)
eval_data = xgb.DMatrix(eval_input.sum(axis = 1), label = eval_label)
data_list = [(train_data, 'train'),(eval_data,'valid')]
xgboost 를 사용하려면 입력값을 DMatrix 형태로 변환해줘야 한다.
학습 데이터와 검증 데이터 모두 적용한다.
sum 을 해주는 이유는 각 데이터에 대해 두 질문의 값을 하나의 값으로 만들어 주기 위함임 (array 두 개가 31 차원을 공유하고 있다.)
모델을 만들고 파라미터 값 설정하기
params = {}
params['objective'] = 'binary:logistic' #모델의 목적 함수. 결과는 내기 위한 함수.
params['eval_metric'] = 'rmse' #경사하강법을 사용한다니까 그런듯. 평가 지표.
#num boost round 는 데이터를 반복 학습하는 횟수
#early stopping rounds 는 성능이 그대로이거나 나빠진게 10번이상일떄 학습을 조기 멈추게 해준다.
#epochs = 1000 은 1000번 이걸 반복한다는 의미.
bst = xgb.train(params, train_data, num_boost_round = 1000, evals = data_list,
early_stopping_rounds = 10)
목적 함수를 binary logistic 함수 사용.
평가 지표를 rmse 를 사용.
Test 데이터 불러오기
TEST_Q1_DATA_FILE = 'test_q1.npy'
TEST_Q2_DATA_FILE = 'test_q2.npy'
TEST_ID_DATA_FILE = 'test_id.npy'
test_q1_data = np.load(open(DATA_IN_PATH + TEST_Q1_DATA_FILE,'rb'))
test_q2_data = np.load(open(DATA_IN_PATH + TEST_Q2_DATA_FILE,'rb'))
test_id_data = np.load(open(DATA_IN_PATH + TEST_ID_DATA_FILE,'rb'), allow_pickle = True)
Test 데이터 DMatrix 변환 후 예측 결과값 내기
test_input = np.stack((test_q1_data, test_q2_data), axis = 1)
test_data = xgb.DMatrix(test_input.sum(axis = 1))
test_predict = bst.predict(test_data)
CNN 모델
감성 분석(텍스트 분류)에서의 CNN과 유사하지만, 이번 데이터는 각 텍스트 문서가 두 개로 되어 있기 때문에 두 데이터를 병렬적인 구조를 가진 모델을 만든다.
기준 문장 : 기준이 되는 문장
대상 문장 : 대상이 되는 문장
예) I love deep NLP 를 기준문장으로 두고 그것과 비교할 대상을 Deep NLP is awesome 으로 두고 유사한지 유사하지 않은지 비교한다.
데이터 불러오기
DATA_IN_PATH = './data_in/'
DATA_OUT_PATH = './data_out/'
TRAIN_Q1_DATA_FILE = 'q1_train.npy'
TRAIN_Q2_DATA_FILE = 'q2_train.npy'
TRAIN_LABEL_DATA_FILE = 'label_train.npy'
DATA_CONFIGS = 'data_configs.npy'
q1_data = np.load(open(DATA_IN_PATH + TRAIN_Q1_DATA_FILE, 'rb'))
q2_data = np.load(open(DATA_IN_PATH + TRAIN_Q2_DATA_FILE, 'rb'))
labels = np.load(open(DATA_IN_PATH + TRAIN_LABEL_DATA_FILE, 'rb'))
prepro_configs = json.load(open(DATA_IN_PATH + DATA_CONFIGS , 'r'))
Sentence Embedding layer 구현
import tensorflow as tf
class SentenceEmbedding(tf.keras.layers.Layer):
def __init__(self, **kargs):
super(SentenceEmbedding, self).__init__()
self.conv = tf.keras.layers.Conv1D(kargs['conv_num_filters'], kargs['conv_window_size'],
activation = tf.keras.activations.relu,
padding = 'same')
self.max_pool = tf.keras.layers.MaxPool1D(kargs['max_pool_seq_len'],1)
self.dense = tf.keras.layers.Dense(kargs['sent_embedding_dimension'],
activation = tf.keras.activations.relu)
def call(self, x):
x = self.conv(x)
x = self.max_pool(x)
x = self.dense(x)
return tf.squeeze(x, 1)
문장을 하나의 벡터로.
필터의 개수와 윈도의 크기를 파라미터로 지정.
각 필터마다 벡터가 생성이 됨. 만일 필터가 3개면 3개의 벡터가 생성이 됨.
맥스 풀링 해서 생긴 벡터를 합쳐서 dense 계층에 전달.
Sentence Similarity layer 구현
class SentenceSimilarityModel(tf.keras.Model):
def __init__(self, **kargs):
super(SentenceSimilarityModel,self).__init__(name =kargs['model_name'])
self.word_embedding = tf.keras.layers.Embedding(kargs['vocab_size'],
kargs['word_embedding_dimension'])
self.base_encoder = SentenceEmbedding(**kargs) #기존 문장
self.hypo_encoder = SentenceEmbedding(**kargs) #비교할 대상 문장
self.dense = tf.keras.layers.Dense(kargs['hidden_dimension'],
activation = tf.keras.activations.relu)
self.logit = tf.keras.layers.Dense(1, activation= tf.keras.activations.sigmoid)
self.dropout = tf.keras.layers.Dropout(kargs['dropout_rate'])
def call(self, x):
x1, x2 = x
b_x = self.word_embedding(x1)
h_x = self.word_embedding(x2)
b_x = self.dropout(b_x)
h_x = self.dropout(h_x)
b_x = self.base_encoder(b_x)
h_x = self.hypo_encoder(h_x)
e_x = tf.concat([b_x,h_x],-1)
e_x = self.dense(e_x)
e_x = self.dropout(e_x)
return self.logit(e_x)
기준 문장과 비교 대상 문장을 각각 Sentence Embedding 해준다.
concat 으로 합쳐주고, 그걸 dense 계층에 전달한다.
모델 하이퍼파라미터 정의
model_name = 'cnn_similarity'
BATCH_SIZE = 1024
NUM_EPOCHS = 100
VALID_SPLIT = 0.1
MAX_LEN = 31
kargs = {'model_name' : model_name,
'vocab_size' : prepro_configs['vocab_size'],
'word_embedding_dimension' : 100,
'conv_num_filters' : 300,
'conv_window_size' : 3,
'max_pool_seq_len' : MAX_LEN,
'sent_embedding_dimension' : 128,
'dropout_rate' : 0.2,
'hidden_dimension' : 200,
'output_dimension' : 1}
하이퍼 파라미터를 kargs 에 저장해둔다.
model = SentenceSimilarityModel(**kargs)
model.compile(optimizer = tf.keras.optimizers.Adam(1e-3),
loss = tf.keras.losses.BinaryCrossentropy(),
metrics = [tf.keras.metrics.BinaryAccuracy(name = 'accuracy')])
모델을 생성하고 학습을 정의내린다.
모델 학습
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
earlystop_callback = EarlyStopping(monitor = 'val_accuracy', min_delta = 0.0001, patience = 1)
checkpoint_path = DATA_IN_PATH + model_name + '/weights.h5'
checkpoint_dir = os.path.dirname(checkpoint_path)
if os.path.exists(checkpoint_dir):
print('{} -- Folder already exists \n'.format(checkpoint_dir))
else:
os.makedirs(checkpoint_dir, exist_ok = True)
print('{} -- Folder create complete \n'.format(checkpoint_dir))
cp_callback = ModelCheckpoint(checkpoint_path,
monitor = 'val_accuracy',
verbose = 1,
save_best_only = True,
save_weights_only = True)
history = model.fit((q1_data, q2_data), labels, batch_size = BATCH_SIZE, epochs = NUM_EPOCHS,
validation_split = VALID_SPLIT, callbacks = [earlystop_callback,
cp_callback])
오버피팅을 막을 callback 들을 설정한다.
그리고 모델을 fit함수를 사용해서 학습시킨다.
LSTM(MaLSTM) 모델
유사도를 측정하기 위해서 코사인 유사도를 사용하는 대신에 LSTM 모델 중에서 맨허튼 거리를 사용한 LSTM 모델인 MaLSTM 을 살펴보도록 하자.
모델 구현
class Model(tf.keras.Model):
def __init__(self, **kargs):
super(Model,self).__init__(name=model_name)
self.embedding = tf.keras.layers.Embedding(input_dim = kargs['vocab_size'],
output_dim = kargs['embedding_dimension'])
self.lstm = tf.keras.layers.LSTM(units = kargs['lstm_dimension'])
def call(self,x):
x1, x2 = x
x1 = self.embedding(x1)
x2 = self.embedding(x2)
x1 = self.lstm(x1)
x2 = self.lstm(x2)
x = tf.exp(-tf.reduce_sum(tf.abs(x1-x2), axis = 1)) #맨하튼 거리 계산
#x1-x2 한 값을 절댓값 씌우고 두 벡터 간의 원소 간의 차이에 대한 절댓값 합을 계산하다.
return x
기준 문장과 대상 문장에 대해서 각각 embedding 하고 lstm 모델에 넘긴다.
그리고 각각의 값들을 맨하튼 거리 계산 식에 집어넣는다.
모델 하이퍼파라미터 정의 및 학습 정의
model_name = 'malstm_similarity'
BATCH_SIZE = 128
NUM_EPOCHS = 5
VALID_SPLIT = 0.1
kargs = {
'vocab_size' : prepro_configs['vocab_size'],
'embedding_dimension' : 100,
'lstm_dimension' : 150
}
model = Model(**kargs)
model.compile(optimizer = tf.keras.optimizers.Adam(1e-3),
loss = tf.keras.losses.BinaryCrossentropy(),
metrics = [tf.keras.metrics.BinaryAccuracy(name = 'accuracy')])
call back 설정 및 학습
earlystop_callback = EarlyStopping(monitor = 'val_accuracy', min_delta = 0.0001, patience = 1)
checkpoint_path = DATA_OUT_PATH + model_name + '/weights.h5'
checkpoint_dir = os.path.dirname(checkpoint_path)
if os.path.exists(checkpoint_dir):
print("{} -- Folder already exists \n".format(checkpoint_dif))
else:
os.makedirs(checkpoint_dir, exist_ok = True)
print("{} -- Folder create complete \n".format(checkpoint_dir))
cp_callback = ModelCheckpoint(
checkpoint_path,monitor = 'val_accuracy',
verbose = 1,
save_best_only = True,
save_weights_only = True)
history = model.fit((q1_data, q2_data), labels, batch_size = BATCH_SIZE, epochs = NUM_EPOCHS,
validation_split = VALID_SPLIT, callbacks = [earlystop_callback,
cp_callback])
이상으로 두 벡터의 유사도를 확인하는 모델들을 만들어봤다.
XgBoost, CNN, MaLSTM
'NLP > 텐서플로2와 머신러닝으로 시작하는 자연어처리' 카테고리의 다른 글
텍스트 분류(2) - 다양한 모델링 기법 (0) | 2022.01.27 |
---|---|
텍스트 분류(1) - 텍스트 데이터 EDA 및 전처리 (0) | 2022.01.27 |
자연어 처리 개요_자연어 생성과 기계 이해 (0) | 2022.01.09 |
자연어 처리 개요_텍스트 분류 (0) | 2022.01.09 |
자연어 처리 개요_텍스트 유사도 (0) | 2022.01.09 |