📚 Módulo 11: Proyecto Integrado Final — Ajuste Fino de Qwen2.5-0.5B para Generación de Descripciones de Productos

11.1 Objetivo del Proyecto

Entrenar un pequeño modelo de lenguaje (Qwen2.5-0.5B) para generar descripciones comerciales atractivas y concisas de productos a partir de nombres y características de productos. Este proyecto integra todos los conceptos aprendidos: carga de modelo QLoRA, configuración LoRA, preparación de conjunto de datos, entrenamiento SFTTrainer, evaluación y guardado de adaptador.

11.2 Conjunto de Datos de Ejemplo

Crear un conjunto de datos sintético de 50 ejemplos en formato Alpaca:

import json

dataset = [
    {
        "instruction": "Escribe una descripción comercial atractiva para un producto tecnológico.",
        "input": "Nombre: Drone plegable 4K. Características: Cámara estabilizada, 30 min de tiempo de vuelo, control por app, modo de seguimiento automático. Precio: $349.99.",
        "output": "¡Captura el mundo desde arriba con nuestro Drone plegable 4K! Ligero, potente y listo para viajar. Disfruta de video ultra nítido gracias a su cámara estabilizada, vuela hasta 30 minutos sin preocupaciones y controla todo desde tu smartphone. Con modo de seguimiento automático, nunca pierdas de vista la acción. Tecnología profesional al alcance de tu mano. Solo $349.99."
    },
    {
        "instruction": "Escribe una descripción comercial atractiva para un producto tecnológico.",
        "input": "Nombre: Teclado Mecánico RGB. Características: Interruptores táctiles, iluminación personalizable, diseño ergonómico, anti-ghosting. Precio: $89.99.",
        "output": "Domina cada juego o proyecto con nuestro Teclado Mecánico RGB. Diseñado para quienes exigen precisión y estilo. Interruptores táctiles que responden a tu toque, iluminación RGB que te permite crear tu ambiente ideal y un diseño ergonómico que protege tus manos durante largas sesiones. Anti-ghosting asegura que cada pulsación cuente. Rendimiento y elegancia en un solo dispositivo. Solo $89.99."
    },
    # ... 48 ejemplos más
]

# Guardar como JSON
with open("product_descriptions_dataset.json", "w") as f:
    json.dump(dataset, f, indent=2)

11.3 Script Completo de Entrenamiento

# --- Importaciones ---
import torch
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    BitsAndBytesConfig,
    TrainingArguments
)
from peft import LoraConfig, get_peft_model, PeftModel
from trl import SFTTrainer
from datasets import Dataset
import json
import wandb

# --- Configuración Inicial ---
wandb.login()  # Opcional

# --- Cargar Conjunto de Datos ---
with open("product_descriptions_dataset.json", "r") as f:
    raw_data = json.load(f)

dataset = Dataset.from_list(raw_data)

# --- Cargar Modelo y Tokenizer con QLoRA ---
model_name = "Qwen/Qwen2.5-0.5B-Instruct"

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,
)

model = AutoModelForCausalLM.from_pretrained(
    model_name,
    quantization_config=bnb_config,
    device_map="auto",
    trust_remote_code=True
)

tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token  # Requerido para padding

# --- Configurar LoRA ---
lora_config = LoraConfig(
    r=8,
    lora_alpha=16,
    target_modules=["q_proj", "v_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)

model = get_peft_model(model, lora_config)
model.print_trainable_parameters()

# --- Función de Formateo ---
def formatting_prompts_func(examples):
    instructions = examples["instruction"]
    inputs = examples["input"]
    outputs = examples["output"]
    texts = []
    for instruction, input_text, output in zip(instructions, inputs, outputs):
        text = f"### Instrucción:\n{instruction}\n\n### Entrada:\n{input_text}\n\n### Respuesta:\n{output}"
        texts.append(text)
    return texts

# --- Configuración de Entrenamiento ---
training_args = TrainingArguments(
    output_dir="./product_desc_results",
    num_train_epochs=5,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=8,
    optim="paged_adamw_8bit",
    save_steps=100,
    logging_steps=25,
    learning_rate=2e-4,
    weight_decay=0.01,
    fp16=True,
    max_grad_norm=0.3,
    warmup_ratio=0.03,
    lr_scheduler_type="cosine",
    report_to="wandb",
    evaluation_strategy="no",  # Saltar evaluación para simplificar
    save_total_limit=2,
)

# --- Crear y Ejecutar Trainer ---
trainer = SFTTrainer(
    model=model,
    args=training_args,
    train_dataset=dataset,
    formatting_func=formatting_prompts_func,
    max_seq_length=512,
    tokenizer=tokenizer,
    packing=False,
)

trainer.train()

# --- Guardar Adaptador ---
model.save_pretrained("./product_desc_adapter")
tokenizer.save_pretrained("./product_desc_adapter")

# --- Opcionalmente Fusionar y Guardar Modelo Completo ---
# model = model.merge_and_unload()
# model.save_pretrained("./product_desc_merged")
# tokenizer.save_pretrained("./product_desc_merged")

11.4 Evaluación Cualitativa

Después del entrenamiento, prueba el modelo en nuevos productos:

def generate_product_description(product_name, features, price):
    instruction = "Escribe una descripción comercial atractiva para un producto tecnológico."
    input_text = f"Nombre: {product_name}. Características: {features}. Precio: ${price}."
    prompt = f"### Instrucción:\n{instruction}\n\n### Entrada:\n{input_text}\n\n### Respuesta:\n"

    inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
    outputs = model.generate(
        **inputs,
        max_new_tokens=200,
        temperature=0.7,
        top_p=0.9,
        do_sample=True
    )
    full_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
    response = full_text.split("### Respuesta:")[-1].strip()
    return response

# Probar
desc = generate_product_description(
    "Altavoz Bluetooth Impermeable",
    "20W de potencia, 15 horas de batería, conexión multi-punto, diseño portátil",
    "79.99"
)

print(desc)

Salida Esperada (Ejemplo):
*"Lleva la fiesta a cualquier lugar con nuestro Altavoz Bluetooth Impermeable. Pura potencia de 20W que llena cualquier espacio con sonido inmersivo. Disfruta hasta 15 horas de música ininterrumpida, conecta dos dispositivos simultáneamente y llévalo a la playa, piscina o montaña con su diseño resistente y portátil. Diversión sin restricciones dondequiera que vayas. Solo $79.99."

Course Info

Course: AI-course3

Language: ES

Lesson: Module11