컴굥일지

참여형 동화 서비스 - NLP(KoGPT2) 본문

프로젝트/졸프 - 참여형 동화 서비스

참여형 동화 서비스 - NLP(KoGPT2)

gyong 2021. 11. 25. 17:36
반응형

1. 프로젝트 주제 

어릴 때, 동화책을 읽다 보면 부모님이 옆에서 "이다음엔 어떻게 되었을까?" 라던가 "여기서 다른 선택을 했으면 어땠을까?"와 같은 질문을 던지곤 합니다.
그러면 어렸던 저는 이제 질문을 듣고 막 상상을 하면서 뒷 이야기를 생각하곤 했습니다. 저희 팀의 프로젝트 주제(참여형 동화 서비스)는 이러한 경험으로부터 나오게 되었습니다.

현재 저희 팀은 5~7세 대상의 동화책을 dataset으로 모으고 있습니다. 이 dataset으로 학습을 시킬 예정입니다.

제가 이번에 맡은 부분은 동화를 생성하는 파트입니다.
기술 조사에서 동화 생성을 맡는 기술로 GPT3와 KoGPT2가 거론되었습니다. 그러나 GPT3가 유료이기 때문에 일단은 KoGPT2를 사용해보기로 했습니다.

 


 

2. 진행 내용 

<9월/10월>

프로젝트 주제를 정하고, 프로젝트에 사용될 기술에 대해 조사하는 기간을 가졌습니다.
중간에 주제도 바뀌고, AI에 대한 지식도 없어서 팀원들이 많이 고민하는 시간을 가졌습니다.
멘토님과 멘토링을 진행했으며, 보고서와 중간 발표, 창업경진대회 준비를 했습니다.
비록 창업경진대회 본선 진출은 하지 못했지만, 프로젝트에 대한 좀 더 깊은 고민을 할 수 있었습니다. 

 

<11월>

프로젝트 진행을 위해 소설에서 단어를 추출하는 부분과, 소설을 창작하는 부분으로 팀을 나누어 진행했습니다. 

 


 

3. KoGPT2 시작 

KoGPT2를 사용하기로 결정하고 나서, 어떻게 사용해야하는 것인지 공부를 시작했습니다.
유튜브에서 KoGPT2에 대한 간단한 설명과 사용법을 설명해 둔 동영상이 있어서, 이걸 보고 따라 해 보았습니다.
깃허브 README가 있어도 어떻게 사용할지 막막했었는데, 이 동영상은 차례차례 과정을 보여주어서 입문자들에게는 많은 도움이 되지 않을까 싶습니다.

https://youtu.be/FQ0vq2CBaL4

(한국어 문장 생성기 KoGPT2 - 빵형의 개발도상국)

 


 

4. 눈물의 KoGPT2 

유튜브를 보며 KoGPT2를 가볍게 접해보고 나서, 실제로 데이터를 추가해서 모델을 학습시키는 것은 어떻게 해야 하는지 알아보았습니다.
깃허브에 가보면, KoGPT2를 사용한 많은 예제들이 있는데, 한 7~8개 정도 clone 해서 코드를 돌려보았습니다.

예제는 아래의 기준으로 선정하여 공부했습니다.
1. 코랩으로 돌리기 위해 .ipynb 파일 일 것
2. dataset을 이용하여 모델을 추가적으로 학습시킨 경우

일단 결론부터 말하자면 한 가지 예제를 제외하고는 전부 실패했습니다.
예제 선정을 잘못한 건지... 중간에 버전 오류도 많이 발생하고 import 오류도 종종 발생했습니다.
위와 같은 문제는 구글링을 하면 어떻게든 고칠 수 있었는데, 절대 해결 못하는 오류 메시지가 있습니다. 

FileNotFoundError: [Errno 2] No such file or directory: '../kogpt2/pytorch_kogpt2_676e9bcfa7.params' 

또는

HTTPSConnectionPool(host='kobert.blob.core.windows.net', port=443): Max retries exceeded with url: /models/kogpt2/pytorch (Caused by NewConnectionError('<urllib3.connection.VerifiedHTTPSConnection object at 0x7f0cbd7bef90>: Failed to establish a new connection: [Errno -2] Name or service not known'))

혹시라도 위와 같은 에러 메시지를 본다면, 그냥 그 예제는 포기하도록 합시다....

