Prize 훈련 (파트 2): $0.08 USD로 5,500줄 데이터셋 만들기

발행: (2025년 12월 7일 오전 09:32 GMT+9)
7 min read
원문: Dev.to

Source: Dev.to

소개: “주말 프로젝트”의 리듬

두 번째 Prize 여정을 찾아주신 여러분, 환영합니다. Prize는 텍스트 분류를 위한 나만의 신경망입니다. 본격적인 이야기에 들어가기 전에, Prize는 공식적으로 주말 프로젝트라는 점을 분명히 해야 합니다. 여러분과 마찬가지로 저는 일상적인 직장과 책임이 있기 때문에, 토요일과 일요일에만 “머신러닝 엔지니어” 모자를 쓸 수 있습니다. 진행은 꾸준히 이루어지겠지만, 각자의 속도에 맞춰 진행됩니다.

이전 포스트에서는 아키텍처를 정의했습니다. 오늘은 어떤 프로젝트든 가장 큰 문제인 데이터 확보 방법을 공유하려 합니다.

1단계: 수동 정리

첫 번째 데이터 원천은 6 000개의 아이템을 보유한 약국 체인 재고 목록이었습니다. 멋져 보였지만 파일을 열어보니 상황이 달랐습니다.

신경망은 가르치는 대로 학습합니다. 만약 “코카콜라”를 약으로 가르친다면, 모델은 틀린 예측을 할 것입니다. 저는 재고 목록에 건강과 전혀 관련 없는 제품들이 섞여 있다는 것을 깨달았습니다. 그래서 직접 손으로 지저분한 작업을 해야 했습니다. 주말 내내 여러 차례에 걸쳐 수동으로 리스트를 필터링하고, 탄산음료, 초콜릿, 세제, 빗자루, 잡동사니 등 수백 개의 행을 삭제했습니다.

최종 결과물은 inventarioSoloMedicinas.txt라는 깔끔한 파일입니다. 지루하고 힘든 작업이었지만, 없었다면 Prize는 성공할 수 없었을 겁니다.

2단계: 합성 데이터 공장 ($0.08 투자)

여기가 진짜 도전이었습니다. 약국에서는 수천 개의 아이템을 가지고 있었지만, “치과 실험실”에서는 겨우 50개 정도의 예시만 있었습니다. 완전한 불균형이었죠. 50줄을 5 500줄로 늘려 약국과 맞추어야 했습니다. 해결책은? AI를 이용해 AI를 훈련하는 것이었습니다.

Google Gemini의 도움을 받아, OpenRouter API(모델 Llama 3 사용)와 연결되는 파이썬 스크립트를 작성했습니다. 로직은 간단했습니다:

  1. 실제 치과 서비스 예시 5개를 선택합니다.
  2. AI에게 해당 서비스에 대한 사용자 요청 문장을 20가지 변형으로 생성하도록 요청합니다(전문 용어, 오타, 다양한 어조 포함).
  3. 목표량에 도달할 때까지 반복합니다.

생성 스크립트

import requests
import json
import random
import time
import os

# --- CONFIGURACIÓN ---
API_KEY = "XXXXXXXXXX"
OUTPUT_FILE = "dataset_laboratorio_sintetico.txt"
TARGET_COUNT = 5500
BATCH_SIZE = 20  # Cuántas frases pedimos por llamada

MODEL_ID = "meta-llama/llama-3.1-70b-instruct"

semillas_laboratorio = [
    "protesis total superior acrilico",
    "reparacion de puente fijo",
    "ferula de descarga rigida",
    "corona de zirconio sobre implante",
    # ... Aquí va el resto de los 50 items ...
]

