[pytorch] Full NMT model from pretrained BERT

Full NMT model from pretrained BERT

Source Code
Github : https://github.com/RichardMinsooGo/51_Pretrained_BERT_NMT

이번 기사에서 집중해야 할 부분은 "1. Data Load " 와 "2. Build Input text, Output Text " 입니다.

그 이후의 부분은 batch_size 제외하고는 모두 동일 합니다.
항상 설명 하듯이 tensorflow나 Pytorch는 tensor를 사용한 processing 입니다. 메모리가 허락하는 한 batch size는 크게 잡는것이 연상에서 이득을 가져다 줍니다.

pytorch_pretrained_bert를 사용할 것이므로 이를 설치 합니다.

!pip install pytorch_pretrained_bert

필요한 라이브러리를 설정하고 GPU가 사용되어지는지 확인합니다. 이부분은 이전 기사와 동일 합니다.

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils import data
from pytorch_pretrained_bert import BertTokenizer, BertModel, BertForMaskedLM, BertForQuestionAnswering, BertForPreTraining

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)

1. Data Load

www.manythings.org 에서 제공하는 영어-프랑스어 데이터를 불러러오도록 하겠습니다. Web상에 존재하는 데이터를 불러오는 방법중에 이번 기사에서는 wget을 사용하였고, 또한 다른 방법인 urllib.request 을 사용하는 방법을 표현하였습니다.

홈페이지 마다 막혀있는 명령어가 있으므로 몇가지 방법을 숙지해 놓으면 프로그래밍에 도움이 됩니다,

import pandas as pd
# import sentencepiece as spm
import urllib.request
import csv

# urllib.request.urlretrieve("http://www.manythings.org/anki/fra-eng.zip", filename="fra-eng.zip")
! wget http://www.manythings.org/anki/fra-eng.zip

! unzip fra-eng.zip

2. Build Input text, Output Text

아래의 코드는 pandas를 사용하여 입력과 출력을 list의 형태로 만드는 과정이다.
pandas를 사용하는 법은 별도의 기사를 만들 예정이다.
이번 기사에서는 pandas를 사용하여 입출력의 데이터가 list 의 형태로 변환되어지는것을 확인하자.

total_df = pd.read_csv('fra.txt', sep="\t", header=None)

# total_df = total_df.sample(frac=1)  # row 전체 shuffle
# total_df = total_df[:20000]
total_df[:5]

total_df.rename(columns={0: 'english', 1: 'french', 2: 'speaker'}, inplace=True)

total_df[:5]

print('Translation Pair :',len(total_df)) # 리뷰 개수 출력

total_df["eng_len"] = ""
total_df["fra_len"] = ""
total_df.head()

import sys
for idx in range(len(total_df['english'])):
    # initialize string
    text_eng = str(total_df.iloc[idx]['english'])

    # default separator: space
    result_eng = len(text_eng.split())
    total_df.at[idx, 'eng_len'] = int(result_eng)

    text_fra = str(total_df.iloc[idx]['french'])
    # default separator: space
    result_fra = len(text_fra.split())
    total_df.at[idx, 'fra_len'] = int(result_fra)

# country 컬럼을 선택합니다.
# 컬럼의 값과 조건을 비교합니다.
# 그 결과를 새로운 변수에 할당합니다.
is_within_len = ( 7 < total_df['eng_len']) & ( total_df['eng_len']<17)

# 조건를 충족하는 데이터를 필터링하여 새로운 변수에 저장합니다.
total_df = total_df[is_within_len]

# 결과를 출력합니다.
total_df.head()

# n_samples = 43693
n_samples = 256
total_df = total_df.sample(n=n_samples, # number of items from axis to return.
          random_state=1234) # seed for random number generator for reproducibility
len(total_df)

with open('corpus_src.txt', 'w', encoding='utf8') as f:
    f.write('\n'.join(total_df['english']))

with open('corpus_trg.txt', 'w', encoding='utf8') as f:
    f.write('\n'.join(total_df['french']))

raw_encoder_input = total_df['english'].tolist()
raw_data_fr = total_df['french'].tolist()

print(raw_encoder_input)
print(raw_data_fr)

3. Preprocess

