본문 바로가기

Programming/(Python)(Ubuntu)

BERT 모델을 이용해 SQuAD 1.1v 학습하기

트랜스포머의 양방향 인코더 표현 (BERT)은 Google에서 개발한 NLP(Natural Language Processing, 자연어 처리) 사전 교육 기술입니다. BERT는 2018년 Jacob Devlin과 Google의 동료가 작성하고 게시했습니다.

 

성능은 BERT가 출시되었을 때, 여러 NLU(Natural Language Understanding, 자연 언어 이해) 과제에서 최첨단 성능을 달성했습니다.

  • GLUE (일반 언어 이해 평가) 작업 세트 (9개 작업으로 구성)
  • SQuAD (Stanford Question Answering Dataset) v1.1 및 v2.0.
  • SWAG (대적 세대의 상황)
 BERT 논문 : arxiv.org/abs/1810.04805

SQuAD 데이터 다운로드

 

The Stanford Question Answering Dataset

What is SQuAD? Stanford Question Answering Dataset (SQuAD) is a reading comprehension dataset, consisting of questions posed by crowdworkers on a set of Wikipedia articles, where the answer to every question is a segment of text, or span, from the correspo

rajpurkar.github.io

여기에서도 다운로드가 가능합니다.

 

Stanford Question Answering Dataset

New Reading Comprehension Dataset on 100,000+ Question-Answer Pairs

www.kaggle.com

 

SQuAD 데이터

입력 : Context / question 쌍의 형태로 제공되며

출력 : Answer, 정수 쌍으로, Context 내에 포함된 답변 Text의 시작과 끝을 색인화 합니다.

 

Stanford Question Answering Dataset (SQuAD)은 일련의 Wikipedia 기사에서 크라우드 워커가 제기 한 질문으로 구성된 독해 이해 데이터 세트입니다. 모든 질문에 대한 답은 해당하는 읽기 구절의 텍스트 또는 범위입니다. 500개 이상의 기사에 100,000개 이상의 질문 - 응답 쌍이 있습니다.

 

다음은 SQuAD 데이터 셋의 예제입니다.

 

BERT

BERT는 Wikipedia 모음에서 생성된 두 가지 감독 된 작업에 대해 훈련 된 Transformer의 인코더입니다.

  1. 문장에서 무작위로 가린 단어 예측
  2. 텍스트 구절에서 A 문장 뒤에 B 문장이 따를 수 있는지 여부 결정

그 결과는 단어들이 주변 문맥을 파악하면서 단어를 내장하는 사전 훈련된 인코더입니다.

 

Attention 메커니즘 : papers.nips.cc/paper/7181-attention-is-all-you-need.pdf

BERT 논문 : arxiv.org/pdf/1810.04805.pdf

 

Hugging Face pytorch-transformers 리포지토리에는 사전 훈련된 BERT 모델을 제공할 뿐만 아니라 SQuAD에 대한 질문 응답을 포함하여 여러 NLP 작업을 위한 다양한 유틸리티 및 교육 스크립트가 포함되어 있습니다. QA 작업 (또는)에 맞게 이미 미세 조정된 훈련된 BERT 기반 모델을 다운로드하거나 SQuAD 교육에 제공하는 스크립트를 사용할 수 있습니다.

 

다음에는 BERT 뿐만 아니라 다른 기반 모델들이 포함되어 있습니다. 여기 에서 확인하실 수 있습니다.

모델을 더 잘 이해하기 위해 Hugging Face 코드를 자세히 살펴 보겠습니다.

 

먼저 Hugging Face 구현을 사용하려면 pytorch_transformers 패키지를 설치해야합니다.

pip install pytorch-transformers

그 다음 SQuAD 1.1 Train 및 dev 데이터 세트와 평가 스크립트, utils_squad.py, bert-base-uncased-vocab.txt

텍스트의 경우에는 화면에 띄워져 있는 내용을 텍스트에 복사 붙여넣기 하면 됩니다.

 

2 epoch 모델을 학습한 후 결과를 저장해보겠습니다.

import time

