먼저 라이브러리를 가져온다.
import pandas as pd
import numpy as np
import re
import json
from tensorflow.python.keras.preprocessing.text import Tokenizer
from tensorflow.python.keras.preprocessing.sequence import pad_sequences
이제 학습 데이터를 불러온다.
DATA_IN_PATH='./data_in2/'
train_data=pd.read_csv(DATA_IN_PATH+'train.csv',encoding='utf-8')
먼저 진행하는 전처리 과정은 분석에서 확인했던 라벨 개수의 균형을 맞추는 것이다. 확인했듯 중복이 아닌 데이터가 더욱 많기에 해당하는 데이터의 개수를 줄인 후 분석한다. 먼저 중복인 경우와 아닌 경우로 나눈 후 중복이 아닌 개수가 비슷하도록 데이터의 일부를 다시 뽑는다.
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)
먼저 라벨에 따라 질문이 유사한 경우와 아닌 데이터셋으로 구분한다. 데이터프레임 객체의 loc라는 기능을 활용해 데어터를 추출하고 라벨이 1인 경우와 0인 경우를 분리해서 변수를 생성한다.
이제 두 변수의 길이를 맞춰야 하는데 우선 두 변수의 길이 차이를 계산하고 샘플링하기 위해 적은 데이터(중복 질문)의 개수가 많은 데이터(중복 아닌 짐룬)에 대한 비율을 계산한다. 그리고 개수가 많은 데이터에 대해 방금 구한 비율만큼 샘플링하면 두 데이터 간의 개수가 거의 비슷해진다. 샘플링한 이후 각 데이터의 개수를 확인해보자
print("중복 질문 개수: {}".format(len(train_pos_data)))
print("중복이 아닌 질문 개수: {}".format(len(train_neg_data)))
샘플링 후 개수가 동일해 졌다. 균형있게 학습할 수 있다. 우선 라벨에 따라 나눠진 데이터를 다시 하나로 합친다.
train_data=pd.concat([train_neg_data,train_pos_data])
이제 전처리를 진행한다. 문장 문자열에 대한 전처리를 먼저 진행한다. 우선 학습 데이터의 질문 쌍을 하나의 질문 리스트로 만들고, 정규 표현식을 사용해 물음표와 마침표 같은 구두점 및 기호를 제거하고 모든 문자를 소문자를 바꾸는 처리를 한다.
각 데이터에 있는 두 개의 질문을 각각 리스트로 만든 후 각 리스트의 전처리를 진행해서 두 개의 전처리된 리스트를 만든다.
FILTERS="([~.,!?\"':;'])"
change_filter=re.compile(FILTERS)
questions1=[str(s) for s in train_data['question1']]
questions2=[str(s) for s in train_data['question2']]
filtered_questions1=list()
filtered_questions2=list()
for q in questions1:
filtered_questions1.append(re.sub(change_filter, "",q).lower())
for q in questions2:
filtered_questions2.append(re.sub(change_filter, "",q).lower())
물음표와 마침표 같은 기호에 대해 정규 표현식으로 전처리 하기 위해 re를 이용한다. 먼저 정규 표현식을 사용할 패턴 객체를 만들어야 한다. re.compile 함수로 패턴 객체를 만드는데 이때 함수 인자에는 내가 찾고자 하는 문자열 패턴에 대한 내용을 입력한다. FILTERS 변수는 물음표와 마침표를 표함해서 제거하고자 하는 기호의 집합을 정규 표현식으로 나타낸 문자열임. 그리고 패턴을 정규 표현식의 컴파일 함수로 컴파일 해둔다.
데이터의 두 질문을 각 리스트로 만든 후, 각 리스트의 전처리를 진행한다. 필터에 해당하는 문자열을 제거하고 모든 알파벳 문자를 소문자로 바꾼다. 그리고 전처리 진행한 결과를 두 개의 변수에 저장한다.
텍스트 정제는 끝났고 이제 정제된 텍스트 데이터를 토크나이징하고 각 단어를 인덱스로 바꾼 후 전체 데이터의 길이를 맞추기 위해 정의한 최대 길이보다 긴 문장은 자르고 짧은 문장은 패딩처리한다.
문자열 토크 나이징은 텐서플로우 케라스가 제공하는 자연어 전처리 모듈을 이용한다. 그리고 토크나이징 객체를 만들 때 두 질문 텍스트를 합친 리스트에 대해 적용하고, 토크나이징은 해당 객체를 활용해 각 질문에 대해 따로 진행한다. 이런 이유는 두 질문에 대해 토크나이징 방식을 동일하게 진행하고, 두 질문을 합쳐 전체 단어 사전을 만들기 위함이다. 토크나이징 이후는 패딩처리를 한 벡터화를 한다.
tokenizer=Tokenizer()
tokenizer.fit_on_texts(filtered_questions1+filtered_questions2)
이렇게 생성한 토크나이징 객체를 두 질문 리스트에 적용해 질문들을 토크나이징하고 단어들을 각 단어의 인덱스로 변환한다.
questions1_sequence=tokenizer.texts_to_sequences(filtered_questions1)
questions2_sequence=tokenizer.texts_to_sequences(filtered_questions2)
이제 모델에 적용하기 위해 특정 길이로 동일하게 맞춰야함. 따라서 최대 길이를 정하고 길면 자르고 짧으면 부족한 부분을 0으로 채운다.
MAX_SEQUENCE_LENGTH=31
q1_data=pad_sequences(questions1_sequence, maxlen=MAX_SEQUENCE_LENGTH, padding='post')
q2_data=pad_sequences(questions2_sequence, maxlen=MAX_SEQUENCE_LENGTH, padding='post')
최대 길이의 경우 앞서 데이터 분석에서 확인했던 단어 개수의 99퍼센트인 31로 했고 이 이유는 이상치를 뺀 나머지를 포함하기 위해서다.
전처리가 끝나면 저장한다. 저장하기 전에 라벨값과 단어 사전을 저장하기 위해 값을 저장하고 각 데이터 크기를 확인한다.
word_vocab={}
word_vocab=tokenizer.word_index
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)))
두 질문 문장은 각 길이를 31로 설정했고, 단어 사전의 길이인 전체 단어 개수는 75,938개로 돼 있다. 그리고 단어 사전과 전체 단어의 수는 딕셔너리로 저장해둔다.
data_configs={}
data_configs['vocab']=word_vocab
data_configs['vocab_size']=len(word_vocab)+1
이제 각 데이터를 모델링 과정에서 사용할 수 있게 저장한다.
TRAIN_Q1_DATA = 'train_q1.npy'
TRAIN_Q2_DATA = 'train_q2.npy'
TRAIN_LABEL_DATA = 'train_label.npy'
DATA_CONFIGS = 'data_configs.json'
np.save(open(DATA_IN_PATH + TRAIN_Q1_DATA, 'wb'), q1_data)
np.save(open(DATA_IN_PATH + TRAIN_Q2_DATA , 'wb'), q2_data)
np.save(open(DATA_IN_PATH + TRAIN_LABEL_DATA , 'wb'), labels)
json.dump(data_configs, open(DATA_IN_PATH + DATA_CONFIGS, 'w'))
넘파이의 save 함수를 활용해 각 질문과 라벨 데이터를 저장한다. 딕셔너리 형태의 데이터 정보는 json 파일로 저장했다. 이렇게 하면 학습할 모델의 데이터 전처리를 했다. 전처리한 데이터는 모델 학습할 때 쓰인다.
이제 평가 데이터에 대해서도 앞의 전처리 과정을 동일하게 한 후 저장한다. 평가 데이터를 불러온다.
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_questions1=[str(s) for s in test_data['question1']]
test_questions2=[str(s) for s in test_data['question2']]
filtered_test_questions1=list()
filtered_test_questions2=list()
for q in test_questions1:
filtered_test_questions1.append(re.sub(change_filter,"",q).lower())
for q in test_questions2:
filtered_test_questions2.append(re.sub(change_filter,"",q).lower())
정제한 평가 데이터를 인덱스 벡터로 만든 후 동일하게 패딩 처리한다. 이때 사용하는 토크나이징 객체는 이전에 학습 데이터에서 사용했던 객체를 사용해서 동일한 인덱스를 가짐
test_questions1_sequence=tokenizer.texts_to_sequences(filtered_test_questions1)
test_questions2_sequence=tokenizer.texts_to_sequences(filtered_test_questions2)
test_q1_data=pad_sequences(test_questions1_sequence, maxlen=MAX_SEQUENCE_LENGTH, padding='post')
test_q2_data=pad_sequences(test_questions2_sequence, maxlen=MAX_SEQUENCE_LENGTH, padding='post')
평가 데이터는 라벨이 존재하므로 라벨은 저장할 필요 없고 평가 데이터에 대한 단어 사전 정보도 이미 학습 데이터 전처리에서 저장했기에 추가로 할 필요가 없다. 하지만 평가 데이터에 대한 결과를 캐글에 제출하기 위해선 id값이 필요하다. 따라서 평가 데이터의 id값을 넘파이 배열로 만들자. 그리고 평가 데이터를 전처리한 값들의 크기를 출력한다.
test_id=np.array(test_data['test_id'])
print('Shape of question1 data: {}'.format(test_q1_data.shape))
print('Shape of question2 data: {}'.format(test_q2_data.shape))
print('Shape of ids: {}'.format(test_id.shape))
평가 데이터도 전체 문장 길이를 11로 맞춰 전처리를 마무리했다. 이제 전처리 평가데이터를 저장한다.
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)
이제 모든 전처리가 끝났고 다음은 질문 간의 유사도 측정을 위한 모델을 만들것이다.
[ 출처 : 책 ( 텐서플로와 머신러닝으로 시작하는 자연어 처리) ]
[ NLP ] 텍스트 유사도 (4) (0) | 2021.03.28 |
---|---|
[ NLP ] 텍스트 유사도 (3) (2) | 2021.03.09 |
[ NLP ] 텍스트 유사도 (1) (1) | 2021.01.30 |
[ NLP ] 한글 텍스트 분류 (3) (0) | 2021.01.21 |
[ NLP ] 한글 텍스트 분류 (2) (0) | 2021.01.13 |