이는 데이터 수집, 지침/답안 쌍 생성, 미세 조정 및 결과 평가를 포함하는 엔드 투 엔드 프로젝트입니다.
다음을 사용하여 종속성을 설치하여 시작하십시오.
pip install -r requirements.txt
미세 조정을 위한 데이터를 찾기 위해 Arxiv는 Llama 3 출시일 이후에 출판된 LLM 논문을 스크랩했습니다.
Selenium 스크래핑 코드는 llama3_8b_finetuning/arxiv_scraping/Arxiv_pdfs_download.py
에서 찾을 수 있습니다(이 스크립트를 실행하기 전에 웹 드라이버를 다운로드해야 합니다).
스크래핑 코드는 첫 번째 Arxiv 페이지의 문서를 가져와 llama3_8b_finetuning/data/pdfs
폴더에 다운로드합니다.
이 단계의 코드는 /llama3_8b_finetuning/creating_instruction_dataset.py에서 찾을 수 있습니다.
다운로드한 논문의 텍스트 콘텐츠는 Langchain의 PyPDFLoader를 사용하여 구문 분석되었습니다. 그런 다음 텍스트는 Grok을 통해 Llama 3 70B 모델로 전송되었습니다. Grok은 속도와 저렴한 비용 때문에 선택되었습니다. Llama 3 사용자 라이선스는 Llama LLM 교육/미세 조정에만 사용할 수 있다는 점에 유의해야 합니다. 따라서 Llama 3를 사용하여 다른 모델, 심지어 오픈 소스 모델 또는 비상업적 용도에 대한 지침/답안 쌍을 생성할 수 없습니다.
쌍 생성에 대한 프롬프트는 utils 파일에 있으며 아래에서도 볼 수 있습니다.
'''
You are a highly intelligent and knowledgeable assistant tasked with generating triples of instruction, input, and output from academic papers related to Large Language Models (LLMs). Each triple should consist of:
Instruction: A clear and concise task description that can be performed by an LLM.
Input: A sample input that corresponds to the instruction.
Output: The expected result or answer when the LLM processes the input according to the instruction.
Below are some example triples:
Example 1:
Instruction: Summarize the following abstract.
Input: "In this paper, we present a new approach to training large language models by incorporating a multi-task learning framework. Our method improves the performance on a variety of downstream tasks."
Output: "A new multi-task learning framework improves the performance of large language models on various tasks."
Example 2:
Instruction: Provide a brief explanation of the benefits of using multi-task learning for large language models.
Input: "Multi-task learning allows a model to learn from multiple related tasks simultaneously, which can lead to better generalization and performance improvements across all tasks. This approach leverages shared representations and can reduce overfitting."
Output: "Multi-task learning helps large language models generalize better and improve performance by learning from multiple related tasks simultaneously."
Now, generate similar triples based on the provided text from academic papers related to LLMs:
Source Text
(Provide the text from the academic papers here)
Generated Triples
Triple 1:
Instruction:
Input:
Output:
Triple 2:
Instruction:
Input:
Output:
Triple 3:
Instruction:
Input:
Output:
'''
마지막으로 지침은 llama3_8b_finetuning/data/arxiv_instruction_dataset.json
에 저장됩니다.
이 단계의 코드는 /llama3_8b_finetuning/model_trainer.py
에서 찾을 수 있습니다.
먼저 지침/답변 쌍을 로드하고 이를 테스트 및 학습 데이터 세트로 분할한 다음
올바른 구조로 형식을 지정하십시오.
class DatasetHandler :
def __init__ ( self , data_path ):
self . data_path = data_path
def load_and_split_dataset ( self ):
dataset = load_dataset ( "json" , data_files = self . data_path )
train_test_split = dataset [ 'train' ]. train_test_split ( test_size = 0.2 )
dataset_dict = DatasetDict ({
'train' : train_test_split [ 'train' ],
'test' : train_test_split [ 'test' ]
})
return dataset_dict [ 'train' ], dataset_dict [ 'test' ]
@ staticmethod
def format_instruction ( sample ):
return f"""
Below is an instruction that describes a task, paired with an input that provides further context.
Write a response that appropriately completes the request.
### Instruction:
{ sample [ 'Instruction' ] }
### Input:
{ sample [ 'Input' ] }
### Response:
{ sample [ 'Output' ] }
"""
그런 다음 Hugging Face에서 모델과 토크나이저를 로드하는 클래스를 정의합니다.
class ModelManager :
def __init__ ( self , model_id , use_flash_attention2 , hf_token ):
self . model_id = model_id
self . use_flash_attention2 = use_flash_attention2
self . hf_token = hf_token
self . bnb_config = BitsAndBytesConfig (
load_in_4bit = True ,
bnb_4bit_use_double_quant = True ,
bnb_4bit_quant_type = "nf4" ,
bnb_4bit_compute_dtype = torch . bfloat16 if use_flash_attention2 else torch . float16
)
def load_model_and_tokenizer ( self ):
model = AutoModelForCausalLM . from_pretrained (
self . model_id ,
quantization_config = self . bnb_config ,
use_cache = False ,
device_map = "auto" ,
token = self . hf_token ,
attn_implementation = "flash_attention_2" if self . use_flash_attention2 else "sdpa"
)
model . config . pretraining_tp = 1
tokenizer = AutoTokenizer . from_pretrained (
self . model_id ,
token = self . hf_token
)
tokenizer . pad_token = tokenizer . eos_token
tokenizer . padding_side = "right"
return model , tokenizer
Trainer
클래스와 훈련 구성을 정의합니다.
class Trainer :
def __init__ ( self , model , tokenizer , train_dataset , peft_config , use_flash_attention2 , output_dir ):
self . model = model
self . tokenizer = tokenizer
self . train_dataset = train_dataset
self . peft_config = peft_config
self . args = TrainingArguments (
output_dir = output_dir ,
num_train_epochs = 3 ,
per_device_train_batch_size = 4 ,
gradient_accumulation_steps = 4 ,
gradient_checkpointing = True ,
optim = "paged_adamw_8bit" ,
logging_steps = 10 ,
save_strategy = "epoch" ,
learning_rate = 2e-4 ,
bf16 = use_flash_attention2 ,
fp16 = not use_flash_attention2 ,
tf32 = use_flash_attention2 ,
max_grad_norm = 0.3 ,
warmup_steps = 5 ,
lr_scheduler_type = "linear" ,
disable_tqdm = False ,
report_to = "none"
)
self . model = get_peft_model ( self . model , self . peft_config )
def train_model ( self , format_instruction_func ):
trainer = SFTTrainer (
model = self . model ,
train_dataset = self . train_dataset ,
peft_config = self . peft_config ,
max_seq_length = 2048 ,
tokenizer = self . tokenizer ,
packing = True ,
formatting_func = format_instruction_func ,
args = self . args ,
)
trainer . train ()
return trainer
마지막으로 클래스가 인스턴스화되고 훈련이 시작됩니다.
Llama 모델은 제한적입니다. 즉, Hugging Face는 사용 약관에 동의하고 Meta가 액세스를 승인한 후(거의 즉시) 제공되는 토큰이 필요하다는 것을 의미합니다.
dataset_handler = DatasetHandler ( data_path = utils . Variables . INSTRUCTION_DATASET_JSON_PATH )
train_dataset , test_dataset = dataset_handler . load_and_split_dataset ()
new_test_dataset = []
for dict_ in test_dataset :
dict_ [ 'Output' ] = ''
new_test_dataset . append ( dict_ )
model_manager = ModelManager (
model_id = "meta-llama/Meta-Llama-3-8B" ,
use_flash_attention2 = True ,
hf_token = os . environ [ "HF_TOKEN" ]
)
model , tokenizer = model_manager . load_model_and_tokenizer ()
model_manager . save_model_and_tokenizer ( model , tokenizer , save_directory = utils . Variables . BASE_MODEL_PATH )
model = model_manager . prepare_for_training ( model )
peft_config = LoraConfig (
lora_alpha = 16 ,
lora_dropout = 0.1 ,
r = 64 ,
bias = "none" ,
task_type = "CAUSAL_LM" ,
target_modules = [
"q_proj" , "k_proj" , "v_proj" , "o_proj" , "gate_proj" , "up_proj" , "down_proj" ,
]
)
trainer = Trainer (
model = model ,
tokenizer = tokenizer ,
train_dataset = train_dataset ,
peft_config = peft_config ,
use_flash_attention2 = True ,
output_dir = utils . Variables . FINE_TUNED_MODEL_PATH
)
trained_model = trainer . train_model ( format_instruction_func = dataset_handler . format_instruction )
trained_model . save_model ()
미세 조정 결과를 평가하기 위해 두 텍스트 세트 간의 중복을 비교하여 유사성을 측정하는 ROUGE(Recall-Oriented Understudy for Gisting Evaluation) 점수를 사용했습니다.
구체적으로, 우리는 rouge_scorer 라이브러리를 사용하여 텍스트 간의 1-gram과 2-gram 중첩을 측정하는 ROUGE-1과 ROUGE-2를 계산했습니다.
import pandas as pd
from rouge_score import rouge_scorer
def calculate_rouge_scores ( generated_answers , ground_truth ):
scorer = rouge_scorer . RougeScorer ([ 'rouge1' , 'rouge2' , 'rougeL' ], use_stemmer = True )
total_rouge1 , total_rouge2 , total_rougeL = 0 , 0 , 0
for gen , ref in zip ( generated_answers , ground_truth ):
scores = scorer . score ( gen , ref )
total_rouge1 += scores [ 'rouge1' ]. fmeasure
total_rouge2 += scores [ 'rouge2' ]. fmeasure
total_rougeL += scores [ 'rougeL' ]. fmeasure
average_rouge1 = total_rouge1 / len ( generated_answers )
average_rouge2 = total_rouge2 / len ( generated_answers )
average_rougeL = total_rougeL / len ( generated_answers )
return { 'average_rouge1' : average_rouge1 ,
'average_rouge2' : average_rouge2 ,
'average_rougeL' : average_rougeL }
이 계산을 수행하기 위해 테스트 데이터 세트에서 지침을 가져와 기본 모델과 미세 조정 모델 모두에 전달한 다음 출력을 지침/답변 데이터 세트의 예상 결과와 비교합니다.
평가용 코드는 /llama3_8b_finetuning/model_evaluation.py에서 찾을 수 있습니다.
class ModelHandler :
def __init__ ( self ):
pass
def loading_model ( self , model_chosen = 'fine_tuned_model' ):
if model_chosen == 'fine_tuned_model' :
model_dir = utils . Variables . FINE_TUNED_MODEL_PATH
self . model = AutoPeftModelForCausalLM . from_pretrained (
model_dir ,
low_cpu_mem_usage = True ,
torch_dtype = torch . float16 ,
load_in_4bit = True ,
)
elif model_chosen == 'base_model' :
model_dir = utils . Variables . BASE_MODEL_PATH
self . model = AutoModelForCausalLM . from_pretrained (
model_dir ,
low_cpu_mem_usage = True ,
torch_dtype = torch . float16 ,
load_in_4bit = True ,
)
self . tokenizer = AutoTokenizer . from_pretrained ( model_dir )
def ask_question ( self , instruction , temperature = 0.5 , max_new_tokens = 1000 ):
prompt = format_instruction ( instruction )
input_ids = self . tokenizer ( prompt , return_tensors = "pt" , truncation = True ). input_ids . cuda ()
start_time = time . time ()
with torch . inference_mode ():
outputs = self . model . generate ( input_ids = input_ids , pad_token_id = self . tokenizer . eos_token_id , max_new_tokens = max_new_tokens , do_sample = True , top_p = 0.5 , temperature = temperature )
end_time = time . time ()
total_time = end_time - start_time
output_length = len ( outputs [ 0 ]) - len ( input_ids [ 0 ])
self . output = self . tokenizer . batch_decode ( outputs . detach (). cpu (). numpy (), skip_special_tokens = True )[ 0 ]
return self . output
ROUGE 점수는 다음과 같습니다.
미세 조정된 모델:
{'average_rouge1': 0.39997816307812206, 'average_rouge2': 0.2213826792342886, 'average_rougeL': 0.33508922374837047}
기본 모델:
{'average_rouge1': 0.2524191394349585, 'average_rouge2': 0.13402054342344535, 'average_rougeL': 0.2115590931984475}
따라서, 테스트 데이터 세트에서는 미세 조정 모델의 성능이 기본 모델의 성능보다 훨씬 우수함을 알 수 있습니다.
이 코드를 작성하고 작동시키는 데 꽤 오랜 시간이 걸렸습니다. 좋은 습관이었지만 일상적인 미세 조정 관련 작업의 경우 로컬에서 호스팅되는 Hugging Face AutoTrain(https://github.com/huggingface/autotrain-advanced)을 사용하면 됩니다.