과정 "3. Preprocess" 부터 "8. Convert indexes to tensors"까지는 이전의 기사 "51.3 NMT model with 8 sentence"와 동일하다.

해당 코드에성는 두가지의 기능을 한다.
단어와 구두점 사이에 공백을 만듭니다.
Ex) "he is a boy." => "he is a boy ."
(a-z, A-Z, ".", "?", "!", ",") 이들을 제외하고는 전부 공백으로 변환합니다.

영어 문장 "Have you had dinner?" 와 프랑스어 문장 Avez-vous déjà diné?"를 가지고 전처리에 대한 시험을 해본다.

BertTokenizer의 경우에는 전처리 기능을 포함하고 있으나, 다른 tokenizer를 사용할 경우를 위하여 전처리 함수를 정의 하였습니다.

def unicode_to_ascii(s):
    return ''.join(c for c in unicodedata.normalize('NFD', s)
            if unicodedata.category(c) != 'Mn')

def preprocess(sent):
    # 위에서 구현한 함수를 내부적으로 호출
    sent = unicode_to_ascii(sent.lower())

    # 단어와 구두점 사이에 공백을 만듭니다.
    # Ex) "he is a boy." => "he is a boy ."
    sent = re.sub(r"([?.!,¿])", r" 91300", sent)

    # (a-z, A-Z, ".", "?", "!", ",") 이들을 제외하고는 전부 공백으로 변환합니다.
    sent = re.sub(r"[^a-zA-Z!.?]+", r" ", sent)

    sent = re.sub(r"\s+", " ", sent)
    return sent

# 인코딩 테스트
en_sent = u"Have you had dinner?"
fr_sent = u"Avez-vous déjà diné?"

print(preprocess(en_sent))
print(preprocess(fr_sent).encode('utf-8'))

Build Input/Output text data

입력과 출력 sentence들을 batch data 로 만들기 위하여 가공되지 않은 데이터를 전처리를 거친 후에 list로 변환 하여서 출력해 봅니다.

input_text = ['[CLS] ' + preprocess(data) + ' [SEP]' for data in raw_encoder_input]
target_text = [preprocess(data) for data in raw_data_fr]

print(input_text[:5])
print(target_text[:5])

Load pretrained BERT Model

Predefined된 BERT model을 불러오고 입출력 데이터가 잘 만들어 졌는지 확인해 봅니다.
이 기사에서는 입력과 출력 문장의 길이가 좀더 길어 졌고, 프랑스어를 포함하여 tokening을 실행할 때 token들이 여러개로 분리되므로 입출력 sequence의 길이를 80으로 정의 합니다.

# Load pre-trained model tokenizer (vocabulary)
modelpath = "bert-base-uncased"

# Load pre-trained model tokenizer (vocabulary)
model = BertForMaskedLM.from_pretrained(modelpath)
model = model.to(device)

n_seq_length = 80

4. Build Vocabulary

BertTokenizer의 경우에는 별도의 vocabulary 를 만들 필요가 없다. 자체에 내장된 vocabulary를 가지고 있으므로 tokenizer 만 정의 하면 됩니다.

tokenizer = BertTokenizer.from_pretrained(modelpath)

5. Tokenize

Tokenizing하는 방식은 이전 기사의 한개의 문장만 가지고 학습하는 경우와 동일하다. 다만 차이점은 여러개의 문장으로 이루어 졌으므로, 차이점은 함수를 사용하여 각각의 문장에 대해서 실시한다는 점만 다르다.

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

for idx in range(len(input_text)):

    tokenized_inp_text = tokenizer.tokenize(input_text[idx])
    tokenized_trg_text = tokenizer.tokenize(target_text[idx])
    len_input_text = len(tokenized_inp_text)

6. Data Processing

이번 기사에서는 "6. Data Processing" 과 "7. Convert tokens to indexes"에 동시에 이루어 지게 만들었습니다.

7. Convert tokens to indexes

