🧠 LLM 엔지니어링

Fine-tuned GPT-4o-mini를 이용한 제품 가격 예측 모델 구축 가이드 02

개발자 린다씨 2025. 5. 5. 16:37
반응형

📝 개요

이 글에서는 제품 설명 텍스트만을 입력받아
가격을 예측하는 Fine-tuned GPT-4o-mini 모델을 구축하는 과정을 다룹니다.

데이터 준비부터 모델 파인튜닝,
완성된 모델 테스트까지 전 과정을 코드와 함께 실습하며,
Weights & Biases를 이용한 학습 모니터링과 모델 성능 평가 방법도 소개합니다.

주요 목표:

  • 제품 설명을 기반으로 가격을 추론하는 맞춤형 GPT 모델 만들기
  • OpenAI Fine-tuning API 실습
  • 학습 진행 상황을 Weights & Biases로 실시간 추적
  • Fine-tuned 모델을 활용해 테스트 및 성능 평가하기

🎨 Weights & Biases(W&B) 연동 준비

Weights and Biases(W&B)는 학습 과정을 모니터링할 수 있는 무료인 플랫폼입니다.
OpenAI 파인튜닝과도 공식적으로 통합되어 있습니다.

✨ W&B 계정 설정 방법

  • Weights & Biases 무료 계정 생성하기
    👉https://wandb.ai
  • API Key 발급받기
    • 사이트에 로그인한 뒤
    • Avatar(프로필) → Settings → API Key 생성 메뉴로 이동합니다.
  • OpenAI 대시보드에서 통합 설정하기
    👉 https://platform.openai.com/account/organization
    • 대시보드의 Integrations 섹션에서
    • 방금 생성한 Weights & Biases API Key를 추가합니다.

🎯 Weights & Biases(W&B) 연동 설정 (Set Up Weights & Biases Integration)

파인튜닝 진행 상황을 Weights & Biases 프로젝트에 기록하기 위한 연동 설정을 만듭니다.

# 🎯 W&B 연동 설정을 위한 딕셔너리 생성
wandb_integration = {
    "type": "wandb",           # 통합 유형(type)을 "wandb"로 설정
    "wandb": {
        "project": "gpt-pricer"  # W&B에서 기록할 프로젝트 이름 지정
    }
}

✨ 설명

  • type: "wandb": 통합하려는 대상이 Weights & Biases임을 명시합니다.
  • wandb.project: W&B 대시보드에 기록할 프로젝트 이름을 지정합니다.(이 예제에서는 "gpt-pricer"라는 이름의 프로젝트로 기록됩니다.)
  • 이 설정을 나중에 fine-tuning 요청 시 추가 파라미터로 넘기면 됩니다.

🔔 참고

  • 프로젝트 이름 "gpt-pricer"는 자유롭게 바꿀 수 있습니다. (예: "product-price-tuner")
  • 만약 OpenAI 대시보드에 API Key 연동을 안 해두었다면, 이 통합은 작동하지 않습니다.

🆔 업로드된 학습 파일의 ID (Training File ID)

train_file 객체에서 업로드된 학습 파일의 고유 ID를 가져옵니다.

# 🆔 학습 데이터 파일의 고유 ID 가져오기
train_file.id
# - OpenAI에 업로드한 fine_tune_train.jsonl 파일의 고유 식별자(ID)
# - 이 ID는 파인튜닝 작업을 시작할 때 training_file 파라미터로 필요함

✨ 설명

  • train_fileopenai.files.create()를 통해 서버에 업로드된 파일 객체입니다.
  • train_file.id는 해당 파일을 OpenAI 시스템 내부에서 식별하는 고유 ID입니다.
  • 이 ID를 이용해 나중에 fine-tuning 요청을 할 때 어떤 파일을 학습 데이터로 쓸지 지정할 수 있습니다.

📋 예시

만약 train_file.id를 출력하면 이런 값이 나올 수 있습니다:

file-abc123xyz456

🔔 참고

  • 파일 ID는 업로드할 때마다 새로 생성됩니다.
  • 만약 파일을 새로 수정해서 다시 업로드하면 ID도 새로 바뀌므로, 그때마다 새로운 ID를 써야 합니다!

🚀 Fine-tuning Job 시작 (Start Fine-tuning Job)

업로드한 학습 데이터와 검증 데이터를 사용해 OpenAI에서 파인튜닝을 시작합니다.

