4 분 소요

RoBERTa란


우선 BERT란 pretrained-model로 2018년에 위대한 구글에서 개발한 언어 모델인데, NLP 전반적인 분야에 아주 좋은 성능을 보여주는 모델이라고 한다.

하지만 아직 BERT는 under-fit되어 있기에 여러 하이퍼 파라미터들을 조정하여 기존 bert보다 우월한 성능을 지닌 roberta가 개발되었다.

import pandas as pd
import numpy as np
from tqdm import tqdm

데이터셋 준비


KorNLI - 두 문장의 관계를 entailment/neutral/contradiction 으로 분류

https://github.com/kakaobrain/KorNLUDatasets/tree/master/KorNLI 에서 받아온다.

path = '/content/drive/MyDrive/KorNLUDatasets-master/KorNLI/'

train_snli = pd.read_csv(path + "snli_1.0_train.ko.tsv", sep='\t', quoting=3)
train_xnli = pd.read_csv(path + "multinli.train.ko.tsv", sep='\t', quoting=3)
# 결합 후 섞기
train_data = train_snli.append(train_xnli)
train = train_data.sample(frac=1).reset_index(drop=True)
def drop_na_and_duplciates(df):
  df = df.dropna()
  df = df.drop_duplicates()
  df = df.reset_index(drop=True)
  return df
# 전제, 가설에 존재하는 한글 단어가 아닌 다른 단어들은 전부 제거해줍니다.
# train['sentence1'] = train['sentence1'].str.replace('[^ㄱ-ㅎㅏ-ㅣ가-힣 0-9]', '')
# train['sentence2'] = train['sentence2'].str.replace('[^ㄱ-ㅎㅏ-ㅣ가-힣 0-9]', '')
# 결측값 및 중복 샘플 제거
train = drop_na_and_duplciates(train)
train.head()
                                          sentence1  \
0                                     전화를 많이 받으셨나요?   
1  제노바인들은 농업과 무역에 가장 가치 있는 것으로 여겨졌던 동부 에게 제도를 장악했다.   
2           그리고 베네딕트 자신도 그것을 잘 믿을 수 없었지만, 정말로 보스였다.   
3                 다른 의상을 입은 몇몇 사람들이 할로윈 파티에 함께 모인다.   
4                       미니 트램펄린에 앉아 물병을 공중에 던지는 여자.   

                           sentence2  gold_label  
0                    전화를 받은 적이 있습니까?  entailment  
1                제노바인들은 농업을 높이 평가했다.     neutral  
2  그는 그곳에 도착하기 위해 열심히 일했기 때문에 사장이었다.     neutral  
3                  몇몇 사람들이 술을 마시고 있다     neutral  
4                       여자는 점프에 지쳤다.     neutral  
trains = train[:20000]
valid = train[20000:22000]
test = train[22000:24000]
print("premise 최대 길이:", trains['sentence1'].map(len).max())
print("hypothesis 최대 길이:", trains['sentence2'].map(len).max())

print("premise 최대 길이:", valid['sentence1'].map(len).max())
print("hypothesis 최대 길이:", valid['sentence2'].map(len).max())

print("premise 최대 길이:", test['sentence1'].map(len).max())
print("hypothesis 최대 길이:", test['sentence2'].map(len).max())
premise 최대 길이: 710
hypothesis 최대 길이: 163
premise 최대 길이: 412
hypothesis 최대 길이: 85
premise 최대 길이: 301
hypothesis 최대 길이: 109
max_seq_len = 250
MAX_LEN = 250

커스텀 학습

!pip install transformers
import torch
from torch.utils.data import Dataset, DataLoader
import torch.nn.functional as F
from transformers import TrainingArguments, Trainer
from transformers import AutoModel,AutoModelForSequenceClassification, AutoConfig, AutoTokenizer
from transformers import AdamW
from transformers import get_scheduler, get_cosine_with_hard_restarts_schedule_with_warmup
import warnings
warnings.filterwarnings('ignore')

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

BERT 파인 튜닝을 위한 입력데이터 변환

입력데이터를 (input_ids, attention_mask, 세그먼트 인코딩)으로 변환

input_ids는 토큰화 된 문장이다.

attention_mask는 pad 토큰이 있는 자리는 0, 나머지는 1을 반환한다다.

attention_mask의 길이는 항상 input_ids길이와 같아야 한다.

from transformers import AutoTokenizer

model_name = "klue/roberta-base"
tokenizer = AutoTokenizer.from_pretrained(model_name)
class BertSet(Dataset):
    def __init__(self, dataset, labels, mode):
        self.dataset = dataset
        self.labels = labels
        self.mode = mode
        
    def __len__(self):
        return len(self.dataset)
    
    def __getitem__(self, idx):
        sen1 = self.dataset.loc[idx, 'sentence1']
        sen2 = self.dataset.loc[idx, 'sentence2']
        item = tokenizer(sen1, 
                          sen2, 
                          return_tensors='pt', 
                          max_length=MAX_LEN, 
                          padding='max_length',
                          truncation=True,
                          add_special_tokens=True,
                          return_token_type_ids=True)
        item['input_ids'] = item['input_ids'].squeeze(0)
        item['attention_mask'] = item['attention_mask'].squeeze(0)
        item['token_type_ids'] = item['token_type_ids'].squeeze(0)
        if self.mode == 'train':
            item['label'] = self.labels[idx]
        
        return item
from sklearn.preprocessing import LabelEncoder

le = LabelEncoder()
y_train = le.fit_transform(trains['gold_label'])
y_valid = le.transform(valid['gold_label'])
trainset = BertSet(trains, y_train, 'train')
train_loader = DataLoader(trainset, batch_size=16, shuffle=False, num_workers=4)

