dukim's blog

[WK07-Day032][21.09.15.Wed] ELMo, GPT-1, Layer Normalization, Hugging Face Transformers 기본 사용법 본문

Boostcamp AI Tech 2th

[WK07-Day032][21.09.15.Wed] ELMo, GPT-1, Layer Normalization, Hugging Face Transformers 기본 사용법

eliza.dukim 2021. 9. 25. 16:29

Intro

  • 논문 읽기 모임 3회차 : ELMo, GPT-1
  • 어제 해결하지 못했던 Layer Normalization 정리
  • 9, 10강 실습 코드
  • 선택과제 3 BPE

학습내용

[논문 읽기 모임] ELMo, GPT-1

ELMo

  • Peters et al., Deep contextualized word representations, NAACL 2018
  • 예전 같았으면 자세히 읽었을텐데, 지금은 BERT 논문을 더 잘 이해하기 위한 배경지식 정도로만 내용 파악하는 게 맞는 것 같다.
  • BERT 이전의 기법들을 통합하는 논문

Abstract

  • 주요 특징
    • 단어의 복잡한 특징 모델링(syntax, semantics)
    • 다양한 언어 맥락 상에서 어떻게 사용되는지 학습(polysemy, 다의어)
  • 구조
    • biLM pre-trained on a large text corpus
    • CharCNN (for rich subword information)
    • Feature-based pre-training (not fine-tuning)
  • 성능
    • 6개 NLP Task에서 SoTA

Related Works

  1. 기존 Word Embeddings
    • Pre-trained vector(e.g. Word2Vec, GloVe)
    • 한계: 각 단어에 대해 맥락에 무관한 하나의 표현만(Single Context-Independent) 제공
  2. 기존 Word Embeddings의 한계점 해결을 위한 방법들
    • subword information을 활용하여 word-vectors의 단점을 보완하는 방안
    • 각각의 word sense를 나타내는 별도의 벡터를 학습하여 여러 의미를 나타내는 방안
    • 제안하는 방법인 ELMo에서는 Char-CNN을 사용해 subword를 사용하는 이점을 얻고, Multi-sense information을 downstream task에 통합시킴
  3. Context-Dependent Representation
    • context-dependent한 표현을 학습하려던 기존 연구
      • context2vec(Melamud et al., 2016) : pivot word 주변 맥락을 인코딩하는데 biLSTM 이용
      • CoVe(McCann et al., 2017) : 기계번역의 인코더를 사용
      • Pre-ELMo(Peters et al., 2017) : unsupervised language model 사용
    • 위의 방법 모두 대량의 데이터셋이 필요하지만, CoVe의 경우엔 supervised learning을 위한 parallel corpora를 구하기 어려움.
    • 따라서 본 연구에서는 상대적으로 풍부한 monolingual 데이터(약 30M sentences로 구성)로 biLM을 학습
    • 위 방법들을 일반화하여 Deep Contextual Representation을 만들고 NLP의 다양한 task에 잘 작동하는 것을 보임
  4. Layer Representation
  5. Fix pre-train

ELMo - CharCNN

ELMo - Pre-training

ELMo - Fine-tune

  • Feature-based 방식
  • 우리가 아는 일반적인 Fine-tuning 방식과는 조금 다르다. layer 별로 적합한 Task가 다르다는 이전 연구를 기반으로 각 layer에 학습 가능한 가중치를 주어 가중합한 embedding을 사용
  • 기존 task-specific한 LSTM 모델의 input embedding과 output에 ELMo Embedding을 concatenate하여 사용함
    image

Results

  • 아래 표에서 볼 수 있듯이 6종의 NLU benchmark tasks에서 기존 SoTA를 능가하는 성능을 보여주었음
    image

  • 문맥에 따라 다의어(polysemy)의 의미를 다르게 표현. 아래 표에서 play와 유사한 embedding vector로 GloVe는 스포츠 도메인에 대해서만 유사한 embedding vector가 나타나는 반면에, ELMo의 biLM 방식은 스포츠에서의 play와 연극에서의 play의 의미를 문맥에 따라 다르게 임베딩하여 해당 문맥에서 쓰인 play의 embedding vector가 가장 유사한 것으로 나타남
    image