# 🚀 파인튜닝 작업 생성 (Fine-tuning Job Start)
openai.fine_tuning.jobs.create(
    training_file=train_file.id,                  # 학습 데이터 파일 ID
    validation_file=validation_file.id,            # 검증 데이터 파일 ID
    model="gpt-4o-mini-2024-07-18",                 # 베이스 모델 (GPT-4o mini 버전, 2024-07-18 기준)
    seed=42,                                        # 랜덤 시드를 고정하여 재현 가능한 결과 생성
    hyperparameters={"n_epochs": 1},                # 학습 반복 횟수(epoch) 설정 (1회만)
    integrations=[wandb_integration],               # Weights & Biases 연동 설정
    suffix="pricer"                                 # 파인튜닝된 모델 이름에 붙일 접미사 설정
)

📋 결과

이 명령이 실행되면 OpenAI 서버에서 fine-tuning 작업이 시작됩니다.

이후 작업 진행 상황은:

  • OpenAI 대시보드
  • Weights & Biases 대시보드(연동한 경우)에서 실시간으로 확인할 수 있습니다.

🔔 참고

  • suffix를 붙이면, 나중에 생성된 모델 이름이 명확하게 구분됩니다.
  • seed를 고정하면, 같은 데이터/설정으로 다시 학습했을 때 결과가 거의 일관되게 나옵니다.
  • epoch 수를 늘리고 싶으면 n_epochs 값을 조정하면 됩니다. (예: {"n_epochs": 3})

📋 최근 Fine-tuning Job 조회 (List Recent Fine-tuning Jobs)

가장 최근에 실행된 파인튜닝 작업 정보를 가져옵니다.

# 📋 최근 생성된 파인튜닝 작업 1개 조회
openai.fine_tuning.jobs.list(limit=1)

# 📝 작동 과정:
# - 서버에 등록된 Fine-tuning 작업 중
# - 가장 최근(recent) 1개만 가져옵니다.

✨ 설명

  • openai.fine_tuning.jobs.list()는 파인튜닝 작업 목록을 불러오는 함수입니다.
  • limit=1을 지정하면 가장 최근 작업 하나만 가져옵니다.
  • 반환되는 결과에는 다음과 같은 정보가 포함됩니다:
    • 작업 ID (job ID)
    • 사용한 베이스 모델
    • 파인튜닝된 모델 ID (완료 시)
    • 현재 상태(status: running / succeeded / failed)
    • 시작 시간, 종료 시간 등 메타데이터

📋 예시 출력

(실제로 실행하면 이런 형태의 JSON 데이터가 나옵니다)

{
  "data": [
    {
      "id": "ftjob-xyz123abc456",
      "object": "fine_tuning.job",
      "model": "gpt-4o-mini-2024-07-18",
      "fine_tuned_model": "ft:gpt-4o-mini-2024-07-18:pricer:abcde12345",
      "status": "succeeded",
      "created_at": 1710002000,
      "finished_at": 1710004000
    }
  ]
}

🔔 참고

  • 상태(status)가 "succeeded"이면 파인튜닝이 정상적으로 완료된 것입니다.
  • 완료된 모델 ID(fine_tuned_model)를 복사해 바로 사용할 수 있습니다.
  • 만약 여러 작업을 관리해야 할 경우, limit을 늘리거나 openai.fine_tuning.jobs.list() 전체 목록을 가져올 수도 있습니다.

🆔 가장 최근 파인튜닝 작업의 ID 가져오기 (Get Most Recent Fine-tuning Job ID)

가장 최근 실행된 파인튜닝 작업의 ID를 가져와 job_id 변수에 저장합니다.

# 🆔 가장 최근 Fine-tuning 작업의 ID 추출
job_id = openai.fine_tuning.jobs.list(limit=1).data[0].id

# 📝 작동 과정:
# - fine_tuning.jobs.list(limit=1)로 최근 파인튜닝 작업 1개를 가져옴
# - data 리스트에서 첫 번째 작업 선택
# - 선택된 작업의 id 필드를 추출해 job_id 변수에 저장

✨ 설명

  • openai.fine_tuning.jobs.list(limit=1): 최근 생성된 Fine-tuning Job 1개를 조회합니다.
  • .data[0]: 가져온 작업 목록(리스트) 중 첫 번째 작업을 선택합니다.
  • .id: 선택된 작업 객체의 고유 ID를 추출합니다.
  • job_id: 이 ID를 나중에 작업 상태 확인이나 세부정보 조회할 때 사용합니다.

🔔 참고

  • 파인튜닝 작업이 여러 개 있는 경우, limit=1로 가장 최근 작업만 가져온 것입니다.
  • 만약 특정 조건(예: 특정 suffix)으로 필터링하고 싶다면 추가적인 코드가 필요합니다.