이전에서 설명 드렸듯이 이 과정은 정확히 정해진 순서는 없습니다. 이전 기사의 한개의 문장만 가지고 학습하는 경우와 동일하다.

    # Processing for model
    for _ in range(n_seq_length-len(tokenized_inp_text)):
        tokenized_inp_text.append('[MASK]')

    indexed_inp_tokens = tokenizer.convert_tokens_to_ids(tokenized_inp_text)

    pad_idx = -1
    converted_trg_inds = []
    converted_trg_inds = [pad_idx] * len_input_text

    indexed_trg_tokens = tokenizer.convert_tokens_to_ids(tokenized_trg_text)
    tmp_trg_tensors   = torch.tensor([indexed_trg_tokens])
    converted_trg_inds += tmp_trg_tensors[0].tolist()

    converted_trg_inds.append(tokenizer.convert_tokens_to_ids(['[SEP]'])[0])

    for _ in range(n_seq_length-len(converted_trg_inds)):
        converted_trg_inds.append(pad_idx)

8. Convert indexes to tensors

Step 7에서 만들어진 index를 tensors로 변환 시켜 준다.
Deep learning 에서는 batch로 만들어진 tensors가 입력으로 주어진다는것을 명심 하자.

    src_tensor = torch.tensor([indexed_inp_tokens]).to(device)
    trg_tensor = torch.tensor([converted_trg_inds]).to(device)

여러개의 문장을 가지고 입출력 토큰을 만들때에는 data 전부를 포함하는 tensors를 생성 시켜줘야 한다. 그 과정은 아래와 같이 표현되어 진다.

    if idx == 0:
        tensors_src = src_tensor
    else :
        tensors_src = torch.cat((tensors_src, src_tensor), 0)

    if idx == 0:
        tensors_trg = trg_tensor
    else :
        tensors_trg = torch.cat((tensors_trg, trg_tensor), 0)

9. Build batches

이번기사에서 자세하게 살펴 볼 부분이다. 아래의 코드는 pytorch batch processing 의 일반적인 과정을 따릅니다.

아래의 코드와 같이 dataset 은 source tensors와 target tensors 로 구성되어 집니다.
dataloader는 위에서 주어진 dataset과 batch size, 그리고 shuffle을 사용할지 정도만 정의하면 간단하게 구성될수 있습니다.

from torch.utils.data import TensorDataset   # 텐서데이터셋
from torch.utils.data import DataLoader      # 데이터로더

batch_size = 64
dataset = TensorDataset(tensors_src, tensors_trg)
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)

Others are normal batch training process

학습과정은 pytorch의 일반적인 batch processing을 따른다.
이 과정은 이전의 기사들과 거의 동일하나, 아래의 몇몇 부분만 다르다.
* number of batches 를 정의한다. epoch별로 loss를 계산하는데 쓰인다. * dataloader에는 n개의 배치가 있으므로, "for loop"를 사용하여 batch별로 학습을 진행한다. * epoch 별로 loss를 구하는 과정이 필요하다.

# optimizer = torch.optim.Adam(model.parameters(), lr=5e-7)
# optimizer = torch.optim.SGD(model.parameters(), lr = 5e-5, momentum=0.9)
optimizer = torch.optim.Adamax(model.parameters(), lr = 5e-5)

num_epochs = 300

model.train()
# number of batches 를 정의한다. epoch별로 loss를 계산하는데 쓰인다.
n_batches = len(dataset)/ batch_size

for i in range(num_epochs):

    # dataloader에는 n개의 배치가 있으므로, "for loop"를 사용하여 batch별로 학습을 진행한다.
    epoch_loss = 0
    for batch_idx, samples in enumerate(dataloader):
        x_train, y_train = samples
        loss = model(x_train, masked_lm_labels=y_train)
        eveloss = loss.mean().item()
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        # epoch 별로 loss를 구하는 과정이 필요하다.
        epoch_loss += eveloss / n_batches

    if (i+1)%10 == 0:
        print("step "+ str(i+1) + " : " + str(eveloss))

print(tensors_src[6])
test_list = tensors_src[6].tolist()
test_tokens_tensor = torch.tensor([test_list]).to(device)
print(test_tokens_tensor)

이전의 기사 "51.3 NMT model with 8 sentence" 와 비교 하였을때, 아래의 부분만 다르다.

Inference

이전의 과정에서 학습된 결과를 가지고, 데이터 중 하나를 선택하여 시험을 해본다. 이 과정은 문장 선택 부분만 제외하고는 이전의 기사 "51.2 Single Sentence with BERT Tokenizer"와 동일하다.