def generate_variations(seeds):
    seeds_text = "\n".join([f"- {s}" for s in seeds])

    prompt = f"""
    Actúa como un generador de datos para entrenar una IA.
    Tu tarea es generar {BATCH_SIZE} frases de búsqueda DISTINTAS basadas en los servicios de un laboratorio dental.

    Aquí tienes algunos ejemplos reales (semillas):
    {seeds_text}

    INSTRUCCIONES:
    1. Genera {BATCH_SIZE} variaciones nuevas. NO copies las semillas exactamente.
    2. Contexto: Usuarios en Venezuela/Latinoamérica buscando estos servicios.
    3. Variedad:
       - Usa sinónimos (ej: "plancha" en vez de "prótesis", "férula" en vez de "placa").
       - Mezcla tonos: Formal ("necesito una cotización..."), Informal ("cuanto cuesta la plancha..."), Directo ("precio corona").
       - Incluye ocasionalmente errores leves de ortografía o falta de tildes (como escribe la gente en WhatsApp).
    4. FORMATO DE SALIDA: Únicamente una lista JSON pura de strings. Sin texto extra antes ni después.
    Ejemplo: ["frase 1", "frase 2", "frase 3"]
    """

    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json"
    }

    data = {
        "model": MODEL_ID,
        "messages": [
            {"role": "system", "content": "Eres un asistente útil que responde SOLO en formato JSON válido."},
            {"role": "user", "content": prompt}
        ],
        "temperature": 0.8
    }

    try:
        response = requests.post(
            "https://openrouter.ai/api/v1/chat/completions",
            headers=headers,
            json=data
        )
        response.raise_for_status()
        result = response.json()
        content = result['choices'][0]['message']['content']
        return json.loads(content)
    except Exception as e:
        print(f"Error en la llamada a la API: {e}")
        return []

def main():
    print("--- Iniciando Generación para Proyecto Prize ---")
    print(f"Meta: {TARGET_COUNT} frases.")

    current_count = 0
    if os.path.exists(OUTPUT_FILE):
        with open(OUTPUT_FILE, 'r', encoding='utf-8') as f:
            current_count = len(f.readlines())

    print(f"Progreso actual: {current_count} frases encontradas en {OUTPUT_FILE}")

    while current_count < TARGET_COUNT:
        mis_semillas = random.sample(semillas_laboratorio, k=min(5, len(semillas_laboratorio)))
        print(f"Generando lote... (Total actual: {current_count})")
        nuevas_frases = generate_variations(mis_semillas)

        if not nuevas_frases:
            print("Fallo en este lote, reintentando en 5 segundos...")
            time.sleep(5)
            continue

        with open(OUTPUT_FILE, 'a', encoding='utf-8') as f:
            for frase in nuevas_frases:
                f.write(frase + "\n")

        current_count += len(nuevas_frases)
        time.sleep(1)

    print("\n¡ÉXITO! Generación completada.")
    print(f"Archivo guardado en: {OUTPUT_FILE}")

if __name__ == "__main__":
    main()

결과: 구체적인 데이터

  • 실행 시간: 2시간.
  • 생성된 양: 5 500개의 고유하고 다양한 텍스트 라인.
  • API 총 비용: $0.08 USD.

10센트도 안 되는 비용으로, 손으로 일주일 이상 걸렸을 데이터를 한 번에 만들 수 있었습니다. 목표 달성! 합성 데이터셋이 준비되었습니다.

결론

이제 제 하드디스크에는 금값 같은 두 파일이 있습니다:

  • inventarioSoloMedicinas.txt – 실제, 정제된 데이터.
  • dataset_laboratorio_sintetico.txt – 생성된, 다양화된 데이터.

데이터는 약 11 000개의 고품질 예시로 균형을 이루었습니다. 다음 주말에는 수학 마법을 부릴 차례입니다. 다시 OpenRouter API를 사용해 모든 텍스트를 숫자(임베딩)로 변환하고, 행 벡터를 열 벡터로 바꾼 뒤(network2.py가 기대하는 형태), Prize를 최초 학습시킬 예정입니다.

다음 토요일에 뵙겠습니다!

Back to Blog

관련 글

더 보기 »