🆔 저장된 파인튜닝 작업 ID (Stored Fine-tuning Job ID)

가장 최근 실행된 Fine-tuning 작업의 고유 ID를 job_id 변수에 저장한 상태입니다.

# 🆔 최근 생성된 Fine-tuning 작업의 ID
job_id
# - openai.fine_tuning.jobs.list(limit=1).data[0].id 로 가져온 값
# - 이 ID를 이용해 해당 파인튜닝 작업의 상태 조회, 결과 확인 등을 할 수 있음

✨ 설명

  • job_id가장 최근에 생성된 Fine-tuning 작업의 고유 식별자입니다.
  • 이 ID를 이용하면 다음과 같은 작업을 할 수 있습니다:
    • 파인튜닝 작업 상태 확인 (running, succeeded, failed 등)
    • 파인튜닝이 완료된 후 생성된 모델 ID 확인
    • 파인튜닝 작업 세부정보 조회 (시작 시간, 종료 시간, 에포크 수 등)

📋 예시

예를 들어 job_id를 출력하면 다음과 같은 형태일 수 있습니다:

ftjob-xyz789abc456

이 값을 사용해 세부정보를 조회하거나, fine_tuned_model 이름을 얻어 바로 inference(추론) 요청에 활용할 수 있습니다.

🔔 참고

job_id특정 Fine-tuning 작업을 정확히 추적할 수 있게 해주는 매우 중요한 정보입니다.

특히 fine-tuned 모델 이름을 찾거나, 에러가 발생했을 때 로그를 확인할 때 반드시 필요합니다.


🔍 Fine-tuning 작업 상태 확인 및 모델 ID 추출 (fine_tuned_model_name)

특정 Fine-tuning 작업의 세부 정보 조회

job_id를 이용해 특정 파인튜닝 작업의 상태와 세부 정보를 조회합니다.

# 🔍 특정 Fine-tuning 작업의 세부 정보 조회
openai.fine_tuning.jobs.retrieve(job_id)

# 📝 작동 과정:
# - 지정한 job_id에 해당하는 Fine-tuning 작업을 가져옵니다.
# - 작업의 현재 상태(status), 결과 모델 ID(fine_tuned_model) 등 다양한 정보를 포함합니다.

✨ 설명

  • job_id로 특정 파인튜닝 작업을 직접 조회할 수 있습니다.
  • 반환되는 결과에는 다음과 같은 정보가 포함됩니다:
    • id: 작업 고유 ID
    • model: 베이스로 사용된 원본 모델 (ex: gpt-4o-mini-2024-07-18)
    • status: 현재 작업 상태 (running, succeeded, failed 등)
    • fine_tuned_model: 파인튜닝 완료 시 생성된 새 모델 ID
    • created_at / finished_at: 작업 시작 및 종료 시각
    • hyperparameters: 학습 시 설정한 하이퍼파라미터
    • training_file / validation_file: 사용된 파일 ID
    • error: 실패했을 경우 에러 내용

📋 예시 출력

(정상적으로 완료된 경우)

{
  "id": "ftjob-xyz789abc456",
  "object": "fine_tuning.job",
  "model": "gpt-4o-mini-2024-07-18",
  "fine_tuned_model": "ft:gpt-4o-mini-2024-07-18:pricer:abcde12345",
  "status": "succeeded",
  "created_at": 1710002000,
  "finished_at": 1710004000,
  "hyperparameters": {
    "n_epochs": 1
  },
  "training_file": "file-abc123xyz456",
  "validation_file": "file-xyz789abc123",
  "error": null
}

🔔 참고

  • status"succeeded"이면 파인튜닝이 정상 완료된 것입니다.
  • fine_tuned_model 항목이 생성되면,
    이 모델 ID를 가지고 직접 inference(추론 요청)를 할 수 있습니다.
  • 만약 에러가 발생했다면 error 항목에 상세 원인이 담겨 있습니다.

📜 Fine-tuning 작업 이벤트 로그 조회 (List Fine-tuning Job Events)

지정한 job_id에 대해 최근 발생한 이벤트 10개를 가져옵니다.

# 📜 특정 Fine-tuning 작업의 최근 이벤트 10개 조회
openai.fine_tuning.jobs.list_events(fine_tuning_job_id=job_id, limit=10).data

# 📝 작동 과정:
# - fine_tuning_job_id=job_id로 특정 작업을 지정합니다.
# - limit=10으로 최근 발생한 이벤트 10개만 가져옵니다.
# - 반환된 결과는 이벤트 객체들의 리스트입니다.