GPT-1

Introduction

  • Unlabeled data를 활용하여 성능 향상을 이룬 기존 사례들이 있다(e.g. Word2Vec, GloVe 등)
  • Word-level 수준에서 얻을 수 있는 정보 이상으로 unlabeled text data를 활용하고자 하나 두 가지 문제점이 있다.
  • (1) Target task에 전이시킬 효과적인 Pre-training objective가 무엇인지 불확실하며 (2) 이에 대해 합의된 효과적인 방법이 없다는 점
    • 당시엔 다음 세 가지 방식이 있었음
      • ELMo : task-specific changes to the model architecture
      • ULMFiT : intricate learning schemes
      • Add auxiliary learning objectives
  • 아직 합의된 방식이 없었고 GPT-1은 이러한 semi-supervised 방식으로 Pre-training & Fine-tuning 방식을 사용할 것을 제안(최초는 아님)
  • Model Architecture로 Transformer Decoder 선택:
    • long-term dependency 문제를 해결하기 위한 structured memory가 결과적으로 다양한 task에 대해 robust한 transfer performance를 보일 것이라 기대하기 때문(해당 문구를 아직 이해하지 못함)
  • Transfer Learning을 위한 입력 형식으로 Traversal style의 task-specific input adaptation을 활용
  • 4가지 유형의 NLU Tasks(Natural Language Inference, Question Answering, Semantic Similarity, Text Classification)에 대한 성능 측정 결과 12개 중 9개 SoTA 달성
    • "Task-Agnostic Model"에 약간의 parameter만을 추가하여 각 Task에 대해 고안된 모델의 성능을 뛰어넘음
  • (코멘트) 그동안 하나로 정리되지 못 했던 semi-supervised 방식을 정리했다는 점에서 의의가 있는 논문

Framework

  • Unsupervised pre-training

    • LM Objective로 학습, 아래 likelihood를 최대화하는 방향으로 학습함
      image

    • Transformer Decoder block을 사용(encoder와 decoder를 연결하는 cross-attention은 제거됨), 이때 기존 Transformer가 Positional Encoding을 사용하는 것과 달리 Positional Embedding을 사용, 각 위치에 대한 embedding vector를 학습

    • Token Embedding matrix는 input과 output에 대해 공유되는 parameter
      image

  • Supervised fine-tuning

    • pre-training이 끝난 모델의 parameter를 target task에 대하여 adapt 시키는 과정
    • 마지막 transformer block의 activation 값인 $h_{l}^{m}$을 각 task에 대해 추가한 linear output layer($W_y$)에 입력하여 $y$를 예측함
      image
    • task에 대한 labeled dataset을 $\mathcal{C}$라 할 때, fine-tuning을 위한 다음 objective를 최대화하는 방향으로 학습
      image
    • 최종적으로는 (a) 일반화 성능 향상 및 (b) 수렴 가속화를 위해 위 식에 LM objective를 추가한 objective를 사용($\lambda$는 가중치로 hyperparameter)
      image
    • fine-tuning을 위해 추가되는 parameter는 $W_y$와 special token의 embedding (e.g. <s>, <e>, $)

Task-specific Input Transformations

Experiment Setup

  • Unsupervised Pre-training

    • BookCorpus dataset: 7,000권의 미출간 도서(약 800M 단어), long-range structure
    • 1B Word Benchmark: 문장 단위로 shuffle 되어 있어 long-range structure가 아님
  • Model Specifications

    • 12 layer decoder-only transformer with masked self-attention heads
      • 768 dims for states, 12 heads, 3072 dims for FFN
    • Optimizer: Adam
      • max learning rate 2.5e-4
      • learning rate schedule : cosine annealing, warm-up step 2,000
    • 100 epoch, batch-size 64, max seq length 512
    • Layer Normalization: $N(0, 0.02)$
    • BPE vocab with 40,000 merges
    • Residual, embedding, and attention dropouts with a rate of 0.1
    • L2 Regularization with $w = 0.01$
    • Activation function : Gaussian Error Linear Unit(GELU)
    • Learned Position Embedding instead of sinusoidal version of the original works
    • Cleaning: ftfy library
    • Tokenizer : spaCy library
  • Fine-tuning Details

    • Reuse the hyperparameter settings from unsupervised pre-training
    • Add dropout to the classifier with a rate of 0.1
    • Learning rate : 6.25e-5
    • Batch-size : 32
    • 3 Epochs, Linear learning rate decay, Warm-up Ratio 0.2% of training
    • Auxiliary objective weight: $\lambda = 0.5$

