이전 포스트에서 Reformer의 Encoder를 이용한 이용한 BERT 스타일의 Masked Language Model을 만들었습니다. 동일하게 Reformer의 Decoder를 이용해 대표적인 Decoder 언어모델인 GPT-2를 Pretraing 시켜보고자 합니다.
GitHub
전체 코드는 아래아래 Github에 올려두었습니다.
GPT 계열의 모델은 Autoregressive Languege Model 이라고 합니다. Autoregressive Model의 경우 아래의 사진과 같이 자기 자신의 입력을 예측하는 모델로 한국말로는 자기회귀 언어모델이라고도 합니다.
조금더 자세하게 설명하자면 입력 문장을 토큰으로 나누고 주어진 토큰들이 자기 자신의 입력의 다음 토큰을 예측하는 방식으로 학습하게 됩니다.
아래 사진에서 단순, ##함, ##을, 얻기란, ...
과 같은 입력에서 단순 -> ##함
을 예측하고, ##함->##을
을 예측, ##을 -> 얻기란
을 예측하는 방식입니다. 이러한 autoregressive 모델의 장점은 입력 문장으로 예측할 라벨로 사용할수 있어, 특별한 라벨링이 필요하지 않는 장점이 있습니다.
이전 포스트에서 만든 워드피스 토크나이저를 그대로 사용하여 학습합니다. Wordpiece Tokenizer의 경우 Subword 기반의 토크나이저인 Byte Pair Encoding의 한 종류입니다. 이 포스트에서 앞에서 만든 토크나이저를 사용하며, 다른 토크나이저를 사용해도 무방합니다.
사전 학습 시키고자 하는 데이터를 준비합니다. 제가 언어모델을 학습하기 위해 사용하는 데이터는 한국어 위키피디아 데이터를 사용했습니다.
시작토큰을 [CLS]로, 문장별 구분을 [SEP]를 사용해 최대 1024 토큰의 개수로 데이터를 만들었습니다. 버트의 경우 2문장을 사용하여, pretraining 데이터를 준비했지만, 그와는 조금 다르게 긴문단에 대해서 학습해보았습니다.
[CLS] 수학은 숫자 세기, 계산, 측정 및 물리적 대상의 모양과 움직임을 추상화하고, 이에 논리적 추론을 적용하여 나타났다.이런 기본 개념들은 고대 이집트, 메소포타미아, 고대 인도, 고대 중국 및 고대 그리스의 수학책에서 찾아볼 수 있다. [SEP] 그리고, 유클리드의 원론에서는 엄밀한 논증이 발견된다. [SEP] 이런 발전은 그 뒤로도 계속되어, 16세기의 르네상스에 이르러서는 수학적 발전과 과학적 방법들의 상호 작용이 일어나, 혁명적인 연구들이 진행되며 인류 문명에 큰 영향을 미치게 되었다. [SEP] 그리고, 이는 현재까지도 계속되고 있다. [SEP] 오늘날 수학은 자연과학, 공학, 의학뿐만 아니라, 경제학 등의 사회과학에서도 중요한 도구로서도 사용된다. [SEP] 수학을 이런 분야들에 적용한 응용수학은 그 결과로써 수학 자체의 발전을 이끌고 새로운 분야들을 낳았다. [SEP] 응용이 아닌 수학 자체의 아름다움과 재미를 추구하며 연구하는 것을 순수수학이라 하는데, 긴 시간이 지난 뒤에 순수수학적 연구를 다른 분야에 응용할 방법이 발견된 경우도 많았다고 한다. [SEP] 대부분 자료를 보면, "mathematics"는 "수리적인"이라는 뜻을 가진 라틴어 mathmaticus와 그리스어 mathematikos에서 유래되었거나, "학식있는"을 뜻하는 mathema와 "배우다"를 뜻하는 manthanein에서 유래되었다고 한다. [SEP] 줄여서 "math"라고 표현하기도 한다. [SEP] 수학은 기원전 600년 경에 살았던 탈레스로부터 시작됐다. [SEP] 하지만 탈레스가 태어나기 전에도 수학을 연구한 사람이 있을 수도 있기 때문에, 인류의 역사와 더불어 시작되었다고 할 수 있다. [SEP] 교역•분배•과세 등의 인류의 사회 생활에 필요한 모든 계산을 수학이 담당해 왔고, 농경 생활에 필수적인 천문 관측과 달력의 제정, 토지의 측량 또한 수학이 직접적으로 관여한 분야이다. 고대 수학을 크게 발전시킨 나라로는 이집트, 인도, 그리스, 중국 등이 있다. [SEP] 그 중에서도 그리스는 처음으로 수학의 방정식에서 변수를 문자로 쓴 나라이다. [SEP] 한국의 수학은 약 1,500년 전부터 기록으로 보이기 시작한다. [SEP] 신라 시대에 수학을 가르쳤으며, 탈레스가 최초로 발견한 일식과 월식을 예측할 정도로 발달했다. [SEP]
[CLS] 조선 시대에 훈민정음을 창제한 세종 대왕은 집현전 학자들에게 수학 연구를 명하는 등, 조선의 수학 수준을 향상시키기 위해서 많은 노력을 기울였다.하지만 임진왜란으로 많은 서적들이 불타고, 천문학 분야에서 큰 손실을 입었다. [SEP] 조선 후기의 한국의 수학은 실학자들을 중심으로 다시 발전하였고, 새로운 결과도 성취되었다. [SEP] 수학의 각 분야들은 상업에 필요한 계산을 하기 위해, 숫자들의 관계를 이해하기 위해, 토지를 측량하기 위해, 그리고 천문학적 사건들을 예견하기 위해 발전되어왔다. [SEP] 이 네 가지 목적은 대략적으로 수학이 다루는 대상인 양, 구조, 공간 및 변화에 대응되며, 이들을 다루는 수학의 분야를 각각 산술, 대수학, 기하학, 해석학이라 한다. [SEP] 또한 이 밖에도 근대 이후에 나타난 수학기초론과 이산수학 및 응용수학 등이 있다. [SEP] 산술은 자연수와 정수 및 이에 대한 사칙연산에 대한 연구로서 시작했다. [SEP] 수론은 이런 주제들을 보다 깊게 다루는 학문으로, 그 결과로는 페르마의 마지막 정리 등이 유명하다. [SEP] 또한 쌍둥이 소수 추측과 골드바흐 추측 등을 비롯해 오랜 세월 동안 해결되지 않고 남아있는 문제들도 여럿 있다. [SEP] 수의 체계가 보다 발전하면서, 정수의 집합을 유리수의 집합의 부분집합으로 여기게 되었다. [SEP] 또한 유리수의 집합은 실수의 집합의 부분집합이며, 이는 또다시 복소수 집합의 일부분으로 볼 수 있다. [SEP] 여기에서 더 나아가면 사원수와 팔원수 등의 개념을 생각할 수도 있다. [SEP] 이와는 약간 다른 방향으로, 자연수를 무한대까지 세어나간다는 개념을 형식화하여 순서수의 개념을 얻으며, 집합의 크기 비교를 이용하여 무한대를 다루기 위한 또다른 방법으로는 기수의 개념도 있다. [SEP] 수 대신 문자를 써서 문제해결을 쉽게 하는 것과, 마찬가지로 수학적 법칙을 일반적이고 간명하게 나타내는 것을 포함한다. [SEP] 고전대수학은 대수방정식 및 연립방정식의 해법에서 시작하여 군, 환, 체 등의 추상대수학을 거쳐 현대에 와서는 대수계의 구조를 보는 것을 중심으로 하는 선형대수학으로 전개되었다. [SEP] 수의 집합이나 함수와 같은 많은 수학적 대상들은 내재적인 구조를 보인다. [SEP]
GPT-2 Small 모델 크기. 입력 토큰 수 1024로 학습
Reformer의 경우 긴시퀀스에 대해서 학습이 가능하므로 5120 토큰으로 학습 가능했습니다
max_len = 1024 # AxialPositionalEmbedding을 위한 (79,64) 값 and max_len/(bucket_size*2) == 0 이어야한다.
dim = 768
depth = 12
heads = 12
causal = True # True for ReformerLM Auto Regressive,
import torch
import torch.nn as nn
from reformer_pytorch import ReformerLM
from torch.nn import CrossEntropyLoss
class ReformerAutoRegressiveModel(nn.Module):
def __init__(self, num_tokens, dim, depth, max_seq_len, heads, causal=True):
super().__init__()
self.reformer = ReformerLM(
num_tokens= num_tokens,
dim= dim,
depth= depth,
heads= heads,
max_seq_len= max_seq_len,
causal= causal, # auto-regressive 학습을 위한 설정
return_embeddings=True # reformer 임베딩을 받기 위한 설정
)
self.lm_head = nn.Linear(dim, num_tokens, bias=False)
def forward(self,input_ids=None,labels=None,**kwargs):
reformer_outputs = self.reformer(input_ids,**kwargs)
hidden_states = reformer_outputs
lm_logits = self.lm_head(hidden_states)
loss = None
if labels is not None:
# Shift so that tokens < n predict n
shift_logits = lm_logits[..., :-1, :].contiguous()
shift_labels = labels[..., 1:].contiguous()
# Flatten the tokens
loss_fct = CrossEntropyLoss(ignore_index=0)
loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))
return lm_logits,loss
shift_logits
과 shift_labels
으로 logits과 label을 옆으로 이동하여 CrossEntropyLoss를 계산합니다.
lm_logits = self.lm_head(hidden_states)
loss = None
if labels is not None:
# Shift so that tokens < n predict n
shift_logits = lm_logits[..., :-1, :].contiguous()
shift_labels = labels[..., 1:].contiguous()
# Flatten the tokens
loss_fct = CrossEntropyLoss(ignore_index=0)
loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))
import warnings
warnings.filterwarnings("ignore")
import sys
sys.path.append('../')
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, random_split
from tqdm import tqdm
from transformers import BertTokenizer
from fairseq.optim.adafactor import Adafactor
import os
import json
import logging
from datetime import datetime
from dataset.pretrain import DatasetForAutoRegressive
from util.arg import ModelConfig
from model.autoregressive import ReformerAutoRegressiveModel
class ReformerTrainer(object):
def __init__(self,
dataset,
model,
tokenizer,
max_len,
model_name,
checkpoint_path,
device=None,
train_batch_size=8,
eval_batch_size=None,
tb_writer=False,
tb_dir='./tb_logs',
log_dir='../logs'):
self.dataset = dataset
self.model = model
self.tokenizer = tokenizer
self.max_len = max_len
self.model_name = model_name
self.checkpoint_path = checkpoint_path
self.device = device
self.n_gpu = torch.cuda.device_count() if torch.cuda.is_available() else 0
self.train_batch_size = train_batch_size
self.eval_batch_size = eval_batch_size
self.tb_writer = tb_writer
self.log_dir = log_dir
if device is None:
self.device = 'cuda:0' if torch.cuda.is_available() else 'cpu'
if eval_batch_size is None:
self.eval_batch_size = train_batch_size
if tb_writer:
from torch.utils.tensorboard import SummaryWriter
self.writer = SummaryWriter(log_dir=tb_dir)
logging.basicConfig(filename=f'{log_dir}/{self.model_name}-{datetime.now().date()}.log', level=logging.INFO)
def build_dataloaders(self, train_test_split=0.1, train_shuffle=True, eval_shuffle=True):
dataset_len = len(self.dataset)
eval_len = int(dataset_len * train_test_split)
train_len = dataset_len - eval_len
train_dataset, eval_dataset = random_split(self.dataset, (train_len, eval_len))
train_loader = DataLoader(train_dataset, batch_size=self.train_batch_size, shuffle=train_shuffle)
eval_loader = DataLoader(eval_dataset, batch_size=self.eval_batch_size, shuffle=eval_shuffle)
logging.info(f'''train_dataloader size: {len(train_loader.dataset)} | shuffle: {train_shuffle}
eval_dataloader size: {len(eval_loader.dataset)} | shuffle: {eval_shuffle}''')
return train_loader, eval_loader
def train(self,
epochs,
train_dataloader,
eval_dataloader,
log_steps,
ckpt_steps,
gradient_accumulation_steps=1):
optimizer = Adafactor(self.model.parameters())
losses = {}
global_steps = 0
local_steps = 0
step_loss = 0.0
start_epoch = 0
start_step = 0
if os.path.isfile(f'{self.checkpoint_path}/{self.model_name}.pth'):
checkpoint = torch.load(f'{self.checkpoint_path}/{self.model_name}.pth', map_location=self.device)
start_epoch = checkpoint['epoch']
losses = checkpoint['losses']
global_steps = checkpoint['train_step']
start_step = global_steps if start_epoch==0 else global_steps*self.train_batch_size % len(train_dataloader)
self.model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
self.model.train()
if self.n_gpu > 1:
self.model = nn.DataParallel(self.model)
logging.info(f'{datetime.now()} | Utilizing {self.n_gpu} GPUs')
self.model.to(self.device)
logging.info(f'{datetime.now()} | Moved model to: {self.device}')
logging.info(f'{datetime.now()} | train_batch_size: {self.train_batch_size} | eval_batch_size: {self.eval_batch_size}')
logging.info(f'{datetime.now()} | Epochs: {epochs} | log_steps: {log_steps} | ckpt_steps: {ckpt_steps}')
logging.info(f'{datetime.now()} | gradient_accumulation_steps: {gradient_accumulation_steps}')
for epoch in range(start_epoch, epochs): #tqdm(range(epochs), desc='Epochs', position=0):
logging.info(f'{datetime.now()} | Epoch: {epoch}')
pb = tqdm(enumerate(train_dataloader),
desc=f'Epoch-{epoch} Iterator',
total=len(train_dataloader),
bar_format='{l_bar}{bar:10}{r_bar}'
)
for step, batch in pb:
if step < start_step:
continue
inputs, labels, inputs_mask = batch
inputs, labels, inputs_mask = inputs.to(self.device), labels.to(self.device), inputs_mask.to(self.device)
lm_logit, loss = self.model(inputs,labels,input_mask=inputs_mask)
loss.backward()
step_loss += loss.item()
losses[global_steps] = loss.item()
local_steps += 1
global_steps += 1
if global_steps % gradient_accumulation_steps == 0:
optimizer.step()
self.model.zero_grad()
if global_steps % log_steps == 0:
if self.tb_writer:
self.writer.add_scalar('Train/Loss', step_loss / local_steps, global_steps)
self.writer.close()
pb.set_postfix_str(f'''{datetime.now()} | Train Loss: {step_loss / local_steps} | Steps: {global_steps}''')
with open(f'{self.log_dir}/{self.model_name}_train_results.json', 'w') as results_file:
json.dump(losses, results_file)
results_file.close()
step_loss = 0.0
local_steps = 0
if global_steps % ckpt_steps == 0:
self.save(epoch, self.model, optimizer, losses, global_steps)
logging.info(f'{datetime.now()} | Saved checkpoint to: {self.checkpoint_path}')
# Evaluate every epoch
self.evaluate(eval_dataloader)
self.model.train()
start_step = 0
self.save(epochs,self.model,optimizer,losses, global_steps)
return self.model
def evaluate(self, dataloader):
if self.n_gpu > 1 and not isinstance(self.model, nn.DataParallel):
self.model = nn.DataParallel(self.model)
self.model.eval()
eval_loss = 0.0
perplexity = 0.0
eval_steps = 0
logging.info(f'{datetime.now()} | Evaluating...')
for step, batch in tqdm(enumerate(dataloader),
desc='Evaluating',
leave=True,
total=len(dataloader),
bar_format='{l_bar}{bar:10}{r_bar}'):
inputs, labels = batch
inputs, labels = inputs.to(self.device), labels.to(self.device)
with torch.no_grad():
lm_logit, loss = self.model(inputs, labels)
tmp_eval_loss = loss
tmp_perplexity = torch.exp(tmp_eval_loss)
if self.n_gpu > 1:
tmp_eval_loss = tmp_eval_loss.mean()
eval_loss += tmp_eval_loss.item()
perplexity += tmp_perplexity.item()
eval_steps += 1
total_eval_loss = eval_loss/eval_steps
total_perplexity= perplexity/eval_steps
if self.tb_writer:
self.writer.add_scalar('Eval/Loss', eval_loss, eval_steps)
self.writer.close()
self.writer.add_scalar('Perplexity', perplexity, eval_steps)
self.writer.close()
logging.info(f'{datetime.now()} | Step: {step} | Eval Loss: {total_eval_loss} | Perplexity: {total_perplexity}')
with open(f'{self.log_dir}/{self.model_name}_eval_results.txt', 'a+') as results_file:
results_file.write(f'{datetime.now()} | Step: {step} | Eval Loss: {total_eval_loss} | Perplexity: {total_perplexity}\n')
results_file.close()
return None
def save(self, epoch, model, optimizer, losses, train_step):
torch.save({
'epoch': epoch, # 현재 학습 epoch
'model_state_dict': model.state_dict(), # 모델 저장
'optimizer_state_dict': optimizer.state_dict(), # 옵티마이저 저장
'losses': losses, # Loss 저장
'train_step': train_step, # 현재 진행한 학습
}, f'{self.checkpoint_path}/{self.model_name}.pth')
def main():
torch.manual_seed(9)
# Config
config = ModelConfig(config_path='../config/autoregressive/autoregressive-pretrain.json').get_config()
# Tokenizer
tokenizer = BertTokenizer(vocab_file=config.vocab_path, do_lower_case=False)
# Model Hyperparameter
"""
Model Name layer d_model n_head d_head batchsize learning rate n_params
GPT-3 samll 12 768 12 64 0.5M 6.0 x 10^-4 125M
GPT-3 medium 24 1024 16 65 0.5M 3.0 x 10^-4 350M
"""
# Dataset
dataset = DatasetForAutoRegressive(tokenizer, config.max_seq_len, dir_path=config.data_path)
model = ReformerAutoRegressiveModel(
num_tokens=tokenizer.vocab_size,
dim=config.dim,
depth=config.depth,
heads=config.n_head,
max_seq_len=config.max_seq_len, # AxialPositionalEmbedding을 위한 (79,64) 값 and max_len/(bucket_size*2) == 0 이어야한다. 현재 bucket_size = 64
)
trainer = ReformerTrainer(dataset, model, tokenizer, checkpoint_path=config.checkpoint_path,max_len=config.max_seq_len, model_name=config.model_name, train_batch_size=config.batch_size,
eval_batch_size=config.batch_size)
train_dataloader, eval_dataloader = trainer.build_dataloaders(train_test_split=0.1)
model = trainer.train(epochs=config.epochs,
train_dataloader=train_dataloader,
eval_dataloader=eval_dataloader,
log_steps=config.log_steps,
ckpt_steps=config.ckpt_steps,
gradient_accumulation_steps=config.gradient_accumulation_steps)
if __name__ == '__main__':
main()
1052199 step 학습 도중 서버 중지로 학습 중지.
파인튜닝 없이 사전학습된 언어모델만으로 텍스트 생성 테스트. Top-P Sampling을 사용해 문장을 생성시켜 보았습니다.
사람이 철학적으로 생각하는 것은 그들에게 도움이 되겠음을 말해주고 있기 때문이다.
예를 들었을 수도 있다면 ( 그 사람이 내재해 있기 ), 그 사람이 그 사람을 신에게 말하는 것이었다고 생각할 때 그것은 바로 신에 의해 선택되고 있다.
그러기 때문이며 또한 그것들을 신이 하듯이 그 사람은 내다 ( 내적 · 정신 ) 와 맺어져 있는 것이 아니라고 주장하거나 혹은 인간의 자유로 돌려질 것을 기대할 수가 없다는 것도 믿기 때문이다.
신으로부터 그 사람을 하도록 선택하고 그것을 신에게 선택한 것으로 바꾸고 있는 것에 대해 말한다, 즉 그 사람에게 있어서는, 사람이 다른 사람은 거기에 개입하게 함으로서 자신의 창조나 자유롭거나 또는 보다 신에게 희생과 의무에 관한 질문으로 그 사람의 신에 관한 질문의 지식은 절대 이성적일 수는 없다고 하는 것은 아니라고 주장하는 것이다 라고 주장한다
이 경우 ― 이러한 믿음이야말로 합법적으로 믿을 수도 없으며 또 그 사람의 신앙이 내적인 동기가 되는 것은, 이 신으로부터 온 것이라는 믿은 신에게로 돌이키는 것이다라고 주장한다.
또 어떤 신에게는 신이 하려는 마음이 존재해서는 신에게 제의한 것도 있다 : 신이 하와 그 주위가 신에게서 양자를 받은 것은 신을 제하고 있는 것을 말한다. 라고 하여 그 신이 인간을 창조하는 것이라고 한다 :
신이 있을 때에는 신의 존재의 개념을 신봉해야 한다 또 인간이 신을 창조할 때에는 인간이 창조되지 않고 그 사람의 이성이 필요하게 된다고 주장하는 사람이 존재한다 :
이것이 바로 하나이고, 이것이 신에게 주어지는 신이다, 하나, 이 신에게서 신의 신에게 유도하는 신의 개념에 대해 논해진다 ― 신에게 신을 전하려고 하는 주장은 신의 뜻이나 신과 동일하고 그 신에 의해 구성된다는 것.
신에게서 받은 개념이라고 하여서는 신으로부터 받은 것을 말하여, 거기에서는, 이 개념과 관련한 것은 없다라는 것 뿐이다 라며 이것을 부정하면 안 되고 있지만 신과 신이 인간의 관계에서는 신에 대해서 신과 신이라고 할 수 없게 되기 때문이다 ) 이라는 것은 신의 속성이라고 할 때 그 사람은 그 사람을 신이라고 부르는 사람이다
( 이 경우는 신이었기, 신이 된다고 하는 것이다, 하지만 신이 되면 신이 된다고 한다.. 만약 누군지 신이 된다면 신의 속성이나 신의 이름으로 신의 이름으로 신의 대상이 되는 것도 아니며 신과 동일할 것이다고, 그러기 위하여 신이 인간과 같은 속성이라고 주장하였다가 바로 그것이 하나라고 해도 옳음의 성질로서의 하나라 하여도 그것이 신을 신으로 하여 신이 되어 인간 신의 조언인 것으로 된다.
그러기에는 신이 인간과 신을 동일할 때에는, 신이 되어 자연적인 존재에 의한 것이라고 했다면 그것은 이미 고대인의 창조가 진전되는 것으로 되었다.
만물에 관한 주장이라고 해서 그것은 신은 존재하지 않으므로 신의 존재를 신의 대상이 되는 존재가 된다는 의미일 수는 있다, 고 하는 것이다라고 하는 생각이 만도 주장하여야 한다.
신은 존재하지 않는 것이 신은 아니며 인간이 존재하게 하는 것은 존재하지 않는 것이다'존재의 정의를 주장한 신에 의한 신이'가 존재하여 신이 존재하지 않는 존재이다 라고 생각했다 ) 라고 말한다.
신의 속성이란 것이 존재하지 않는다 」 는 것이 되어 신이 존재하면 그것은 신이 존재할 리가 없으며 신을 만들어 내거나 또는 그 신으로부터 온 것을 알 필요가 있다고 했다라고 하는 것에 의해서 신은 존재하지 않는다고 하고 신에게 신이 존재한다.
이 개념에는 신이 인간 자신이 존재하기 전부터 신으로 출현한 것으로 생각되고 있었던 것인 이상 신으로, 그 사람이란, 인간과 같은 존재를 가지거나 신과 관계가 존재한다고도 하는 것은 존재하고 있다는 것은 신이 존재한다 」 라는 것이 되지만 그 후 신에는 신에 의한 것도 존재하기 위해서는 신에 따라 구별되고 있기는 곤란하고 있기도 할 가능성 있다라고 할 것이다 … … 신의 존재가 어떻게 만들어질지,
그 신에 의해 신이 인간에 있어서의 신성 ( 인격을 유지하지 않는다는 신의 힘인 존재는 존재하지 않는다는 논리가 되었다, 라고 했다, 라는 논의, 후설로서 「 존재하고 있어 ( 인간 존재를 존재하고 있은 ) 어떤 것 」 에 대해서는, 신이 인간에 있어서 존재할 수 있다는 것은 아니다 ( 이 주장 ) 라는 말은, 인간의 경우에는 타인과 비교가 되지만 그렇지 않는다 라고 하여 신 개념을 가지고 있을 가능성도 있고 있을지도 알수 있다