✨ 설명

  • openai.fine_tuning.jobs.list_events(...)
    특정 Fine-tuning 작업에서 발생한 진행 이벤트(로그)를 가져오는 함수입니다.
  • 이벤트에는 다음과 같은 내용이 포함될 수 있습니다:
    • 학습 시작
    • 학습 에포크(epoch) 진행 상황
    • 검증(validation) 진행 상황
    • 학습 완료 또는 실패 알림
    • 기타 경고나 정보 메시지

📋 예시 출력

(이벤트 1개 예시)

{
  "object": "fine_tuning.job.event",
  "created_at": 1710002111,
  "level": "info",
  "message": "Step 50/200: training loss=0.9234"
}

🔔 참고

  • limit=10을 설정했기 때문에 최근 10개 이벤트만 가져옵니다.
  • 전체 이벤트를 보고 싶으면 limit 값을 더 크게 설정하면 됩니다. (예: limit=50)
  • 주요 에포크 완료 시점이나 학습 종료 시점 등을 확인할 때 유용합니다.

✅ 이 명령을 사용하면 Fine-tuning 진행 상황을 구체적으로 추적하고, 문제가 발생했을 때도 어디서 실패했는지 쉽게 파악할 수 있습니다! 🚀

🧪 파인튜닝된 모델 테스트 (Test Our Fine-tuned Model)

파인튜닝이 완료된 모델을 사용해, 실제로 원하는 입력(prompt)에 대해 응답을 생성해 봅니다.

✨ 설명

  • 이제 Fine-tuning이 완료되었으니,
    새로 생성된 fine-tuned 모델 ID를 사용해서 직접 테스트 요청을 보낼 수 있습니다.
  • 튜닝한 모델은 특정한 스타일(예: 제품 가격 예측)에 맞춰 학습되어 있기 때문에,
    기본 모델보다 훨씬 더 정확하고 일관된 응답을 기대할 수 있습니다.

🏷️ 파인튜닝된 모델 이름 가져오기 (Get Fine-tuned Model Name)

Fine-tuning 작업이 완료된 후 생성된 모델의 ID(이름)를 가져와 fine_tuned_model_name 변수에 저장합니다.

# 🏷️ Fine-tuned 모델 ID(이름) 가져오기
fine_tuned_model_name = openai.fine_tuning.jobs.retrieve(job_id).fine_tuned_model

# 📝 작동 과정:
# - 지정한 job_id에 해당하는 파인튜닝 작업을 조회합니다.
# - 작업 결과로 생성된 fine_tuned_model 이름을 추출해 변수에 저장합니다.

✨ 설명

  • openai.fine_tuning.jobs.retrieve(job_id): 해당 job_id에 해당하는 파인튜닝 작업의 전체 정보를 가져옵니다.
  • .fine_tuned_model: 작업이 성공적으로 완료된 경우 생성된 Fine-tuned 모델 ID를 반환합니다.
  • 이 모델 ID는 이후 chat completion 요청이나 API 호출model 파라미터로 사용해야 합니다.

🔔 참고

  • 만약 파인튜닝이 아직 완료되지 않았다면(status != "succeeded"),
    .fine_tuned_model 값은 None이 될 수 있습니다.
  • 반드시 파인튜닝이 완료된 이후에 이 코드를 실행해야 정상적으로 모델 이름을 얻을 수 있습니다.

🏷️ 저장된 Fine-tuned 모델 이름 (Fine-tuned Model Name)

파인튜닝이 완료된 모델의 ID(이름)가 fine_tuned_model_name 변수에 저장된 상태입니다.

# 🏷️ Fine-tuned 모델 ID(이름)
fine_tuned_model_name
# - openai.fine_tuning.jobs.retrieve(job_id).fine_tuned_model 로 가져온 값
# - 추론(inference) 요청 시 사용할 모델 이름

✨ 설명

  • fine_tuned_model_name은 직접 학습시킨 Fine-tuned 모델의 고유 ID입니다.
  • OpenAI API에서 이 ID를 model 파라미터에 입력하면,
    튜닝된 모델로 대화(chat completion) 요청을 보낼 수 있습니다.
  • 이 모델은 기본(gpt-4o-mini 등) 모델보다 특정 작업(예: 제품 가격 예측)에 더 최적화되어 있습니다.

📋 예시

fine_tuned_model_name 변수에 저장된 값은 다음과 같은 형태입니다:

