NLP

NLP 실습 텍스트 유사도 - 01 (데이터 EDA 및 전처리)

텍스트 유사도

  • 텍스트 유사도 문제한 두 문장(글)이 있을 때 두 문장 간의 유사도를 측정할 수 있는 모델을 만드는 것이다.

문제소개

  • 데이터 이름 : Quora Question Pairs
  • 텍스트 용도 : 텍스트 유사도 학습을 목적으로 사용
  • 데이터 권한 : Quora 권한을 가지고 있으며 Kaggle 가입 후 데이터를 내려받으면 문제없다.
  • 데이터 출처 : https://www.kaggle.com/c/quora-question-pairs/data

  • 이번에도 Kaggle의 대회 중 하나를 해결해 보려고 한다. “Quora Questions Pairs”라는 문제를 해결해보도록 할 것이다. Quora는 질문을 하고 다른 사용자들로부터 답변을 받을 수 있는 서비스이다. 실제로 딥러닝 공뷰할 때도 Quora의 질문들은 참고하면서 많은 공부를 할 수 있다. Quora의 월 사용자는 대략 1억명 정도 된다. 매일 수 많은 질문들이 사이트에 올라올 텐데 이 많은 질문 중에는 분명히 중복된 것들이 포함될 것이다. 따라서 Quora 입장에서는 중복된 질문들을 잘 찾기만 한다면 이미 잘 작성된 답변들을 사용자들이 참고하게 할 수 있고, 더 좋은 서비스를 제공할 수 있게 된다.

목표 : 여러 질문들 중에서 어떤 질문이 서로 유사한지 판단하는 모델을 만드는 것

Quora

  • 캐글 API를 colab에서 사용하기 위한 인증 및 google storage에 업로드 되어있는 인증키 파일 현재 colab pwd로 복사해온 후 설정완료하기
1
2
3
4
5
6
7
8
from google.colab import auth
import warnings
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
warnings.filterwarnings("ignore")
auth.authenticate_user()

!gsutil cp gs://kaggle_key/kaggle.json kaggle.json
1
2
3
4
!mkdir -p ~/.kaggle
!mv ./kaggle.json ~/.kaggle/
!chmod 600 ~/.kaggle/kaggle.json
!pip install kaggle

데이터 불러오기와 분석하기

  • 데이터를 내려받는 것부터 시작할 것이다. 필자는 Google colab에서 kaggle API를 통해 다운로드 받을 것이다. 아래 그림과 같은 error가 발생한다면 kaggle API Token파일을 다시 받지 말고 그 전에 먼저 해당 competition의 rule을 수락을 했는지를 확이해보아야 한다. https://www.kaggle.com/c/quora-question-pairs/rules
1
!kaggle competitions download -c quora-question-pairs

캐글 error

해당 competition rule 체크

  • 해당 데이터가 잘 다운로드 됐는지 확인한다. 확인해 보면 다음과 같이 3가지 파일이 있을 것이다.

    • sample_submission.csv.zip
    • test.csv.zip
    • train.csv.zip
  • 총 3개의 파일이 zip 형식으로 압축된 형태다. 이 파일들의 압축을 풀어주는 과정까지 할 것이다.

1
2
3
4
5
6
7
8
9
import zipfile

DATA_IN_PATH = '/content/'
zip_list=['sample_submission.csv.zip', 'test.csv.zip', 'train.csv.zip']

for file in zip_list:
zipRef = zipfile.ZipFile(DATA_IN_PATH + file, 'r')
zipRef.extractall(DATA_IN_PATH)
zipRef.close()
  • 본격적으로 데이터를 불러온 후 데이터 분석을 해보기 위해 필요한 라이브러리들을 모두 Import 할 것이다.