Results

  • NLI Task: RTE를 제외한 모든 결과에서 SoTA 달성(Table 2)
  • (코멘트) RTE는 데이터 샘플이 다른 데이터셋에 비해 작기 때문에 성능이 낮게 나왔을 가능성(2,490 examples)
  • Question answering & Commonsense reasoning Task: SoTA 달성(Table 3)
    image
  • Semantic Similarity Task & Classification Task: SST2, MRPC를 제외한 모든 결과에서 SoTA
    image

Analysis

  • Impact of number of layers transferred

    • 아래 Figure에서 좌측에 해당하는 부분은 pre-training으로 학습한 layer를 몇 개를 쓰느냐에 따른 RACE와 MNLI-m의 dev set의 성능을 보여줌
    • 사용하는 layer가 증가할수록 성능이 비례하여 증가 -> pre-trained model의 각 layer가 target task를 풀기 위한 유용한 functionality를 포함
  • Zero-shot Behaviors

    • Transformer로 LM pre-training 하는 것이 효과적인 이유에 대한 가설
      • Generative model이 학습하는 target tasks가 사실은 language modeling의 성능을 향상에 도움을 줌(사실 두 task에는 연관관계가 있다는 것)
      • LSTM 보다 더 구조화된 transformer의 attentional memory가 transfer learning에 도움을 줌
        • 위 가설을 증명하기 위해 pre-training 업데이트 횟수에 따른 target tasks의 성능을 fine-tuning없이 측정
        • (코멘트) 이때 특이한 점은 task별 fine-tuning없이 성능 측정을 하기 위해서 huristic한 방법을 쓰는데, 이 방식이 이후 GPT-2, 3, Prompt Engineering에서 찾아볼 수 있는 방식이라는 것
        • 예를 들어
      • CoLA(linguistic acceptability) task의 경우엔 각 문장에 대해 generative model이 부여한 token log-probability의 평균을 구해서 threshold를 기준으로 적합 여부를 파악
      • SST-2(sentiment analysis)의 경우엔 각 샘플에 very라는 토큰을 추가하고, 출력 확률 분포를 positive, negative 두 가지 토큰만 나오도록 제한하여 generative model의 very 토큰의 예측 결과로 분류
      • RACE나 DRPD(winograd schemas)에 대한 방법은 논문 참고
        • 실험 결과 pre-training 업데이트 횟수에 따라 안정적&지속적으로 관련 taget task의 성능이 증가하는 것을 확인할 수 있었으며 이는 generative pre-training이 관련 task의 학습에 도움을 준다는 것을 의미
        • 반면 LSTM의 경우엔 업데이트 횟수에 따라 일관되게 안정적으로 증가하지 않고 분산을 가지면서 증가(중간 중간 하락이 발생하면서 다시 상승하는 패턴)함 -> Transformer architecture가 transfer learning에 도움을 준다는 것을 의미
          image
  • Ablation studies

    • Fine-tuning 단계에서 LM objective의 효과(제외한 경우와 비교해 실험)
      • LM objective는 NLI, QQP task 성능향상에 도움
      • LM objective를 적용하면 큰 dataset을 가진 task의 성능을 향상시키며, 반대의 경우는 감소 되는 경향
    • Single-layer, 2048 dim의 LSTM과 비교
      • MRPC를 제외한 모든 task에서 성능 하락(평균 5.6점 하락)
    • Pre-training없이 학습한 조건과 비교
      • 14.8% 성능 하락
        image

[개인 학습] Batch Normalization vs Layer Normalization

  • Batch Normalization과 Layer Normalization의 차이
  • 개인 노션에 정리 : 링크

[실습코드] Hugging Face transformers 기본 사용법

