이번에는 훈련한 모델을 저장하는 방법과 불러오는 방법에 대해 알아보려고 한다. 1편에서 사용한 코드를 거의 그대로 사용하며, Trainer선언 부분만 바뀌는 정도이며, 전체 코드는 깃허브에 있으니 참고하기 바란다.
요약
모델을 특정경로에 체크포인트 저장하기
모델의 하이퍼파라미터 저장하기
체크포인트로 모델을 불러오기
모델을 특정경로에 체크포인트 저장하기
모델을 저장하는 것은 각 epoch마다 자동으로 저장된다. Jupyter 기준으로 설명하면, Jupyter 파일의 같은 디렉터리 내에 lightning_logs폴더에 각 validation_step마다 저장되는 것을 알 수 있다. 하지만, 나는 여기서 특정 경로에 저장하는 방법에 대해 소개하려고 한다.
# 이전 코드
trainer = pl.Trainer(max_epochs=2)
위의 코드는 1편에서 사용된 코드이다. 여기서 저장할 특정경로를 지정하지 않았기 때문에 실행파일과 동일한 디렉토리에 모델이 저장된다.
trainer = pl.Trainer(
max_epochs=2,
# 저장 경로 지정
default_root_dir='ckpt/'
)
위의 코드는 수정된 코드이며, 'ckpt/' 경로에 모델이 저장되는 것이다. 그림으로 살펴보자.
ckpt 폴더 내에 무언가 저장된 것을 볼 수 있는데 각각 요소에 대해 설명하겠다.
version_숫자 : n번째에 실행했을때 저장되는 것들이다.
hparams.yaml : hyperparameter들이 저장되어 있는 yaml 파일이다. 이에 대해서는 밑에서 다루도록 하겠다.
metrics.csv : 우리가 측정하려는 metric들이 각 epoch마다 csv 형태로 저장되는 파일이다.
checkpoints : 모델이 훈련하고 각 epoch가 끝날 때마다 얼마나 훈련되어 있는지 저장되어 있는 파일이다. 이 파일을 통해 어떠한 오류로 실험이 중단되면 저 ~~~.ckpt 파일을 읽어와서 실험을 진행할 수 있다. (그래서 사용한다.)
우리는 모델의 파라미터만 저장하고 하이퍼 파라미터는 저장하지 않았다. 그래서 하이퍼 파라미터를 저장하는 방법에 대해 다루려고 한다.
모델의 하이퍼파라미터 저장하기
하이퍼 파라미터를 저장하는 방법은 간단하다. 코드 한줄을 추가하면 된다.
class LitAutoEncoder(pl.LightningModule):
def __init__(self, encoder, decoder):
super().__init__()
# 코드 추가
self.save_hyperparameters()
self.encoder = encoder
self.decoder = decoder
# 코드 중략
self.save_hyperparameters() 이 함수를 하나 추가하면 된다. 그리고 코드를 돌리면(훈련하면) 다음과 같이 나온다.
체크포인트로 모델을 불러오기
model = LitAutoEncoder(Encoder(), Decoder())
trainer = pl.Trainer()
# 수정 코드
trainer.fit(model,
train_dataloaders=train_loader,
val_dataloaders=valid_loader,
# 체크포인트 저장한 경로
ckpt_path='ckpt/lightning_logs/version_0/checkpoints/epoch=1-step=96000.ckpt')
여기서 Trainer에서는 건드릴 것이 없다. 그 이유는 ckpt파일에 정보가 저장되어 있다. 그래서 필요한 것은 다음과 같다.
model : 훈련시킬 모델의 틀
dataloader: 훈련시킬 데이터를 다시 가져와야 함
ckpt_path = 훈련되어있는 parameter를 불러올 경로
다음 것들을 알면, 이제부터 훈련이 날아가서 처음부터 다시 돌리는 경우가 안 생긴다. 파이토치 라이트닝을 사용한다면, 꼭 이것을 알아주었으면 한다. 다음은 OverFitting을 방지하는 Early stopping 방법에 대해 소개하려고 한다.
오늘은 바트에 대래 리뷰해보려고 한다. 나는 졸업작품에서 BART를 사용했다. 하지만 Architecture에 대해 모르고 그저 성능이 좋다고 사용했는데, 이번에 기회가 되어서 리뷰를 했었고 그에 대해 정리를 해서 블로그에 업로드해보려고 한다. 그리고 논문에 내용을 최대한 함축해서 핵심만 설명하려고 한다.
Introduction
BART Architecture가 나오기 이전에 유명한 2가지 모델이 BERT(Bidirectional Encoder Representations from Transformer)와 GPT(Generative Pre-Trained Transformer)이었다. 이에 대해 간략하게 소개해본다.
BERT와 다르게 Encoder의 Final Hidden Layer에 FFNN(Feed Forward Neural Network)가 존재하지 않음
BERT보다 10% 많은 Parameter의 수
BERT와 GPT와 다른게 크게 4가지 특징이 BART의 핵심이라고 볼 수 있다. 다음은 BART의 Pre-training 기법과 Fine-tuning 기법에 대해 살펴보겠다.
BART Pre-training
Pre-training 기법은 5가지가 있다.
원래 Text : ABC. DE.
Mask 토큰은 언더바('_')로 표기한다.
Token Masking
BERT의 Pre-training 방식과 동일하다. 특정 Token을 Mask 토큰으로 대체해서 Mask Token을 맞추는 방식이다.
예시 : ABC.DC. → A_C. _E.
Token Deletion
특정 Token을 삭제하는 방식이다. 삭제한 Token을 맞추면서 훈련을 하는 방식이다.
예시 : A.C.E.
Token Infilling
Token Masking과 비슷하지만 다른 점이 있다. 일단 포아송 분포에 따른 난수를 정한다. 여기서 나온 수를 Span 범위로 정한 뒤, Masking 하는 방식이다. 만약 난수가 0인 경우는 <Mask> Token을 추가하는 방식이다.
예시 : Span이 각각 2와 0이라고 하면, BC를 _로 대체하며, D와 E 사이 <MASK> 토큰을 추가한다. → A_.D_E.
Sentence Permutation
말 그대로 문장을 회전하는 방식이다. 일단, 문장을 정하는 기준은 온점('.')을 기준으로 정한다. 그리고 문장을 임의로 섞는다.
예시 : ABC. 문장과 DE. 문장을 온점으로 나눈 뒤 섞으면 → DE.ABC. 이러한 방식으로 나온다.
Document Rotation
문서를 특정한 위치를 기준으로 회전시키는 방식이다.
예시 : B와 C 사이를 기준으로 문서를 회전시킨 것이다.
BART Fine-tuning
방식은 총 4가지가 있다.
Sequence Classification Tasks
방식은 그림과 같으며 문장이 주어졌을 때, 문장의 문법이 합당했는지 아니면 문장의 감정을 분류하는 것이 있다. Decoder Final Hiddne Layer가 multi-class Linear Classifier에 입력으로 넣어서 분류하도록 한다. 그 이유는 Encoder, Decoder의 모든 정보를 참조하도록 하기 위함이다.
Encoder, Decoder 입력: 동일
Token Classification Tasks
방식은 그림과 같으며, Decoder Final Hidden State를 참고하는 것이다. Sequence Classification Task와 다른 것은 Token 단위로 Classification을 수행한다는 것이다. 예시로는 NER(Named Entity Recognition)등이 있다.
Encoder, Decoder 입력: 동일
Sequence Generation Tasks
방식은 위의 그림이며, 노이즈가 추가되어 있는 입력을 복원하는 방식이다.
Encoder입력 : 노이즈 추가된 Text, Decoder 입력: 노이즈 제거된 Text
Machine Translation
Encoder의 Embedding Layer만 매개변수가 초기화되어있는 Encoder로 교체 후 학습을 진행한다. 그 이유에 대해는 다음과 같다. Embedding Layer를 제외하고는 언어에 대해 이해를 하고 있다. 그래서 다른 Layer는 교체하지 않으며 Embedding Layer만 교체하는 이유는 특정 다른 언어와 영어를 매핑하기 위함이다. 한국어로 예시를 들어보자.
예시 : '나는 학생이다' 문장을 'I am student' 문장으로 매핑하기 위함이다. '나는' 이 단어와 'I'라는 단어 매핑, '학생'이란 단어를 'student'단어에 매핑하기 위함이다.
이러한 이유로 Embdding Layer를 교체한 뒤, self-attention input projection matrix를 학습시키는 데 이것도 특정 언어에 대해 이해를 시키기 위해 학습시킨다.
2가지 과정을 거치면 특정언어(예시 : 한국어)가 영어로 단어별로 매핑이 되었으며, 그래서 적은 수의 반복으로 End-to-End로 학습시킨다.
Comparing Pre-training Objectives
여기에서는 noising 기법에 대해 알기 위해 여러 Architecture 간 비교를 진행한다. Architecture에 대해 간단하게 소개한다.
Model
Language Model GPT와 유사한, Transformer의 Decoder 구조
Permuted Language Model : XLnet구조와 매우 유사
Masked Language Model : BERT구조와 매우 유사
Multitask Masked Language Model : UniLM기반, Mask Token 예측 및 NER 동시에 예측하는 방식으로 훈련한 모델
Masked Seq-to-Seq : 입력에서 Span Masking 후 Mask 토큰 예측
Task
SQuAD : Wikipedia 기반 extractive question answering task
MNLI : 한 문장이 다른 문장을 함축하는지에 대한 task
ELI5 : 긴 형식의 extractive question answering task
XSUM : 뉴스 요약 task
ConvAI2 : 대화의 답변에 대한 task
CNN/DM : 뉴스 요약 task
Results
결과는 몇 가지로 나온다.
Token masking은 필수적이다. 문서를 회전시키거나 문장을 순열하는 데 기반한 사전 학습 목표는 단독으로는 성능이 좋지 않으며, 좋은 방법은 token deletion, masking 그리고 self-attention mask를 사용한다.
Left-to-Right 방식의 Training은 성능이 좋다. Masked Language Model, Permuted Language Model은 Generation Task에서 성능이 떨어지기 대문이다.
양방향 Encoder는 SQuAD Task에서 필수적이다. 그 이유로는 미래의 Context도 중요하기 때문이다.
BART가 다른 모델과 달리 일관된 좋은 성능을 달성한다.
Large-scale Pre-training Experiments
GPT 논문에서는 대규모의 Corpus를 사용하여 훈련을 하는 경우, 데이터가 많을수록 성능이 향상되는 것이 밝혀졌으며, 대용량의 데이터로 Training을 하고서 다른 모델과 비교를 진행한다.
Training Info
Dataset : 160GB 크기의 뉴스, 책, 이야기, 웹 텍스트
Batch size : 8,000
Step : 5e6
Dropout : 10%를 적용하며, 마지막 10%의 training step에서 dropout 적용 X
Results
Classification Task
다음은 Classification Task와 관련된 지표이다. 각 Task별로 SOTA(State-of-the-art)를 달성하는 것은 RoBERTa와 BART가 번갈아가며 성능을 달성하는 것을 통해 그게 성능이 차이는 없는 것을 알 수 있다.
Generation Task
다음은 Generation Task와 관련된 지표이다. 여기서는 BART가 모두 SOTA를 달성한 것을 볼 수 있다. 그 이유로는 BART의 Encoder의 경우 다른 모델보다 정보를 더욱 잘 갖고 있으며 다른 훈련이 Decoder의 성능을 다른 모델보다 이해를 잘하고 있는 것이다.
Conclusion
논문에서 나온 결론이 아닌 내 결론은 이렇다. Classification과 같은 종류의 Task들은 BERT류를 사용할 것 같다. 아무래도 성능은 크게 차이 나지 않으며 Parameter의 수는 10% 정도 적으니 굳이 BART를 사용하지 않을 것 같다. 대신 Generation Task에서는 BART류를 무조건 사용할 것 같다.
# 필요한 라이브러리 선언
import os
import torch
import numpy as np
import matplotlib.pyplot as plt
from torch import nn
import torch.nn.functional as F
from torchvision import transforms
from torchvision.datasets import MNIST
from torch.utils import data
import pytorch_lightning as pl
데이터셋 선언(분할) 및 확인
dataset은 손글씨로 유명헌 MNIST 데이터셋을 사용한다. 자세한 정보는 여기를 확인하면 된다.
Rouge은 Recall-Oriented Understudy for Gisting Evaluation의 약자이다. ROUGE는 사람이 만든 참조 요약과 컴퓨터가 생성한 요약 사이의 일치 정도를 측정한다. 이 논문은 ROUGE가 포함된 네 가지 다른 ROUGE 메트릭, 즉 ROUGE-N, ROUGE-L, ROUGE-W, ROUGE-S를 소개합니다. 각 메트릭은 요약의 다른 측면을 평가한다.
ROUGE-N: 이 메트릭은 N-gram 기반으로 평가한다. 이는 요약에서 사용된 단어의 연속적인 나열이 참조 요약과 얼마나 일치하는지를 측정하여, 텍스트의 어휘적 정확성을 평가한다. N이 클수록, 문맥적 일관성과 정확성을 더 자세히 분석한다.
ROUGE-L: 가장 긴 공통 부분 문자열(LCS)을 기반으로 하여, 요약문과 참조 요약문 사이에 나타나는 최장 공통 부분문을 찾아냄으로써 요약의 문장 구조적 유사성을 평가한다.
ROUGE-W: 가중치가 있는 가장 긴 공통 부분 문자열을 사용한다. 이는 LCS의 기본 아이디어에 가중치를 추가하여, 더 긴 일치하는 구문을 더 높게 평가함으로써 요약의 문맥적 심도와 정보의 중요성을 평가한다.
ROUGE-S: ‘skip-bigram’을 사용하여, 요약 내 단어들 사이의 임의의 간격에도 불구하고 겹치는 단어 쌍을 찾아내어 요약의 유연성과 단어 간 관계를 평가한다. 이 메트릭은 요약이 원문의 중요한 개념들을 얼마나 포괄적으로 포함하고 있는지를 측정한다.
Formula
ROUGE-N
여기서 $ Count_{match}(gram_n)$은 생성된 요약과 참조 요약에서 발견된 겹치는 n-gram의 수이며, $Count(gram_n)$은 참조 요약에서 n-gram의 수이다.
$$\text{ROUGE-L} = \frac{\text{LCS}(X, Y)}{\max(\text{len}(X), \text{len}(Y))}$$ 여기서 $X$와 $Y$는 각각 참조 요약과 생성된 요약의 단어 시퀀스를 나타내며, $LCS(X,Y)$는 두 시퀀스 간의 가장 긴 공통 부분 시퀀스의 길이이다.
예시 1:
참조 요약: "그는 그녀에게 책을 준다."
생성된 요약: "그는 책을 그녀에게 준다."
LCS 길이: 4 (단어 순서 다름)
계산: $\frac{4}{5} = 0.8$
예시 2:
참조 요약: "아침에 커피를 마신다."
생성된 요약: "커피를 마신다."
LCS 길이: 2
계산:$\frac{2}{3} ≈ 0.67 $
ROUGE-W
$$\text{ROUGE-W} = \frac{\text{WLCS}(X, Y)}{\max(\text{len}(X), \text{len}(Y))}$$ 여기서 $WLCS$는 가중치가 있는 $LCS$를 의미하며, 더 긴 일치하는 시퀀스에 더 큰 가중치를 준다.
예시 1:
참조 요약: "아침에 커피를 마시고 신문을 읽는다."
생성된 요약: "아침에 커피를 마시고 뉴스를 본다."
WLCS 계산은 LCS에 가중치를 추가해야 하지만 간단히 LCS를 사용하고 장문 일치에 더 큰 가중치 부여
계산: 긴 일치 부분, 추정 가중치 추가 후 계산 필요
예시 2:
참조 요약: "그는 아침 조깅을 한 후에 샤워를 했다."
생성된 요약: "그는 샤워를 한 후에 조깅을 했다."
WLCS: 가중치가 있는 LCS 계산, 여기서도 실제 가중치 계산을 위해서는 추가 데이터 필요
ROUGE-S
$$\text{ROUGE-S} = \frac{\sum_{\text{skip-bigram} \in \{ \text{참조 요약} \}} \text{Count}_{\text{match}}(\text{skip-bigram})}{\sum_{\text{skip-bigram} \in \{ \text{참조 요약} \}} \text{Count}(\text{skip-bigram})}$$ 여기서 $skip-bigram$은 두 단어가 연속적이지 않고, 문장 내에서 임의의 다른 단어들에 의해 분리될 수 있는 단어 쌍을 의미한다. $Count_{match}(skip-bigram)$은 생성된 요약과 참조 요약 모두에 나타나는 $skip-bigram$의 수이며, $Count(skip-bigram)$은 참조 요약 내의 $skip-bigram$ 수 이다
예시 1:
참조 요약: "그녀는 시장에서 과일을 샀다."
생성된 요약: "그녀는 과일을 시장에서 구입했다."
일치하는 skip-bigram: "그녀는-과일을", "과일을-시장에서", "그녀는-시장에서"
→ REALM 및 T5 + SSM과 같이 비용이 많이 드는 Span masking 없이도 강력한 결과 산출
2. Abstractive QA
RAG는 최첨단 모델 성능에 다음과 같은 이유를 바탕으로 우수한 성능을 보여줌
RAG는 Gold access 없이 좋은 성능을 보여줌
많은 질문은 Gold access 없이는 답변하기 어려움
모든 질문이 Wikipedia로만 답변 가능한 것이 아님
RAG가 BART보다 Question Generation에서 Hallucination에 강하다.
3. Jeopardy QG
Jeopardy 질문에는 종종 두 개의 별개 정보가 포함되어 있는데, RAG-Token이 여러 문서의 내용을 결합한 응답을 생성할 수 있기 때문에 가장 잘 수행될 수 있다.
4. Fact Verification
3-way 분류에서, RAG 점수는 domain-specific Architecture 및 substantial engineering, trained using intermediate retrieval supervision 과 약 4.3% 범위 내의 최첨단 모델과 일치합니다.(RAG는 다음과 같은 복잡한 과정 X)
1편을 보시면 알겠지만, 저는 한국어로 입력했을 때, 영어로 출력하도록 Task를 만들었습니다. 그래서 저는 한국어-영어 쌍으로 훈련된 모델을 허깅페이스에서 가져와서 사용했습니다. 궁금하신분은 링크를 클릭해보시면 됩니다. 간단히 말하면 한국어를 입력했을 때, 영어로 출력하는 KoBART 모델입니다.
model_id = 'chunwoolee0/circulus-kobart-en-to-ko'
model = AutoModelForSeq2SeqLM.from_pretrained(args['model_path'], cache_dir='/home/hgjeong/hdd1/hub')
tokenizer = AutoTokenizer.from_pretrained(args['tokenizer'])
이번에는 훈련이 잘되고 있는지 확인하기 위해 필요한 것들에 대해 알아보려고 합니다. 모델 인수들이 잘 입력되었는지 log에 확인하기 위한 logger 작성과 훈련 loss들이 잘 훈련되고 있는지에 대해 알려주는 wandb, tensorboard에 대해 알아보려고합니다.(tensorboard는 추후에 작성하도록 하겠습니다.)
logger 작성
import logging
import sys
def get_logger(name: str) -> logging.Logger:
"""Return logger for logging
Args:
name: logger name
"""
logger = logging.getLogger(name)
logger.propagate = False
logger.setLevel(logging.DEBUG)
if not logger.handlers:
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter("[%(asctime)s] %(message)s"))
logger.addHandler(handler)
return logger
logging.getLogger(name) : 이름이 'name'인 로거 객체를 생성하거나, 존재하는 경우 해당 로거를 반환한다.
logger.setLevel(logging.DEBUG) : 로거의 로그 레벨을 'DEBUG'로 설정
logger.handler : 로거에 핸들러(콘솔, 파일)가 없는 경우 설정
handler.setFormatter(logging.Formatter("[%(asctime)s] %(message)s"))는 로그 메시지의 형식을 설정
%(asctime)s는 로그가 기록된 시간, %(message)s는 로그 메시지를 나타낸다.
머신러닝 및 딥러닝 프로젝트에서 모델의 추적을 위해 강력한 도구인 wandb(Weights and Biases)를 사용하여 모델의 성능을 극대화하는 방법을 소개하려 한다.
wandb는 무엇이고 왜 사용하는가
wandb(Weights and Biases)는 머신러닝 및 딥러닝 프로젝트에서 모델의 파라미터에 대한 도구로, 실험 결과를 시각화하고, 하이퍼파라미터를 조정하며, 모델의 성능을 모니터링하는 도구이다. 특히, 하이퍼파라미터 최적화를 통해 최적의 모델 설정을 찾는 데 큰 도움이 된다.
wandb 가입 및 로그인
wandb를 사용하기 위해서는 wandb 공식 웹사이트에서 회원가입과 로그인을 해야 합니다. 이메일 주소와 비밀번호를 입력하거나, 구글 계정이나 GitHub 계정으로 손쉽게 가입하고 로그인할 수 있다.
공식사이트에 들어가서 가입버튼을 클릭한다.
나의 경우는 Github로 가입하고 개인정보를 입력한다.
다음과 같은 창이 나오게 되는데 여기서 API KEY를 복사해 두고 저장해 둔다.
wandb 로그인
pip install wandb
wandb 설치
import wandb
wandb.login()
다음과 같이 셀을 실행하면 밑에와 같은 창이 생긴다. 이때, 여기에 복사한 API KEY를 입력한다.
이번에는 Dataset 구축 및 DataLoader 구축에 대해 설명하려고 한다. 근데 생각을 해보았을 땐, 이 글을 보는 사람들은 데이터셋 구축은 관심이 없을 것이고 아무래도 어떻게 코드를 짜는지 궁금할 것이라 생각하기 때문에 데이터셋 구축에 대해서는 설명하지 않고 데이터셋이 어떻게 구성되어 있는지만 말하려고 한다.
Dataset 구축
# Text : Encoder
# function : Decoder
# 예시
# 음료 개수, 메뉴 개수, 메뉴 개수 -> drink(quantity=1); menu(quantity=3); menu(quantity=2);
{"Text": "아이스 카페 라떼 4잔 주세요 그 외에 따듯한 그린티 라떼 줄래?", "function": "ICE_cafe_latte(quantity=4); HOT_greentea_latte(quantity=1); "}
데이터셋의 구성은 다음과 같다.
Encoder에는 한국어로 되어있는 Text
Decoder는 메뉴에 대해 요약된 Text
결론은 카페 메뉴에 대한 text가 들어갔을 때, 영어로 요약된 text가 출력되도록 하는 것이다.
DataLoader 구축
라이브러리
from datasets import Dataset
from torch.utils.data import DataLoader
from tokenizers.processors import TemplateProcessing
라이브러리는 다음과 같이 사용합니다.
데이터셋 로딩
# json을 dataset으로 변환
dataset = Dataset.from_json(fname)
토크나이저 설정
# cls token과 sep token이 설정되어있지 않으면 설정
if not tokenizer.cls_token:
tokenizer.cls_token = tokenizer.eos_token
if not tokenizer.sep_token:
tokenizer.sep_token = tokenizer.eos_token
토크나이저에 클래스 시작(cls_token)과 분리(sep_token) 토큰이 설정되어 있지 않다면, 각각을 문장 종료(eos_token) 토큰으로 설정
요약을 하면, 입력데이터를 BART의 출력에 맞도록 tokenizing 하는 것이라고 생각하면 된다.
train mode : encoder, decoder 모두 tokenizing
not train mode : encoder만 tokenizing
그 이유는 테스트 모드의 경우에는 decoder는 예측을 하는 것이기 때문에 정답이 없다.
데이터셋 전처리 적용
# dataset에 대해 전처리 함수로 매핑, columns제거 및 torch tensor로 변환
dataset = dataset.map(preprocess_function,remove_columns=dataset.column_names).with_format("torch")
dataset에 대해 전처리를 적용한 뒤, 이전에 필요 없는 column을 삭제한 뒤, pytorch 형태로 변환한다.
오늘은 이전에 캡스톤디자인에서 BART를 이용해서 Text를 요약하는 Task를 진행했었는데, 이것에 대해 설명하면서 코드를 어떻게 작성했는지 설명하려고 한다.
간단하게 요약하면 다음과 같다. 카페 메뉴에 대한 Text가 입력이 되면, BART를 이용해서 영어로 번역한 뒤 필요한 내용만 출력하는 것이다. 아이스 아메리카노 하나, 카페라떼 하나를 영어로 요약하는 것이다. 다음과 같은 Task를 위해 BART를 훈련시키는 코드에 대해 설명하겠다.