1
2
3
4
5
6
7
import numpy as np
import pandas as pd
import os
import matplotlib.pyplot as plt
import seaborn as sns
import pathlib as Path
%matplotlib inline
  • 가장 먼저 학습 데이터를 불러와서 어떤 형태로 데이터가 구성돼 있는지 확인해 볼 것이다.
  • 데이터는 ‘id’, ‘qid1’, ‘qid2’, ‘question1’, ‘question2’, ‘is_duplicate’열로 구성돼 있다. 각각의 description은 아래와 같다.
    • id : 각 행 데이터의 고유한 index 값
    • qid1 : 질문들의 고유한 index 값
    • qid2 : 질문들의 고유한 index 값
    • question1 : 질문의 내용
    • question2 : 질문의 내용
    • is_duplicate : 0 또는 1(0이면 두 개의 질문이 중복이 아님을 의미, 1이면 두 개의 질문이 중복을 의미)
1
2
train_data = pd.read_csv(DATA_IN_PATH + 'train.csv')
train_data.head()

학습 데이터 상위 5개

  • 사용할 데이터가 어떤 데이터이고, 크기는 어느 정도 되는지 알아보기 위해 데이터 파일의 이름과 크기를 각각 출력해서 확인해 볼 것이다.
  • 대부분 train data가 test data 보다 크기가 큰데, 이 데이터는 test data가 train data 보다 5배 정도 더 큰 것을 알 수 있다. test data가 큰 이유는 Quora의 경우 질문에 대해 데이터의 수가 적다면 각각을 검색을 통해 중복을 찾아내는 편볍을 사용할 수 있으므로 이러한 편법을 방지하기 위해 Quora에서 직접 컴퓨터가 만든 질문 싸을 test data에 임의적으로 추가했기 때문이다. 따라서 test data가 크지만 실제 question data는 얼마 되지 않는다. 그리고 Kaggle의 경우 예측 결과를 제출하면 점수를 받을 수 있는데, 컴퓨터가 만든 질문 쌍에 대한 예측은 점수에 포함도지 않는다.
1
2
3
4
print('파일 크기: ')
for file in os.listdir(DATA_IN_PATH):
if ('csv' in file) and ('zip' not in file):
print(file.ljust(30) + str(round(os.path.getsize(DATA_IN_PATH + file) / 1000000, 2)) + 'MB')
결과
1
2
3
4
파일 크기:
test.csv 314.02MB
sample_submission.csv 22.35MB
train.csv 63.4MB
  • 전체 데이터의 개수와 학습 데이터안의 NULL값이 존재하는지 먼저 확인 할 것이다.
  • 결과를 보면 전체 질문 쌍의 개수는 대략 40만개이며 3개의 데이터에 NULL값이 존재한다.
1
train_data.info()
결과
1
2
3
4
5
6
7
8
9
10
11
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 404290 entries, 0 to 404289
Data columns (total 6 columns):
id 404290 non-null int64
qid1 404290 non-null int64
qid2 404290 non-null int64
question1 404289 non-null object
question2 404288 non-null object
is_duplicate 404290 non-null int64
dtypes: int64(4), object(2)
memory usage: 18.5+ MB
  • 전체 질문(두 개의 질문)을 한번에 분석하기 위해 Pandas의 Series를 통해 두 개의 질문을 하나로 합친다.

  • 각 질문을 list로 만든 뒤 하나의 Series 데이터 타입으로 만든다. 결과를 보면 아래와 같은 구조로 합쳐졌다. 기존 데이터에서 질문 쌍의 수가 40만개 정도이고 각각 질문이 2개 이므로 대략 80만개 정도의 질문이 있다.

1
2
train_set = pd.Series(train_data['question1'].to_list() + train_data['question2'].to_list()).astype(str)
train_set.tail()
  • 이제 질문들의 중복 여부를 확인해 볼 것이다. Numpy의 unique함수를 이용해 중복을 제거한 총 질문의 수와 반복해서 나오는 질문의 수를 확인한다.
  • 결과를 보면 80만 개의 데이터에서 537,361건이 Unique한 데이터이므로 262,639건이 중복돼 있음을 알 수 있다. 그러므로 262,639개 데이터는 131,318개의 동일한 질문 쌍으로 이루어져 있음을 알 수 있다.(1쌍은 NULL값이고, 1쌍은 값이 하나만 존재하므로)