BERT pre-trained model 불러오기

  • [모델명]Model 클래스: task-specific head layer가 없는 모델, pooler layer가 있지만 옵션으로 제외하여 불러올 수 있음

    bert_name = 'bert-base-uncased'
    
    # Loading the pre-trained BERT
    from transformers import BertConfig, BertTokenizer, BertModel
    config = BertConfig.from_pretrained(bert_name)
    tokenizer = BertTokenizer.from_pretrained(bert_name)
    model = BertModel.from_pretrained(bert_name)

    BERT tokenizer 기본 사용

    # 1. 모델 입력 형태로 변환
    sentence = "I want to go home."
    output = tokenizer(sentence)
    output
    # >>> {'input_ids': [101, 1045, 2215, 2000, 2175, 2188, 1012, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1]}
    
    # 2. tokenize한 결과를 리스트로 반환
    tokenized = tokenizer.tokenize(sentence)
    tokenized
    # >>> ['i', 'want', 'to', 'go', 'home', '.']
    
    # 3. tokenize한 결과(tokenized)의 각 token을 하나의 string으로 합침
    sentence = tokenizer.convert_tokens_to_string(tokenized)
    sentence
    >>> i want to go home .
    
    # 4. tokenizer의 vocabulary 반환(dict type, word2idx) 
    vocab = tokenizer.get_vocab()
    
    # 5. tokenize한 결과(tokenized)의 각 token을 index로 변환
    token_ids = [tokenizer._convert_token_to_id(token) for token in tokenized]
    token_ids
    >>> [1045, 2215, 2000, 2175, 2188, 1012]
    ## 아래는 위와 동일한 결과를 출력
    token_ids = tokenizer.convert_tokens_to_ids(tokenized)
    ## Special token을 포함하여 index로 변환
    token_ids = tokenizer.encode(sentence)
    token_ids
    >>> [101, 1045, 2215, 2000, 2175, 2188, 1012, 102]
    
    # 6. index로 변환된 결과를 다시 token으로 되돌림
    tokens = tokenizer.convert_ids_to_tokens(token_ids)
    tokens
    >>> ['[CLS]', 'i', 'want', 'to', 'go', 'home', '.', '[SEP]']
    ## 위 결과에 다시 3을 적용
    sentence = tokenizer.convert_tokens_to_string(tokens)
    sentence
    >>> [CLS] i want to go home . [SEP]

BERT 모델 기본 사용

  • 모델 입출력, 출력이 두 개인 것은 알고 있었지만 각 값이 정확하게 어떤 값인지 몰랐기 때문에 이번에 학습하면서 직접 확인해 보았다.

    '''
    input : 
      - input_ids : torch.Tensor
      - attention_mask : torch.Tensor
    output : 
      - last_hidden_state : torch.Tensor # (Batch, Seq_len, Dim)
      - pooler_output : torch.Tensor : last_hidden_state를 pooler layer에 입력해 얻은 값으로, [CLS] token의 hidden state(last_hidden_state[:, 0, :])를 NSP Task에 대해 학습한 linear layer에 입력해 얻은 값
    '''
    outputs = model(input_ids=batch, attention_mask=batch_mask)
    outputs.keys()
    >>> odict_keys(['last_hidden_state', 'pooler_output'])
    
    # last_hidden_states와 pooler_output 값의 관계 확인
    last_hidden_states = outputs[0]
    pooler_output = outputs[1]
    
    model.pooler(last_hidden_states) == pooler_output
    torch.all(model.pooler(last_hidden_states) == pooler_output)
    >>> tensor(True)
  • Downstream task 적용 예시, token-level classification의 경우 각 token에 대해 동일한 linear layer 적용

    # 1. Sentence-level Classification
    num_classes = 10
    sent_linear = nn.Linear(config.hidden_size, num_classes)
    cls_output = last_hidden_states[:, 0, :]
    
    # 2. Token-level Classification
    num_classes = 30
    token_linear = nn.Linear(config.hidden_size, num_classes)
    token_output = token_linear(last_hidden_states)
  • Etc. 기본 제공하는 클래스에는 각각의 head layer를 포함하고 있음, 이때 위의 예시처럼 [CLS]의 hidden state를 classifier layer의 입력으로 쓰지 않고 pooler layer를 거친 값에 dropout을 적용한 값을 classifier layer의 입력으로 사용
    seq_model = BertForSequenceClassification.from_pretrained(bert_name, num_labels=10)

