대규모 언어모델은 일반적인 대화에서 괜찮은 성능을 보여주지만, 회사들은 이 언어모델을 어떻게 특정한 도메인 비즈니스에 적용할 수 있을지 어려움에 직면해 있습니다.
따라서 이 포스팅은 Together.ai에서 제공하는 글을 바탕으로 멀티턴 대화를 위해 언어모델을 파인튜닝 하는 방법을 전체적으로 다뤄보려고 한다.
Together.ai는 클라우드 기반 학습환경을 기반으로 api를 호출하고 배포하는 형태에 최적화되어 있기 때문에,
저와 같이 로컬로 서버 컴퓨터를 세팅해서 학습시키는 분들은 적합하지 않습니다.
그럼에도 불구하고 이것을 포스팅하는 이유는 멀티턴 대화를 학습시키는 전반적인 과정을 이해하기 위함입니다.
추후에, 로컬 학습에서 사용할 수 있는 huggingface 상에서 멀티턴 대화 기반으로 파인튜닝하는 방법을 다뤄보도록 하겠습니다.
언어모델의 주요 한계
- 도메인 적응: 회사들은 언어모델이 그들의 고유한 데이터 형식과 특정한 유저 상호작용을 이해하게 만드는데 어려움을 겪는다.
- 지식 제한: 기본 모델은 지식 컷오프가 있고, 전문 도메인 지식이나 개인 기업 문서에 대한 접근이 부족할 수 있다.
- 예를 들어, 어떤 LLM(대형 언어 모델)이 2023년 10월까지의 데이터를 학습했다고 가정하면, 그 이후에 발생한 사건이나 업데이트된 정보는 모델이 알지 못합니다. 이 시점을 **지식 컷오프(Knowledge Cutoff)**라고 부릅니다.
- 멀티턴 복잡성: 기본모델은 단일 교환(싱글턴)을 잘 처리하지만, 미세한 다단계 대화에서 맥락과 일관성을 유지하려면, 추가적인 후속 훈련이 필요합니다.
파인튜닝이 중요한 이유
사전 훈련과 달리, 방대한 양의 저품질 일반 데이터를 처리하는 것과 달리,
이미 지침에 맞춰 미세조정된 모델을 미세조정하는 것은 도메인 특정 예제의 훨씬 더 작고 고품질의 레이블이 지정된 데이터 세트를 요구하는 훨씬 집중된 과정입니다.
이 글에서는 멀티턴 대화를 위한 LLM 미세 조정의 전체 프로세스에 대해 논의할 것입니다.
- 멀티턴 대화 데이터셋 준비
- instruction tuning 과정에서의 손실 마스킹
- 대화형 데이터 셋으로 Llama 8B 미세 조정 예제
1. 멀티턴 대화 데이터셋 준비
우리는 모델이 전체 대화를 암기하는 대신 적절한 응답을 생성하도록 학습할 수 있도록
대화의 상호작용적인 특성을 포착할 수 있도록 데이터를 구조화해야 합니다.
데이터 셋 준비의 주요 측면
- 명확한 턴 구분이 있는 대화구조
- 맥락을 설정하기 위한 시스템 메세지
- 일관된 역할 레이블링 (사용자/어시스턴트)
- 일반적인 파인튜닝 프레임워크와 호환되는 JSONL 형식
데이터셋은 JSONL 파일의 각 예제가 'messages' 목록이어야 하는 채팅 형식을 사용하여 준비되어야 하며, 각 메시지는 'role'과 'content'을 가져야 합니다. 'role'은 'system', 'user' 또는 'assistant' 중 하나여야 합니다. 형식에 대한 자세한 내용은 문서에서 확인할 수 있습니다.
대화형 미세 조정을 위해 데이터 파일의 각 줄에는 messages 필드가 포함되어야 하며, 각 메시지에 대해 role과 내용이 content 지정되어야 합니다. 각 샘플은 system 메시지 또는 user 메시지로 시작해야 하며, 그 뒤에 user과 assistant 메시지가 번갈아 가며 이어져야 합니다. Together 클라이언트는 이 패턴을 따르지 않는 데이터셋을 거부합니다.
{
"messages": [
{"role": "system", "content": "You are a helpful AI chatbot."},
{"role": "user", "content": "Hello, how are you?"},
{"role": "assistant", "content": "I'm doing well, thank you! How can I help you?"},
{"role": "user", "content": "Can you explain machine learning?"},
{"role": "assistant", "content": "Machine learning is..."}
]
}
결과적인 대화 데이터셋은 해당 모델에 대해 정의된 경우 모델의 채팅 템플릿으로 자동 포맷되거나, 그렇지 않은 경우 기본 템플릿으로 포맷됩니다. 일반적으로 모든 지침 미세 조정 모델은 고유한 채팅 템플릿을 가지고 있으며, 기본 모델은 이를 가지고 있지 않습니다.
기본적으로 모델은 오직 어시스턴트 메시지만 예측하도록 훈련됩니다. 다른 메시지를 훈련에 포함하려면 --train-on-inputs true를 사용하세요. (together.ai를 사용하는 경우에)
위 형식으로 데이터셋을 준비한 후, 아래와 같이 .jsonl 파일을 Together AI에 업로드할 수 있습니다.
데이터셋을 업로드하기 전에 파일이 올바르게 형식화되고 준비되었는지 확인할 것입니다.
train_file_resp = client.files.upload("dataset.jsonl", check=True)
print(train_file_resp)
2. 지침 파인튜닝(instruction fine-tuning)에서 손실 마스킹
대화 작업을 위한 LLM을 미세 조정할 때 중요한 고려 사항은 훈련 중 손실 계산을 처리하는 방법입니다.
전통적으로 많은 실무자들이 손실함수를 계산할때, 지침을 마스킹하는 관행을 따랐지만, 최근 연구에 따르면 항상 최적의 방법은 아닐 수 있습니다.
- 지침을 마스킹하는 관행이란?
훈련 중 손실함수(loss function)을 계산할때 입력 데이터의 특정 부분(즉, 지침)을 제외하고 계산한다는 것을 의미합니다.
지침 파인튜닝에서 손실 마스킹은 훈련 손실을 계산할때 입력의 특정 부분을 선택적으로 포함하거나 제외하는 관행을 의미합니다.
일반적으로 3가지 접근 방식이 있습니다.
- 지침 마스킹 안하기: 지침과 응답을 포함한 모든 토큰에서 손실을 계산하는 방식 (기본접근 방식)
- 전체 지침 마스팅 : 응답 토큰에 대해서만 손실을 계산하는 방식 (현재 일반적인 접근방식)
- 보일러 플레이트 마스킹: 반복적인 템플릿 텍스트(ex. 아래는 지침입니다...)만 손실계산에서 제외 하고 지침과 응답 내용은 손실 계산에 포함하는 방식(하이브리드 방식)
논문 'Instruction Tuning With Loss Over Instruction'에 따르면, 지침을 마스킹하지 않는 것이 전통적인 마스킹 접근 방식에 비해 모델 성능을 향상시키는 경우가 많다는 것을 보여 주었습니다. 그러나 이 전략의 효과는 보편적이지 않습니다.
- 지침과 응답 길이의 비율
- 전체 훈련 데이터 셋의 크기
위의 2가지와 같은 데이터셋의 특성에 따라 결과가 달라집니다.
이러한 발견은 실무자들이 마스킹 전략을 결정할때 전체 지침 마스킹으로 기본 설정하기보다는 특정 사용 사례와 데이터 셋 속성을 신중하게 고려해야 함을 시사 합니다.
Together Fine-Tuning API에 이 새로운 기능이 도입됨에 따라 이제 파인 튜닝 작업에 대해 손실 마스킹을 수행할지 선택할 수 있습니다. train_on_inputs 매개변수가 새로 도입되어 다음을 허용합니다.
- 파인 튜닝 작업에 대해 손실 마스킹을 활성화하려면 False로 설정
- 손실 마스킹을 비활성화하면 모든 토큰에 대해 손실이 계산되며 True로 설정
- 입력 데이터셋 형식에 따라 손실 마스킹을 활성화/비활성화하는 ”auto”로 설정할 수도 있습니다.
손실 마스킹에 대해 더 알아보려면, 문서를 참조해 주세요.
반면, Hugging Face는 기본적으로 이런 기능을 제공하지 않지만, 사용자가 직접 커스텀 손실 함수나 데이터 전처리를 통해 유사한 기능을 구현할 수 있습니다.
3. 대화 데이터 파인튜닝 예제
CoQA는 대화형 질문 응답 시스템을 구축하기 위한 영어로된 대규모 데이터셋입니다.
CoQA 챌린지의 목표는 기계가 텍스트 구문을 이해하고 대화에서 나타는 일련의 상호연결된 질문에 답하는 능력을 측정하는 것입니다.
아래의 코드는 CoQA 데이터셋을 Together Fine-Tuning API에서 기대하는 대화 형식으로 변환하는 방법을 보여줍니다.
이를 사용하여 모델을 미세 조정(fine-tuning)하는 과정을 보여줍니다.
전체 코드는 크게 세 부분으로 나눌 수 있습니다:
- CoQA 데이터셋 변환
- 변환된 데이터 업로드
- Together Fine-Tuning API를 사용한 미세 조정 작업 생성
# datasets 라이브러리에서 CoQA 데이터셋을 불러옴
from datasets import load_dataset
# coqa_dataset: 로드된 데이터셋 객체로, "train", "validation" 등의 분할(split)을 포함
coqa_dataset = load_dataset("stanfordnlp/coqa")
# system_prompt: 대화의 시작 부분에 사용되는 시스템 메시지입니다. 주어진 이야기를 읽고 질문에 대한 답변을 추출하도록 지시합니다.
system_prompt = "Read the story and extract answers for the questions.\nStory: {}"
# map_fields 함수: CoQA 데이터셋의 각 행(row)을 Fine-Tuning API에서 요구하는 대화 형식으로 변환
# 매개변수
# "story" : 이야기 내용
# "questions" : 질문 리스트
# "answers": 정답 리스트 (k-v 형태로 이루어짐. input_text라는 키를 포함)
def map_fields(row):
"""
Maps the fields from a row of data to a structured format for conversation.
Args:
row (dict): A dictionary containing the keys "story", "questions", and "answers".
- "story" (str): The story content to be used in the system prompt.
- "questions" (list of str): A list of questions from the user.
- "answers" (dict): A dictionary containing the key "input_text" which is a list of answers from the assistant.
Returns:
dict: A dictionary with a single key "messages" which is a list of message dictionaries.
Each message dictionary contains:
- "role" (str): The role of the message sender, either "system", "user", or "assistant".
- "content" (str): The content of the message.
"""
messages = [
{
"role": "system",
"content": system_prompt.format(row["story"]),
}
]
for q, a in zip(row["questions"], row["answers"]["input_text"]):
messages.append(
{
"role": "user",
"content": q,
}
)
messages.append(
{
"role": "assistant",
"content": a,
}
)
# 리턴값 : Fine-Tuning API에서 요구하는 메시지 형식의 k-v
# 메세지는 role과 content로 구성됨
return {
"messages": messages
}
# map 함수: map_fields 함수를 사용하여 CoQA 데이터셋의 "train" 분할을 변환
# remove_columns: 원본 데이터셋의 열(column)을 제거하여 변환된 데이터만 남김
train_messages = coqa_dataset["train"].map(map_fields, remove_columns=coqa_dataset["train"].column_names)
# JSONL 파일로 저장: 변환된 데이터를 JSONL(JSON Lines) 형식으로 저장합니다.
# JSONL은 각 행이 독립적인 JSON 객체로 구성된 파일 형식입니다.
train_messages.to_json("coqa_prepared_train.jsonl")
# ogether API를 사용하여 변환된 데이터를 업로드
from together import Together
import os
TOGETHER_API_KEY = os.getenv("TOGETHER_API_KEY")
WANDB_API_KEY = os.getenv("WANDB_API_KEY")
client = Together(api_key=TOGETHER_API_KEY)
train_file_resp = client.files.upload("coqa_prepared_train.jsonl", check=True)
print(train_file_resp)
# Together Fine-Tuning API를 사용한 미세 조정 작업 생성
ft_resp = client.fine_tuning.create(
training_file = train_file_resp.id,
model = 'meta-llama/Meta-Llama-3.1-8B-Instruct-Reference',
train_on_inputs= "auto",
n_epochs = 3,
n_checkpoints = 1,
wandb_api_key = WANDB_API_KEY,
lora = True,
warmup_ratio=0,
learning_rate = 1e-5,
suffix = 'my-demo-finetune',
)
print(ft_resp.id)
'ai' 카테고리의 다른 글
[기술면접] 인공신경망이란 무엇이고, 어떻게 작동되나요? (0) | 2025.01.08 |
---|---|
[기술면접] 딥러닝은 무엇인가요? 딥러닝과 머신러닝의 차이점? (0) | 2025.01.08 |
[기술면접] 감정인식을 어떻게 할까요? (0) | 2025.01.06 |
[용어정리] 모델 vs 아키텍쳐 vs 가중치의 관계 (0) | 2025.01.06 |
[기술면접] 트랜스포머란 무엇인가요? (0) | 2025.01.06 |