import os
import torch
from torch.utils.data import (DataLoader, RandomSampler, SequentialSampler,
                              TensorDataset)
from torch.utils.data.distributed import DistributedSampler

from apex.optimizers import FP16_Optimizer, FusedAdam

from pytorch_transformers import BertForQuestionAnswering, BertTokenizer

from utils_squad import (read_squad_examples, convert_examples_to_features)

num_train_epochs = 2
train_batch_size = 32

SQUAD_DIR = '/root/BERT/SQuAD1'
OUTPUT_DIR = '/root/BERT/output'

random.seed(42)
np.random.seed(42)
torch.manual_seed(42)
torch.cuda.manual_seed_all(42)

# 1. Load the training data from JSON
train_file = SQUAD_DIR + '/train-v1.1.json'
train_examples = read_squad_examples(train_file, is_training = True, 
                                     version_2_with_negative = False)

# 2. Tokenize the training data
tokenizer = BertTokenizer(vocab_file="bert-base-uncased-vocab.txt")
train_features = convert_examples_to_features(train_examples, tokenizer, 
                                              max_seq_length=384, doc_stride=128, 
                                              max_query_length=64, is_training=True)

# 3. Get the tokenized data ready for training the model
all_input_ids = torch.tensor([f.input_ids for f in train_features], dtype=torch.long)
all_input_mask = torch.tensor([f.input_mask for f in train_features], dtype=torch.long)
all_segment_ids = torch.tensor([f.segment_ids for f in train_features], dtype=torch.long)
all_start_positions = torch.tensor([f.start_position for f in train_features], dtype=torch.long)
all_end_positions = torch.tensor([f.end_position for f in train_features], dtype=torch.long)
train_data = TensorDataset(all_input_ids, all_input_mask, all_segment_ids,
                                   all_start_positions, all_end_positions)

train_sampler = RandomSampler(train_data)
train_dataloader = DataLoader(train_data, sampler=train_sampler, batch_size=train_batch_size)

# 4. Initialize the BERT-based model for Question Answering
#    Using half-precision (FP16) for the model
model = BertForQuestionAnswering.from_pretrained('bert-base-uncased')
model.half()

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
    
# 5. Prepare the optimizer (using mixed precision)
param_optimizer = list(model.named_parameters())
param_optimizer = [n for n in param_optimizer if 'pooler' not in n[0]]
no_decay = ['bias', 'LayerNorm.bias', 'LayerNorm.weight']
optimizer_grouped_parameters = [
    {'params': [p for n, p in param_optimizer if not any(nd in n for nd in no_decay)], 'weight_decay': 0.01},
    {'params': [p for n, p in param_optimizer if any(nd in n for nd in no_decay)], 'weight_decay': 0.0}]

optimizer = FusedAdam(optimizer_grouped_parameters,
                                  lr=3e-5,
                                  bias_correction=False,
                                  max_grad_norm=1.0)
optimizer = FP16_Optimizer(optimizer, dynamic_loss_scale=True)

# 6. Train the model
model.train()

start_time = time.time()

for epoch in range(num_train_epochs):
    for step, batch in enumerate(train_dataloader):
        batch = tuple(t.to(device) for t in batch) 
        input_ids, input_mask, segment_ids, start_positions, end_positions = batch
        outputs = model(input_ids, segment_ids, input_mask, start_positions, end_positions)
        loss = outputs[0]  

        loss.backward()

        optimizer.step()
        optimizer.zero_grad()
     
    if epoch==0:
        print("Time it took to complete the first training epoch: ", (time.time()-start_time))
    print("Loss after epoch ", epoch, ": ", loss.item())
    
# 7. Save the trained model to OUTPUT_DIR 
#    (Create the directory if it does not exist; otherwise override the contents)
if not os.path.exists(OUTPUT_DIR):
    os.makedirs(OUTPUT_DIR)

model_to_save = model.module if hasattr(model, 'module') else model
model_to_save.save_pretrained(OUTPUT_DIR)

