Author: Jacob Devlin Ming-Wei Chang Kenton Lee Kristina Toutanova
Published Date: 2019년 3월 1일
keyword: Vit, attention
status: not yet
스터디 주제: 주제: BERT 모델의 기본 개념
논문: BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding
내용:
• BERT 모델 구조 및 특징 이해 (Bidirectional Encoder Representations)
• Pre-training과 Fine-tuning 개념 및 활용 방법 중요, task 별 이해
• BERT와 GPT 모델의 차이점 (개념적 비교)
• BERT의 NLP 태스크 성능 향상에 미친 영향
실습: BERT를 활용한 간단한 문장 분류 모델 구현
주차: 2주차
[NLP | 논문리뷰] BERT : Pre-training of Deep Bidirectional Transformers for Language Understanding 상편
BERT는 "Bidirectional Encoder Representations from Transformers"의 약자로, 구글 AI 연구팀에서 개발한 자연어 처리(NLP) 모델입니다. BERT는 텍스트의 좌우 문맥을 동시에 학습하는 양방향 트랜스포머(Transformer) 구조를 기반으로 합니다. 기존의 언어 모델들이 주로 단방향(좌에서 우 혹은 우에서 좌)으로 문장을 처리한 것과 달리, BERT는 마스크 언어 모델(Masked Language Model, MLM)을 통해 문장 전체의 양방향 문맥을 동시에 학습할 수 있는 것이 가장 큰 특징입니다.
BERT는 두 가지 주요 과제인 Masked Language Model(MLM)과 Next Sentence Prediction(NSP)을 통해 사전 학습을 진행하며, 이후 다양한 NLP 과제에 미세 조정(fine-tuning)을 통해 적용됩니다. 이를 통해 BERT는 다양한 자연어 처리 과제에서 기존의 모델들을 능가하는 성능을 보여줍니다.
Pre-training 방법과 Fine-Tuning의 배경
BERT가 등장하기 이전에도 언어 모델(LM)의 pre-training 방법은 활발히 연구되었으며, 이미 좋은 성능을 내고 있었다. 특히 문장 단위의 작업에서 뛰어난 성능을 보였고, 이러한 연구들은 두 문장 사이의 관계를 분석하여 예측하는 것을 목표로 했다. 또한, 문장뿐만 아니라 토큰 단위의 작업(예: 개체명 인식, 질문응답 시스템)에서도 성능이 우수했다. 토큰 단위 작업에서는 fine-grained output을 생성해야 하는데, 이는 하나의 출력값을 도출하기 위해 더 작은 단위의 출력 프로세스로 나눠 처리하는 것이다.
fine-grained output이란?
fine-grained output이란 하나의 output을 내기 위해 작은 단위의 output 프로세스로 나눈 뒤 수행하는 것을 의미한다.
Pre-trained 언어 표현을 down stream task에 적용하는 방법에는 크게 두 가지가 있다.
ELMo가 해당되며, pre-trained representations을 down stream 작업에 추가적인 feature로 사용하는 방법이다.embedding vector와 단순히 concat(병합)하여 사용한다.공통점과 차이점
Feature-based 접근법과 Fine-tuning 접근법 모두 pre-training 과정에서 동일한 목적함수(objective function)을 사용한다. 일반적으로 언어 표현을 학습하기 위해서는 unidirectional(단방향) language model을 사용하는데, 여기서 중요한 차이점들이 발생한다.
Fine-Tuning Approach의 한계
논문에서는 기존 방법(특히 fine-tuning 방식)이 pre-training된 표현의 성능을 저하시킬 수 있다고 지적하고 있다. 예를 들어 GPT와 같은 단방향 언어 모델의 경우 모든 토큰이 이전 토큰과의 attention만을 계산하기 때문에 문장 수준 작업에서는 최적의 방법이 아니며, 단방향 언어 모델로 인한 성능 제한이 발생할 수 있다. 이에 비해 BERT는 양방향 문맥을 활용하여 이 문제를 해결하고 더 나은 성능을 발휘한다.
양방향성의 중요성
BERT가 특히 강조하는 점은 deep bidirectional context의 중요성이다. 이를 통해 문장의 양방향 문맥을 모두 포함하여 더 풍부한 언어 표현을 학습할 수 있다. 이를 위해 BERT는 Masked Language Model(MLM)을 pre-training 목적으로 도입하여 기존의 단방향 언어 모델의 제약을 완화시켰다.
Masked Language Model(MLM)의 작동 방식
1) 비지도 학습 특징 기반 접근법(Unsupervised Feature-based Approaches)
2) 비지도 학습 미세 조정 접근법(Unsupervised Fine-tuning Approaches)
3) 지도 학습 데이터에서 전이 학습(Transfer Learning from Supervised Data)
BERT(Bidirectional Encoder Representations from Transformers)는 두 가지 주요 단계로 구성
항상 동일한 Pre-trained model의 파라미터가 서로 다른 downstream tasks(QA, 번역 등)의 초기 값으로 사용
: multi-layer bidirectional Transformer encoder(양방향 Transformer encoder를 여러 층 쌓은 것)
BERT의 아키텍처는 다층의 양방향 트랜스포머(Transformer) 인코더로 구성
BERT의 모델은 트랜스포머 블록의 수(L), 히든 크기(H), 그리고 셀프 어텐션 헤드 수(A)로 정의. BERT에는 두 가지 주요 모델 크기가 있습니다:
BERT-BASE는 OpenAI GPT와 비교할 수 있도록 같은 크기로 설계되었지만, GPT는 단방향(좌->우)으로 문맥을 처리하는 반면, BERT는 양방향 문맥을 모두 처리하는 차별점을 가지고 있습니다.
BERT는 다양한 다운스트림 과제를 처리할 수 있도록 입력 표현 방식을 설계.
BERT의 입력은 단일 문장이나 문장 쌍을 명확히 표현해야함!! 여기서 '문장'은 실제 언어적 문장이 아니라 연속된 텍스트의 임의의 부분을 의미하며, '시퀀스'는 BERT에 입력되는 토큰 시퀀스를 의미합니다.
→ 총 3가지 Embedding vector(Token Embeddings, Segment Embeddings, Position Embeddings)를 합쳐 input으로 활용
BERT는 WordPiece 임베딩(Wu et al., 2016)을 사용하며, 총 30,000개의 토큰 어휘를 갖고 있습니다. 모든 시퀀스의 첫 번째 토큰은 항상 [CLS]라는 특수 분류 토큰으로 시작하며, 이 토큰의 최종 히든 상태는 분류 작업에서 전체 시퀀스를 나타내는 데 사용됩니다. 문장 쌍은 하나의 시퀀스로 결합되며, 각 문장은 [SEP] 토큰으로 구분됩니다. 또한, 각 토큰에는 해당 토큰이 문장 A에 속하는지 문장 B에 속하는지를 나타내는 학습된 임베딩이 추가됩니다.
BERT는 전통적인 좌->우 또는 우->좌 언어 모델을 사용하지 않고, 두 가지 비지도 학습 과제를 통해 사전 학습을 진행합니다.
: Masked LM(MLM)이란, input tokens의 일정 비율을 마스킹하고, 마스킹 된 토큰을 예측하는 과정.
BERT는 기존의 언어 모델의 단방향 문맥 학습을 극복하기 위해 입력 토큰의 일부를 무작위로 마스킹한 후, 이 마스킹된 토큰을 예측하는 Masked Language Model(MLM) 방법을 사용합니다. Bert는 오직 [MASK] token만을 예측. BERT는 시퀀스의 모든 WordPiece 토큰 중 15%를 무작위로 마스킹하여 이 과제를 수행합니다.
그러나 사전 학습 중에 [MASK] 토큰을 사용하면, 실제 다운스트림 과제에서는 이 토큰이 나타나지 않기 때문에 사전 학습과 미세 조정 간에 차이가 발생할 수 있습니다. 이를 해결하기 위해 BERT는 마스킹된 토큰을 [MASK]로 대체하는 확률을 80%로 설정하고, 나머지 10%는 무작위 토큰, 또 다른 10%는 원래 토큰을 유지하는 방식으로 처리합니다.
다양한 다운스트림 과제(예: 질문 응답, 자연어 추론 등)는 두 문장 간의 관계를 이해하는 것이 중요. 이를 학습하기 위해, BERT는 다음 문장이 실제로 앞 문장의 다음 문장인지를 예측하는 Next Sentence Prediction (NSP) 과제를 사용. 각 사전 학습 예제에서 문장 A와 문장 B는 50%의 확률로 실제로 이어진 문장이며, 나머지 50%의 확률로는 임의의 다른 문장이 주어집니다.
즉, pre-training example로 문장A와 B를 선택할때, 50퍼센트는 실제 A의 다음 문장인 B를(IsNext), 나머지 50퍼센트는 랜덤 문장 B를(NotNext) 고른다는 것이다.
🧑💻💡예를 들자면, 아래와 같은 예시가 50:50의 비율로 등장한다는 것이다.
Input = [CLS] the man went to [MASK] store [SEP] he bought a gallon [MASK] milk [SEP] Label = IsNext
Input = [CLS] the man [MASK] to the store [SEP] penguin [MASK] are flight ##less birds [SEP] Label = NotNext
BERT의 사전 학습 과정은 기존의 언어 모델 사전 학습 문헌을 따름. BERT는 BooksCorpus(8억 단어)와 영어 Wikipedia(25억 단어)를 사용하여 사전 학습됩니다. Wikipedia에서는 텍스트 부분만을 사용하고, 목록, 표, 헤더 등은 제외합니다. 이는 문장 수준의 코퍼스 대신 문서 수준의 코퍼스를 사용하여 더 긴 연속적인 시퀀스를 학습하는 데 도움을 줍니다.
BERT의 미세 조정 과정은 매우 간단합니다. 트랜스포머의 셀프 어텐션 메커니즘 덕분에, BERT는 단일 텍스트나 텍스트 쌍을 다루는 다양한 다운스트림 과제를 처리할 수 있습니다. 예를 들어, 질문-문단 쌍 또는 문장-가설 쌍과 같은 텍스트 쌍을 입력으로 받는 작업을 처리할 수 있습니다.
BERT에서는 모든 다운스트림 과제에서 동일한 사전 학습된 모델을 사용하며, 과제별로 적합한 입력과 출력을 추가한 후 전체 파라미터를 미세 조정합니다. 예를 들어, 질문 응답(QA) 과제에서는 질문과 문단 쌍이 입력으로 들어가고, [CLS] 토큰의 최종 히든 상태는 분류 과제에서 사용됩니다. 또한, 시퀀스 태깅 또는 질문 응답과 같은 토큰 수준의 작업에서는 각 토큰의 히든 상태가 출력 레이어로 연결됩니다.
수행하고자하는 downstream task
- Sentence pairs in paraphrasing
- Hypothesis-Premise pairs in entailment
- Question-Passage pairs in question answering
- Degenerate-None pair in text classification or sequence tagging
Output역시 downstream task에 따라 달라진다.
- token representation in sequence tagging or question answering
- [CLS] representation in classification(entailment or sentiment analysis)
미세 조정은 사전 학습에 비해 상대적으로 비용이 적게 듭니다. 모든 실험은 단일 Cloud TPU에서 1시간 이내에 재현할 수 있으며, GPU 환경에서는 몇 시간 정도 소요됩니다.
BERT-BASE는 OpenAI GPT와 비교할 수 있도록 같은 크기로 설계되었지만, GPT는 단방향(좌->우)으로 문맥을 처리하는 반면, BERT는 양방향 문맥을 모두 처리하는 차별점을 가지고 있습니다.
🚥 주제 4 : BERT의 NLP 태스크 성능 향상에 미친 영향BERT - (2) Transformer 이해하기, 코드 구현
pip install transformers
pip install torch
import torch
from transformers import BertTokenizer, BertForSequenceClassification
# 토크나이저와 모델 불러오기
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertForSequenceClassification.from_pretrained('bert-base-uncased')
# 입력 데이터 준비
texts = ["I love this movie!", "I didn't like this film."]
labels = [1, 0] # 긍정:1, 부정:0
# 토크나이징
inputs = tokenizer(texts, return_tensors='pt', padding=True, truncation=True)
# 레이블 텐서화
labels = torch.tensor(labels)
# 모델에 입력 및 출력 얻기
outputs = model(**inputs, labels=labels)
# 손실 및 로짓 확인
loss = outputs.loss
logits = outputs.logits
print(f"Loss: {loss}")
print(f"Logits: {logits}")
from torch.optim import AdamW
optimizer = AdamW(model.parameters(), lr=1e-5)
# 학습 루프
for epoch in range(3):
model.train()
outputs = model(**inputs, labels=labels)
loss = outputs.loss
loss.backward()
optimizer.step()
optimizer.zero_grad()
print(f"Epoch {epoch+1}, Loss: {loss.item()}")
loss.backward()를 통해 역전파를 수행하고 가중치를 업데이트합니다.# 새로운 문장 예측
test_texts = ["What a fantastic experience!", "I wouldn't recommend this to anyone."]
test_inputs = tokenizer(test_texts, return_tensors='pt', padding=True, truncation=True)
test_outputs = model(**test_inputs)
predictions = torch.argmax(test_outputs.logits, dim=1)
print(f"Predictions: {predictions}")
single.py
import torch
import torch.nn.functional as F
import torch
import math
# Scaled Dot Product Attention
class Attention(nn.Module) :
def forward(self, query, key, value, mask = None, dropout = None) :
scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(query.size(-1))
if mask is not None :
scores = scores.masked_fill(mask == 0, -1e9)
p_attn = F.softmax(scores, dim = 1)
if dropout is not None :
p_attn = dropout(p_attn)
return torch.matmul(p_attn, value), p_attn
multi_head.py
import torch.nn as nn
from .single import Attention
class MultiHeadAttention(nn.Module) :
def __init__(self, h, d_model, dropout = 0.1) :
super().__init()
assert d_model % h == 0
self.d_k = d_model // h
self.h = h
self.linear_layers = nn.ModuleList([nn.Linear(d_model, d_model) for _ in range(3)])
self.output_linear = nn.Linear(d_model, d_model)
self.attention = Attention()
self.dropout = nn.Dropout(p = dropout)
def forward(self, query, key, value, mask = None) :
batch_size = query.size()
query, key, value = [l(x).view(batch_size, -1, self.h, self.d_k).transpose(1,2) for l, x in zip(self.linear_layers, (query, key, value))]
x, attn = self.attention(query, key, value, mask = mask, dropout = self.dropout)
x = x.transpose(1,2).contiguous().view(batch_size, -1, self.h * self.d_k)
return self.output_linear(x)
feed forward.py
import torch.nn as nn
from .gelu import GELU
class PositionwiseFeedForward(nn.Module):
"Implements FFN equation."
def __init__(self, d_model, d_ff, dropout=0.1):
super(PositionwiseFeedForward, self).__init__()
self.w_1 = nn.Linear(d_model, d_ff)
self.w_2 = nn.Linear(d_ff, d_model)
self.dropout = nn.Dropout(dropout)
self.activation = GELU()
def forward(self, x):
return self.w_2(self.dropout(self.activation(self.w_1(x))))
gelu.py
import torch.nn as nn
import torch
import math
class GELU(nn.Module):
def forward(self, x):
return 0.5 * x * (1 + torch.tanh(math.sqrt(2 / math.pi) * (x + 0.044715 * torch.pow(x, 3))))
layer_norm
import torch.nn as nn
import torch
class LayerNorm(nn.Module):
def __init__(self, features, eps=1e-6):
super(LayerNorm, self).__init__()
self.a_2 = nn.Parameter(torch.ones(features))
self.b_2 = nn.Parameter(torch.zeros(features))
self.eps = eps
def forward(self, x):
mean = x.mean(-1, keepdim=True)
std = x.std(-1, keepdim=True)
return self.a_2 * (x - mean) / (std + self.eps) + self.b_2
sublayer.py
import torch.nn as nn
from .layer_norm import LayerNorm
class SublayerConnection(nn.Module):
"""
A residual connection followed by a layer norm.
"""
def __init__(self, size, dropout):
super(SublayerConnection, self).__init__()
self.norm = LayerNorm(size)
self.dropout = nn.Dropout(dropout)
def forward(self, x, sublayer):
"Apply residual connection to any sublayer with the same size."
return x + self.dropout(sublayer(self.norm(x)))
import torch.nn as nn
from .attention import MultiHeadedAttention
from .utils import SublayerConnection, PositionwiseFeedForward
class TransformerBlock(nn.Module):
"""
Bidirectional Encoder = Transformer (self-attention)
Transformer = MultiHead_Attention + Feed_Forward with sublayer connection
"""
def __init__(self, hidden, attn_heads, feed_forward_hidden, dropout):
"""
:param hidden: hidden size of transformer
:param attn_heads: head sizes of multi-head attention
:param feed_forward_hidden: feed_forward_hidden, usually 4*hidden_size
:param dropout: dropout rate
"""
super().__init__()
self.attention = MultiHeadedAttention(h=attn_heads, d_model=hidden)
self.feed_forward = PositionwiseFeedForward(d_model=hidden, d_ff=feed_forward_hidden, dropout=dropout)
self.input_sublayer = SublayerConnection(size=hidden, dropout=dropout)
self.output_sublayer = SublayerConnection(size=hidden, dropout=dropout)
self.dropout = nn.Dropout(p=dropout)
def forward(self, x, mask):
x = self.input_sublayer(x, lambda _x: self.attention.forward(_x, _x, _x, mask=mask))
x = self.output_sublayer(x, self.feed_forward)
return self.dropout(x)
position.py
import torch.nn as nn
import torch
import math
class PositionalEmbedding(nn.Module) :
def __init__(self, d_model, max_len = 512) :
super().__init__()
# compute positional encoding in log space
pe = torch.zeros(max_len, d_model).float()
pe.required_grad = False
position = torch.arange(0, max_len).float().unsqueeze(1)
div_term = (torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model)).exp()
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0)
self.register_buffer('pe', pe)
def forward(self, x) :
return self.pe[:, :x.size(1)]
segment.py
import torch.nn as nn
class SegmentEmbedding(nn.Embedding) :
def __init__(self, embed_size = 512) :
super().__init__(3, embed_size, padding_idx = 0)
token.py
import torch.nn as nn
class TokenEmbedding(nn.Embedding) :
def __init__(self, vocab_size, embed_size = 512) :
super().__init__(vocab_size, embed_size, padding_idx = 0)
bert.py
: 세가지 임베딩을 최종적으로 모델에 입력할 수 있는 형태로 전달하는 class
import torch.nn as nn
from .token import TokenEmbedding
from .position import PositionalEmbedding
from .segment import SegmentEmbedding
class BERTEmbedding(nn.Module):
def __init__(self, vocab_size, embed_size, dropout=0.1):
super().__init__()
self.token = TokenEmbedding(vocab_size=vocab_size, embed_size=embed_size)
self.position = PositionalEmbedding(d_model=self.token.embedding_dim)
self.segment = SegmentEmbedding(embed_size=self.token.embedding_dim)
self.dropout = nn.Dropout(p=dropout)
self.embed_size = embed_size
def forward(self, sequence, segment_label):
x = self.token(sequence) + self.position(sequence) + self.segment(segment_label)
return self.dropout(x)
Bert model
import torch.nn as nn
from .transformer import TransformerBlock
from .embedding import BERTEmbedding
class BERT(nn.Module) :
def __init__(self, vocab_size, hidden = 768, n_layers = 12, attn_heads = 12, dropout = 0.1) :
super().__init__()
self.hidden = hidden
self.n_layer = n_layers
self.attn_heads = attn_heads
# paper : use 4*hidden_size for ff network hidden size
self.feed_forward_hidden = hidden * 4
# embedding for BERT = token + segment + position
self.embedding = BERTEmbedding(vocab_size = vocab_size, embed_size = hidden)
# transformer block
self.transformer_blocks = nn.ModuleList(
[TransformerBlock(hidden, attn_heads, hidden * 4, dropout) for _ in range(n_layers)]
)
def forward(self, x, segment_info) :
# attention masking
mask = (x > 0).unsqueeze(1).repeat(1, x.size(1), 1).unsqueeze(1)
# embedding the indexed sequence to sequence of vectors
x = self.embedding(x, segment_info)
# run multiple transformer block
for transformer in self.transformer_blocks :
x = transformer.forward(x, mask)
return x
MLM(masked language model)
class MaskedLanguageModel(nn.Module) :
def __init__(self, hidden, vocab_size) :
super().__init__()
self.linear = nn.Linear(hidden, vocab_size)
self.softmax = nn.LogSoftmax(dim = -1)
def forward(self, x) :
return self.softmax(self.linear(x))
NSP(next sentence prediction)
class NextSentencePrediction(nn.Module) :
def __init__(self, hidden) :
super().__init__()
self.linear = nn.Linear(hidden, 2)
self.softmaz = nn.LogSoftmax(dim = -1)
def forward(self, x) :
return self.softmax(self.linear(x[:, 0]))
languagae_model.py (MLM + NSP 학습)
import torch.nn as nn
from .bert import BERT
class BERTLTM(nn.Module) :
def __init__(self, bert : BERT, vocab_size) :
super().__init__()
self.bert = bert
self.next_sentence = NextSentencePrediction(self.bert.hidden)
self.mask_lm = MaskedLanguageModel(self.bert.hidden, vocab_size)
def forward(self, x, segment_label) :
x = self.bert(x, segment_label)
return self.next_sentence(x), self.mask_lm
class NextSentencePrediction(nn.Module) :
def __init__(self, hidden) :
super().__init__()
self.linear = nn.Linear(hidden, 2)
self.softmaz = nn.LogSoftmax(dim = -1)
def forward(self, x) :
return self.softmax(self.linear(x[:, 0]))
class MaskedLanguageModel(nn.Module) :
def __init__(self, hidden, vocab_size) :
super().__init__()
self.linear = nn.Linear(hidden, vocab_size)
self.softmax = nn.LogSoftmax(dim = -1)
def forward(self, x) :
return self.softmax(self.linear(x))
import torch
import torch.nn as nn
from torch.optim import Adam
from torch.utils.data import DataLoader
from ..model import BERTLM, BERT
from .optim_schedule import ScheduledOptim
import tqdm
class BERTTrainer :
def __init__(self, bert : BERT, vocab_size : int, train_dataloader : DataLoader, test_dataloader : DataLoader = None,
lr : float = 1e-4, betas = (0.9, 0.999), weight_decay : float = 0.01, warmup_steps = 10000,
with_cuda : bool = True, cuda_devices = None, log_freq : int = 10) :
"""
:param bert: BERT model which you want to train
:param vocab_size: total word vocab size
:param train_dataloader: train dataset data loader
:param test_dataloader: test dataset data loader [can be None]
:param lr: learning rate of optimizer
:param betas: Adam optimizer betas
:param weight_decay: Adam optimizer weight decay param
:param with_cuda: traning with cuda
:param log_freq: logging frequency of the batch iteration
"""
# Setup cuda device for BERT training, argument -c, --cuda should be true
cuda_condition = torch.cuda.is_available() and with_cuda
self.device = torch.device("cuda:0" if cuda_condition else "cpu")
# This BERT model will be saved every epoch
self.bert = bert
# Initialize the BERT Language Model, with BERT model
self.model = BERTLM(bert, vocab_size).to(self.device)
# Distributed GPU training if CUDA can detect more than 1 GPU
if with_cuda and torch.cuda.device_count() > 1:
print("Using %d GPUS for BERT" % torch.cuda.device_count())
self.model = nn.DataParallel(self.model, device_ids=cuda_devices)
# Setting the train and test data loader
self.train_data = train_dataloader
self.test_data = test_dataloader
# Setting the Adam optimizer with hyper-param
self.optim = Adam(self.model.parameters(), lr=lr, betas=betas, weight_decay=weight_decay)
self.optim_schedule = ScheduledOptim(self.optim, self.bert.hidden, n_warmup_steps=warmup_steps)
# Using Negative Log Likelihood Loss function for predicting the masked_token
self.criterion = nn.NLLLoss(ignore_index=0)
self.log_freq = log_freq
print("Total Parameters:", sum([p.nelement() for p in self.model.parameters()]))
def train(self, epoch):
self.iteration(epoch, self.train_data)
def test(self, epoch):
self.iteration(epoch, self.test_data, train=False)
def iteration(self, epoch, data_loader, train=True):
"""
loop over the data_loader for training or testing
if on train status, backward operation is activated
and also auto save the model every peoch
:param epoch: current epoch index
:param data_loader: torch.utils.data.DataLoader for iteration
:param train: boolean value of is train or test
:return: None
"""
str_code = "train" if train else "test"
# Setting the tqdm progress bar
data_iter = tqdm.tqdm(enumerate(data_loader),
desc="EP_%s:%d" % (str_code, epoch),
total=len(data_loader),
bar_format="{l_bar}{r_bar}")
avg_loss = 0.0
total_correct = 0
total_element = 0
for i, data in data_iter:
# 0. batch_data will be sent into the device(GPU or cpu)
data = {key: value.to(self.device) for key, value in data.items()}
# 1. forward the next_sentence_prediction and masked_lm model
next_sent_output, mask_lm_output = self.model.forward(data["bert_input"], data["segment_label"])
# 2-1. NLL(negative log likelihood) loss of is_next classification result
next_loss = self.criterion(next_sent_output, data["is_next"])
# 2-2. NLLLoss of predicting masked token word
mask_loss = self.criterion(mask_lm_output.transpose(1, 2), data["bert_label"])
# 2-3. Adding next_loss and mask_loss : 3.4 Pre-training Procedure
loss = next_loss + mask_loss
# 3. backward and optimization only in train
if train:
self.optim_schedule.zero_grad()
loss.backward()
self.optim_schedule.step_and_update_lr()
# next sentence prediction accuracy
correct = next_sent_output.argmax(dim=-1).eq(data["is_next"]).sum().item()
avg_loss += loss.item()
total_correct += correct
total_element += data["is_next"].nelement()
post_fix = {
"epoch": epoch,
"iter": i,
"avg_loss": avg_loss / (i + 1),
"avg_acc": total_correct / total_element * 100,
"loss": loss.item()
}
if i % self.log_freq == 0:
data_iter.write(str(post_fix))
print("EP%d_%s, avg_loss=" % (epoch, str_code), avg_loss / len(data_iter), "total_acc=",
total_correct * 100.0 / total_element)
def save(self, epoch, file_path="output/bert_trained.model"):
"""
Saving the current BERT model on file_path
:param epoch: current epoch number
:param file_path: model output path which gonna be file_path+"ep%d" % epoch
:return: final_output_path
"""
output_path = file_path + ".ep%d" % epoch
torch.save(self.bert.cpu(), output_path)
self.bert.to(self.device)
print("EP:%d Model Saved on:" % epoch, output_path)
return output_path