elevne's Study Note
KoBART 분석해보기 (1) 본문
저번 시간에 git clone으로 가져온 KoBART 코드를 분석해보기로 하였다. 아직 torch를 사용해본 적도 없고 모르는 내장 라이브러리도 많아서 많이 찾아보면서 천천히 분석해야할 것 같다.
우선 dataset.py 파일을 들여다보았다.
import argparse
import os
import glob
import torch
import ast
import numpy as np
import pandas as pd
from tqdm import tqdm, trange
from torch.utils.data import Dataset, DataLoader
import pytorch_lightning as pl
from functools import partial
가장 처음 import 한 argparse 라이브러리부터 찾아보았다. Argparse 모듈을 사용하는 이유는, 명령창에서 python 파일을 ./main.py 등과 같은 명령어로 실행시킬 때 인자를 전달해주기 위함이다. Argparse 모듈을 사용하면 명령행의 인자를 파싱할 수 있는 것이다. 기초적인 활용법에 대해서만 알아보자면 아래 명령행에서 -d는 어떤 추가 인자 값을 하나 받는 형태이고, -f 옵션은 더 이상 추가 인자는 필요없는 형태이다.
./run.py -d 1 -f
위에서 파싱할 인자를 add_argument 메서드를 통해 추가해줄 수 있다. 이 때, 추가 옵션을 받는 경우 action="store"를 사용, 추가옵션을 받지 않고 단지 옵션의 유/무만 필요한 경우에는 action="store_true"를 사용한다. 아래와 같이 코드를 작성할 수 있다고 한다.
# run.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-d", "--decimal", dest="decimal", action="store")
# extra value
parser.add_argument("-f", "--fast", dest="fast", action="store_true")
# existence/nonexistence
args = parser.parse_args()
print(args.decimal)
print(args.fast)
그 다음으로는 glob 모듈이 있다. 많은 파일들을 다뤄야 하는 Python 프로그램을 작성할 때는 특정한 패턴, 확장자를 가진 파일들의 경로나 이름이 필요할 때가 있다. 이 때 glob 모듈을 사용할 수 있는데, glob 모듈은 *, ? 와 같은 와일드카드를 지원한다고 한다.
아래 예시를 보면 쉽게 이해할 수 있을 것이다.
# dir폴더의 모든 서브폴더 및 파일 목록
# dir : file1.txt, file2.txt, file101.txt, file102.txt, filea.txt, fileb.txt, file1.jpg, file2.jpg
# dir/subdir : subfile1.txt, subfile2.txt
import glob
# '*'는 임의 길이의 모든 문자열을 의미한다.
>>> output = glob.glob('dir/*.txt')
>>> print(output)
['dir\\file1.txt', 'dir\\file101.txt', 'dir\\file102.txt', 'dir\\file2.txt', 'dir\\filea.txt', 'dir\\fileb.txt']
# '?'는 한자리의 문자를 의미한다.
>>> output = glob.glob('dir/file?.*')
>>> print(output)
['dir\\file1.bmp', 'dir\\file1.txt', 'dir\\file2.bmp', 'dir\\file2.txt', 'dir\\filea.txt', 'dir\\fileb.txt']
# recursive=True로 설정하고 '**'를 사용하면 모든 하위 디렉토리까지 탐색한다.
# 기본값은 False이며, 파일이 너무 많을 경우에 사용하면 과도한 cost가 소모된다고 한다.
>>> output = glob.glob('dir/**', recursive=True)
>>> print(output)
['dir\\', 'dir\\file1.bmp', 'dir\\file1.txt', 'dir\\file101.txt', 'dir\\file102.txt', 'dir\\file2.bmp', 'dir\\file2.txt', 'dir\\filea.txt', 'dir\\fileb.txt', 'dir\\subdir', 'dir\\subdir\\subfile1.txt', 'dir\\subdir\\subfile2.txt']
그 뒤에는 ast 모듈이다. ast는 Abstract Syntax Tree의 약자로 Python 소스코드를 입력인자로 넣으면 syntax를 분석할 수 있는 tree가 나오고 이 tree를 따라가면서 코드를 분석할 수 있게끔 해준다. 입력소스의 단위에는 제약이 없다고 한다. 모듈, 클래스, 함수 등의 단위도 되고 코드 몇 줄을 넣을 수도 있는 것이다. 이 모듈의 자세한 사용법은 이후 KoBART 파일에 직접 사용될 때 자세히 알아보도록 하겠다.
그 외에도 또 PyTorch Lightning이 import 되어 있다. PyTorch Lightning이란 PyTorch 문법을 가지면서 학습 코드를 PyTorch보다 더 효율적으로 작성할 수 있는 파이썬 오픈소스 라이브러리라고 한다. PyTorch를 통해 쉽게 딥러닝 모델을 만들 수 있긴 하지만 CPU, GPU, TPU 간 변경, mixed_precision_training(16bit) 등의 복잡한 조건과 반복되는 코드 (training, validation, testing, inference)들을 좀 더 효율적으로 추상화시키는 것을 목적으로 PyTorch Lightning이 나오게 되었다고 한다. 이에 대한 자세한 사용법 또한 이후 코드를 살펴보면서 알아보겠다.
이제 우선 첫 번째 Class로 선언되어 있는 KoBARTSummaryDataset Class를 분석해보겠다.
class KoBARTSummaryDataset(Dataset):
def __init__(self, file, tokenizer, max_len, ignore_index=-100):
super().__init__()
self.tokenizer = tokenizer
self.max_len = max_len
self.docs = pd.read_csv(file, sep='\t')
self.len = self.docs.shape[0]
self.pad_index = self.tokenizer.pad_token_id
self.ignore_index = ignore_index
def add_padding_data(self, inputs):
if len(inputs) < self.max_len:
pad = np.array([self.pad_index] *(self.max_len - len(inputs)))
inputs = np.concatenate([inputs, pad])
else:
inputs = inputs[:self.max_len]
return inputs
def add_ignored_data(self, inputs):
if len(inputs) < self.max_len:
pad = np.array([self.ignore_index] *(self.max_len - len(inputs)))
inputs = np.concatenate([inputs, pad])
else:
inputs = inputs[:self.max_len]
return inputs
def __getitem__(self, idx):
instance = self.docs.iloc[idx]
input_ids = self.tokenizer.encode(instance['news'])
input_ids = self.add_padding_data(input_ids)
label_ids = self.tokenizer.encode(instance['summary'])
label_ids.append(self.tokenizer.eos_token_id)
dec_input_ids = [self.tokenizer.eos_token_id]
dec_input_ids += label_ids[:-1]
dec_input_ids = self.add_padding_data(dec_input_ids)
label_ids = self.add_ignored_data(label_ids)
return {'input_ids': np.array(input_ids, dtype=np.int_),
'decoder_input_ids': np.array(dec_input_ids, dtype=np.int_),
'labels': np.array(label_ids, dtype=np.int_)}
def __len__(self):
return self.len
우선 위 클래스는 torch.utils.data.Dataset을 상속받는다. 이 클래스는 데이터셋을 나타내는 추상클래스이다. Pytorch로 학습을 시킬 때 데이터셋은 Dataset class에 상속하고 오버라이드를 시켜줘야 한다.
__init__() 함수에서는 tokenizer(이후 transformer 라이브러리에서 tokenizer를 직접 불러올 것으로 생각됨), max_len(최대길이는 따로 다른 파일에서 정해질 예정으로 생각됨), docs(pd.read_csv, sep="\t" 로 tsv 데이터 파일을 불러오는 것), len(데이터 길이), pad_index(패딩 시 적용할 토큰의 인덱스 값)를 지정해주고 있고 ignore_index(ignore 인덱스)는 -100이 Default 값으로 지정되어 있다.
이후 add_padding_data, add_ignore_data 에서는 패딩(ignore) 처리를 동일한 방식으로 해주는 것을 확인할 수 있다. (np.concatenate 함수를 사용한다.)
마지막으로는 __getitem__ 과 __len__ 함수인데, 이는 위에서 언급하였듯이 오버라이드를 시켜줘야 하는 함수들이다. __len__ 은 데이터셋의 크기를 리턴해주고 있으며 __getitem__ 은 학습을 위해 필요한 encoder input id, decoder input id, labels 데이터를 np.array(dtype=int_) 형태로 추출해준다.
그 다음 Class로 KobartSummaryModule이 작성되어 있다. 코드는 아래와 같다.
class KobartSummaryModule(pl.LightningDataModule):
def __init__(self, train_file,
test_file, tok,
max_len=512,
batch_size=8,
num_workers=4):
super().__init__()
self.batch_size = batch_size
self.max_len = max_len
self.train_file_path = train_file
self.test_file_path = test_file
self.tok = tok
self.num_workers = num_workers
@staticmethod
def add_model_specific_args(parent_parser):
parser = argparse.ArgumentParser(
parents=[parent_parser], add_help=False)
parser.add_argument('--num_workers',
type=int,
default=4,
help='num of worker for dataloader')
return parser
# OPTIONAL, called for every GPU/machine (assigning state is OK)
def setup(self, stage):
# split dataset
self.train = KoBARTSummaryDataset(self.train_file_path,
self.tok,
self.max_len)
self.test = KoBARTSummaryDataset(self.test_file_path,
self.tok,
self.max_len)
def train_dataloader(self):
train = DataLoader(self.train,
batch_size=self.batch_size,
num_workers=self.num_workers, shuffle=True)
return train
def val_dataloader(self):
val = DataLoader(self.test,
batch_size=self.batch_size,
num_workers=self.num_workers, shuffle=False)
return val
def test_dataloader(self):
test = DataLoader(self.test,
batch_size=self.batch_size,
num_workers=self.num_workers, shuffle=False)
return test
위 클래스는 pl.LightningDataModule 을 상속받고 있는데 이는 Data Loader, Dataset을 지정해주는 역할을 해준다고 한다. 해당 Data Loader에 Batch size, Num workers, Max_len 등을 지정해주는 것을 확인할 수 있다.
또, 밑의 add_model_specific_args 위에 Decorator 가 달려있는 것을 확인할 수 있다. Python에서는 정적메서드를 지원하는 두 가지 방법이 있는데 하나는 @classmethod, 다른 하나는 @staticmethod를 사용하는 것이다. @classmethod에 대해서는 나중에 알아보겠다. 우선 두 방법의 공통점은 둘 다 인스턴스를 생성하지 않고도 Class의 메서드를 실행할 수 있게끔 해준다는 점이다. 아래 예시를 보면 이해가 될 것이다.
#staticmethod
class hello:
num = 10
@staticmethod
def calc(x):
return x + 10
print(hello.calc(10))
#결과
20
위와 같이 hello Class를 따로 지정해주지 않고도 사용할 수 있게끔 만들 수 있는 것이다.
이 함수에는 argparse 모듈이 사용되었다. 위에서 알아보았던 것처럼 ArgumentParser이 사용되었는데 인자로 parents, add_help가 들어가있다. parents 인자는 ArgumentParser 객체들의 리스트로, 이들의 인자들도 포함되게끔 한다. add_help 인자는 Parser에 -h/--help 옵션을 추가해준다고 한다(Default: True)
그 이후의 setup 함수는 KoBARTSummaryDataset 클래스를 사용하여 데이터셋을 불러오는 것이다.
이후 train_dataloader, val_dataloader, test_dataloader에서는 DataLoader 클래스를 사용한다. Dataset은 데이터셋의 feature을 가져오고 하나의 Sample에 하나의 Label을 지정하는 일을 한 번에 진행한다. Model을 train 시킬 때, 일반적으로 sample들을 Mini-batch로 전달하고 매 Epoch 마다 데이터를 다시 섞어서 overfitting을 방지하고 Python의 Multiprocessing 을 사용하여 데이터 검색 속도를 높인다고 한다. DataLoader은 간단한 API로 이러한 복잡한 과정들을 추상화한 Iterable 객체라고 한다. 아래의 예시처럼 사용할 수 있는 것이다.
from torch.utils.data import DataLoader
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
DataLoader을 통해 데이터셋을 불러온 뒤에는 필요에 따라서 데이터셋을 순회(Iterate)하는 것이 가능하다. 위에서 각 순회는 각각 Batch_size=64 size의 train_feature, train_labels의 묶음을 반환한다. shuffle=True 로 지정하게 되면 모든 배치를 순회한 뒤 데이터가 섞이게 된다. 또 위 실제코드에서 넣어준 num_workers 를 지정해줌으로써 Multi-process data loading을 진행할 수 있게된다.
그래서 지금까지 dataset.py만 분석해보았는데, 전체적으로 파일명과 같이 데이터를 불러오고 학습할 데이터를 준비할 수 있게끔 해주는 과정을 거친다. 다음 시간에는 다른 파일을 분석해보며 이어가도록 하겠다.
출처:
https://tutorials.pytorch.kr/beginner/basics/data_tutorial.html
https://docs.python.org/ko/3/library/argparse.html
https://wikidocs.net/21054
https://visionhong.tistory.com/30
https://wikidocs.net/73785
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=siniphia&logNo=221397012627
https://medium.com/signal9/python-%EC%86%8C%EC%8A%A4-%EC%A0%95%EC%A0%81-%EB%B6%84%EC%84%9D-e56658856fef
'Machine Learning > NLP' 카테고리의 다른 글
Huggingface 공부 - Transformer (1: Attention Mechanism) (0) | 2022.12.11 |
---|---|
KoBART 분석해보기 (2) (0) | 2022.11.26 |
GPT에 대해서~ (0) | 2022.11.21 |
KoBART 전이학습 (Colab) (0) | 2022.11.20 |
BERT - KorQuAD (2) (0) | 2022.11.19 |