1
2
print('교육 데이터의 총 질문 수 : {} 건'.format(len(np.unique(train_set))))
print('반복해서 나타나는 질문의 수 : {} 건'.format(np.sum(train_set.value_counts() > 1)))
결과
1
2
교육 데이터의 총 질문 수 : 537361 건
반복해서 나타나는 질문의 수 : 111873 건
  • 위의 결과를 시각화 해 볼 것이다. y축의 범위를 줄이기 위해 log을 사용했다. x값은 중복의 개수이며, y값은 동일한 중복 횟수를 가진 질문의 개수를 의미한다.
  • histogram을 살펴보면 우선 중복 횟수가 1인 질문들, 즉 유일한 질문들이 가장 많고 대부분의 질문이 중복 횟수가 50번 이하이다. 그리고 매우 큰 빈도를 가진 질문은 이상치가 될 것이다.
1
2
3
4
5
6
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.ylabel('Number of questions')
plt.show()

중복 질문의 개수에 관한 histogram

1
print('교육 데이터의 총 질문 수 : {} 건'.format(len(np.unique(train_set))))
결과
1
교육 데이터의 총 질문 수 : 537361 건
  • 중복이 최대로 발생한 개수는 161번이고, 평균적으로 보면 문장당 1.5개의 중복을 가지며, 표준편차는 1.9다. 중복이 발생하는 횟수의 평균이 1.5라는 것은 많은 데이터가 최소 1개 이상 중복돼 있음을 의미한다. 즉 중복이 많다는 의미이다.
1
train_set.value_counts().describe()
결과
1
2
3
4
5
6
7
8
9
count    537361.000000
mean 1.504724
std 1.911439
min 1.000000
25% 1.000000
50% 1.000000
75% 1.000000
max 161.000000
dtype: float64
  • 이제 box plot을 통해 중복횟수와 관련해서 데이터를 직관적으로 이해해 보자.

  • 아래의 분포는 중복 횟수의 이상치가 너무 넓고 많이 분포해서 box plot의 다른 값을 확인하기조차 어려운 데이터이다.

1
2
3
4
5
plt.figure(figsize=(12, 5))
plt.boxplot([train_set.value_counts()],
labels=['counts'],
showmeans=True)
plt.show()

중복 질문의 개수에관한 box plot

  • 데이터에 어떤 단어가 포함됐는지 간단히 알아보기 위해 워드클라우드를 사용할 것이다.
  • 워드 클라우드로 그려진 결과를 확인해 보면 best, way, good, difference 등의 단어들이 질문을 할 때 일반적으로 가장 많이 사용된다는 것을 알 수 있다. 특이한 점은 해당 결과에서 ‘Donald Trump’가 존재하는 것이다. ‘Donald Trump’가 존재하는 이유는 선거 기간 중 학습 데이터를 만들었기 때문이라고 많은 캐글러들이 말하고 있다.
1
2
3
4
5
6
from wordcloud import WordCloud
cloud = WordCloud(width=700, height=400).generate(' '.join(train_set.astype(str)))
plt.figure(figsize=(15,13))
plt.imshow(cloud)
plt.axis('off')
plt.show()

Quora 학습데이터 워드 클라우드

  • 질문 텍스트가 아닌 데이터의 라벨인 ‘is_duplicate’에 대해 count plot을 통해 살펴볼 것이다.
  • 라벨값의 개수를 확인해 보면 총 40만 개의 데이터에서 중복이 아닌 데이터가 25만개 정도이고 중복된 데이터가 약 15만개 정도로 보인다. 이 상태로 학습한다면 중복이 아닌 데이터 25만개에 의존도가 높아지면서 데이터가 한쪽 라벨로 편향된다. 이러한 경우 학습이 원활하게 되지 않을 수도 있으므로 최대한 라벨의 개수를 균형 있게 맞춰준 후 진행하는 것이 좋다. 많은 수의 데이터를 줄인 후 학습할 수도 있고, 적은 수의 데이터를 늘린 후 학습할 수도 있다.