저런 에러 메시지가 뜬 프로젝트 중 하나는 https://github.com/shbictai/narrativeKoGPT2 입니다.
깃허브에 올라와있는 파일 중, NarrativeKoGPT2.ipynb를 돌려보았습니다.
README가 꽤 자세하게 설명되어 있어, 코드 이해하는데 도움이 될 거라 생각했었는데....
아주 장렬하게 실패했습니다 ㅎㅎ...

 

문제의 코드.....

사진의 코드 중 문제의 부분은 아래와 같습니다.

pytorch_kogpt2 = {
    'url':
    'https://kobert.blob.core.windows.net/models/kogpt2/pytorch',
    'fname': 'pytorch_kogpt2',
    'chksum': '676e9bcfa7'
}

코드를 돌리면 첫 번째 cell 자체는 문제가 없지만, 두 번째 cell에서 위 코드로 인한 오류가 발생합니다.

ConnectionError: HTTPSConnectionPool(host='kobert.blob.core.windows.net', port=443): Max retries exceeded with url: /models/kogpt2/pytorch (Caused by NewConnectionError('<urllib3.connection.VerifiedHTTPSConnection object at 0x7f0cbd7bef90>: Failed to establish a new connection: [Errno -2] Name or service not known'))

에러 메시지는 다음과 같습니다. 위에서 언급한 ~pytorch_kogpt2_676e9bcfa7.params 에러도 밑으로 파고 들어가 보면 같은 이유였습니다.
저런 에러가 나타나는 여러 예제들의 github issue를 확인해보았더니, 아래와 같은 답변이 있었습니다.

 

이 답변을 보고 링크로 모델을 받아오는 방식의 예제는 포기하게 되었습니다.
ㅎㅎ.... 이 에러 때문에 여러 예제를 시도했던 건데... 결국 해결이 안 되더라고요.....
구글링해도 저런 에러가 나타난다는 사람을 저 말고 딱 2명 봤는데, 아마 최근에 모델의 디렉토리 저장 방식(구조)이 바뀌거나 아예 모델을 내려버리지 않았을까 싶습니다.
올해 초 정도까지는 사용이 가능하다가 얼마 전부터 안 되는 것 같습니다. (언젠가 해결될지도....?)
이거 읽으시는 KoGPT2 사용자가 있다면... 얼른 다른 방식을 생각해보세요!! 

 


 

5. 조금은 희망이 보이는 것도 같은데...? 

예제로 인해 고통받다가, 소설 창작 부분을 맡은 다른 팀원이 예제를 추천해주어서 바로 코드를 돌려보았습니다.
이번에 시도한 예제는 https://github.com/ttop32/KoGPT2novel 입니다.
파일도 여러 개 볼 것 없이, train.ipynb 파일 하나만 보면 됩니다.
저는 중간에 코드를 조금씩 수정하긴 했습니다. (제 코드 기준으로 설명하겠습니다.)

 

1. 구글 드라이브와 코랩 연결하기

구글 드라이브와  마운트 하는 명령어를 입력합니다.

from google.colab import drive
drive.mount('/content/drive')

 

그러면 아래와 같은 URL이 뜹니다.
들어가서 연결할 계정을 누르고 로그인을 한 뒤에, 드라이브 마운트 코드를 복사해주세요. 

 

시 코드로 돌아와서 Enter your Authorization code: 에 복사한 코드를 입력하고 enter키 누르면 연동이 됩니다. 
연동이 되었으면 !ls 명령어를 통해 잘 연동되었는지 확인해 봅니다.

 **  drive.mount('/content/drive/My Drive/Colab Notebooks/a1')과 같이 처음부터 경로 설정을 해두어도 됩니다.

 

 


 

2. 라이브러리 설치 및 불러오기

필요한 라이브러리들을 설치하고 import 해줍니다. 

!pip install fastai==2.2.5
!pip install transformers

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

print(torch.__version__)
print(transformers.__version__)
print( fastai.__version__)

 


 

3. KoGPT2 모델 다운로드하고 사용해보기

아래의 코드로 KoGPT2 model과 tokenizer를 다운로드할 수 있습니다.

#download model and tokenizer
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")

 

잘 되었는지 확인해보는 코드입니다. 실행결과는 아래 사진과 같습니다.

#test tokenizer
print(tokenizer.tokenize("안녕하세요. 한국어 GPT-2 입니다.😤:)l^o"))  #문장을 토크나이저로 나눈다

#test model ouput
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)

 


 

4. dataset 불러오기

아까 위에서 코랩과 구글 드라이브를 연동시켜두었습니다.
제 dataset의 파일 명은  "tale.txt"이고, 파일 위치는 '/drive/My Drive/Colab Notebooks/a1' 입니다.

