Fine-Tuning¶
사전 학습된 모델을 기반으로 개별 작업에 맞게 추가 학습을 하는 것입니다.
처음부터 학습할 때보다 훨씬 적은 학습 데이터와 학습 시간이 필요하며, 학습에 소요되는 시간도 훨씬 적은 것이 특징 입니다.
파인튜닝의 장점은 프롬프트에서 예시를 제공할 필요가 없어 토큰 수가 절약되고, 지연이 적은 요청이 가능하다는 점입니다.
2023.11.20 기준 Fine-Tuning 추천모델 : gpt-3.5-turbo-1106
가격:
Book & Data : GPT-4, ChatGPT, 라마인덱스, 랭체인을 활용한 인공지능 프로그래밍
참조 :
Medium : Fine-Tuning GPT-3.5 on Custom Dataset: A Step-by-Step Guide
import openai
from openai import OpenAI
from dotenv import load_dotenv
import os
import pandas as pd
from sklearn.model_selection import train_test_split
import datetime
load_dotenv()
True
Dataset 준비¶
모델을 미세 조정하려면 최소 10개의 예제를 제공해야 합니다.
일반적으로 gpt-3.5 터보로 50~100개의 훈련 예제를 미세 조정하면 뚜렷한 개선 효과를 볼 수 있지만, 정확한 사용 사례에 따라 적절한 개수는 크게 달라집니다.\
50개의 잘 만들어진 데모로 시작하여 미세 조정 후 모델에 개선의 조짐이 보이는지 확인하는 것이 좋습니다.
경우에 따라서는 이 정도면 충분할 수도 있지만, 아직 생산 품질에 미치지 못하더라도 분명한 개선이 있다면 더 많은 데이터를 제공하면 모델을 계속 개선할 수 있다는 좋은 신호입니다.
개선이 없다면 제한된 예제 세트를 넘어 확장하기 전에 모델에 대한 작업 설정 방법을 재고하거나 데이터를 재구성해야 할 수도 있음을 의미합니다.
fine_tuning_model = "gpt-3.5-turbo"
# 데이터 세트 준비
df = pd.read_csv(
'book/4_openai_api/tsukuyomi.csv',
usecols=[1,2],
names=['prompt', 'completion'],
skiprows=2)
df.to_json('book/4_openai_api/tsukuyomi_legacy.jsonl', orient='records',
lines=True,
force_ascii=False)
import json
jLst = []
for index, row in df.iterrows():
j = {"messages": [{"role": "system", "content": "일본 애니메이션 귀여운 여성 캐릭터 말투로 대화 한다."},
{"role": "user", "content": row['prompt']},
{"role": "assistant", "content": row['completion']}]}
jLst.append(j)
# 원본 데이터 저장
df2 = pd.DataFrame(jLst)
df2.to_json('book/4_openai_api/tsukuyomi.jsonl', orient='records',
lines=True,
force_ascii=False)
Train and test splits¶
초기 데이터 세트를 수집한 후에는 훈련과 테스트 부분으로 분리하는 것이 좋습니다.
훈련 파일과 테스트 파일이 모두 포함된 미세 조정 작업을 제출하면 훈련 과정에서 두 파일에 대한 통계를 제공합니다.
이러한 통계는 모델이 얼마나 개선되고 있는지에 대한 초기 신호가 될 것입니다.
또한 초기에 테스트 세트를 구축하면 훈련 후 테스트 세트에서 샘플을 생성하여 모델을 평가하는 데 유용합니다.
# random_state : seed
train_set, test_set = train_test_split(df2, test_size=0.2, random_state=42)
# train set 저장
train_set.to_json('book/4_openai_api/tsukuyomi_train.jsonl', orient='records',
lines=True,
force_ascii=False)
# test set 저장
test_set.to_json('book/4_openai_api/tsukuyomi_test.jsonl', orient='records',
lines=True,
force_ascii=False)
Token limits¶
각 훈련 예제는 4096개의 토큰으로 제한됩니다.
이보다 긴 예제는 학습 시 처음 4096개 토큰으로 잘립니다.
전체 훈련 예제가 문맥에 맞는지 확인하려면 메시지 콘텐츠의 총 토큰 수가 4,000개 미만인지 확인하세요.
OpenAI 쿡북의 토큰 수 계산 노트북을 사용하여 토큰 수를 계산할 수 있습니다.
Check data formatting¶
데이터 세트를 컴파일한 후 미세 조정 작업을 생성하기 전에 데이터 형식을 확인하는 것이 중요합니다.
이를 위해, 잠재적인 오류를 찾고, 토큰 수를 검토하고, 미세 조정 작업의 비용을 추정하는 데 사용할 수 있는 간단한 Python 스크립트를 만들었습니다.
Estimate costs¶
1k 입력 및 출력 토큰당 비용에 대한 자세한 내용은 가격 페이지를 참조하세요(유효성 검사 데이터의 일부인 토큰에 대해서는 비용을 청구합니다). 특정 미세 조정 작업에 대한 비용을 추정하려면 다음 공식을 사용하세요:
토큰 1,000개당 기본 비용 * 입력 파일에 있는 토큰 수 * 학습된 에포크 수
3개의 에포크에 걸쳐 100,000개의 토큰이 학습된 학습 파일의 경우 예상 비용은 약 2.40달러입니다.
fine_tuning_data_prepare_analysis.py import¶
from fine_tuning_data_prepare_analysis import FineTuneDataAnalysis
print('====== Train Set 분석======')
analysis = FineTuneDataAnalysis('book/4_openai_api/tsukuyomi_train.jsonl')
analysis.analyze()
print('====== Test Set 분석======')
analysis = FineTuneDataAnalysis('book/4_openai_api/tsukuyomi_test.jsonl')
analysis.analyze()
print('====== 전체 Data Set 분석======')
analysis = FineTuneDataAnalysis('book/4_openai_api/tsukuyomi.jsonl')
analysis.analyze()
====== Train Set 분석====== Num examples: 345 First example: {'role': 'system', 'content': '일본 애니메이션 귀여운 여성 캐릭터 말투로 대화 한다.'} {'role': 'user', 'content': '빨래는 항상 어떻게 하고 있어요?'} {'role': 'assistant', 'content': ' 더러워져도 어느새 깨끗해져 있잖아요. 어떻게 작동하는지 나도 잘 모르겠지만 아마도 원래의 이미지로 복원되는 것 같다. '} No errors found Num examples missing system message: 0 Num examples missing user message: 0 #### Distribution of num_messages_per_example: min / max: 3, 3 mean / median: 3.0, 3.0 p5 / p95: 3.0, 3.0 #### Distribution of num_total_tokens_per_example: min / max: 52, 2393 mean / median: 98.66666666666667, 88.0 p5 / p95: 67.0, 121.0 #### Distribution of num_assistant_tokens_per_example: min / max: 2, 118 mean / median: 30.466666666666665, 26.0 p5 / p95: 10.399999999999999, 56.0 0 examples may be over the 4096 token limit, they will be truncated during fine-tuning Dataset has ~34040 tokens that will be charged for during training By default, you'll train for 3 epochs on this dataset By default, you'll be charged for ~102120 tokens Cost : $0.81696 ====== Test Set 분석====== Num examples: 87 First example: {'role': 'system', 'content': '일본 애니메이션 귀여운 여성 캐릭터 말투로 대화 한다.'} {'role': 'user', 'content': '츠쿠요미짱이 좋아하는 동물은?'} {'role': 'assistant', 'content': ' 인간 여러분을 좋아합니다! 인간은 동물인가요? '} No errors found Num examples missing system message: 0 Num examples missing user message: 0 #### Distribution of num_messages_per_example: min / max: 3, 3 mean / median: 3.0, 3.0 p5 / p95: 3.0, 3.0 #### Distribution of num_total_tokens_per_example: min / max: 57, 159 mean / median: 94.60919540229885, 95.0 p5 / p95: 68.2, 121.00000000000003 #### Distribution of num_assistant_tokens_per_example: min / max: 7, 98 mean / median: 32.50574712643678, 31.0 p5 / p95: 12.0, 53.0 0 examples may be over the 4096 token limit, they will be truncated during fine-tuning Dataset has ~8231 tokens that will be charged for during training By default, you'll train for 3 epochs on this dataset By default, you'll be charged for ~24693 tokens Cost : $0.197544 ====== 전체 Data Set 분석====== Num examples: 432 First example: {'role': 'system', 'content': '일본 애니메이션 귀여운 여성 캐릭터 말투로 대화 한다.'} {'role': 'user', 'content': '배가 고프다'} {'role': 'assistant', 'content': '뭐 드실래요? '} No errors found Num examples missing system message: 0 Num examples missing user message: 0 #### Distribution of num_messages_per_example: min / max: 3, 3 mean / median: 3.0, 3.0 p5 / p95: 3.0, 3.0 #### Distribution of num_total_tokens_per_example: min / max: 52, 2393 mean / median: 97.84953703703704, 90.0 p5 / p95: 67.0, 121.0 #### Distribution of num_assistant_tokens_per_example: min / max: 2, 118 mean / median: 30.877314814814813, 27.0 p5 / p95: 11.0, 55.0 0 examples may be over the 4096 token limit, they will be truncated during fine-tuning Dataset has ~42271 tokens that will be charged for during training By default, you'll train for 3 epochs on this dataset By default, you'll be charged for ~126813 tokens Cost : $1.014504
Upload a training file¶
데이터의 유효성을 검사한 후에는 미세 조정 작업에서 사용하려면 파일 API를 사용하여 파일을 업로드해야 합니다.
파일을 업로드한 후 처리하는 데 다소 시간이 걸릴 수 있습니다.
파일이 처리되는 동안에도 미세 조정 작업을 만들 수 있지만 파일 처리가 완료될 때까지 시작되지 않습니다.
client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
training_file = client.files.create(file=open('book/4_openai_api/tsukuyomi_train.jsonl', 'rb'),
purpose='fine-tune')
test_file = client.files.create(file=open('book/4_openai_api/tsukuyomi_test.jsonl', 'rb'),
purpose='fine-tune')
print("Training file id: " + training_file.id)
print("Test file id: " + test_file.id)
Training file id: file-1VC0uxjtT5WQDj6oxIr0deY9 Test file id: file-f8qUF4hQ8wxuFqU4GNFrTtjD
Create a fine-tuned model¶
데이터 세트에 적합한 양과 구조가 있는지 확인하고 파일을 업로드했다면, 다음 단계는 미세 조정 작업을 만드는 것입니다.
미세 조정 UI를 통해 또는 프로그래밍 방식으로 미세 조정 작업을 생성할 수 있도록 지원합니다.
OpenAI SDK를 사용하여 미세 조정 작업을 시작하려면 다음과 같이 하세요:
suffix_name = 'tsukuyomi'
response = client.fine_tuning.jobs.create(
training_file=training_file.id,
validation_file=test_file.id,
model=fine_tuning_model,
suffix=suffix_name
)
response
FineTuningJob(id='ftjob-XWQTrfzhiXWmmr64qDhb7bUb', created_at=1700562695, error=None, fine_tuned_model=None, finished_at=None, hyperparameters=Hyperparameters(n_epochs='auto', batch_size='auto', learning_rate_multiplier='auto'), model='gpt-3.5-turbo-0613', object='fine_tuning.job', organization_id='org-9rVeGmcv32mU17bhU5j5tmq3', result_files=[], status='validating_files', trained_tokens=None, training_file='file-1VC0uxjtT5WQDj6oxIr0deY9', validation_file='file-f8qUF4hQ8wxuFqU4GNFrTtjD')
All Fine-Tuning Jobs¶
client.fine_tuning.jobs.list(limit=10)
SyncCursorPage[FineTuningJob](data=[FineTuningJob(id='ftjob-XWQTrfzhiXWmmr64qDhb7bUb', created_at=1700562695, error=None, fine_tuned_model='ft:gpt-3.5-turbo-0613:e4:tsukuyomi:8NIwPhd5', finished_at=1700565044, hyperparameters=Hyperparameters(n_epochs=3, batch_size=1, learning_rate_multiplier=2), model='gpt-3.5-turbo-0613', object='fine_tuning.job', organization_id='org-9rVeGmcv32mU17bhU5j5tmq3', result_files=['file-WBcLgfJfulZxXk4gsdoBfpWf'], status='succeeded', trained_tokens=100050, training_file='file-1VC0uxjtT5WQDj6oxIr0deY9', validation_file='file-f8qUF4hQ8wxuFqU4GNFrTtjD')], object='list', has_more=False)
Retrieve Specific Job¶
created_at : 2023-11-21 19:31:35
finished_at : 2023-11-21 20:10:44
model : gpt-3.5-turbo-0613
fine_tuned_model : ft:gpt-3.5-turbo-0613:e4:tsukuyomi:8NIwPhd5
result_files : ['file-WBcLgfJfulZxXk4gsdoBfpWf']
status : succeeded
Hyperparameters:
- n_epochs = 3
- batch_size=1
- learning_rate_multiplier=2
실제 비용 : $0.8
response = client.fine_tuning.jobs.retrieve('ftjob-XWQTrfzhiXWmmr64qDhb7bUb')
print(response)
print(f"created_at : {datetime.datetime.fromtimestamp(response.created_at)}")
print(f"finished_at : {datetime.datetime.fromtimestamp(response.finished_at)}")
print(f"model : {response.model}")
print(f"fine_tuned_model : {response.fine_tuned_model}")
print(f"result_files : {response.result_files}")
print(f"status : {response.status}")
print(f"error : {response.error}")
FineTuningJob(id='ftjob-XWQTrfzhiXWmmr64qDhb7bUb', created_at=1700562695, error=None, fine_tuned_model='ft:gpt-3.5-turbo-0613:e4:tsukuyomi:8NIwPhd5', finished_at=1700565044, hyperparameters=Hyperparameters(n_epochs=3, batch_size=1, learning_rate_multiplier=2), model='gpt-3.5-turbo-0613', object='fine_tuning.job', organization_id='org-9rVeGmcv32mU17bhU5j5tmq3', result_files=['file-WBcLgfJfulZxXk4gsdoBfpWf'], status='succeeded', trained_tokens=100050, training_file='file-1VC0uxjtT5WQDj6oxIr0deY9', validation_file='file-f8qUF4hQ8wxuFqU4GNFrTtjD') created_at : 2023-11-21 19:31:35 finished_at : 2023-11-21 20:10:44 model : gpt-3.5-turbo-0613 fine_tuned_model : ft:gpt-3.5-turbo-0613:e4:tsukuyomi:8NIwPhd5 result_files : ['file-WBcLgfJfulZxXk4gsdoBfpWf'] status : succeeded error : None
Fine-Tuning 진행 상태 모니터링¶
response = client.fine_tuning.jobs.list_events(fine_tuning_job_id="ftjob-XWQTrfzhiXWmmr64qDhb7bUb", limit=10)
for ft_event in response.data:
print(f"{datetime.datetime.fromtimestamp(ft_event.created_at)} | {ft_event}" )
2023-11-21 20:10:50 | FineTuningJobEvent(id='ftevent-h4HFTJERSnSuhi2FxLxV2LI3', created_at=1700565050, level='info', message='The job has successfully completed', object='fine_tuning.job.event', data={}, type='message') 2023-11-21 20:10:46 | FineTuningJobEvent(id='ftevent-ietXK9rF5c83nr3vtp7qz2z9', created_at=1700565046, level='info', message='New fine-tuned model created: ft:gpt-3.5-turbo-0613:e4:tsukuyomi:8NIwPhd5', object='fine_tuning.job.event', data={}, type='message') 2023-11-21 20:09:27 | FineTuningJobEvent(id='ftevent-yHoQf5dThwzloJGAnxXH3AAx', created_at=1700564967, level='info', message='Step 1001/1035: training loss=0.92, validation loss=1.27', object='fine_tuning.job.event', data={'step': 1001, 'train_loss': 0.9220174551010132, 'valid_loss': 1.2738130887349446, 'train_mean_token_accuracy': 0.7358490824699402, 'valid_mean_token_accuracy': 0.3333333333333333}, type='metrics') 2023-11-21 20:06:00 | FineTuningJobEvent(id='ftevent-AW4NdBTRqePLmXCfi6cKKSEk', created_at=1700564760, level='info', message='Step 901/1035: training loss=1.14, validation loss=1.16', object='fine_tuning.job.event', data={'step': 901, 'train_loss': 1.138753056526184, 'valid_loss': 1.1612161908830916, 'train_mean_token_accuracy': 0.7288135886192322, 'valid_mean_token_accuracy': 0.42857142857142855}, type='metrics') 2023-11-21 20:02:35 | FineTuningJobEvent(id='ftevent-AyGOsUhXNRt2mGgnpt3v3uWM', created_at=1700564555, level='info', message='Step 801/1035: training loss=0.41, validation loss=1.18', object='fine_tuning.job.event', data={'step': 801, 'train_loss': 0.4073687791824341, 'valid_loss': 1.1824234702370384, 'train_mean_token_accuracy': 0.7777777910232544, 'valid_mean_token_accuracy': 0.43636363636363634}, type='metrics') 2023-11-21 19:59:10 | FineTuningJobEvent(id='ftevent-5QJvZ0BV8ZDyAXyDQJURihbS', created_at=1700564350, level='info', message='Step 701/1035: training loss=0.45, validation loss=1.35', object='fine_tuning.job.event', data={'step': 701, 'train_loss': 0.4544571042060852, 'valid_loss': 1.3509497825915997, 'train_mean_token_accuracy': 0.7727272510528564, 'valid_mean_token_accuracy': 0.2692307692307692}, type='metrics') 2023-11-21 19:55:45 | FineTuningJobEvent(id='ftevent-G4LnqsQs57CXhdv9rAFkaOQh', created_at=1700564145, level='info', message='Step 601/1035: training loss=0.75, validation loss=1.01', object='fine_tuning.job.event', data={'step': 601, 'train_loss': 0.7494182586669922, 'valid_loss': 1.0061046812269423, 'train_mean_token_accuracy': 0.8260869383811951, 'valid_mean_token_accuracy': 0.3888888888888889}, type='metrics') 2023-11-21 19:52:23 | FineTuningJobEvent(id='ftevent-gKeSPVQhDFzSpz7wzZxr5BNR', created_at=1700563943, level='info', message='Step 501/1035: training loss=0.68, validation loss=1.34', object='fine_tuning.job.event', data={'step': 501, 'train_loss': 0.6799123287200928, 'valid_loss': 1.3439248126486074, 'train_mean_token_accuracy': 0.8048780560493469, 'valid_mean_token_accuracy': 0.3695652173913043}, type='metrics') 2023-11-21 19:49:01 | FineTuningJobEvent(id='ftevent-U96jyZePad3VN8qmS9m1uOFw', created_at=1700563741, level='info', message='Step 401/1035: training loss=0.94, validation loss=2.15', object='fine_tuning.job.event', data={'step': 401, 'train_loss': 0.9435685276985168, 'valid_loss': 2.151079389784071, 'train_mean_token_accuracy': 0.7428571581840515, 'valid_mean_token_accuracy': 0.0}, type='metrics') 2023-11-21 19:45:36 | FineTuningJobEvent(id='ftevent-c2J3Bqsx886QR31QQz8OUVm1', created_at=1700563536, level='info', message='Step 301/1035: training loss=0.42, validation loss=1.34', object='fine_tuning.job.event', data={'step': 301, 'train_loss': 0.4217841327190399, 'valid_loss': 1.3372265338897704, 'train_mean_token_accuracy': 0.8235294222831726, 'valid_mean_token_accuracy': 0.425}, type='metrics')
작업결과 Step Metrics 파일 저장¶
client.files.list()
SyncPage[FileObject](data=[FileObject(id='file-WBcLgfJfulZxXk4gsdoBfpWf', bytes=22198, created_at=1700565048, filename='step_metrics.csv', object='file', purpose='fine-tune-results', status='processed', status_details=None), FileObject(id='file-f8qUF4hQ8wxuFqU4GNFrTtjD', bytes=25795, created_at=1700562577, filename='tsukuyomi_test.jsonl', object='file', purpose='fine-tune', status='processed', status_details=None), FileObject(id='file-1VC0uxjtT5WQDj6oxIr0deY9', bytes=105344, created_at=1700562576, filename='tsukuyomi_train.jsonl', object='file', purpose='fine-tune', status='processed', status_details=None), FileObject(id='file-aXSLRg8ZJK29DW0WUHs6NOF3', bytes=105344, created_at=1700533697, filename='tsukuyomi_train.jsonl', object='file', purpose='fine-tune', status='processed', status_details=None)], object='list', has_more=False)
file_contents = client.files.content(file_id= 'file-WBcLgfJfulZxXk4gsdoBfpWf')
with open('book/4_openai_api/step_metrics.csv', 'w') as f:
f.write(file_contents.text)
fine_tunned_model = "ft:gpt-3.5-turbo-0613:e4:tsukuyomi:8NIwPhd5"
messages = [
{"role": "system", "content": "일본 애니메이션 귀여운 여성 캐릭터 말투로 대화 한다."},
{"role": "user", "content": "좋아하는 음식은 뭐야?"}
]
response = client.chat.completions.create(
model=fine_tunned_model,
messages=messages,
max_tokens=100,
temperature=0.7,
# stop=["\n"]
)
print(response.choices[0].message.content)
맛있는 음식은 무엇이든지 좋아합니다!