print(tensors_src[6])
test_list = tensors_src[6].tolist()
test_tokens_tensor = torch.tensor([test_list]).to(device)
print(test_tokens_tensor)

result = []
result_ids = []
model.eval()
with torch.no_grad():
    predictions = model(test_tokens_tensor)

    start = len(tokenizer.tokenize(input_text[6]))
    count = 0
    while start < len(predictions[0]):
        predicted_index = torch.argmax(predictions[0,start]).item()

        predicted_token = tokenizer.convert_ids_to_tokens([predicted_index])
        if '[SEP]' in predicted_token:
            break
        if count == 0:
            result = predicted_token
            result_ids = [predicted_index]
        else:
            result+= predicted_token
            result_ids+= [predicted_index]

        count += 1
        start += 1
print("input_text       :", input_text[6])
print("target_text      :", target_text[6])
print("tokenized target :", tokenizer.tokenize(target_text[6]))
print("result_ids       :",result_ids)
print("result           :",result)

결과를 출력해보면 선택된 문장과 출력의 tokenized된 결과가 동일함을 알수 있다.

input_text : [CLS] both tom and mary work as models . [SEP]
target_text : tom et mary travaillent tous les deux comme mannequins .
tokenized target : ['tom', 'et', 'mary', 'tr', '##ava', '##ille', '##nt', 'to', '##us', 'les', 'deux', 'com', '##me', 'mann', '##e', '##quin', '##s', '.']
result_ids : [3419, 3802, 2984, 19817, 12462, 10484, 3372, 2000, 2271, 4649, 24756, 4012, 4168, 10856, 2063, 12519, 2015, 1012]
result : ['tom', 'et', 'mary', 'tr', '##ava', '##ille', '##nt', 'to', '##us', 'les', 'deux', 'com', '##me', 'mann', '##e', '##quin', '##s', '.']

Source Code
Github : https://github.com/RichardMinsooGo/51_Pretrained_BERT_NMT

 

[출처] https://wikidocs.net/160187

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
공지 오라클 기본 샘플 데이터베이스 졸리운_곰 2014.01.02 87148
공지 [SQL컨셉] 서적 "SQL컨셉"의 샘플 데이타 베이스 SAMPLE DATABASE of ORACLE 가을의 곰을... 2013.02.10 79349
공지 [G_SQL] Sample Database 가을의 곰을... 2012.05.20 96073
16 [ADsP] 취업 깡패 ADP 뿌시기! "빅데이터 분석가 최고의 자격증이에요" file 졸리운_곰 2022.11.20 1548
15 ADsP Chap01.Chap02 file 졸리운_곰 2018.01.01 1924
14 [ADsP준비] chap04.데이터마트 file 졸리운_곰 2018.01.01 1149
13 [ADsP준비] chap03.데이터분석 개요 file 졸리운_곰 2018.01.01 1926
12 [ADsP준비] chap02. 데이터의 이해 file 졸리운_곰 2018.01.01 1548
11 [ADsP준비] chap01.데이터의 이해 file 졸리운_곰 2018.01.01 1647
10 [ADsP] 제12회 데이터분석준전문가 시험 복원 file 졸리운_곰 2017.11.23 2098
9 [빅데이터자격증] 제3회 데이터분석 준전문가(ADsP) 시험 후기 기출문제 정리 file 졸리운_곰 2017.11.19 1945
8 [시험후기] 제 11회 ADsP 시험 후기 & 기출문제 복원 file 졸리운_곰 2017.11.19 1820
7 [ADsP] 제12회 데이터분석준전문가 시험 복원 file 졸리운_곰 2017.11.05 4999
6 제 9회 ADsP - 시험 후기 file 졸리운_곰 2017.05.03 1675
5 ADSP요약정리.pdf file 졸리운_곰 2016.10.23 8214
4 [빅데이터자격증] 제3회 데이터분석 준전문가(ADsP) 시험 후기 기출문제 정리 file 졸리운_곰 2016.10.23 3175
3 R 기본 문법 및 통계 프로그래밍 file 졸리운_곰 2014.10.24 3349
2 R을 이용한 데이터 분석 실무.pdf file 졸리운_곰 2014.10.24 3581
1 R 기초입문 - XMLArchive file 졸리운_곰 2014.10.24 1663
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED