딥러닝 이야기 / Generative Pre-Training Transformer (GPT) / 2. GPT-2와 DailyDialog를 이용한 Multi-turn 챗봇 구현

작성일: 2023.01.07
시작하기 앞서 틀린 부분이 있을 수 있으니, 틀린 부분이 있다면 지적해주시면 감사하겠습니다.
이전글에서는 GPT 모델들에 대해 살펴보았습니다.
이번글에서는 모델의 크기가 많이 크지 않아서 우리가 비교적 사용하기 용이하며, 성능도 좋은 GPT-2를 이용하여 multi-turn 대화형 챗봇을 구현해보겠습니다.
그리고 pre-trained GPT-2를 사용하기 때문에 Hugging Face API를 통해 가져옵니다.
본 글에서 설명하는 코드는 기본적으로 학습을 진행할 때 모델 저장의 지표로 사용되는 BLEU-4 계산은 NLTK를 사용합니다.
그리고 코드의 구현은 python의 PyTorch를 이용하였습니다.
학습에 사용한 데이터는 DailyDialog 데이터를 사용하였습니다.
그리고 GPT 대한 글은 GPT-1, GPT-2, GPT-3를 참고하면 됩니다.
본 글에서 설명하는 GPT-2 multi-turn 챗봇 구현 코드는 GitHub에 올려놓았으니 아래 링크를 참고하시기 바랍니다(본 글에서는 Hugging Face API 사용에 초점을 맞추고 있기 때문에 데이터 전처리 등 학습을 위한 전체 코드는 아래 GitHub 링크를 참고하시기 바랍니다).
오늘의 컨텐츠입니다.
- Multi-turn 학습을 위한 데이터 구성
- Pre-trained 토크나이저 가져오기
- Pre-trained GPT-2 가져오기
- Multi-turn 챗봇 학습
- Multi-turn 챗봇 학습 결과
GPT-2 Multi-turn 챗봇
”
여기서는 multi-turn 챗봇 학습을 위해 어떻게 데이터를 구성하는지 알아보겠습니다.
class DLoader(Dataset):
def __init__(self, data, tokenizer, config):
self.data = data
self.tokenizer = tokenizer
self.pad_token_id = self.tokenizer.pad_token_id
self.cls_token_id = self.tokenizer.cls_token_id
self.sep_token_id = self.tokenizer.sep_token_id
self.max_len = config.max_len
self.length = len(self.data)
def make_data(self, data_list):
total_s = [self.cls_token_id]
for s in data_list:
tmp_s = self.tokenizer.encode(s) + [self.sep_token_id]
if len(total_s) + len(tmp_s) > self.max_len
break
total_s += tmp_s
total_s += [self.pad_token_id] * (self.max_len - len(total_s))
return total_s
def __getitem__(self, idx):
s = self.make_data(self.data[idx])
return torch.LongTensor(s)
def __len__(self):
return self.length
- 3 ~ 9번째 줄: Multi-turn 챗봇 학습 데이터를 만들기 위해 필요한 데이터들 사전 정의.
- 3번째 줄: [(s1, s2, ..., sn), (s1, s2, ..., sn), ... , (s1, s2, ..., sn)] 형식으로 구성 되어있음.
- 12 ~ 20번째 줄: Multi-turn 챗봇 학습을 위해 "[CLS] s1 [SEP] s2 [SEP]..." 형식으로 데이터 구성.
- 23 ~ 25번째 줄: 최종적으로 데이터를 내보내는 함수.
- 24번째 줄: 12 ~ 20번째 함수 적용.
”
여기서는 Hugging Face를 이용하여 pre-trained 토크나이저를 가져오는 코드를 살펴보겠습니다. 먼저 Hugging Face를 사용하기 위해서는 아래 코드를 실행해서 transformers 모듈을 설치해야 합니다.
# terminal pip install transformers
이제는 pre-trained 토크나이저를 가져오는 코드입니다.
Pre-trained 토크나이저
from transformers import GPT2Tokenizer
class Tokenizer:
def __init__(self, config):
self.pretrained_model = config.pretrained_model
self.model = GPT2Tokenizer.from_pretrained(self.pretrained_model)
self.tokenizer.add_special_tokens({'cls_token': '[CLS]', 'sep_token': '[SEP]', 'pad_token': '[PAD]'})
self.pad_token, self.pad_token_id = self.tokenizer.pad_token, self.tokenizer.pad_token_id
self.cls_token, self.cls_token_id = self.tokenizer.cls_token, self.tokenizer.cls_token_id
self.sep_token, self.sep_token_id = self.tokenizer.sep_token, self.tokenizer.sep_token_id
self.unk_token, self.unk_token_id = self.tokenizer.unk_token, self.tokenizer.unk_token_id
self.vocab_size = len(self.tokenizer)
def tokenize(self, s):
return self.tokenizer.tokenize(s)
def encode(self, s):
return self.tokenizer.encode(s)
def decode(self, tok):
try:
tok = tok[:(len(tok) - list(reversed(tok)).index(self.sep_token_id))]
except ValueError:
try:
tok = tok[:tok.index(self.pad_token_id)]
except:
pass
return self.tokenizer.decode(tok)
- 6번째 줄: Hugging Face에서 pre-trained 토크나이저를 불러올 모델의 종류(e.g. gpt2, gpt-large).
- 7번째 줄: Hugging Face의 pre-trained 토크나이저 불러오는 부분.
- 8번째 줄: Hugging Face의 pre-trained 토크나이저에 사용자 지정 토큰 추가하는 부분.
- 15번째 줄: 추가한 토큰을 포함한 최종 vocabulary size.
- 18 ~ 19번째 줄: 들어온 문장을 tokenize 하는 함수.
- 22 ~ 23번째 줄: 들어온 문장을 encoding 하는 함수.
- 26 ~ 34번째 줄: 들어온 문장을 decoding 하는 함수.
- 28번째 줄: 들어온 토큰 중 맨 마지막 sep_token_id를 찾아 거기 위치까지만 decoding 하는 부분(이후 pad_token_id를 제외하기 위함).
”
이제는 Hugging Face API를 통해 pre-trained GPT-2를 가져오는 코드입니다.
Pre-trained GPT-2
import torch
import torch.nn as nn
from transformers import GPT2LMHeadModel
class GPTChatbot(nn.Module):
def __init__(self, config, device):
super(GPTChatbot, self).__init__()
self.pretrained_model = config.pretrained_model
self.model = GPT2LMHeadModel.from_pretrained(self.pretrained_model)
self.model.resize_token_embeddings(config.vocab_size)
self.pad_token_id = tokenizer.pad_token_id
def make_mask(self, x):
mask = torch.where(x==self.pad_token_id, 0, 1)
return pad_mask
def forward(self, x):
pad_mask = self.make_mask(x)
output = self.model(input_ids=x, attention_mask=pad_mask)
return output.logits
- 9번째 줄: Hugging Face에서 pre-trained GPT-2를 불러올 모델의 종류(e.g. gpt2, gpt-large).
- 10번째 줄: Hugging Face의 pre-trained GPT-2 불러오는 부분.
- 11번째 줄: 이전에 추가한 vocab 때문에 모델의 임베딩 및 fully-connected layer 크기 조정.
- 15 ~ 17번째 줄: Pad mask 만드는 함수.
- 20 ~ 23번째 줄: Multi-turn 데이터가 모델에 들어간 후, fully-connected layer를 거치는 부분.
”
이제 pre-trained GPT-2를 이용해 multi-turn 챗봇 학습하는 방법입니다.
아래 코드에 self. 이라고 나와있는 부분은 GitHub 코드에 보면 알겠지만 학습하는 코드가 class 내부의 변수이기 때문에 있는 것입니다.
여기서는 무시해도 좋습니다.
그리고 아래 학습 코드는 실제 학습 코드를 간소화한 것입니다. Scheduler 등 전체 학습 코드는 GitHub 코드를 참고하면 됩니다.
self.model = GPTChatbot(self.config, self.tokenizer).to(self.device)
self.criterion = nn.CrossEntropyLoss(ignore_index=self.tokenizer.pad_token_id)
self.optimizer = optim.Adam(self.model.parameters(), lr=self.lr)
self.model.train()
for i, x in enumerate(self.dataloaders['train']):
x = x.to(self.device)
self.optimizer.zero_grad()
with torch.set_grad_enabled(phase=='train'):
output = self.model(x)
loss = self.criterion(output[:, :-1, :].reshape(-1, output.size(-1)), x[:, 1:].reshape(-1))
loss.backward()
self.optimizer.step()
학습에 필요한 것들 선언
먼저 위에 코드에서 정의한 모델을 불러오고 학습에 필요한 loss function, optimizer 등을 선언하는 부분입니다.
- 1 ~ 3번째 줄: Loss function, 모델 및 optimizer 선언.
모델 학습
- 5 ~ 13번째 줄: Cross entropy loss를 이용하여 모델 학습하는 부분.
- 14 ~ 15번째 줄: Loss를 계산하고 모델을 업데이트 하는 부분.
”
이제 train/validation set의 BLEU, NIST score history와 validation set에서 최대 BLEU-4를 달성한 모델에 대해 test set의 score도 살펴보겠습니다.
마지막으로 multi-turn 챗봇 대화를 살펴보겠습니다.
Validation Set BLEU History
BLEU history
Validation Set NIST History
NIST history
아래 결과는 validation set의 가장 높은 BLEU-4를 달성했을 때 모델의 test set 결과입니다.
- BLEU-2: 0.4052
- BLEU-4: 0.2268
- NIST-2: 5.1797
- NIST-4: 5.5162
Multi-turn 대화 샘플
Chatbot starts.. If you want to chat new topic, enter the "new()".. If you want to exit, enter the "exit()".. Please enter the 3 multi-turn dialogues.. Q1: I saw her yesterday. A2: What did she doing? Q3: She tried to fix her car. A4: I'm sure she's going to do it again. Q5: Why do you think so? A6: I think she's trying to get donations. Q7: You mean, the car? A8: Yes, it's the one she used to drive.It's broken now. Q9: Oh I didn't know about that. A10: I'm sorry, she's driving again.She'll pay for it.It's her birthday today.Donations for sure.Thanks a lot. Q11: okay. A12: I'll call her. Q13: please stop A14: Q15: new() Please enter new 3 multi-turn dialogues.. Q1: Do you know Kane? A2: Yes of course. He is one of my best friends. Q3: Does he like playing soccer? A4: Yes.But he is not good at it. Q5: But he was a soccer player, wasn't he? A6: Yes.But he is a very good player. Q7: Why? A8: Kane is a very good player.He is a very creative player.He can dribble, he can dribble, he can make free throws. Q9: Okay. A10: But he is not good at shooting. Q11: exit() Chatbot ends..
지금까지 Hugging Face API를 통해 pre-treined GPT-2를 불러와서 DailyDialog 데이터셋을 이용하여 multi-turn 챗봇을 학습해보았습니다.
학습 과정에 대한 전체 코드와 학습에 필요한 데이터는 GitHub에 있으니 참고하시면 될 것 같습니다.