** 이전 model 과 데이터 등의 선언부분은 다음 포스팅을 참조해주길 바란다!
https://mari970.tistory.com/57
HuggingFace PEFT 학습코드
def evaluate(model, eval_loader, device):
model.eval()
eval_loss = 0
with torch.no_grad():
for step, batch in enumerate(tqdm(eval_loader)):
batch = {k: v.to(device) for k, v in batch.items()}
outputs = model(**batch, labels = batch['input_ids'])
loss = outputs.loss
eval_loss += loss.detach().float()
eval_epoch_loss = eval_loss / len(eval_loader)
eval_ppl = torch.exp(eval_epoch_loss)
return eval_epoch_loss, eval_ppl
def train(model, train_loader, eval_loader, optimizer, lr_scheduler, device, args):
model = model.to(device)
train_losses = []
eval_losses = [1e5]
# train 과 test lodd 와 ppl 을 기록하기 위한 파일 만들기
tr_output_file = os.path.join(args.output_dir, 'tr_result.txt')
te_output_file = os.path.join(args.output_dir, 'te_result.txt')
with open(tr_output_file, 'w') as f:
f.write('trloss, trppl\\n')
with open(te_output_file, 'w') as f:
f.write('teloss, teppl\\n')
for epoch in range(args.epochs):
model.train()
train_loss = 0
for step, batch in enumerate(tqdm(train_loader)):
batch = {k: v.to(device) for k, v in batch.items()}
outputs = model(**batch, labels = batch['input_ids'])
loss = outputs.loss
train_loss += loss.detach().float()# .item()?
loss.backward()
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
if step % args.interval == 0:
eval_epoch_loss, eval_ppl = evaluate(model, eval_loader, device)
if args.save_mode == 'all': # 학습과정중 모든 파라미터 저장
model_name = 'model_ep_{ep:d}_loss_{ls:3.3f}.pt'.format(ep=epoch, ls=eval_epoch_loss)
model.save_pretrained(os.path.join(args.output_dir, model_name))
elif args.save_mode == 'best': # 가장 로스가 낮은 파라미터 저장
model_name = 'model.pt'
if eval_epoch_loss < min(eval_losses):
model.save_pretrained(os.path.join(args.output_dir, model_name))
print(' - [Info] The checkpoint file has been updated.')
else:
raise NotImplementedError
eval_losses.append(eval_epoch_loss.item())
with open(te_output_file, 'a') as f:
f.write(f'{eval_epoch_loss.item()}, {eval_ppl}\\n')
train_epoch_loss = train_loss / len(train_loader)
train_ppl = torch.exp(train_epoch_loss)
with open(tr_output_file, 'a') as f:
f.write(f'{train_epoch_loss}, {train_ppl}\\n')
train_losses.append(train_epoch_loss.item())
print(f"{epoch=}: {train_ppl=} {train_epoch_loss=} {eval_ppl=} {eval_epoch_loss=}")
학습 코드를 만드는 과정에서 제일 중요한 것은 학습 결과와 모델 파라미터를 저장하는 방법이라고 생각한다..ㅜㅠ
보통 튜토리얼에는 이런 과정은 별로 자세히 나와있지 않지만 한번 돌렸다가 잘못 코드 짜서 다시돌리고싶지않으면 조심해야 한다!
PeftConfig
peft_config = PrefixTuningConfig(
task_type=TaskType.CAUSAL_LM, # TaskType.SEQ_2_SEQ_LM,
inference_mode=False,
num_virtual_tokens=20
)
TaskType
Tasktype 은 peft 튜닝으로 어떤 태스크를 학습시킬 것인지 세팅하는 것인데 HuggingFace 는 이렇게 task 를 다로 설정할 수 있도록 되어있어 편하다.
필자는 Causal language modeling 을 사용하였다.
num_virtual_tokens
virtual token 개수는 prefix 튜닝에서 사용하는 prefix 토큰의 개수를 말한다. 이 length 가 많아질수록 모델안에서 학습되는 파라미터 개수 비율이 더 커진다.
논문에서는 실험은 default length 는 10 이고, 20 개 이하로는 간단한 task 에 대해 학습(NLU)이 가능하고 복잡한 task (요약과 같은 NLG 등)를 하기 위해서는 100 까지도 올린다고 한다.
** 대충 정리하였기 때문에 위에 대해 자세히 알고 싶다면 prefix tuning 논문을 확인하기 바란다!
model = AutoModelForCausalLM.from_pretrained(args.model_name_or_path)
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
그 후에 위의 코드와 같이 Peft 설정을 model 에 get_peft_model() 함수를 이용해 씌워주기만 하면 된다.
만약 일반 모델과 trainable 파라미터 개수나 학습을 비교하고 싶을 때는 get_peft_model() 함수만 주석처리하고 사용할 수 있기 때문에 편하다!
print_trainable_parameters() 는 peft 학습 중 학습 가능한 파라미터 수와 비율을 확인할 수 있도록 한다.
그래서 밑의 코드와 같이 한꺼번에 peft 와 model 을 설정하는 방법도 있지만 필자는 위와 같이 코드를 사용했다.
model = AutoPeftModelForCausalLM()
model.zero_grad() / optimizer.zero_grad() 차이
model.zero_grad()와 optimizer.zero_grad()의 차이 레퍼런스
이 두개가 서로 뭐가 다른지 몰라서 혼용해서 쓰기도 하고 그랬다가
이번 공부하면서 순간 무서워져서 확인해보았다.
보통은 optimizer.zero_grad() 를 많이 사용하는데, model.zero_grad() 도 혼용해서 사용할 수 있다고 한다.
하지만 차이점은 복잡한 학습을 진행할 때, 원하는 모듈의 파라미터만 초기화할 때 model.zero_grad() 를 사용한다.
보통은 하나의 모델 파라미터 셋에 대해 하나의 optimizer 를 사용하므로 그냥 optimizer.zero_grad() 를 사용하면 된다고 한다!
linear lr scheduler
lr_scheduler = get_linear_schedule_with_warmup(
optimizer = optimizer,
num_warmup_steps = 0, # 8e4,
num_training_steps = (len(train_loader)*args.epochs)
)
linear 하게 감소하는 learning rate 를 만들기 위해서는 다음과 같은 get_linear_schedule_with_warmup() 을 사용하고, warm up setp 을 0 으로 주면 된다.
Random Seed 정하기
parser = argparse.ArgmentParser()
parser.add_argment('-seed', type=int, default=None, help='random seed')
opt = parser.parse_args()
if opt.seed is not None:
random.seed(opt.seed) # python random seed
np.random.seed(opt.seed) # numpy random seed
torch.manual_seed(opt.seed) # torch random seed
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
torch.cuda.manual_seed(opt.seed)
# torch.cuda.manual_seed_all(opt.seed)
학습을 Epoch 10 에서 멈췄다가 다시 진행하고 싶을 때 시드를 고정해두지 않으면 똑같은 조건에서 다시 학습을 진행할 수 없다. 매번 다른 랜덤 시드가 생성되기 때문에 같은 결과를 얻을 수 없기 때문이다.
그를 위해 위와 같은 랜덤 시드 고정을 할 필요가 있다.
torch.cuda.manual_seed(seed) : 현재 gpu의 난수를 생성하기 위한 seed 고정. cuda 를 available 하지 않을때는 그냥 무시된다. (즉 safe 하다)
torch.cuda.manual_seed_all(seed) : 다중 gpu 를 사용해 모델 작업을 하는 경우 모든 gpu 의 시드를 고정하기 위해서 사용한다.
** 위는 둘 중 하나만 사용하면 될 듯.... gpu 마다 다른 시드로 실험 돌리고 있으면 후자는 사용하지 않는 것이 맞는 것 같다.
추가적인 Model 선언 관련 주의사항!
** 이전 포스팅 코드를 조금 따왔다!
model = AutoModelForCausalLM.from_pretrained(args.model_name_or_path)
model.resize_token_embeddings(len(tokenizer))
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
다음과 같이 모델의 임베딩 레이어 사이즈를 늘려주는 resize_token_embeddings() 함수를 사용할 때는
반드시 get_peft_model() 을 사용하기 전에 (peft config를 씌우기 전에) 사용해줘야 한다!
(안그러면 임베딩 사이즈가 늘어나지 않아 나중에 Error 가 생긴다)
'Python 및 Torch 코딩 이모저모' 카테고리의 다른 글
Parallelism (0) | 2023.11.07 |
---|---|
리눅스에 파이썬 새로운 버전 설치하기! (0) | 2023.10.25 |
HuggingFace 실습(PEFT) : 1. Data (0) | 2023.10.15 |
GGML 개념 정리 (1) | 2023.10.09 |
*list 와 **dict : unpacking 방법 (0) | 2023.08.09 |