1
2
3
4
fig, axe = plt.subplots(ncols=1)
fig.set_size_inches(10, 5)
sns.countplot(train_data['is_duplicate'])
plt.show()

라벨값의 분포

  • 학습 데이터의 길이를 분석해 볼 것이다. 문자단위로 먼저 길이를 분석한 후 단어 단위로 분석 할 것이다.
  • 데이터의 각 질문의 길이 분포는 15 ~ 150에 대부분 모여 있으며 길이가 150에서 급격하게 주어드는 것을 볼 때 Quora의 질문 길이 제한이 150 정도라는 것을 추정해 볼 수 있다. 길이가 150 이상인 데이터는 거의 없기 때문에 해당 데이터 때문에 문제가 되지는 않을 것이다.
1
2
3
4
5
6
7
8
9
train_length = train_set.apply(len)

plt.figure(figsize=(15, 10))
plt.hist(train_length, bins=200, range=[0, 200], facecolor='r', normed=True, label='train')
plt.title('Normalized histogram of chracter count in questions', fontsize=15)
plt.legend()
plt.xlabel('Number of characters', fontsize=15)
plt.ylabel('Probability', fontsize=15)
plt.show()

문자 단위 질문 길이에 관한 histogram

  • 그에 따른 기초 통계량은 다음과 같으며, 평균적으로 길이가 60 정도라는 것을 확인할 수 있다. 그리고 중앙값의 경우 51 정도이다. 하지만 최댓값을 확인해 보면 1169로서 평균, 중앙값에 비해 매우 큰 차이를 보인다. 이런 데이터는 제외하고 학습하는 것이 좋을 것이다.
1
train_length.describe()
결과
1
2
3
4
5
6
7
8
9
count    808580.000000
mean 59.822548
std 31.963751
min 1.000000
25% 39.000000
50% 51.000000
75% 72.000000
max 1169.000000
dtype: float64
  • 데이터의 질문 길이값에 대해서도 box plot을 그려서 확인해 볼 것이다.
  • 분포를 보면 문자 수의 이상치 데이터가 너무 많이 분포해서 box plot의 다른 값을 확인하기 조차 어려운 상태다.
1
2
3
plt.figure(figsize=(12, 5))
plt.boxplot(train_length, labels=['char counts'], showmeans=True)
plt.show()

문자 단위 길이 box plot

  • 이제 문자가 아닌 단어를 한 단위로 사용해 길이값을 분석해 볼 것이다. 하나의 단어로 나누는 기준은 단순히 띄어쓰기로 정의할 것이다.
  • histogram을 보면 대부분 10개 정도의 단어로 구성된 데이터가 가장 많다는 것을 볼 수 있다. 20개 이상의 단어로 구성되 데이터는 매우 적다는 것을 확인할 수 있다.
1
2
3
4
5
6
7
8
9
train_word_counts = train_set.apply(lambda x: len(x.split(' ')))

plt.figure(figsize=(15, 10))
plt.hist(train_word_counts, bins=50, range=[0, 50], color='r', label='train', normed=True)
plt.title('Normalized histogram of word count in one question', fontsize=15)
plt.legend()
plt.xlabel('Number of Words', fontsize=15)
plt.ylabel('Probability')
plt.show()

질문 당 단어 단위 길이 histogram

  • 그에 따른 기초통계량을 살펴볼 것이다.
  • 데이터의 문자 단위 길이를 확인했을 때와 비슷한 분포를 갖는다. 평균 개수의 경우 11개이며, 중앙값의 경우 평균 보다 1개 적은 10개를 갖는다. 문자 길이의 최댓값인 경우 1100 정도의 값을 보인다. 단어 길이는 최대 237개이다. 해당 데이터의 경우 지나치게 긴 문자 길이와 단어 개수를 보여준다.
1
train_word_counts.describe()
결과
1
2
3
4
5
6
7
8
9
count    808580.000000
mean 11.064856
std 5.889168
min 1.000000
25% 7.000000
50% 10.000000
75% 13.000000
max 237.000000
dtype: float64
1
np.quantile(train_word_counts, 0.99)
결과
1
31.0
  • box plot을 통해 데이터 분포를 다시 한번 확인해보자.
  • 문자 길이에 대한 box plot과 비슷한 모양의 그래프를 보여준다. Quora 데이터의 경우 이상치가 넓고 많이 분포 돼 있음을 알 수 있다.
