import argparse
import pandas as pd
from tqdm.auto import tqdm
import transformers
import torch
import torchmetrics
import pytorch_lightning as pl # Lightning을 사용
class Dataset(torch.utils.data.Dataset):
def __init__(self,inputs,target=[]):
self.inputs = inputs
self.targets = targets
# 학습 및 추론 과정에서 데이터를 1개씩 꺼내오는 곳
def __getitem__(self,idx):
# 정답이 있다면 else문을, 없다면 if문 수행
if(self.targets) == 0:
return torch.tensor(self.inputs[idx])
else:
return torch.tensor(self.inputs[idx]), torch.tensor(self.targets[idx])
# 입력하는 개수만큼 데이터를 사용합니다.
def __len__(self):
return len(self.inputs)
class Dataloader(pl.LightningDataModule):
def __init__(self,model_name,batch_size,shuffle,train_path,dev_path,test_path,predict_path):
super().__init__()
self.model_name = model_name
self.batch_size = batch_size
self.shuffle = shuffle
self.train_path = train_path
self.dev_path = dev_path
self.test_path = test_path
self.predict_path = predict_path
self.train_dataset = None
self.val_dataset = None
self.test_dataset = None
self.predict_dataset = None
# HuggingFace에서 pretrained된 Tokenizer를 들고옴
# AutoTokenizer를 사용함으로 model_name을 넣어주면 HuggingFace에 저장된 모델을 자동으로 불러옴
self.tokenizer = transformers.AutoTokenizer.from_pretrained(model_name, max_length = (160)
self.target_columns = ['label']
self.delete_columns = ['id']
self.text_columns = ['sentence_1', 'sentence_2']
def tokenizing(self, dataframe):
data = []
# tqdm을 통해 for문 진행상황을 Progress Bar로 볼 수 있게 함
# iterrows를 통해 행 순환 반복 접근
# desc를 사용하여 tqdm 설명을 추가할 수 있습니다.
for idx, item in tqdm(dataframe.iterrows(), desc='tokenizing', total=len(dataframe)):
# 두 입력 문장을 [SEP] 토큰으로 이어붙여서 전처리합니다.
text = '[SEP]'.join([item[text_column] for text_column in self.text_column)
# add_special_tokens 옵션을 통해 [CLS], [SEP] 등을 자동으로 붙여준다.
outputs = self.tokenizer(text, add_special_tokens=True, padding='max_length', truncation=True)
data.append(outputs['input_ids'])
return data
def preprocessing(self,data):
data = data.drop(columns=self.delete_columns)
# 타겟 데이터가 없으면 빈 배열을 리턴
try:
targets = data[self.target_columns].values.tolist()
except:
targets = []
# 텍스트 데이터를 전처리
inputs = self.tokenizing(data)
return inputs,targets
# trainer.fit 및 trainer.test시 사용될 setup을 설정
def setup(self, stage='fit'):
if stage == 'fit':
# 학습 데이터와 검증 데이터셋을 호출합니다
train_data = pd.read_csv(self.train_path)
val_data = pd.read_csv(self.dev_path)
# 학습데이터 준비
train_inputs, train_targets = self.preprocessing(train_data)
# 검증데이터 준비
val_inputs, val_targets = self.preprocessing(val_data)
# train 데이터만 shuffle을 적용해줍니다, 필요하다면 val, test 데이터에도 shuffle을 적용할 수 있습니다
self.train_dataset = Dataset(train_inputs, train_targets)
self.val_dataset = Dataset(val_inputs, val_targets)
else:
# 평가데이터 준비
test_data = pd.read_csv(self.test_path)
test_inputs, test_targets = self.preprocessing(test_data)
self.test_dataset = Dataset(test_inputs, test_targets)
predict_data = pd.read_csv(self.predict_path)
predict_inputs, predict_targets = self.preprocessing(predict_data)
self.predict_dataset = Dataset(predict_inputs, [])
# Pytorch Lightning에서는 Dataloader을 반환해주는 방식으로 사용
def train_dataloader(self):
return torch.utils.data.DataLoader(self.train_dataset, batch_size=self.batch_size, shuffle=args.data.shuffle)
def val_dataloader(self):
return torch.utils.data.DataLoader(self.val_dataset, batch_size=self.batch_size)
def test_dataloader(self):
return torch.utils.data.DataLoader(self.test_dataset, batch_size=self.batch_size)
def predict_dataloader(self):
return torch.utils.data.DataLoader(self.predict_dataset, batch_size=self.batch_size)
class Model(pl.LightningModule):
def __init__(self, model_name, lr):
super().__init__()
self.save_hyperparameters()
self.model_name = model_name
self.lr = lr
# 사용할 모델을 호출합니다.
# N21 Task의 일종인 STS이기에 AutoModelForSequenceClassification을 사용한다.
# 이 때 logit 한개를 output하면 되기에 num_labels = 1로 설정한다.
self.plm = transformers.AutoModelForSequenceClassification.from_pretrained(
pretrained_model_name_or_path=model_name, num_labels=1)
# Loss 계산을 위해 사용될 L1Loss를 호출합니다.
self.loss_func = torch.nn.L1loss()
def forward(self,x):
# model에 input을 하고 나온 데이터 중 'logits'를 return
x = self.plm(x)['logits']
return x
def training_step(self,batch,batch_idx):
x,y = batch
logits = self(x) # foward
loss = self.loss_func(logits, y.float())
self.log("train_loss",loss) # loss를 train_loss로 로깅
return loss
def validation_step(self,batch,batch_idx):
x,y = batch
logits = self(x)
loss = self.loss_func(logits, y.float())
self.log("val_loss",loss)
self.log("val_pearson", torchmetrics.functional.pearson_corecoef(logits.squeeze(),y.squeeze())
return loss
def test_step(self,batch,batch_idx):
x,y = batch
logits = self(x)
self.log("test_pearson", torchmetrics.functional.pearson_corecoef(logits.squeeze(),y.squeeze())
def predict_step(self,batch,batch_idx):
x = batch
logits = self(x)
return logits.squeeze()
# 해당 Function을 통해 optimzer 설정 가능
# weight decay, LR Scheduler 등
def configure_optimizers(self):
optimizer = torch.optim.AdamW(self.parameters(),lr=self.lr)
return optimizer
if __name__ =='__main__':
# 하이퍼 파라미터 등 각종 설정값을 입력받습니다
# 터미널 실행 예시 : python3 run.py --batch_size=64 ...
# 실행 시 '--batch_size=64' 같은 인자를 입력하지 않으면 default 값이 기본으로 실행됩니다
parser = argparse.ArgumentParser()
parser.add_argument('--model_name', default='klue/roberta-large', type=str)
parser.add_argument('--batch_size', default=2, type=int)
parser.add_argument('--max_epoch', default=10, type=int)
parser.add_argument('--shuffle', default=True)
parser.add_argument('--learning_rate', default=1e-5, type=float)
parser.add_argument('--train_path', default='../data/train.csv')
parser.add_argument('--dev_path', default='../data/dev.csv')
parser.add_argument('--test_path', default='../data/dev.csv')
parser.add_argument('--predict_path', default='../data/test.csv')
args = parser.parse_args()
# dataloader와 model을 생성합니다.
# dataloader와 model을 생성합니다.
dataloader = Dataloader(args.model_name, args.batch_size, args.shuffle, args.train_path, args.dev_path,
args.test_path, args.predict_path)
model = Model(args.model_name, args.learning_rate)
# gpu가 없으면 'gpus=0'을, gpu가 여러개면 'gpus=4'처럼 사용하실 gpu의 개수를 입력해주세요
trainer = pl.Trainer(gpus=1, max_epochs=args.max_epoch, log_every_n_steps=1)
# Train part
trainer.fit(model=model, datamodule=dataloader)
trainer.test(model=model, datamodule=dataloader)
# 학습이 완료된 모델을 저장합니다.
torch.save(model, 'model.pt')
모델명 | Test_Pearson | Setting | 사용 여부 |
---|---|---|---|
klue/roberta-small | 0.8551 | X | X |
klue/roberta-base | 0.8916 | X | X |
klue/roberta-large | 0.9019 | B_S : 8, LR : 1e-6 | O |
jhgan/ko-sroberta-multitask | 0.8477 | X | X |
snunlp/KR-SBERT-V40K-klueNLI-augSTS | 0.8628 | X | X |
sentence-transformers/paraphrase-multilingual-mpnet-base-v2 | 0.82 | X | X |
beomi/KcELECTRA-base | 0.9113 | X | O |
snunlp/KR-ELECTRA-discriminator | 0.9267 | X | O (SOTA) |
from pytorch_lightning.callbacks import ModelCheckpoint
from pytorch_lightning.callbacks.early_stopping import EarlyStopping
,,,
checkpoint_callback = ModelCheckpoint(monitor='val_pearson',
save_top_k=1,
save_last=True,
save_weights_only=False,
verbose=False,
mode='max')
earlystopping = EarlyStopping(monitor='val_pearson', patience=2, mode='max')
...
trainer = pl.Trainer(
gpus=-1,
max_epochs=cfg.train.max_epoch,
log_every_n_steps=cfg.train.logging_step,
logger=wandb_logger, # W&B integration
callbacks = [checkpoint_callback, earlystopping]
)
import wandb
from pytorch_lightning.loggers import WandbLogger
wandb_dict = {
'user' : 'API_KEY'
}
...
parser.add_argument('--wandb_username', default='user')
parser.add_argument('--wandb_project', default='ELECTRA')
parser.add_argument('--wandb_entity', default='sts')
...
sweep_config = {
'method': 'bayes',
'parameters': {
'lr':{
'distribution': 'uniform', # parameter를 설정하는 기준을 선택합니다. uniform은 연속적으로 균등한 값들을 선택합니다.
'min':1e-5, # 최소값을 설정합니다.
'max':5e-5 # 최대값을 설정합니다.
},
'batch_size': {
'values': [16, 32]
},
},
'name' : 'snunlp-KcELECTRA-nof',
'metric':{'name':'val_pearson', 'goal':'maximize'},
'early_terminate' : {'type' : 'hyperband', 'max_iter' : 10, 's' : 2, 'eta': 3},
"entity" : 'sts',
'project' : 'ELECTRA'
}
...
# Sweep 안쓰는 경우
wandb.login(key = wandb_dict[cfg.wandb.wandb_username])
model_name_ch = re.sub('/','_',cfg.model.model_name)
wandb_logger = WandbLogger(
log_model="all",
name=f'{cfg.model.saved_name}_{cfg.train.batch_size}_{cfg.train.learning_rate}_{time_now}',
project=cfg.wandb.wandb_project,
entity=cfg.wandb.wandb_entity
)
...
# 스윕 적용
def sweep_train(config=None):
wandb.init(config=config)
config = wandb.config
dataloader = Dataloader(args.model_name, config.batch_size, args.shuffle, args.train_path, args.dev_path,
args.test_path, args.predict_path)
model = Model(args.model_name, config.lr)
wandb_logger = WandbLogger(
log_model="all",
name=f'{model_name_ch}_{config.batch_size}_{config.lr}_{args.time_now}',
project=args.wandb_project,
entity=args.wandb_entity
)
trainer = pl.Trainer(gpus=1,
max_epochs=10,
log_every_n_steps=5,
logger=wandb_logger,
callbacks = [checkpoint_callback, earlystopping]
)
trainer.fit(model=model, datamodule=dataloader)
trainer.test(model=model, datamodule=dataloader)
output_dir_path = 'output'
if not os.path.exists(output_dir_path):
os.makedirs(output_dir_path)
output_path = os.path.join(output_dir_path,
f'{model_name_ch}_{config.batch_size}_{config.lr}_{args.time_now}_model.pt')
torch.save(model, output_path)
sweep_id = wandb.sweep(
sweep=sweep_config, # config 딕셔너리를 추가합니다.
project=args.wandb_project,# project의 이름을 추가합니다.
)
wandb.agent(
sweep_id=sweep_id, # sweep의 정보를 입력하고
function=sweep_train, # train이라는 모델을 학습하는 코드를
count=7 # 총 5회 실행해봅니다.
)
from xml.dom.minidom import Entity
from omegaconf import OmegaConf
...
class Model(pl.LightningModule):
def __init__(self, config):
super().__init__()
self.save_hyperparameters()
self.model_name = config.model.model_name
self.lr = config.train.learning_rate
...
if __name__ == '__main__':
# 하이퍼 파라미터 등 각종 설정값을 입력받습니다
# 터미널 실행 예시 : python3 run.py --batch_size=64 ...
# 실행 시 '--batch_size=64' 같은 인자를 입력하지 않으면 default 값이 기본으로 실행됩니다
parser = argparse.ArgumentParser()
parser.add_argument('--config',type=str,default='base_config')
args, _ = parser.parse_known_args()
cfg = OmegaConf.load(f'./config/{args.config}.yaml')
...
model = Model(cfg)
trainer = pl.Trainer(
gpus=-1,
max_epochs=cfg.train.max_epoch,
log_every_n_steps=cfg.train.logging_step,
logger=wandb_logger, # W&B integration
callbacks = [checkpoint_callback, earlystopping]
)
#!/bin/bash
CONFIGS=("config_1" "config_2")
config_length=${#CONFIGS[@]}
for (( i=0; i<${config_length}; i++ ));
do
echo ${CONFIGS[$i]}
python3 train_y.py \
--config ${CONFIGS[$i]}
done
path:
train_path: ../data/train.csv
dev_path: ../data/dev.csv
test_path: ../data/dev.csv
predict_path: ../data/test.csv
data:
shuffle: True
augmentation: # adea, bt 등등
model:
model_name: beomi/KcELECTRA-base
saved_name: beomi/electra
train:
seed: 21
gpus: 1
batch_size: 32
max_epoch: 30
learning_rate: 3e-5
logging_step: 1
wandb:
wandb_username: user
wandb_project: sts
wandb_entity: sts
- Loss : MSE
- LR : 3e-5
- Optimzer : AdamW
- Data : Label Smoothing, Copied Translation, Swap Sentence
- Val_Pearson : 0.9309
- Loss : MSE
- LR : 7e-6
- Optimzer : AdamW
- Data : Label Smoothing, Copied Translation, Swap Sentence
- Val_Pearson : 0.9256
<NLP 5조>
Pearson : 0.9368
등수 : 3등
출처 : Naver BoostCamp