학습이 진행되면 불러온 BERT모델이 미세 조정됩니다. 즉, 사전 훈련된 BERT 레이어는 고정되지 않으며 SQuAD 교육 중에 가중치가 업데이트 됩니다. 이는 추가 선형 레이어의 가중치가 BERT위에 추가 된 것입니다.

 

BERT를 사용하려면 GPU사용은 필수입니다. 

 

그리고 OUTPUT_DIR에 저장한 모델을 로드하고, SQuAD dev 에서 확인을 합니다.

import os

import torch
from torch.utils.data import (DataLoader, RandomSampler, SequentialSampler, TensorDataset)

from utils_squad import read_squad_examples, convert_examples_to_features, RawResult, write_predictions

from pytorch_transformers import BertForQuestionAnswering, BertTokenizer

# 1. Load a trained model

OUTPUT_DIR = '/root/BERT/test_output'
model = BertForQuestionAnswering.from_pretrained(OUTPUT_DIR)

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

# 2. Load and pre-process the test set

dev_file = "/root/BERT/SQuAD1/dev-v1.1.json"
predict_batch_size = 32

eval_examples = read_squad_examples(input_file=dev_file, is_training=False, version_2_with_negative=False)

tokenizer = BertTokenizer(vocab_file="bert-base-uncased-vocab.txt")
eval_features = convert_examples_to_features(
            examples=eval_examples,
            tokenizer=tokenizer,
            max_seq_length=384,
            doc_stride=128,
            max_query_length=64,
            is_training=False)

all_input_ids = torch.tensor([f.input_ids for f in eval_features], dtype=torch.long)
all_input_mask = torch.tensor([f.input_mask for f in eval_features], dtype=torch.long)
all_segment_ids = torch.tensor([f.segment_ids for f in eval_features], dtype=torch.long)
all_example_index = torch.arange(all_input_ids.size(0), dtype=torch.long)
eval_data = TensorDataset(all_input_ids, all_input_mask, all_segment_ids, all_example_index)

eval_sampler = SequentialSampler(eval_data)
eval_dataloader = DataLoader(eval_data, sampler=eval_sampler, batch_size=predict_batch_size)

# 3. Run inference on the test set

model.eval()
all_results = []
for input_ids, input_mask, segment_ids, example_indices in eval_dataloader:
    input_ids = input_ids.to(device)
    input_mask = input_mask.to(device)
    segment_ids = segment_ids.to(device)
    with torch.no_grad():
        batch_start_logits, batch_end_logits = model(input_ids, segment_ids, input_mask)
    for i, example_index in enumerate(example_indices):
        start_logits = batch_start_logits[i].detach().cpu().tolist()
        end_logits = batch_end_logits[i].detach().cpu().tolist()
        eval_feature = eval_features[example_index.item()]
        unique_id = int(eval_feature.unique_id)
        all_results.append(RawResult(unique_id=unique_id,
                                             start_logits=start_logits,
                                             end_logits=end_logits))
        
output_prediction_file = os.path.join(OUTPUT_DIR, "predictions.json")
output_nbest_file = os.path.join(OUTPUT_DIR, "nbest_predictions.json")
output_null_log_odds_file = os.path.join(OUTPUT_DIR, "null_odds.json")

preds = write_predictions(eval_examples, eval_features, all_results, 20,
                      30, True, output_prediction_file,
                      output_nbest_file, output_null_log_odds_file, True,
                      False, 0.0)

 

~ python SQuAD1/evaluate-v1.1.py SQuAD1/dev-v1.1.json /root/BERT/output/predictions.json
{"exact_match": 78.40113528855251, "f1": 86.50679014002237}

 

코드 원본

 

--- 코드 수정본은 추후 업데이트 -- 자기것으로 한번 만들어보세요~!

반응형

'Programming > (Python)(Ubuntu)' 카테고리의 다른 글

임의의 데이터에서 이진 분류 하기  (0) 2020.06.09
NLP Tools / 자연어처리 툴 소개  (0) 2020.06.05
GLUE  (0) 2020.06.04
우분투 Read-only 문제 해결  (0) 2020.05.29
Python Decorator(데코레이터) @의 의미  (0) 2020.04.30