KoGPT2 동화 데이터 학습하기

Yerin·2021년 11월 23일
6

졸업 프로젝트

목록 보기
1/2
post-thumbnail

졸업 프로젝트로 '상호작용을 통한 동화 창작 서비스'를 기획하게 되었다.
머신러닝에 대해 무지했던 터라 삽질을 많이 했지만 이번 실습을 통해 GPT를 조금이나마 이해하게 된 것 같다. 물론 아직 갈 길이 너무 멀다.
미래의 나야 미리 힘내라 😂

이번 실습에서는 SKT에서 공개한 오픈 소스인 KoGPT2를 활용한다.
KoGPT2에 미리 준비한 동화 데이터를 학습시킬 것이다.

KoGPT2 Gihub : https://github.com/SKT-AI/KoGPT2

'상호작용을 통한 동화 창작 서비스'는 어떤 서비스인가?

해당 서비스는 5-7세 아동들을 주 타겟층으로 하며, 결말이 정해진 기존 동화와 달리
아이들의 선택에 따라 동화의 흐름이 달라지는 서비스이다.

  • 예상 UI 화면 (5-7세임을 고려하여 선택지를 더욱 간단하게 만들 예정)

이렇게 해당 동화의 내용, 질문의 대답을 반영하여 동화를 창작하기 위해서는
문장 생성 알고리즘이 필요하다.

분명 아래의 내용들을 머리로는 이해를 했다.
1. 문장 생성 모델인 KoGPT2가 있다.
2. KoGPT2에 동화 데이터를 학습시킨다.
3. 2에서의 과정이 기존에 학습된 모델을 새로운 목적에 맞게 학습을 업데이트하는 Fine-Tuning이다.

여기서부터 문제였다. Fine-Tuning을 알게 되었지만, 대부분의 예제들을 해석할 수 없어서 조금 시간이 걸렸다.

그러던 와중 소설 데이터를 KoGPT2에 학습시킨 좋은 예제를 찾게 되었다.

실습하기

KoGPT2novel : https://github.com/ttop32/KoGPT2novel

오늘은 이 예제를 참고하여, 동화 데이터를 학습시켜 KoGPT2가 프로젝트에 사용할 만한지 실습해 볼 것이다.

실습은 colab에서 진행하였다.

1. transformers, fastai 설치

fastai는 import시 오류가 발생하여 하위 버전인 2.2.5버전을 설치하였다.

!pip install transformers
!pip install fastai==2.2.5

2. 필요한 라이브러리 import

from typing import Optional
import torch
import transformers
from transformers import AutoModelWithLMHead, PreTrainedTokenizerFast
from fastai.text.all import *
import fastai
import re

transformers
: 자연어 처리 모델, RNN을 사용하지 않고 Attention 만으로도 충분이 seq2seq 자연어 처리를 할 수 있다.
torch, fastai : 딥러닝 라이브러리

3. kogpt2 불러오기 및 동화 데이터 훈련 전 테스트

tokenizer = PreTrainedTokenizerFast.from_pretrained("skt/kogpt2-base-v2",
  bos_token='</s>', eos_token='</s>', unk_token='<unk>',
  pad_token='<pad>', mask_token='<mask>') 
model = AutoModelWithLMHead.from_pretrained("skt/kogpt2-base-v2")
text = """ 옛날 옛날 어느 마을에 흥부와 놀부 형제가 """
input_ids = tokenizer.encode(text)
gen_ids = model.generate(torch.tensor([input_ids]),
                           max_length=128,
                           repetition_penalty=2.0,
                           pad_token_id=tokenizer.pad_token_id,
                           eos_token_id=tokenizer.eos_token_id,
                           bos_token_id=tokenizer.bos_token_id,
                           use_cache=True
                        )
generated = tokenizer.decode(gen_ids[0,:].tolist())
print(generated)

tokenizer: Text를 여러개의 Token으로 분류하며 보통 공백, 구두점, 특수문자 등으로 분류한다.
fine-tuning 전과 후의 차이를 알기 위해 " 옛날 옛날 어느 마을에 흥부와 놀부 형제가 "라는 같은 문장을 input으로 입력하였다.

실행 결과

The tokenizer class you load from this checkpoint is not the same type as the class this function is called from. It may result in unexpected tokenization. 
The tokenizer class you load from this checkpoint is 'GPT2Tokenizer'. 
The class this function is called from is 'PreTrainedTokenizerFast'.
/usr/local/lib/python3.7/dist-packages/transformers/models/auto/modeling_auto.py:698: FutureWarning: The class `AutoModelWithLMHead` is deprecated and will be removed in a future version. Please use `AutoModelForCausalLM` for causal language models, `AutoModelForMaskedLM` for masked language models and `AutoModelForSeq2SeqLM` for encoder-decoder models.
  FutureWarning,
