
Dataset / Preprocessing
load_dataset() 을 통해서 Huggingface에서 제공하는 데이터셋을 불러와서 사용할 수 있다. load_dataset() 을 통해서 불러온 데이터셋은 DatasetDict 클래스로 제공된다. (그냥 dictionary라고 생각하면 된다.) split 에 따라서 "train", "validation", "test" 등으로 구분되어 있어서 해당 split의 이름으로 데이터셋에 접근할 수 있다.
raw_dataset = load_dataset('...')
# train dataset
# 5개 출력
raw_dataset['train'][:5]
# 데이터셋의 추가적인 정보 출력
raw_dataset['train'].features
이렇게 불러온 데이터를 모델의 입력으로 제공하기 위해서는 tokenizer를 이용해서 string을 token id로 변환해야 한다.
# 불러온 데이터를 tokenizer를 통해서 token id로 변환하는 함수
def tokenize_function(example):
return tokenizer(example['sentence1'],
example['sentence2'],
padding='max_length',
truncation=True,
max_legnth=128)
# tokenized된 결과들은 remove_columns(), rename_columns(), with_format() 등의 함수로 전처리
tokenized = raw_dataset.map(tokenize_function, batch=True)
위 코드와 같이 max_length로 입력 최대 길이를 지정하면, 모든 데이터에 대해서 padding을 입력 최대 길에 대해서 추가하기 때문에, 입력 문장이 짧아질수록 padding token이 더 많이 추가되는 현상이 발생하고, 이는 추가적인 computation을 발생시킨다. 이를 해결하기 위해서 batch 단위로 padding을 할 수 있다. 이렇게 하면, 동일한 batch 내에서 가장 긴 문장의 길이에 맞게 padding을 주고, (max_length만큼 줬을 때 보다 훨씬 적은 padding token을 추가하게 된다.) 이는 CPU/GPU에서의 연산량을 감소시키는 효과가 있다. 하지만, TPU와 같은 환경에서는 더 느려질 수 있다는 점을 주의해야 한다. (TPU에서는 fixed batch size를 사용하도록 한다.) 이렇게 batch 내에서 가장 긴 문장의 길이에 맞게 padding을 하는 dynamic padding 기법은 DataCollectorWithPadding을 사용해서 정의할 수 있고, Dataloader에서 collate_fn의 파라미터로 넘겨주면, Dataloader에서 데이터를 제공할 때 dynamic padding이 적용된 상태로 제공된다.
GLUE benchmark를 보면 COLA, SST-2 등의 task는 하나의 문장을 처리하는 task이고 MRPC, STS-B, QQP, MNLI, QNLI, RTE, WNLI 등의 task는 두 개의 문장(sentence-pair)을 처리하는 Task로 구성되어 있다. 두 개의 문장을 처리해야 하는 경우, 그냥 단순하게 tokenizer에 2개의 문장을 입력으로 넣어주면 된다.
tokenizer("... sentence 1 ...", "... sentence 2 ...")
이렇게 2개의 문장을 입력으로 넣어주면 결과로는 input_ids, token_type_ids, attention_mask 등이 출력으로 나오는데, 2개의 문장을 사용했기 때문에 두 문장을 구분해줄 필요가 있다. 이러한 역할을 token_type_ids 에서 하는데, 첫 번째 문장에 해당하는 token들의 인덱스에는 0을, 두 번째 문장에 해당하는 token들의 인덱스에는 1을 지정해서, 모델로 하여금 지금 처리하는 token이 몇 번째 문장에 해당하는지 알 수 있게 한다. Special token을 보면, 첫 번째 문장의 앞에 [SEP] token, 뒤에는 [SEP] token이 들어가게 되고 (이 두 token들의 token_type_ids 는 0으로 지정된다.), 두 번째 문장의 제일 뒤에는 [SEP] token이 들어가게 된다. (해당 token의 token_type_ids는 1로 지정된다.)
TrainerAPI
Trainer class에 학습에 필요한 hyperparemter를 제공하면 자동으로 해당 설정에 맞게 학습을 진행할 수 있다. 이 때, hyperparameter는 TrainingArguments 라는 클래스에 명시적으로 정의할 수 있다.
# Trainer 정의 예시
trainer = Trainer(model,
training_args,
# 학습 데이터
train_dataset=...,
# evaluation 데이터
eval_dataset=...,
data_collector=...,
tokenizer=...)
이렇게 trainer를 정의했다면, trainer.train() 을 통해서 자동으로 학습이 가능하다.
trainer.predict() 를 통해서 예측도 할 수 있는데, 예시 코드는 다음과 같다.
output = trainer.predict(EVAL_DATA)
# logit을 의미
predictions = output.predictions
label_ids = output.label_ids
metrics = output.metircs
# 사용하고 싶은 metric 불러오기
metric = load_metric('...')
preds = np.argmax(predictions, axis=1)
# 지정한 metric으로 결과 계산, dict 형태로 결과를 받아볼 수 있음.
metric.compute(predictions=preds, references=label_ids)
Training loop
1. training data 불러오기 : Dataloader (shuffle, batch_size, colalte_fn 등을 정의) 를 이용해서 Dataloader를 정의한다.
2. model : from_pretrained() 로 사전 학습된 모델을 불러오고, model.train()으로 학습 가능하도록 모드를 변경한다.
3. model(**batch) 를 통해서 loss를 구하고, gradient를 계산한다.
4. optimizer를 통해서 모델의 파라미터들을 업데이트 한다.
loss = output.loss
loss.backward()
optimizer.step()
optimizer.zero_grad()
Evaluate model
model.eval()
for batch in eval_loader:
with torch.no_grad():
outputs = model(**batch)
logits = outputs.logits
predictions = np.argmax(logits, dim=1)
metric.add_batch(prediction=predictions, references=batch['labels'])
metric.compute()
Accelerator
한 번도 사용해본 경험이 없다. 이번 기회에 좀 자세히 알아보고 사용해봐야겠다.
Accelerator() 를 사용하면, 데이터를 명시적으로 특정 device에 추가할 필요가 없어지고, 연산을 현재의 환경에 알맞게 최적화해서 학습이 가능하도록 한다고 한다고 한다. -
'필기 노트 > Huggingface Transformers' 카테고리의 다른 글
[Huggingface] PreTrainedTokenizer class (1) | 2021.07.18 |
---|---|
[Huggingface] PretrainedConfig class (1) | 2021.07.16 |
[Huggingface] PreTrainedModel class (0) | 2021.07.16 |
[Huggingface] Huggingface Tokenizer (1) | 2021.07.09 |
댓글