1
2
3
plt.figure(figsize=(12, 5))
plt.boxplot(train_word_counts, labels=['word counts'], showmeans=True)
plt.show()

질문 당 단어 단위 길이 box plot

  • 몇 가지 특정 경우에 대한 비율을 확인해 볼 것이다. 특수 문자 중 구두점, 물음표, 마침표가 사용된 비율과 수학 기호가 사용된 비율, 대/소문자의 비율을 확인해 본다.
  • 대문자가 첫 글자인 질문과 물음표를 동반하는 질문이 99% 이상을 차지한다. 전체적으로 질문들이 물음표와 대문자로 된 첫 문자를 가지고 있음을 알 수 있다. 그럼 여기서 생각해 볼 부분이 있다. 즉, 모든 질문이 보편적으로 가지고 있는 이 특징의 유지 여부에 대해서인데, 모두가 가지고 있는 보편적인 특징은 여기서 제거한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
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]))) # 대문자가 사용된 질문이 몇 개인지
numbers = np.mean(train_set.apply(lambda x : max([y.isdigit() 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))
print('숫자가 있는 질문: {:.2f}%'.format(numbers * 100))
결과
1
2
3
4
5
6
물음표가 있는 질문: 99.87%
수학 태그가 있는 질문: 0.12%
마침표가 있는 질문: 6.31%
첫 글자가 대문자인 질문: 99.81%
대문자가 있는 질문: 99.95%
숫자가 있는 질문: 11.83%

데이터 전처리

  • 지금까지 데이터 EDA(탐색적 데이터 분석)를 통해 데이터의 구조와 분포를 확인했다. 질문 데이터의 중복 여부 분포, 즉 라벨의 분포가 크게 차이나서 학습에 편향을 주므로 좋지 않은 영향을 줄 수 있다. 따라서 전처리 과정에서 분포를 맞춰줄 것이다. 그리고 대부분의 질문에 포함된 첫 번째 대문자는 소문자로 통일한다. 물음표 같은 구두점은 삭제하는 식으로 보편적인 특성은 제거함으로써 필요한 부분만 학습하게 하는 이점을 얻을 수 있다.
1
2
3
4
5
import re
import json

from tensorflow.python.keras.preprocessing.text import Tokenizer
from tensorflow.python.keras.preprocessing.sequence import pad_sequences
1
2
3
DATA_IN_PATH = '/content/'

train_data = pd.read_csv(DATA_IN_PATH + 'train.csv', encoding='utf-8')
  • 맨 먼저 진행할 전처리 과정은 앞서 분석 과정에서 확인했던 내용 중 하나인 라벨 개수의 균형을 맞추는 것이다. 앞서 분석 과정에서 확인했듯이 중복이 아닌 데이터의 개수가 더욱 많기 때문에 이 경우에 해당하는 데이터의 개수를 줄인 후 분석을 진행하겠다. 먼저 중복인 경우와 아닌 경우로 데이터를 나눈 후 중복이 아닌 개수가 비슷하도록 데이터의 일부를 다시 뽑는다.
1
2
3
4
5
6
7
train_duplicate_data = train_data.loc[train_data['is_duplicate']==1]
train_non_duplicate_data = train_data.loc[train_data['is_duplicate']==0]

class_difference = len(train_non_duplicate_data) - len(train_duplicate_data)
sample_frac = 1 - (class_difference / len(train_non_duplicate_data))

train_non_duplicate_data = train_non_duplicate_data.sample(frac = sample_frac)
  • 샘플링한 후 데이터의 개수가 동일해졌다. 이제 해당 데이터를 사용하먄 균형 있게 학습할 수 있을 것이다.
1
2
print("중복 질문 개수 : {} 건".format(len(train_duplicate_data)))
print("중복이 아닌 질문 개수 : {} 건".format(len(train_non_duplicate_data)))
결과
1
2
중복 질문 개수 : 149263 건
중복이 아닌 질문 개수 : 149263 건
  • 우선 라벨에 따라 나눠진 데이터를 다시 하나로 합치자.
1
train_data = pd.concat([train_non_duplicate_data, train_duplicate_data])
  • 앞서 전처리에서 분석한 대로 문장 문자열에 대한 전처리를 먼저 진행한다. 우선 학습 데이터의 질문 쌍을 하나의 질문 리스트로 만들고, 정규 표현식을 사용해 물음표와 마침표 같은 구두점 및 기호를 제거하고 모든 문자를 소문자로 바꾸는 처리를 한다.
1
train_data.head()
  • 물음표와 마침표 같은 기호에 대해 정규 표현식을 사용하여 전처리하기 위해 re 라이브러리를 활용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 = []
filtered_questions2 = []

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())
  • 이제 남은 과정은 정제된 위의 텍스트 테이터를 토크나이징하고 각 단어를 인덱스로 바꾼 후, 전체 데이터의 길이를 맞추기 위해 정의한 최대 길이보다 긴 문장은 자르고 짧은 문장은 패딩 처리를 하는 것이다. 문자열 토크나이징은 tensorflow keras에서 제공하는 NLP Processing 모듈을 활용한다.객체를 만들 때는 두 질문 텍스트를 합친 리스트에 적용하고, 토크나이징은 해당 객체를 활용해 각 질문에 대해 따로 진행할 것이다. 이러한 방법은 두 질문에 대해 동일한 토크나이징 방식을 사용해야하며, 두 질문을 합친 전체 vocabulary를 만들어야 하기 때문이다. 토크나이징 이후에는 패딩 처리를 한 벡터화를 진행할 것이다.
1
2
3
4
5
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으로 채우는 패딩 과정을 진행 할 것이다.

  • 최대 길이는 앞서 EDA에서 확인했던 단어 개수의 99%인 31로 설정했다. 이렇게 설정한 이유는 이상치를 뺀 나머지를 포함하기 위해서이다.(다양한 값으로 실험했을 때 이 값이 가장 좋은 값이었다.) 전처리 모듈의 패딩 함수를 사용해 최대 길이로 자르고 짧은 데이터에 대해서는 데이터 뒤에 패딩값을 채워넣었다.

1
2
3
4
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')
  • 전처리가 끝난 데이터를 저장한다. 저장하기 전에 라벨값과 단어 사전을 저장하기 위해 값을 저장한 후 각 데이터의 크기를 확인해 보자. 두 개의 질문 문장의 경우 각각 길이를 31로 설정했고, vocabulary의 길이인 전체 단어 개수는 76,594개로 돼 있다.
1
2
3
4
5
6
7
8
9
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 question1 data : {}'.format(labels.shape))
print('Words in index : {}'.format(len(word_vocab)))
결과
1
2
3
4
Shape of question1 data : (298526, 31)
Shape of question2 data : (298526, 31)
Shape of question1 data : (298526,)
Words in index : 76594
  • 단어 사전과 전체 단어의 개수는 dictionary 형태로 저장해 둘 것이다.
1
2
3
data_configs = {}
data_configs['vocab'] = word_vocab
data_configs['vocab_size'] = len(word_vocab)+1
  • 이제 각 데이터를 모델링 과정에서 사용할 수 있게 저장하면 된다.
1
2
3
4
5
6
7
8
9
10
TRAIN_Q1_DATA = 'q1_train.npy'
TRAIN_Q2_DATA = 'q2_train.npy'
TRAIN_LABEL_DATA = 'label_train.npy'
DATA_CONFIGS = 'data_configs.npy'

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'))
  • 이제 평가 데이터에 대해서도 동일한 전처리를 실행해줄 것이다.
1
2
3
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()
1
2
3
4
5
6
7
8
9
10
11
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())
1
2
3
4
5
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')
1
2
3
4
5
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))
1
2
3
4
5
6
7
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)