옛날 옛날 어느 마을에 흥부와 놀부 형제가 왁자지껄 떠들어대며 "우리 집엔 왜 이렇게 많은 사람들이 모여 사는 거야?" 하고 묻는다.
그런데 그 마을 사람들은 모두 다들 자기네 동네에 살고 있는 사람들이라고 한다.
이렇게 해서 우리 마을은 '흥부가 살던 곳'이라는 뜻의 '고향'이 되었다.
그리고 이 고향은 바로 지금의 서울 종로구 숭인동이다.
숭인동은 원래 종로에서 가장 오래된 주택가였다.
1970년대까지만 해도 이곳은 재개발로 인해 헐리고 빈집이 많아졌다.
하지만 1980년대 들어 다시 활기를 되찾기 시작했다.
당시만 하더라도 이곳에는 낡은 건물들이 많이 남아 있었다.

결과를 보면 사전을 보는 것과 같은 느낌이 든다.
그 이유는 KoGPT2가 한국어 위키 백과 이외, 뉴스, 모두의 말뭉치 v1.0, 청와대 국민청원등을 이용해 학습되었기 때문이다.


4. 동화 데이터 로딩

약 100여편의 동화책을 타이핑한 tale.txt파일을 불러왔다.
데이터 로딩 후 tokenizer 과정을 거친다.

with open('tale.txt') as f:
   lines = f.read()
lines=" ".join(lines.split())

#model input output tokenizer
class TransformersTokenizer(Transform):
   def __init__(self, tokenizer): self.tokenizer = tokenizer
   def encodes(self, x): 
       toks = self.tokenizer.tokenize(x)
       return tensor(self.tokenizer.convert_tokens_to_ids(toks))
   def decodes(self, x): return TitledStr(self.tokenizer.decode(x.cpu().numpy()))

#split data
train=lines[:int(len(lines)*0.9)]
test=lines[int(len(lines)*0.9):]
splits = [[0],[1]]

#init dataloader
tls = TfmdLists([train,test], TransformersTokenizer(tokenizer), splits=splits, dl_type=LMDataLoader)
batch,seq_len = 8,256
dls = tls.dataloaders(bs=batch, seq_len=seq_len)

5. 동화 데이터 학습

#gpt2 ouput is tuple, we need just one val
class DropOutput(Callback):
  def after_pred(self): self.learn.pred = self.pred[0]
      
      
learn = Learner(dls, model, loss_func=CrossEntropyLossFlat(), cbs=[DropOutput], metrics=Perplexity()).to_fp16()
lr=learn.lr_find()
print(lr)
learn.fine_tune(6)

총 6번의 학습을 진행하였다. 이 과정에서 약 20-30분 정도가 소요되었다.

6. 학습된 모델 테스트

prompt=" 옛날 옛날 어느 마을에 흥부와 놀부 형제가 "
prompt_ids = tokenizer.encode(prompt)
inp = tensor(prompt_ids)[None].cuda()
preds = learn.model.generate(inp,
                           max_length=128,
                           pad_token_id=tokenizer.pad_token_id,
                           eos_token_id=tokenizer.eos_token_id,
                           bos_token_id=tokenizer.bos_token_id,
                           repetition_penalty=2.0,       
                           use_cache=True
                          ) 
tokenizer.decode(preds[0].cpu().numpy())

실행 결과

옛날 옛날 어느 마을에 흥부와 놀부 형제가 살고 있었어요.
형은 부자이고, 동생은 가난했지요. 하지만 가난한 동생이 순무 몇 개만 가지고 온 거리로 나가곤 했죠. 
그러다 그만 암탉에게 말하길, "얘야, 너희들 중 누가 오셨니?" 그 남자가 물었기에, 대답을 할 수 있는 힘껏 차림을 하고서 
2층으로 올라가고 난 다음 가던 길을 계속 갔답니다. 집에 돌아온 그가 냉큼 자신의 꼬리에서 깃털 하나를 뽑아당겼는데 이게 어찌된 영문인지 그의 동료였더랬다. 

학습 전과 같은 문장을 input하였다.
동화 데이터를 학습 시킨 후 문체가 조금 동화에 가깝게 바뀌었다.
하지만 갑자기 암탉이 나오기도 하고 전반적으로 개연성이 떨어지는 것을 볼 수 있다.
이와 같은 문제를 해결하기 위해 더 많은 데이터를 학습시키고, 학습 데이터를 분석하고 학습 시키는 방식을 보완해야 할 것이다.


앞으로의 개선 사항

  1. 동화마다 다른 말투로 인해 매끄럽지 않은 부분 보완 필요
  2. 해당 원작 동화 내용에 가중치를 주는 방식 필요
  3. 해당 실습에서는 100여편의 동화를 한 텍스트 파일에 넣어 학습시킴
    → 동화들을 비슷한 내용끼리 그룹핑하여 동화의 흐름을 매끄럽게 할 필요가 있음
  4. 더욱 많은 동화 데이터 필요 (1000개 목표)

결론

갈 길이 멀다. 🤣

profile
재밌는 코딩 공부

2개의 댓글

comment-user-thumbnail
2021년 12월 19일

잘 읽었어요~ 김현수교수

답글 달기
comment-user-thumbnail
2022년 5월 18일

안녕하세요. 블로그에 실린 컨텐츠가 저의 관심사와 일치합니다.
조금 더 공부에 대해 교류하고 싶습니다.
과학기술정보통신부에 근무하고 있습니다.
urigachi@korea.kr입니다.

답글 달기