저번 글에 이어서 이번엔 파인 튜닝을 통해 추론 성능을 상승시켜보도록 하겠습니다. 어떻게 접근할지 고민하다가 누가 Dacon에 "Gemma-2-2B-it Full Finetuning 모델"을 공유 해주어서 이 코드를 먼저 실행 해 보았습니다.
!pip install transformers==4.40.1 accelerate==0.30.0 bitsandbytes==0.43.1 auto-gptq==0.7.1 autoawq==0.2.5 optimum==1.19.1 -qqq
!pip uninstall -y torch torchvision torchaudio
!pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu122
!pip uninstall -y bitsandbytes triton
!pip install --no-cache-dir bitsandbytes triton
!pip install -U transformers
from datetime import datetime, timedelta, timezone
# KST (한국 표준시, UTC+9) 설정
kst = timezone(timedelta(hours=9))
# 현재 시간 (KST)
kst_now = datetime.now(kst)
print(kst_now.strftime("%Y-%m-%d %H:%M:%S KST"))
import pandas as pd
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
def remove_repeated_phrases(text):
phrases = text.split(" ")
seen = set()
result = []
for phrase in phrases:
if phrase not in seen:
result.append(phrase)
seen.add(phrase)
result[0] = result[0].replace("model\n", "")
return " ".join(result)
train = pd.read_csv('./train.csv', encoding = 'utf-8-sig')
test = pd.read_csv('./test.csv', encoding = 'utf-8-sig')
samples = []
for i in range(10):
sample = f"input : {train['input'][i]} \n output : {train['output'][i]}"
samples.append(sample)
model_name = "mindw96/Gemma-2-2B-it-DACON-LLM"
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.bfloat16,
device_map="auto",
trust_remote_code=True,
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = 'right'
restored_reviews = []
for index, row in test.iterrows():
query = row['input']
system_prompt = f"You are a helpful assistant specializing in restoring obfuscated Korean reviews. \
Your task is to transform the given obfuscated Korean review into a clear, correct,\
and natural-sounding Korean review that reflects its original meaning.\
Below are examples of obfuscated Korean reviews and their restored forms:\n\n \
Example, {samples[0]} \n {samples[1]} \n {samples[2]} \n {samples[3]} \n {samples[4]} \
Spacing and word length in the output must be restored to the same as in the input.\
Do not provide any description. Print only in Korean."
messages = [
{"role": "user", "content": '{}\ninput: {}, output:'.format(system_prompt, query)}
]
input_ids = tokenizer.apply_chat_template(messages, return_tensors="pt", return_dict=True).to("cuda")
outputs = model.generate(**input_ids, max_new_tokens=len(query))
generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
result = generated_text[len(messages[0]['content'])+6:].strip()
result = remove_repeated_phrases(result)
restored_reviews.append(result)
print(index, result)
submission = pd.read_csv('./sample_submission.csv', encoding = 'utf-8-sig')
submission['output'] = restored_reviews
submission.to_csv('./gemma_2b_it_submission.csv', index = False, encoding = 'utf-8-sig')
from datetime import datetime, timedelta, timezone
# KST (한국 표준시, UTC+9) 설정
kst = timezone(timedelta(hours=9))
# 현재 시간 (KST)
kst_now = datetime.now(kst)
print(kst_now.strftime("%Y-%m-%d %H:%M:%S KST"))
위 코드로 실행해보았는데
10시간이 소요되었습니다... max_token을 Input 길이만큼 하도록 코드를 작성해서 배치 처리가 힘들었고, 제한된 gpu 메모리 상황에서 처리하다보니 생각보다 긴 시간이 소요되었습니다...
제출해보았는데 이전 샘플코드에 비해 확실히 정확도가 올라간 모습입니다. 그치만 위 소스코드는 남이 파인튜닝 해놓은 모델을 가져다 추론만 한 것이기때문에 제가 직접 모델을 파인튜닝해서 정확도를 상승시켜보고 싶단 생각이 들었습니다. 처음엔 2B 모델을 풀파인튜닝을 했을때 0.75의 수치가 나왔으니, 7~8B 정도의 모델로 풀 파인튜닝을 한다면 쉽게 성능이 나올 것이라고 생각했습니다. 하지만 문제는 너무나도 부족한 GPU 메모리였습니다. 엘리스 클라우드가 저렴하다고해도 고스펙 혹은 장시간 사용하기에는 금전적 부담이 됐습니다. (제가 코드를 잘못 작성한건지 Epoch가 1일경우에도 몇십시간 걸려서 이건 아니다 싶었습니다...) 결국 모델의 크기를 낮추고 양자화를 해서 적은 리소스로 파인튜닝을 진행하였습니다. 대신 기존 train 데이터를 증강시켜서 더 다양한 패턴을 학습시키자는 생각이 들어서 데이터를 증강하는 방법을 선택했습니다.
import pandas as pd
from konoise import NoiseGenerator
# train.csv 파일 불러오기
train_df = pd.read_csv("train.csv")
# NoiseGenerator 객체 초기화 (konoise)
generator = NoiseGenerator()
# 증강된 데이터 저장할 리스트
augmented_data = []
# 각 데이터에 대해 증강
for idx, row in train_df.iterrows():
input_text = row['input']
output_text = row['output']
# input 텍스트에 대해 노이즈 추가 (난독화된 텍스트 생성)
augmented_input_list = generator.generate(input_text, methods='disattach-letters', prob=1.0)
# 리스트에서 첫 번째 항목을 가져와서 슬라이싱
augmented_input = augmented_input_list[0][0]
# output 텍스트는 그대로 교정된 상태로 유지
augmented_output = output_text.strip() # 교정된 텍스트
# 증강된 데이터를 리스트에 저장
augmented_data.append([augmented_input, augmented_output])
# 증강된 데이터를 새로운 DataFrame에 저장
augmented_df = pd.DataFrame(augmented_data, columns=['input', 'output'])
# 증강된 데이터만 저장한 CSV 파일
augmented_df.to_csv("augmented_data.csv", index=False)
# 기존 데이터와 증강된 데이터를 합친 CSV 파일
combined_df = pd.concat([train_df[['input', 'output']], augmented_df], ignore_index=True)
combined_df.to_csv("combined_data.csv", index=False)
konise 라이브러리를 이용해 기존 난독화 텍스트에 노이즈를 추가해 새로운 데이터셋을 만들었고, 기존 데이터셋과 결합한 csv 파일과 증강한 데이터셋만 가진 csv 파일 2개를 만들었습니다.
데이터가 준비되었으니 파인튜닝을 진행하려했는데 파인튜닝에 필요한 여러 라이브러리들이 서로 의존성 충돌이 많이 일어나서 위와 같은 에러들이 너무 많이 발생하였습니다. 물론 버전만 잘 맞춰줬다면 실행에 큰 문제는 없었겠지만, 경험도 부족하고 시간도 부족하였기에 다른 사람들에게 조언을 구했습니다. 조언을 통해 제가 선택한 방법은 "unsloth"를 사용하는 것이었습니다. unsloth에 대한 자세한 소개는 다른 글에서 추가적으로 남기고 여기서는 "단일 gpu 환경에서 적은 리소스로도 파인튜닝을 쉽게할 수 있도록 도와주는 편리한 라이브러리다. "정도만 이해하고 넘어가시면 되겠습니다. 보일러플레이트 코드도 제공해주어서 자신의 task에 맞게 편하게 커스터마이징 할 수 있었습니다.
unsloth에서 지원하는 llm 모델중 선택해서 사용하면 되는구조였는데 저는 llama 3B 모델을 선택했습니다.
빠르게 프로토타입을 만들어보고자 Epoch를 1로 해서 학습시켜보았습니다. 몇십시간이 예상되던때와 다르게 2시간도 안돼서 학습이 완료됐습니다. 이어서 추론도 진행해보았습니다.
제가 간과한 사실이 있었습니다... 초반 추론과정에선 문제가 없었는데 중간부터 input 데이터중에 모델의 최대 시퀀스 길이를 넘어가는 데이터가 있었던겁니다... 별 생각 없이 예제코드로나온 2048을 값으로 주면 충분할 것이라고 생각했는데 에러가 발생했고 input 데이터중 제일 길이가 큰 값이 몇인지 확인해보았습니다.
import pandas as pd
# test.csv 파일을 불러옵니다.
test_df = pd.read_csv("test.csv")
max_len = 0
max_index = None
# 각 input 텍스트의 토큰 길이를 계산 (특수 토큰 포함 여부는 필요에 따라 조정)
for i, text in enumerate(test_df['input']):
tokens = tokenizer.encode(text, add_special_tokens=True)
if len(tokens) > max_len:
max_len = len(tokens)
max_index = i
print("최대 토큰 길이:", max_len)
print("해당 인덱스:", max_index)
최대 토큰길이가 2439로 나왔습니다... 추론에 사용할 프롬프트도 생각해서 여유롭게 최대 시퀀스 값을 조정해야겠다는 생각이 들었습니다. 그래도 값을 4096으로 바꾸고 이참에 Epoch 수도 증가시켜서 테스트 해보기로했습니다.
3시간이라는 비교적 짧은 시간이 소요된 모습입니다.
사용된 메모리도 많지 않습니다. 추론도 약 3시간 정도 소요되었습니다.
제출결과 Gemma 2B 풀파인튜닝 모델보다 더 좋은 성적을 받은 모습입니다! 대회를 늦게 접해 더 다양한 방법을 시도하며 최적화 해보지 못한게 아쉬웠지만 LLM Fine-Tuning을 경험해본 뜻깊은 시간이었습니다.
참고자료
https://dacon.io/competitions/official/236446/codeshare/12157?page=1&dtype=recent
https://huggingface.co/mindw96/Gemma-2-2B-it-DACON-LLM
https://github.com/wisenut-research/konoise
'Fine-Tuning' 카테고리의 다른 글
[Fine-Tuning] LLM 파인튜닝 솔루션 - Unsloth (0) | 2025.02.26 |
---|---|
[Fine-Tuning] LLM fine-tuning (/w Elice Cloud) (1) (0) | 2025.02.19 |