LASSL Tech Blog

lightning-transformers로 살펴본 Hydra 어플리케이션 구성

이번 편에서는 간단한 예제로 살펴본 Hydra를 이용한 어플리케이션 구성 내용에 이어 lightning-transformers을 사례를 보면서 정리해보겠습니다.

conf/
┣ backbone/  # Configs defining the backbone of the model/pre-trained model if any
┣ dataset/ # Configs defining datasets
┣ optimizer/ # Configs for optimizers
┣ scheduler/ # Configs for schedulers
┣ task/ # Configs defining the task, and any task specific parameters
┣ tokenizer/ # Configs defining tokenizers, if any.
┣ trainer/ # Configs defining PyTorch Lightning Trainers, with different configurations
┣ training/ # Configs defining training specific parameters, such as batch size.
┗ config.yaml # The main entrypoint containing all our chosen config components

먼저 가장 기본이 되는 config.yaml을 정의합니다. 아래의 예시를 보면 lightning-transformers에서는 conf/config.yaml에서 defaults로 task, optimizer, scheduler, training, trainer로 하위 계층 구조를 정의하고 있습니다.

defaults: # loads default configs
  - task: default
  - optimizer: adamw
  - scheduler: linear_schedule_with_warmup
  - training: default
  - trainer: default

experiment_name: ${now:%Y-%m-%d}_${now:%H-%M-%S}
log: False
ignore_warnings: True # todo: check warnings before release

주) now는 Hydra에서 미리 등록되어 사용할 수 있습니다. https://github.com/facebookresearch/hydra/blob/master/hydra/core/utils.py#L185

다음으로 conf/task/default.yaml를 살펴보면, package_group_ 개념이 사용되고 있습니다.

conf/task/default.yaml

# @package task
defaults:
  - /dataset@_group_: default

# By default we turn off recursive instantiation, allowing the user to instantiate themselves at the appropriate times.
_recursive_: false

_target_: lightning_transformers.core.model.TaskTransformer
optimizer: ${optimizer}
scheduler: ${scheduler}

dataset의 config group을 default로 설정하였기 때문에, conf/dataset/default.yaml을 로드합니다. 또한 optimizer와 scheduler는 각각 conf/config.yaml에서 앞서 정의된 optimizerscheduler를 변수로 받습니다. _recursive_ 는 해당 파일에 종속된 다른 config들을 instantiate 할 것인지를 나타냅니다.


대표적인 예로 LanguageModeling를 살펴봅니다.

python train.py task=nlp/language_modeling

train.py를 살펴보면 가장 기본이 되는 conf/config.yaml config_path와 config_name을 정의하고 있습니다.

"""The shell entry point `$ pl-transformers-train` is also available"""
import hydra
from omegaconf import DictConfig

from lightning_transformers.cli.train import main


@hydra.main(config_path="./conf", config_name="config")
def hydra_entry(cfg: DictConfig) -> None:
    main(cfg)


if __name__ == "__main__":
    hydra_entry()

conf/config.yaml

defaults: # loads default configs
  - task: default
  - optimizer: adamw
  - scheduler: linear_schedule_with_warmup
  - training: default
  - trainer: default

experiment_name: ${now:%Y-%m-%d}_${now:%H-%M-%S}
log: False
ignore_warnings: True # todo: check warnings before release

이전 편에서 배운 overrding을 살펴봅니다. 예를 들어 Q. batch 사이즈를 바꾸고 싶은데 어떻게 해야할까요? batch 사이즈는 training/default.yaml에 정의되어 있습니다. 따라서 CLI 명령어에 training.batch_size=8 등을 추가하면 변경이 됩니다.

conf/training/default.yaml

run_test_after_fit: True
lr: 5e-5
output_dir: '.'

# read in dataset
batch_size: 16
num_workers: 16
python train.py task=nlp/language_modeling training.batch_size=8

본격적으로 task에 대해 알아봅니다. 먼저 conf/config.yaml에 의해 conf/task/default.yaml 내용을 입력 받아야 합니다. 그런데, CLI 명령에 의해 conf/task/nlp/language_modeling.yaml로 대체됩니다.

conf/task/nlp/language_modeling.yaml

# @package task
defaults:
  - nlp/default
  - override /dataset@_group_: nlp/language_modeling/default
_target_: lightning_transformers.task.nlp.language_modeling.LanguageModelingTransformer
downstream_model_type: transformers.AutoModelForCausalLM

그런데, 처음 defaults에 의해 conf/task/nlp/default.yaml 필요한 값들을 불러옵니다.