ft:gpt-4o-mini-2024-07-18:pricer:abcde12345

  • ft: 접두사가 붙어 있는 것이 특징입니다.
  • 이후 이 모델 이름을 사용해 바로 테스트할 수 있습니다.

🔔 참고

  • fine_tuned_model_name은 자동으로 생성되며,
    기존 모델명 + suffix + 고유 식별자로 구성됩니다.
  • 여러 개의 Fine-tuned 모델을 관리할 때는 이 이름으로 구분합니다.

✏️ 파인튜닝용 프롬프트 구성 함수 (Prompt Formatting for Fine-tuning)

제품 설명을 입력받아, 파인튜닝 및 테스트용 메시지 포맷을 만들어 반환하는 함수입니다.

# ✏️ 주어진 아이템(item)에 대해 프롬프트 메시지 리스트를 생성하는 함수
def messages_for(item):
    # 🛠️ 시스템 역할(system message) 설정
    system_message = "You estimate prices of items. Reply only with the price, no explanation"
    # - 모델에게 '가격만 간결하게 답변하라'는 지시를 전달

    # 📩 사용자 입력(user prompt) 준비
    user_prompt = item.test_prompt().replace(" to the nearest dollar", "").replace("\n\nPrice is $", "")
    # - 원본 프롬프트에서 "to the nearest dollar" 문구 삭제
    # - "Price is $" 안내문도 삭제하여 더 깔끔한 입력 문장으로 정제

    # 📨 시스템/사용자/어시스턴트 메시지 포맷으로 묶어 반환
    return [
        {"role": "system", "content": system_message},  # 시스템 역할 지시
        {"role": "user", "content": user_prompt},        # 사용자 입력
        {"role": "assistant", "content": "Price is $"}   # 어시스턴트 답변 양식 (가격 입력 예상 위치)
    ]

✨ 설명

  • system_message: 모델에게 "가격만 답하라"라고 명확히 지시합니다.
  • user_prompt: 제품 설명 문장에서 필요 없는 문구를 제거해 모델이 핵심 정보만 이해할 수 있도록 합니다.
  • assistant 응답 포맷: "Price is $" 형태로 기본 응답 포맷을 제공해, 모델이 가격만 자연스럽게 채우도록 유도합니다.

📋 결과 예시

(만약 item.test_prompt()
"Wireless mouse with ergonomic design, to the nearest dollar\n\nPrice is $"
였다면, 변환 결과는 다음과 같습니다.)

[
    {"role": "system", "content": "You estimate prices of items. Reply only with the price, no explanation"},
    {"role": "user", "content": "Wireless mouse with ergonomic design,"},
    {"role": "assistant", "content": "Price is $"}
]

🔔 참고

  • 실제 모델 테스트 시에는 assistant"Price is $" 부분에 이어서 가격을 완성하도록 모델이 학습됩니다.
  • 여기서는 학습(fine-tuning)과 테스트를 모두 지원할 수 있도록 general 한 프롬프트 구조를 준비하고 있습니다.

🧪 테스트용 프롬프트 생성해 보기 (Try This Out)

테스트 데이터셋의 첫 번째 아이템을 이용해, 모델 입력용 메시지 포맷을 만들어 봅니다.

# 🧪 test 데이터셋의 첫 번째 아이템으로 프롬프트 생성
messages_for(test[0])

# 📝 작동 과정:
# - test[0]: 테스트 데이터셋의 첫 번째 항목을 선택합니다.
# - messages_for(test[0]): 선택한 항목을 기반으로 system/user/assistant 역할 메시지 리스트를 생성합니다.

✨ 설명

  • test 데이터셋은 모델의 성능을 검증하기 위해 별도로 준비한 데이터입니다.
  • messages_for(test[0])를 호출하면,
    • system message (가격만 답하라고 지시)
    • user message (제품 설명)
    • assistant message (가격 양식)로 구성된 메시지 리스트가 만들어집니다.

📋 예시 결과

(만약 test[0]이 "Eco-friendly reusable water bottle" 설명을 가진 아이템이라면, 결과는 다음과 같습니다.)

[
    {"role": "system", "content": "You estimate prices of items. Reply only with the price, no explanation"},
    {"role": "user", "content": "Eco-friendly reusable water bottle"},
    {"role": "assistant", "content": "Price is $"}
]

🔔 참고

  • 이 출력 결과는 곧바로 fine-tuned 모델에게 입력으로 주어져,
    모델이 "Price is $19.99" 같은 식으로 가격을 채워 응답하게 됩니다.
  • 학습용 데이터와 테스트용 데이터 모두 같은 포맷을 유지하는 것이 중요합니다.
반응형