주말동안 한 거 정리
주말 동안 데이터 증강(SR, RI, Adverb Addition, back translation 등)을 해놓고 잘 합쳐서 불용어 처리까지 해봤는데도 여전히 성능이 좋게 나오지 않았다.
SR 데이터 성능
RI 데이터 성능
Adverb Addition 데이터 성능
이렇게 진행을 하다가 solar mini로 데이터 증강을 시도한 결과는 유의미했다! 실제로 눈으로 solar mini로 증강된 데이터를 보면 그렇게 데이터 증강이 잘 된 것 같진 않은데 성능은 좋게 나와서 신기했다. 아마 LLM을 이용해서 나온 데이터다 보니 LLM 특유의 번역체 말투가 기존 원본 데이터셋과 문장 구조가 유사하거나 비슷한 형태의 데이터로 증강되었기 때문에 '정답 요약'을 더 쉽게 생성해 낼 수 있던 거 아닐까 생각해 봤다.
중간 멘토링
중간에 해당 경진대회 내용으로 멘토링을 받을 수 있어서 받았다.
이 멘토링에서 나온 얘기를 대략적으로 정리해 보자면 다음과 같다.
- 데이터 증강 > 모델 선정 > 하이퍼파라미터 순으로 중요한 경진대회다. (데이터 증강이 최우선)
- 데이터 증강에서 EDA는 별로다. 한국어에는 잘 통하지 않는다.
- 데이터 증강에서 중요한 건 원본과도 스타일이 맞아야 하고 데이터가 클린 해야 한다.
- KeT5, PKO-T5 같은 모델도 있고 더 좋은 모델도 있으니 더 찾아보라
- LLM도 써봐라. 근데 프롬프트 엔지니어링은 크게 효과 없을 것이다. 오픈소스 LLM 모델을 튜닝해 보는 방향도 괜찮다.
- https://github.com/unslothai/unsloth 튜닝할 때 이 라이브러리 추천
- https://huggingface.co/LGAI-EXAONE 이 LLM 모델도 추천
- 기존에 로컬 점수와 서버 제출 점수가 다른 이유는 아마 서버에서 현태소 분석기를 도입해서 rouge를 다르게 계산했을 확률이 높다.
- mecab, kkma
- batch size는 속도에는 영향을 줄 수 있어도 결과에는 큰 영향을 없을 것이다. batch를 늘리면 lr도 같이 올려야 한다.
- 생성 모델에서는 모델 앙상블은 어려울 거다.
- LLM을 쓸 때 결국 어느 모델을 쓰느냐가 제일 중요할 것 같다.
- bt 할 때 구글 말고 다른 걸 사용해도 좋다
모델 변경
위 멘토링 내용을 기반으로
- KETI-AIR/ke-t5-small
- paust/pko-t5-small
이렇게 사용해 봤는데 pko-t5를 사용했을 때 성능은 49.9985로 약간의 개선이 있었다!!
이전에 모델 좋은 게 최고이었던 경험이 있어서 paust/pko-t5-base로 올리고 진행하니 최고성능이 갱신되었다!
이 외에도 paust에는 다양한 모델이 있었다.
https://huggingface.co/paust
- pko-flan-t5-large: pko-t5-large 모델을 기반으로 다양한 태스크에 대해 인스트럭션 파인튜닝을 수행한 모델이다. 이는 모델이 주어진 지시에 따라 다양한 작업을 수행할 수 있도록 설계되었다.
- pko-chat-t5-large: pko-flan-t5-large를 기반으로 KoAlpaca와 Evolve-Instruct에서 제공하는 데이터셋을 활용하여 학습한 모델이다. 이 모델은 사용자와의 대화에서 질문에 답하거나 명령에 응답하는 비서 역할을 수행하도록 설계되었다.
- pko-t5-base-finetuned-korquad: pko-t5-base 모델을 한국어 기계 독해 데이터셋인 KorQuAD에 맞게 파인튜닝한 모델이다. 이는 한국어 질문 응답 작업에 특화되어 있다.
이 중에서 base보다 더 큰 pko-flan-t5-large 모델을 시도해보고 싶어서 이것저것 해봤는데 계속 메모리 이슈가 났다.
그래서 병렬로 진행할 수 있지 않을까,, 했는데 사실 우리는 gpu가 단일 gpu로 정해져 있어서 병렬은 진행 못했다..
그래서 그냥 단일 GPU에서 메모리 최적화를 통해 모델을 학습하는 방향으로 진행했다. (그리고 단일 GPU 기반 장시간 학습 작업에는. py 파일이 안정적이라고 해서 이제서아 파이썬 파일로 수정했다.)
gradient_checkpointing와 gradient_accumulation_steps를 활용해서 메모리 사용량을 최적화했다.
- GPU 하나에 모델 전체를 로드해서 학습하고 GPU 메모리 사용량을 줄이기 위해 gradient_checkpointing으로 계산 그래프를 부분적으로 저장하고 필요할 때만 다시 계산한다.
- gradient_accumulation_steps는 배치 크기를 줄이기 위해 여러 스텝의 그래디언트를 누적해 효과적으로 더 큰 배치 크기를 흉내 낸다.
import os
import torch
import pandas as pd
from transformers import T5ForConditionalGeneration, T5TokenizerFast, Seq2SeqTrainer, Seq2SeqTrainingArguments
from tqdm import tqdm
import evaluate
# CUDA 메모리 최적화 설정
os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "max_split_size_mb:128"
# ROUGE 메트릭 로드
rouge = evaluate.load("rouge")
best_rouge_avg = 0 # 최고 평균 ROUGE 점수
best_model_dir = "./large/best_model" # 최고 성능 모델 저장 디렉토리
# 데이터 전처리 함수
def preprocess_data(data, tokenizer):
inputs = ["summarize: " + text for text in data["dialogue"]]
targets = data["summary"]
model_inputs = tokenizer(
inputs,
max_length=256,
truncation=True,
padding="max_length",
return_tensors="pt"
)
labels = tokenizer(
targets.tolist(),
max_length=64,
truncation=True,
padding="max_length",
return_tensors="pt"
)
model_inputs["labels"] = labels["input_ids"]
return model_inputs
# 데이터셋 클래스 정의
class SummaryDataset(torch.utils.data.Dataset):
def __init__(self, encodings):
self.encodings = encodings
def __len__(self):
return len(self.encodings["input_ids"])
def __getitem__(self, idx):
return {key: val[idx] for key, val in self.encodings.items()}
# ROUGE 계산 및 모델 저장 함수
def compute_and_save_metrics(predictions, labels, tokenizer, model):
global best_rouge_avg
decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True)
decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)
# ROUGE 계산
result = rouge.compute(predictions=decoded_preds, references=decoded_labels, use_stemmer=True)
# 평균 ROUGE 계산
result["rouge_avg"] = (result["rouge1"] + result["rouge2"] + result["rougeL"]) / 3
print(f"ROUGE-1: {result['rouge1']:.4f}")
print(f"ROUGE-2: {result['rouge2']:.4f}")
print(f"ROUGE-L: {result['rougeL']:.4f}")
print(f"Average ROUGE: {result['rouge_avg']:.4f}")
# 최고 성능 모델 저장
if result["rouge_avg"] > best_rouge_avg:
print("New best model found!")
best_rouge_avg = result["rouge_avg"]
# 디렉토리 생성 및 모델 저장
os.makedirs(best_model_dir, exist_ok=True)
model.save_pretrained(best_model_dir)
tokenizer.save_pretrained(best_model_dir)
print(f"Best model saved to {best_model_dir}")
# 테스트 데이터 예측 및 저장 함수
def generate_and_save_predictions(test_file_path, model_dir, output_file_path):
# 모델 및 토크나이저 로드
tokenizer = T5TokenizerFast.from_pretrained(model_dir)
model = T5ForConditionalGeneration.from_pretrained(model_dir).to("cuda")
# 테스트 데이터 로드
test_data = pd.read_csv(test_file_path)
# 배치별로 요약 생성
def generate_summary_batch(dialogues, batch_size=16):
summaries = []
for i in range(0, len(dialogues), batch_size):
batch = dialogues[i:i+batch_size]
input_texts = ["summarize: " + dialogue for dialogue in batch]
inputs = tokenizer(
input_texts,
return_tensors="pt",
padding=True,
truncation=True,
max_length=512
).to("cuda")
outputs = model.generate(
**inputs,
max_length=128,
num_beams=4,
early_stopping=True
)
batch_summaries = tokenizer.batch_decode(outputs, skip_special_tokens=True)
summaries.extend(batch_summaries)
return summaries
# 예측 요약 생성
print("Generating summaries for test data...")
test_data["summary"] = generate_summary_batch(test_data["dialogue"])
# 결과 저장
os.makedirs(os.path.dirname(output_file_path), exist_ok=True)
test_data[["fname", "summary"]].to_csv(output_file_path, index=False)
print(f"Predictions saved to {output_file_path}")
# 메인 학습 및 검증 함수
def main():
# 학습 및 검증 데이터 로드
train_data = pd.read_csv("../data/train_backtrans_double.csv")
valid_data = pd.read_csv("../data/dev.csv")
# 토크나이저 및 모델 로드
tokenizer = T5TokenizerFast.from_pretrained("paust/pko-flan-t5-large")
model = T5ForConditionalGeneration.from_pretrained("paust/pko-flan-t5-large").to("cuda")
# Gradient Checkpointing 활성화
model.gradient_checkpointing_enable()
# 데이터 전처리
train_encodings = preprocess_data(train_data, tokenizer)
valid_encodings = preprocess_data(valid_data, tokenizer)
# 데이터셋 생성
train_dataset = SummaryDataset(train_encodings)
valid_dataset = SummaryDataset(valid_encodings)
# TrainingArguments 정의
training_args = Seq2SeqTrainingArguments(
output_dir="./results",
per_device_train_batch_size=32,
gradient_accumulation_steps=16,
num_train_epochs=4,
learning_rate=1e-4,
fp16=True,
logging_dir="./logs",
predict_with_generate=True,
generation_max_length=32,
generation_num_beams=4,
report_to=[]
)
# Trainer 정의
trainer = Seq2SeqTrainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=valid_dataset,
tokenizer=tokenizer
)
# 학습 진행
for epoch in range(training_args.num_train_epochs):
print(f"Starting Epoch {epoch + 1}/{training_args.num_train_epochs}")
trainer.train()
# 검증 후 메트릭 계산 및 모델 저장
eval_preds = trainer.predict(valid_dataset)
compute_and_save_metrics(eval_preds.predictions, eval_preds.label_ids, tokenizer, model)
# 학습 완료 후 테스트 데이터 예측 및 저장
generate_and_save_predictions("../data/test.csv", best_model_dir, "./large/predict/output.csv")
if __name__ == "__main__":
main()
이렇게 진행했는데 사실 성능이 그렇게 좋지는 않았다,,,,,,,,,,,, (그냥 일단 large로 올려보고 시도할걸 ㅠㅠㅠㅠ)
결국 마무리는 위에서 갱신된 50점으로 경진대회가 마무리되었다!!!!!!
최종 등수
public 점수로 50.3062를 달성해서 2위를 했다.
그리고 private 점수는 47.8024로 동일하게 2등을 유지했다.
4위부터 6위까지는 public 등수와 private 등수가 섞였는데 1,2,3 등은 유지를 했다. 다행이라고 생각했다... 휴우....
최종적으로 private dataset에서 최고 점수를 기록한 모델을 solar mini로 데이터를 증간한 모델이었다. 아무래도 private dataset에서도 더 잘 동작한 건 이전에 유추한 가설처럼 똑같이 원본 데이터도 번역체이다 보니 solar mini로 데이터를 증간한 게 똑같이 LLM 특유의 번역체로 증간된 게 크게 영향을 미친것으로 생각된다.
자체 평가 및 회고
학습목표 달성을 위한 노력
- 이번 경진대회 때는 데이터 증강이 목표라 NLP 비정형 데이터 증강을 열심히 해봤다. 확실히 비정형 데이터다 보니 데이터 증강하는 방법이 기존과는 달라서 어려웠다.
- 다양한 모델을 사용하고 모델의 크기를 점차 늘려가면서 성능이 좋아지는 걸 경험했다.
전과 비교하여 새롭게 시도한 점
- 모델을 큰 걸 사용하고 싶어서 단일 gpu에서 어떻게 하면 메모리 효율을 높일지 알아보는 좋은 기회였다고 생각한다.
- 데이터 증강
마주한 한계와 아쉬운 점
- 모델 레이어 freezing을 다른 팀은 시도해 봤는데 우린 시도하지 않았던 점
- 다양한 LLM을 사용하지 않고 전이학습으로만 시도했던 점. (물론 이번 경진대회에서 LLM을 이용해 프롬프트 엔지니어링을 하는 것은 경진 대회 취지에 맞지 않다고 생각해서 더 딥하게 파지 않았던 것도 있다...)
- 다른 팀이 하는 걸 봤을 때 우리 팀은 생성형 모델 앙상블은 어떻게 해야 할까 하고 시도하지 못한 점이 있는데 다른 팀은 동일한 모델을 사용해서 각 인덱스에 나온 단어 토큰의 확률을 계산해서 가장 높은 확률이 나온 단어를 선택하는 방법으로 앙상블을 진행했다. 동일 모델을 사용했기 때문에 output 길이가 다른 이슈는 생기지 않았다고 한다.
- 만약 LLM을 이번 경진대회에서 사용한다고 가정하면 LLM을 사용해서 결과 후처리를 진행해 보는 것도 나쁘지 않을 거 같다. 모델을 학습시켜서 나온 결과를 더 정답 데이터와 유사하게 형태를 수정하거나 바꿔주는 처리도 해보면 좋았을 것 같다.
- LLM으로 데이터증강을 시도할 때 우리에게 주어진 기본 데이터셋이 영어를 한글로 번역한 건데 그 형태랑 LLM이 생성한 거랑 형태가 다를 수 있어서 few shot learning을 사용했다면 더 비슷한 데이터셋이 만들어지지 않았을까 하는 아쉬움이 있다.
한계/교훈을 바탕으로 다음 경진대회에서 시도해보고 싶은 점
- 메모리 이슈로 다른 팀은 LoRA/QLoRA를 사용했는데 우리 팀은 이걸 알지 못해서 더 다양한 방법으로 메모리 효율을 올리지 못한 게 아쉽다. LoRA를 사용하면 기존 풀파인 튜닝할 때보다 메모리를 95% 절약할수있다는데 적용해보지 못해서 아쉬웠다.
- 다음번엔 Optuna로 하이퍼파라미터 튜닝할때 기존의 하이퍼파라미터 탐색 결과를 재사용하거나, 중간 학습 결과를 이어받아 튜닝을 지속하는 방법으로 시도해보고 싶다.
- 작은 학습데이터셋에서 잘 학습하기 위해서는 Embedding Initialization을 잘해야 하는데 다음 경진대회부터는 이걸 적용해 봐야겠다.
회고
- 확실히 데이터 증강이 모델 성능에 큰 영향을 미친걸 직접 확인했던 귀한 시간인 것 같다.
- 예전에는 데이터도 작고 모델도 작아서 하이퍼파라미터튜닝이 의미가 있었는데 이제 모델이 커지고 학습데이터도 커지고 학습시간이 길어지니 반복학습하면서 하이퍼파라미터 튜닝을 하면서 열심히 바꾸는 게 큰 효용은 없는 거 같다. 요즘 LLM들은 하이퍼파라미터를 변경한다고 해서 성능이 극단적으로 바꾸는 것도 아니고 성능이 크게 바뀌는 건 적어서 요즘 AutoML 쪽은 좀 안 하는 추세인 것 같다.
'Upstage AI Lab 4기' 카테고리의 다른 글
Dialogue Summarization (일상 대화 요약) 경진대회 1주차 (1) | 2024.11.28 |
---|---|
[CV 경진대회] 최종 제출, 자체 평가 및 회고 (1) | 2024.11.08 |
[CV 경진대회] offline 데이터 증강 그리고 하이퍼파라미터 수정 (7) | 2024.11.07 |
[CV 경진대회] TTA 실패기,,, (0) | 2024.11.04 |
[CV 경진대회] K-fold 적용 (0) | 2024.11.04 |