conf/task/nlp/default.yaml

# @package task
defaults:
  - /dataset@_group_: default

# By default we turn off recursive instantiation, allowing the user to instantiate themselves at the appropriate times.
_recursive_: false

_target_: lightning_transformers.core.model.TaskTransformer
optimizer: ${optimizer}
scheduler: ${scheduler}

dataset은 conf/dataset/default.yaml 파일을 가져옵니다.

conf/dataset/default.yaml

# @package dataset
_target_: lightning_transformers.core.data.TransformerDataModule
cfg:
  # torch data-loader specific arguments
  batch_size: ${training.batch_size}
  num_workers: ${training.num_workers}

그런데, language_modelingTransformerDataModule 대신 LanguageModelingTransformer을 사용합니다. 그리고 모형 아키텍쳐 역시 transformers.AutoModelForCausalLM 사용해야합니다. 따라서 CLI 명령어를 통해 python train.py task=nlp/language_modeling 실행이 필요합니다.

conf/nlp/language_modeling.yaml

# @package task
defaults:
  - nlp/default
  - override /dataset@_group_: nlp/language_modeling/default
_target_: lightning_transformers.task.nlp.language_modeling.LanguageModelingTransformer
downstream_model_type: transformers.AutoModelForCausalLM

또한, 데이터셋 역시 그룹을 default(task/nlp/default.yaml에서 정의된)에서 dataset/nlp/language_modeling/default.yaml로 overrding 합니다. 그리고 _target_downstream_model_type로 각각 정의되어 있습니다.

override에 관한 자세한 것은 https://hydra.cc/docs/next/advanced/override_grammar/basic/ 참고하시기 바랍니다.

따라서, task=nlp/language_modeling.yaml를 정리해보면 (1) conf/task/nlp/default.yaml에 의해

# @package task
defaults:
  - /task/default
  - /backbone@_group_: nlp/default # use AutoModel backbone by default
  - override /dataset@_group_: nlp/default
backbone: ${backbone}

backbone값을 받고, (2) conf/nlp/language_modeling.yaml 에 의해 _target_downstream_model_type을 받습니다. 그리고, (3) 최초 conf/task/default.yaml에서 _recuresive_optimizer 그리고 scheduler를 가져옵니다. 이때 _target_lightning_transformers.task.nlp.language_modeling.LanguageModelingTransformer로 override 되었습니다.

conf/task/default.yaml

# @package task
defaults:
  - /dataset@_group_: default

# By default we turn off recursive instantiation, allowing the user to instantiate themselves at the appropriate times.
_recursive_: false

_target_: lightning_transformers.core.model.TaskTransformer
optimizer: ${optimizer}
scheduler: ${scheduler}

이를 task 전체에 대해 정리해보면 다음과 같습니다.

task:
  _recursive_: false
  _target_: lightning_transformers.task.nlp.language_modeling.LanguageModelingTransformer
  optimizer: ${optimizer}
  scheduler: ${scheduler}
  backbone: ${backbone}
  downstream_model_type: transformers.AutoModelForCausalLM

task는 앞서 살펴본대로 defaults에 의해 backbonedataset에도 영향을 줍니다. 먼저 backbone을 살펴보면 tokenizer를 설정하고 pretrained_model_name_or_path를 결정하고 있습니다.

conf/backbone/nlp/default.yaml

# @package backbone
defaults:
  - /tokenizer@_group_: autotokenizer # use AutoTokenizer by default
pretrained_model_name_or_path: bert-base-cased

datasetnlp/language_modeling/default.yaml에서

# @package task
defaults:
  - nlp/default
  - override /dataset@_group_: nlp/language_modeling/default
_target_: lightning_transformers.task.nlp.language_modeling.LanguageModelingTransformer
downstream_model_type: transformers.AutoModelForCausalLM

nlp/default로 부터 cfg 다음을 입력 받고,

# @package dataset
defaults:
  - /dataset/default
_target_: lightning_transformers.core.nlp.HFDataModule
cfg:
  dataset_name: null
  dataset_config_name: null
  train_file: null
  validation_file: null
  test_file: null
  train_val_split: null
  max_samples: null
  cache_dir: null
  padding: 'max_length'
  truncation: 'only_first'
  preprocessing_num_workers: 1
  load_from_cache_file: True
  max_length: 128
  limit_train_samples: null
  limit_val_samples: null
  limit_test_samples: null

dataset/default로 부터

# @package dataset
_target_: lightning_transformers.core.data.TransformerDataModule
cfg:
  # torch data-loader specific arguments
  batch_size: ${training.batch_size}
  num_workers: ${training.num_workers}

