4-1공부/캡스톤-딥러닝

009. 딥러닝 공부 5일차

KGW2027 2023. 3. 12. 20:13
728x90
반응형

 오늘은 게임 데이터셋을 가지고 문장 생성을 시도해봤다.

 

문장 데이터셋 : 200개 후반대의 문장 ( 대략 1500Line, JSON크기 1.4MB )

 - https://universe-meeps.leagueoflegends.com/v1/en_gb/story/{champ}-color-story/index.json

   여기서 모든 챔피언에 대해 파싱해서 모은 정보로 json파일을 만들었다.

사용한 모델 : GPT2LMHeadModel, GPT2TokenizerFast (gpt2)

 - 문장을 추가학습시키고 Seed_text를 통해 문장을 생성하는게 목표였으므로, Decoder 모델인 GPT2를 이용했다.

 

 [Dataset]

 대충 이런 형태를 하고 있는 JSON파일이다.

위의 링크를 들어가보면 챔피언마다 스토리랑 Featured?(아마 관계된 스토리?) 뭐 그런게 같이 적혀있는데

그부분이랑 탐색한 챔피언 이름 같은것만 따와서 JSON파일을 만들었다.

물론 딱히 뭐 질문에 대한 답변을 하게 하거나, 문장 분류를 하려는 것이 아니므로, context만 가져와서 학습시켰다.

class SentenceDataset(Dataset):
    def __init__(self, sentences, tokenizer_):
        self.sentences = sentences
        self.tokenizer = tokenizer_

    def __getitem__(self, index):
        v_dict = dict(self.sentences[index])
        context = self.tokenizer.encode_plus(v_dict['context'], max_length=max_seq_len,
                                             padding=True, truncation=True, stride=stride,
                                             return_tensors='pt')

        context['input_ids'] = context['input_ids'].squeeze(0).cuda()
        context['attention_mask'] = context['attention_mask'].squeeze(0).cuda()
        return context

    def __len__(self):
        return len(self.sentences)

이렇게 SentenceDataset을 만들어 3가지 기본 함수를 구현했다.

(Squeeze는 또 가운데에 크기 1 차원이 들어가 있었으므로 입력했다. )

※ 대부분의 문장이 max_seq_len이었던 768을 오버했었는데, stride랑 이런걸 써서 분할하게 했다.

근데 잘 분할이 됬는지는 모르겠다... 디버그 창으로 봤을 때는 하나밖에 없던거같은데.. 뒤는 다짤린듯..? (아니면 Squeeze로 자른곳에 그것들이 있던걸까..)

 

expect = set()
for v in data:
    v_dict = dict(v)
    if 'quote' not in v_dict:
        encode = tokenizer.encode_plus(v_dict['context'], truncation=True, max_length=max_seq_len, stride=stride, return_overflowing_tokens=True)
        for encoding in encode.encodings:
            for idx in range(0, len(encoding.ids)):
                if encoding.ids[idx] == tokenizer.unk_token_id:
                    tok = encoding.tokens[idx]
                    expect.add(tok)

 처음에는 이것도 BERT모델을 써서 학습했었는데, 문장 생성을 해야되는걸 깨닫고 바꿨다.

이 때, Vocab에 없는거는 특수문자들만 있길래 불용어처리를 해보려고 만들었는데 잘 되지는 않았다.

시도한 방법은

 tokenize를 한다. -> 전체 순회를 하면서 불용어 list에 없는것만 추가한다. -> 그 list를 ' '.join으로 합친다. -> 그 string을 다시 getitem에서 tokenize한다.

이런 방식이었는데, 이렇게 하니까 BPE때문에 앞에 __라던가 붙어서 나오는 토큰들의 그 부분까지 다 합쳐버려서 유니코드상으로도 이상해지고 그랬던것같다. 어떻게 해보려다가 귀찮아서 빼버리니까 잘 작동하더라...

 

train_data = SentenceDataset(data, tokenizer)
data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=True, mlm_probability=0.15)
train_args = TrainingArguments(output_dir="./ckpt/sentence_train", num_train_epochs=40, per_device_train_batch_size=batch_size)
trainer = Trainer(model=model, args=train_args, data_collator=data_collator, train_dataset=train_data)

model = model.cuda()

trainer.train()
save_path = "./ckpt/sentence_train_gpt2_complete"
trainer.save_model(save_path)
tokenizer.save_pretrained(save_path)

 단순히 학습만 시키려고 하니까 이렇게 Trainer라는 친구를 이용해서 쉽게 만들 수 있었다.

데이터셋과 data_collator(MASK로 추정하게 하는..), train_args(epochs나 batch_size 지정)을 이용해 Trainer를 만들고 trainer.train()으로 학습을 시작시켰다.

여기서도 어떤건 cuda:0에 있고, 어떤건 cpu에 있고 하면서 에러가 났었는데, 난 분명히 input_ids랑 attention_mask, model 모두 cuda로 보냈는데 계속 발생하길래, 그냥 내부코드에 들어가서도 cuda를 넣어줬다.