GPT-2 모델 기본 사용

  • 불러오는 방법은 BERT와 동일하므로 생략, 모델 입출력으로 BERT와는 다르게 pooler_output 대신 past_key_values가 존재함

  • past_key_values(Tuple(Tuple[torch.Tensor])) : 각 레이어별 attention block에서의 key와 value 값(각각의 shape은 (Batch, N_head, Seq_len, Dim_head)), sequential decoding시의 속도를 높이는 역할(현재 time step에 들어온 input_ids 중에서 여기에 이미 존재하는 값은 다시 계산하지 않음)

    outputs = model(input_ids=batch, attention_mask=batch_mask)
    outputs.keys()
    >>> odict_keys(['last_hidden_state', 'past_key_values'])
  • Downstream task에 적용하는 방법은 BERT와 동일하므로 생략

  • Language Modeling을 위한 head layer가 제공되는 클래스의 경우 입력에 labels까지 포함하면 cross-entropy loss까지 자동으로 계산

    from transformers import GPT2LMHeadModel
    lm_model = GPT2LMHeadModel.from_pretrained('gpt2')
    
    outputs = lm_model(input_ids=batch, attention_mask=batch_mask, labels=batch)
    outputs.keys()
    >>> odict_keys(['loss', 'logits', 'past_key_values'])
    
    ### Special Token 추가
      - 토크나이저와 모델 모두에 추가해야함
        ```python
        special_tokens = {
            'bos_token': '[BOS]',
            'eos_token': '[EOS]',
            'pad_token': '[PAD]',
            'additional_special_tokens': ['[SP1]', '[SP2]']
        }
    
    # special token 추가 메서드는 추가된 토큰의 개수를 반환
    num_new_tokens = tokenizer.add_special_tokens(special_tokens)
    
    # 추가된 토큰 확인
    tokenizer
    >>> PreTrainedTokenizer(name_or_path='gpt2', vocab_size=50257, model_max_len=1024, is_fast=False, padding_side='right', special_tokens={'bos_token': '[BOS]', 'eos_token': '[EOS]', 'unk_token': AddedToken("<|endoftext|>", rstrip=False, lstrip=False, single_word=False, normalized=True), 'pad_token': '[PAD]', 'additional_special_tokens': ['[SP1]', '[SP2]']})
    
    # 모델의 Embedding Layer 수정
    vocab = tokenizer.get_vocab()
    model.resize_token_embeddings(len(vocab))

과제수행

  • BPE 과제를 시도했지만 해결하지 못함. 논문에서 제공된 코드를 그대로 사용하는 것 만으로는 새로 추가된 subword를 누적해서 저장할 수 없었고, 매번 추가되는 subword를 별도의 객체에 누적해서 저장해야함. 계속 시도중

피어세션

9강 Self-supervised Pre-training Models

  • 발표자 : 전준영
  • 발표 자료 링크
  • 내용 : GPT-1, BERT
  • Further Question: BERT의 Masked Language Model의 단점은 무엇이 있을까요? 사람이 실제로 언어를 배우는 방식과의 차이를 생각해보며 떠올려봅시다
    • Mask를 씌우는 방식에서 문제가 있다.
    • 여러개의 빈칸이 있을 경우 사람은 Mask 간의 관계도 고려해서 파악
    • 일상 Task에서도 Mask가 뚫려있지않음, 사전학습과 실제 Task간의 불일치
    • 외부데이터(배경지식, 상식)를 활용시 문제가 있을 수 있다.
  • Discussion: CV에서 이와 비슷한 방법은?
    • DeiT가 MLM과 유사한 방법을 씀

10강 Advanced Self-supervised Pre-training Models

학습회고

  • 대충 알던 내용(ELMo, GPT-1, Layer Normalization, Hugging Face 각 모델의 output)을 명확히 짚고 넘어감
  • GPT-1에서 zero-shot 성능 파악을 위해 사용한 방법이 현재 prompting method의 시초처럼 보여서 신기했음
Comments