논문 보고 구현하기 카테고리에 있는 RNNsearch 모델을 구현해보려고 하는데 이때 한국어 - 영어 데이터가 필요해서 pytorch를 이용하여 만들어보기로 하였다. 이 포스팅에서 사용된 pytorch는 1.10.0이며 torchtext는 0.11.0 버전이다.
먼저 한국어-영어 번역 데이터는 AI Hub에서 한국어-영어 번역(병렬) 말뭉치 데이터를 이용하기로 하였다.
AI Hub에서 데이터 사용허가를 받고 다운로드를 하면 다음과 같이 엑셀 파일 여러 개가 얻을 수 있다..
그리고 각 엑셀 파일은 다음과 같이 한국어 - 영어 병렬적으로 구성되어 있는데
이를 다시 pandas와 scikit-learn을 이용하여 train, validation, test로 나누어 csv 형식으로 저장해주었다.
import pandas as pd
from sklearn.model_selection import train_test_split
import os
from tqdm import tqdm
data = []
data.append(pd.read_excel('datasets/origin_kor2eng_corpus/1_구어체(1).xlsx'))
data.append(pd.read_excel('datasets/origin_kor2eng_corpus/1_구어체(2).xlsx'))
data.append(pd.read_excel('datasets/origin_kor2eng_corpus/2_대화체.xlsx'))
data.append(pd.read_excel('datasets/origin_kor2eng_corpus/3_문어체_뉴스(1).xlsx'))
data.append(pd.read_excel('datasets/origin_kor2eng_corpus/3_문어체_뉴스(2).xlsx'))
data.append(pd.read_excel('datasets/origin_kor2eng_corpus/3_문어체_뉴스(3).xlsx'))
data.append(pd.read_excel('datasets/origin_kor2eng_corpus/3_문어체_뉴스(4).xlsx'))
data.append(pd.read_excel('datasets/origin_kor2eng_corpus/4_문어체_한국문화.xlsx'))
data.append(pd.read_excel('datasets/origin_kor2eng_corpus/5_문어체_조례.xlsx'))
data.append(pd.read_excel('datasets/origin_kor2eng_corpus/6_문어체_지자체웹사이트.xlsx'))
total_data = pd.DataFrame(columns=['KOR', 'ENG'])
for d in tqdm(data):
total_data = total_data.append(d[['원문', '번역문']].rename(columns={'원문':'KOR', '번역문':'ENG'}))
train_df, val_df = train_test_split(total_data, test_size = 0.3, random_state=1234)
val_df, test_df = train_test_split(val_df, test_size=0.3, random_state=1234)
path = './datasets/korNeng_corpus'
os.makedirs(path, exist_ok=True)
total_data.to_csv(path + '/korneng.csv', index=None, encoding='utf-8-sig')
train_df.to_csv(path + '/train_data.csv', index=None, encoding='utf-8-sig')
val_df.to_csv(path + '/validation_data.csv', index=None, encoding='utf-8-sig')
test_df.to_csv(path + '/test_data.csv', index=None, encoding='utf-8-sig')
이제 AI Hub에서 받아온 데이터의 전처기가 마무리되었으니 본격적으로 데이터셋을 구축할 때이다.
먼저 다음과 같이 한국어와 영어의 tokenizer를 torchtext의 get_tokenizer 함수를 이용하여 생성해주었다.
한국어의 tokenizer로는 konlpy의 Hannanum을 사용했으며, 영어는 spacy를 사용하였다.
kor_tokenizer = get_tokenizer(Hannanum().morphs)
eng_tokenizer = get_tokenizer('spacy', language='en')
이제는 각 언어의 tokenizer가 만들어내는 각각의 token들을 nlp 모델이 처리할 수 있도록 각 token에 해당하는 숫자를 저장하는 vocab을 생성할 차례이다. vocab은 torchtext의 build_vocab_from_iterator를 이용하여 생성할 예정인데 이를 위해서는 당연히 각 언어의 iterator를 생성해야 한다. 이는 각 언어의 문자열들을 tokenizer를 이용하여 token으로 생성해서 반환하는 generator를 이용하여 생성하였다. 그리고 vocab에는 알 수 없는 token을 의미하는 <unk> token, padding 되었음을 의미하는 <pad>, 각각 문장의 시작과 끝을 알리는 <bos>와 <eos> token을 추가해주었다.
def vocab_iterator(strings, tokenizer):
for string_ in tqdm(strings):
yield tokenizer(string_)
if os.path.isfile('./datasets/korNeng_corpus/kor_vocab.pt'):
kor_vocab = torch.load('./datasets/korNeng_corpus/kor_vocab.pt')
else:
kor_vocab_base = pd.read_csv('./datasets/korNeng_corpus/train_data.csv')['KOR']
kor_vocab = build_vocab_from_iterator(vocab_iterator(kor_vocab_base, kor_tokenizer), specials=['<unk>', '<pad>', '<bos>', '<eos>'], min_freq=5)
kor_vocab.set_default_index(kor_vocab['<unk>'])
torch.save(kor_vocab, './datasets/korNeng_corpus/kor_vocab.pt')
if os.path.isfile('./datasets/korNeng_corpus/eng_vocab.pt'):
eng_vocab = torch.load('./datasets/korNeng_corpus/eng_vocab.pt')
else:
eng_vocab_base = pd.read_csv('./datasets/korNeng_corpus/train_data.csv')['ENG']
eng_vocab = build_vocab_from_iterator(vocab_iterator(eng_vocab_base, eng_tokenizer), specials=['<unk>', '<pad>', '<bos>', '<eos>'], min_freq=5)
eng_vocab.set_default_index(eng_vocab['<unk>'])
torch.save(eng_vocab, './datasets/korNeng_corpus/eng_vocab.pt')
이제는 vocab을 이용하여 앞서 만들어주었던 train, validation, test data들을 모두 token화한 후, 숫자로 바꾸어 DataLoader가 사용할 dataset을 만들어줄 차례이다. 이때 한국어 token들과 대응되는 영어 token을 하나의 tuple로 묶어주었다.
def data_process(file_path):
raw_kor_iter = iter(pd.read_csv(file_path)['KOR'])
raw_eng_iter = iter(pd.read_csv(file_path)['ENG'])
data = []
for (raw_kor, raw_eng) in tqdm(zip(raw_kor_iter, raw_eng_iter)):
kor_tensor_ = torch.tensor([kor_vocab[token] for token in kor_tokenizer(raw_kor)], dtype = torch.long)
eng_tensor_ = torch.tensor([eng_vocab[token] for token in eng_tokenizer(raw_eng)], dtype = torch.long)
data.append((kor_tensor_, eng_tensor_))
return data
if os.path.isfile('./datasets/korNeng_corpus/train_tensor.pt'):
train_data = torch.load('./datasets/korNeng_corpus/train_tensor.pt')
else:
train_data = data_process('./datasets/korNeng_corpus/train_data.csv')
torch.save(train_data, './datasets/korNeng_corpus/train_tensor.pt')
if os.path.isfile('./datasets/korNeng_corpus/valid_tensor.pt'):
valid_data = torch.load('./datasets/korNeng_corpus/valid_tensor.pt')
else:
valid_data = data_process('./datasets/korNeng_corpus/validation_data.csv')
torch.save(valid_data, './datasets/korNeng_corpus/valid_tensor.pt')
if os.path.isfile('./datasets/korNeng_corpus/test_tensor.pt'):
test_data = torch.load('./datasets/korNeng_corpus/test_tensor.pt')
else:
test_data = data_process('./datasets/korNeng_corpus/test_data.csv')
torch.save(test_data, './datasets/korNeng_corpus/test_tensor.pt')
이제는 마지막으로 DataLoader를 구현해주었다. DataLoader가 이용할 dataset은 앞서 만든 것을 사용하도록 하였고, DataLoader가 dataset을 읽을 때 사용할 임의의 함수 generate_batch를 구현해주었다. generate_batch는 다음과 같이 각 문장의 시작과 끝을 알려주는 <bos>와 <eos> token으로 문장을 감싸주는 식으로 구현하였으며 또한 하나의 batch 내에서는 각각의 길이가 동일하도록 pytorch의 pad_sequence 메소드를 이용하여 padding 해주는 식으로 구현하였다.
from torch.nn.utils.rnn import pad_sequence
from torch.utils.data import DataLoader
BATCH_SIZE = 128
PAD_IDX = kor_vocab['<pad>']
BOS_IDX = kor_vocab['<bos>']
EOS_IDX = kor_vocab['<eos>']
device = torch.device('cuda')
def generate_batch(data_batch):
kor_batch, eng_batch = [], []
for (kor_item, eng_item) in data_batch:
kor_batch.append(torch.cat([torch.tensor([BOS_IDX]), kor_item, torch.tensor([EOS_IDX])], dim=0))
eng_batch.append(torch.cat([torch.tensor([BOS_IDX]), eng_item, torch.tensor([EOS_IDX])], dim=0))
kor_batch = pad_sequence(kor_batch, padding_value=PAD_IDX)
eng_batch = pad_sequence(eng_batch, padding_value=PAD_IDX)
return kor_batch, eng_batch
train_iter = DataLoader(train_data, batch_size=BATCH_SIZE,
shuffle=True, collate_fn=generate_batch)
valid_iter = DataLoader(valid_data, batch_size=BATCH_SIZE,
shuffle=True, collate_fn=generate_batch)
test_iter = DataLoader(test_data, batch_size=BATCH_SIZE,
shuffle=True, collate_fn=generate_batch)
한국어 - 영어 병렬 데이터를 구현하는 전체 코드는 다음 링크에 있다.