valid = valid.reset_index(drop=True)
validset = BertSet(valid, y_valid, 'train')
valid_loader = DataLoader(validset, batch_size=16, shuffle=False, num_workers=4)

test = test.reset_index(drop=True)
testset = BertSet(test, None, 'test')
test_loader = DataLoader(testset, batch_size=16, shuffle=False, num_workers=4)

모델

class 수 만큼 num_labels를 지정

config = AutoConfig.from_pretrained(model_name)
config.num_labels = 3
model = AutoModelForSequenceClassification.from_pretrained(model_name, config=config).to(device)
num_epochs = 10

optimizer = AdamW(model.parameters(), lr=	1e-5)
lr_scheduler = get_cosine_with_hard_restarts_schedule_with_warmup(
      optimizer=optimizer,
      num_warmup_steps=1,
      num_training_steps=num_epochs * len(train_loader),
  )
def calc_accuracy(X,Y):
    max_vals, max_indices = torch.max(X, 1)
    train_acc = (max_indices == Y).sum().data.cpu().numpy()/max_indices.size()[0]
    return train_acc

Train

for epoch in range(num_epochs):
    train_acc = 0
    train_losses = 0
    model.train()
    for batch_id, batch in enumerate(tqdm(train_loader)):
        input_ids = batch['input_ids'].to(device)
        attention_mask = batch['attention_mask'].to(device)
        token_type_ids = batch['token_type_ids'].to(device)
        label = batch['label'].to(device)
        
        outputs = model(input_ids, attention_mask, token_type_ids)
        
        loss = F.cross_entropy(outputs[0], label)
        
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        lr_scheduler.step()
        train_acc += calc_accuracy(outputs.logits, label)
        train_losses += loss.item()
    
    print("Epoch: {}, acc: {}, loss: {}".format(epoch+1, train_acc/(batch_id+1), train_losses/(batch_id+1)))
100%|██████████| 1250/1250 [14:42<00:00,  1.42it/s]
Epoch: 1, acc: 0.6602, loss: 0.750719735455513
100%|██████████| 1250/1250 [14:48<00:00,  1.41it/s]
Epoch: 2, acc: 0.8147, loss: 0.48823210109472276
100%|██████████| 1250/1250 [14:48<00:00,  1.41it/s]
Epoch: 3, acc: 0.87865, loss: 0.33945019143223765
100%|██████████| 1250/1250 [14:48<00:00,  1.41it/s]
Epoch: 4, acc: 0.92245, loss: 0.2307316340766847
100%|██████████| 1250/1250 [14:48<00:00,  1.41it/s]
Epoch: 5, acc: 0.9482, loss: 0.16217252725437284
100%|██████████| 1250/1250 [14:47<00:00,  1.41it/s]
Epoch: 6, acc: 0.96265, loss: 0.11964335745833814
100%|██████████| 1250/1250 [14:48<00:00,  1.41it/s]
Epoch: 7, acc: 0.9727, loss: 0.09230715118460357
100%|██████████| 1250/1250 [14:47<00:00,  1.41it/s]
Epoch: 8, acc: 0.97885, loss: 0.0730009714672342
100%|██████████| 1250/1250 [14:48<00:00,  1.41it/s]
Epoch: 9, acc: 0.9837, loss: 0.05952671880833805
100%|██████████| 1250/1250 [14:47<00:00,  1.41it/s]
Epoch: 10, acc: 0.98435, loss: 0.055092274013161656

Test

output_pred = []

model.eval()
for batch_id, batch in enumerate(test_loader):
  with torch.no_grad():
      input_ids = batch['input_ids'].to(device)
      attention_mask = batch['attention_mask'].to(device)
      token_type_ids = batch['token_type_ids'].to(device)

  outputs = model(input_ids, attention_mask, token_type_ids)
  logits = outputs[0]
  logits = logits.detach().cpu().numpy()
  result = np.argmax(logits, axis=-1)
  output_pred.extend(result)
label_idx = dict(zip(list(le.classes_), le.transform(list(le.classes_))))
pred = [list(label_idx.keys())[_] for _ in output_pred]
pred[:5]
['contradiction', 'entailment', 'contradiction', 'entailment', 'contradiction']

Trainer를 이용한 학습

!pip install datasets
from transformers import Trainer, TrainingArguments, DataCollatorWithPadding, EarlyStoppingCallback
from datasets import load_metric

_collator = DataCollatorWithPadding(tokenizer=tokenizer)
_metric = load_metric('accuracy')

def METRIC(p):
    logits, labels = p
    output =  _metric.compute(references=labels, predictions=np.argmax(logits, axis=-1))
    return output

args = TrainingArguments(
    '/content/',
    do_eval=True,
    do_train=True,
    load_best_model_at_end = True,
    save_strategy="epoch",
    logging_strategy="epoch",
    evaluation_strategy="epoch",
    num_train_epochs = num_epochs,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=16,
    label_smoothing_factor=0.1
)

Trained_Model = Trainer(
        model=model,
        args=args,
        data_collator=_collator,
        train_dataset=trainset,
        eval_dataset=validset,
        tokenizer=tokenizer,
        optimizers=(optimizer, lr_scheduler),
        callbacks = [EarlyStoppingCallback(early_stopping_patience=3)],
        compute_metrics=METRIC,
        )
Trained_Model.train()
preds = Trained_Model.predict(testset)
output_pred = np.argmax(preds[0], axis=-1)
label_idx = dict(zip(list(le.classes_), le.transform(list(le.classes_))))
pred = [list(label_idx.keys())[_] for _ in output_pred]
pred[:5]
['contradiction', 'contradiction', 'contradiction', 'entailment', 'entailment']

댓글남기기