위의 cfg를 입력 받습니다.

따라서, 최종적으로 2개의 cfg가 합쳐지며,

dataset:
  _target_: lightning_transformers.task.nlp.language_modeling.LanguageModelingDataModule
  cfg:
    batch_size: ${training.batch_size}
    num_workers: ${training.num_workers}
    dataset_name: null
    dataset_config_name: null
    train_file: null
    validation_file: null
    test_file: null
    train_val_split: null
    max_samples: null
    cache_dir: null
    padding: max_length
    truncation: only_first
    preprocessing_num_workers: 1
    load_from_cache_file: true
    max_length: 128
    limit_train_samples: null
    limit_val_samples: null
    limit_test_samples: null
    block_size: null

_target_conf/task/nlp/language_modeling.yaml_target_lightning_transformers.task.nlp.language_modeling.LanguageModelingDataModule로 변경 됩니다.

또 다른 예제, Causal Language Modeling에서 configdatasets, 그리고 model 등을 Hydra와 연결하여 잘 설명하고 있습니다.


그렇다면 새로운 흥미로운 예제에 응용을 해볼까요?

최근 한국에서 공개된 KLUE(Korean Language Understanding Evlauation) 데이터가 있습니다. 현재 KLUE 데이터셋과 벤치마크 모형이 오픈소스로 제공되고 있으므로 이를 Hydra와 Ligtning-transformer를 이용하여 학습하는 법에 대해 살펴봅니다.

KLUE Leaderboard

다양한 TASK 중에서 비교적 손쉬운 KLUE-KNLI 분류 문제(classification)를 다뤄봅니다.

데이터셋은 https://huggingface.co/datasets/viewer/?dataset=klue 에서도 확인할 수 있으며, KLUE 벤치마크 모형은 https://huggingface.co/klue 에서 확인할 수 있습니다.

KLUE 데이터셋은 minho.ryu님과 jungwhank님께서 기여하였습니다. https://github.com/huggingface/datasets/blob/master/datasets/klue/klue.py

ligtning-transformers를 돌아와서 아래의 레포지토리를 install 혹은 clone 합니다.

git clone https://github.com/PyTorchLightning/lightning-transformers

먼저 데이터셋 부터 정의합니다.

nli에 맞게 task를 nlp/text_classification로 설정하고, dataset_namedataset_config_name을 정의합니다. 그리고 모형 backbone으로는 klue/bert-base로 설정하고, gpu 갯수와 max_epochs 등 가장 기본적인 파라미터만 셋팅합니다.

python train.py task=nlp/text_classification dataset.cfg.dataset_name=klue dataset.cfg.dataset_config_name=nli backbone.pretrained_model_name_or_path=klue/bert-base trainer.gpus=1 trainer.max_epochs=10

KLUE-NLI 리더보드에서 KLUE-BERT-base의 ACC는 81.63%로 보고되고 있습니다. 위의 스크립트 실행 결과 약 81.4%의 정확도를 보입니다. 위의 스크립트에서 배치 사이즈와 learning rate, 시퀀스 최대 길이 등을 간단히 변경한 것만으로도 아래의 스크립트로도 벤치마크 성능보다 뛰어난 82%의 정확도를 얻을 수 있습니다.

python train.py task=nlp/text_classification dataset.cfg.dataset_name=klue dataset.cfg.dataset_config_name=nli backbone.pretrained_model_name_or_path=klue/bert-base trainer.gpus=1 trainer.max_epochs=10 training.batch_size=64 training.lr=5e-05 dataset.cfg.max_length=64

현재 lightning-transformers에서는 text_classification 시 data에서 idxlabel을 제외하고 첫번째 field와 두번째 field를 label을 예측하는 데 사용하고 있습니다.(https://github.com/PyTorchLightning/lightning-transformers/blob/master/lightning_transformers/task/nlp/text_classification/data.py#L59 참조) 따라서 위 코드로 실행 시 데이터셋을 재정의하거나 해당 라인을 아래와 같이 수정한 후 실행하면 됩니다.

zip(example_batch[input_feature_fields[1]], example_batch[input_feature_fields[2]])

다음 편으로는 ligtning-transformer으로 특정 task를 해결하기 위해 tokenizer, dataset, model 등을 직접 정의하는 방법에 대하여 알아보겠습니다.


본 블로그는 hydra-core==1.1.0rc1, lightning-transformers==0.1 버전을 기준으로 작성되었습니다.

References

hyungjun.kim

hyungjun.kimFollow
Done is better than perfect.