(근데 불용어 처리부분 제거하니까 작동하기 시작했는데, 이 부분도 다 지웠었는지는 기억이 잘 안난다)

 

 아무튼, 추가학습은 어제의 SequenceClassification보다는 더 쉽게 코드를 짤 수 있었다.

물론 중간에 cuda-cpu 자원처리과정에서 오류가 뜨거나, cuda에서 assert가 뜨거나 하는 등으로 좀 고생해서 어제보다 조금 더 걸려서 완성되긴 했지만, 아무튼 쉬웠다...!

 

이렇게 만들어진 모델을 통해 문장을 생성해봤다.

from transformers import GPT2TokenizerFast, GPT2LMHeadModel

# BERT로 문장을 생성하려고 하지말자...

# Load the pre-trained model using the model path
model_path = "./ckpt/sentence_train_gpt2_complete"
model = GPT2LMHeadModel.from_pretrained(model_path)

# Load the tokenizer
tokenizer = GPT2TokenizerFast.from_pretrained(model_path)

seed_text = "What lives in Tagon Mountain?"
input_ids = tokenizer.encode(seed_text, return_tensors='pt')
gen_ids = model.generate(
    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,
    top_k=50,
    do_sample=True,
    num_return_sequences=5
)

for idx in range(0, len(gen_ids)):
    print(f"[결과 {idx+1}]\n{tokenizer.decode(gen_ids[idx])}\n")

top_k와 num_return_sequences를 통해 seed_text로 부터 생성되는 문장 개수를 조절할 수 있다.

나는 5개의 문장을 생성해보았다.

 

 seed_text는 What lives in Tagon Mountain? 으로 타곤산에는 무엇이 살고있을까? 라는 내용인데,

질문답변 모델이 아니라 그냥 문장 생성모델이라서 큰 기대는 없이 테스트했다.

대충 데마시아인, 라칸, 필트오버, 직스, 다리우스 이런 롤 세계관의 용어들이 등장했다.

물론 말은 잘 안된다. 중간에 특수문자도 들어가있고, 문장도 완성되지 않은 상태로 끝난다.

 

이번엔 seed_text로 Jinx and Vi met으로 해보았다. 둘다 자운의 챔피언인데, 과연 관련있는 문장이 나올까?

 어림도없지... 징크스와 바이로 시작해서 야스오와 빅토르, 아칼리에 엘리스, 그리고 녹서스인(Noxian) 까지 나온다.

이번 실험의 결과는 그냥 문장셋만 추가학습을 시켜도 어휘가 들어가게 된다.. 라는 점을 알게됬다?는 것이다.

이게 당연하다고 해도, 1주차인 나에게는 확신하고 있지 못하던 부분이라 확인하게 되서 좋았다.

 


 

 어제 혐오 문장에 대해 Sequence Classification을 시도했었다.

근데 사용 모델이 GPT2 였는데 어차피 문장 생성을 하려던 것이 아니였으므로,

https://huggingface.co/kykim/albert-kor-base 를 이용하여 다시 Fine-tunning을 시도해보았다.

 Epoch가 진행될 수록 Loss가 점점 감소하고, 성공률도 조금씩 오르는게 보여진다.

8회차때(Epoch 7)가 일반적으로 가장 좋은 성능이고, 이후로 갈수록 bias에 오버피팅 되는 것 같다.

어제도 비슷비슷하게 마무리 됬던것같은데 성능상으로는 큰 향상이 없던걸까?

 

어제 GPT2모델에서 살펴봤던 71, 87, 88, 89 ,95, 96 에 대해 살펴봤다.

어제 잘못구분했던 71번의 성별 차별에 대해서 NONE으로 정상판단했다.

87번의 웃기다는 내용도 오늘은 NONE으로 잘 나왔고,

88번은 역시 gender보다는 other(성차별은 아니니까.. 인종차별?)에 가까울 것같은데 잘못 분류됬고,

89번은 hate같으니 틀린거 ( 말 자체를 약하게 해서 애매하긴 하지만.. )

95번도 회사에 대해 공격적이니까 Offensive. (차별은 아닌게 맞으니 잘 분류 한듯?)

96번도 hate에서 offensive로 욕설을 쓰니까 offensive로 들어가는걸까?

 

아무든 대체적으로 어제의 결과보다는 좀 더 잘 맞췄다 싶은 정도의 정확도를 보여줬다.

확실히 문장 이해는 GPT보다는 BERT인가보다..

728x90
반응형

'4-1공부 > 캡스톤-딥러닝' 카테고리의 다른 글

010. 딥러닝 6일차  (0) 2023.03.14
008. 딥러닝 공부 4일차 [추가]  (0) 2023.03.11
007. 딥러닝 공부 4일차  (0) 2023.03.11
006. 딥러닝 공부 3일차 2  (0) 2023.03.10
005. 딥러닝 공부 3일차 1  (0) 2023.03.10