with open('drive/My Drive/Colab Notebooks/a1/tale.txt') as f:
    lines = f.read()
lines=" ".join(lines.split())

lines=re.sub('"', ' ', lines)
lines=re.sub("'", ' ', lines)

참고로 re.sub() 함수는 정규 표현식 문자열 치환 함수입니다. 

re.sub( 정규 표현식, 대상 문자열, 치환 문자 )

정규 표현식 - 검색 패턴 지정
대상 문자열 - 검색 대상이 되는 문자열
치환 문자 - 변경하고 싶은 문자

 


 

5. 데이터 추가하여 학습시키기

train 데이터와 test 데이터로 나누어 사용합니다.

#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)
dls.show_batch(max_n=2)

 

lr_find() 함수를 이용하여, 그래프에서 loss가 감소하는 부분을 찾아야 합니다.

#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)

코드 실행 결과, 좌측과 같은 그래프가 나왔습니다.
그래프를 보면 10^-7 ~ 10^-3에서 loss가 감소하는 것으로 나왔습니다.

 

이제 fit_one_cycle() 함수를 사용해 보겠습니다.
fit_one_cycle()은 이미 잘 학습된 모델에서 fc layer만 학습시키는 함수입니다. (transfer learning 이라고 하죠)
fit_one_cycle() 함수를 쓰기 이전에 unfreeze() 를 쓰면, 모든 layer를 학습시킬 수 있습니다. (시간이 엄청 오래 걸립니다)
저는 그래프를 바탕으로 값을 임의로 1e-4로 정하고, epoch는 5로 설정했습니다. 

learn.fit_one_cycle(5,1e-4)  # 1e-4 는 10^-4

colab이 돌아가면서 표로 결과를 출력해줍니다.

 

 

 

 

다시 문장을 넣고 돌려보겠습니다.

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
                          ) 
sentence = tokenizer.decode(preds[0].cpu().numpy())
print(sentence)

문맥이 약간 이상하기는 하지만 결과는 나왔네요. 

 


 

6. 모델 저장하기

지금 돌린 모델을 드라이브에 저장하고자 합니다.
저는 "novel1_backup" 폴더에 저장했습니다.

learn.model.save_pretrained("drive/My Drive/Colab Notebooks/a1/novel1_backup")  #모델 저장

 


 

7. 맞춤법 검사 (선택)

맞춤법 교정을 위한 라이브러리를 알아보았습니다.
종류가 PyKoSpacing, Py-Hanspell, SOYNLP 등이 있었는데 일단은 Py-Hanspell을 선택했습니다. 

#Py-Hanspell 설치
!pip install git+https://github.com/ssut/py-hanspell.git

#import 해서 문장 돌려보기
from hanspell import spell_checker

sent = "맞춤법 틀리면 외 않되? 쓰고싶은대로쓰면돼지 "
spelled_sent = spell_checker.check(sent)

hanspell_sent = spelled_sent.checked
print(hanspell_sent)

라이브러리를 설치하고 실행해보면 위와 같은 결과가 나온다는 것을 알 수 있습니다.

 


 

6. 마무리 

 

위에서 학습을 시킨 후 출력한 결과를 보면, 문장이 생성되기는 했는데 약간 뭔가 이상합니다.
그 이유에 대해 생각해 보았습니다.

1. 학습 데이터셋이 부족하다
2. 학습 데이터들의 어미가 제각각이다.

일단 1번에 관해서, 데이터셋은 현재 50권 정도 모아서 학습을 시켰습니다.
저희 팀은 방학 기간에 더 많은 데이터를 모을 예정이며, 최소한 400권은 학습시키려고 합니다. (400권도 부족할 수도....)

그리고 2번 문제는 동화라는 특성상 따옴표가 많고, 어미도 제각각이라 결과가 이상하게 나오는 것 같습니다. 
이 부분은 팀원들과 상의해서 해결책을 찾아야겠네요.
비슷한 말투의 동화만 dataset으로 모을지, 아니면 변환하는 과정을 거칠지 결정해야 할 것 같습니다.

졸프 요소기술 검증하면서 KoGPT2가 꽤... 많은 오류를 일으켰지만 그래도 사용 방법을 알게 되어서 정말 기쁩니다.
링크가 막혀서 많은 예제들을 실행해보지 못했다는 점은 좀 아쉽지만, 이번 예제를 통해 머신러닝을 어떻게 쓰는 것인지 좀 알게 된 것 같습니다.

반응형
Comments