El último modelo de IA de código abierto de Google, Gemma 3, lanzado en marzo de 2025, ofrece un rendimiento impresionante que iguala al de muchos LLM propietarios, a la vez que funciona eficientemente en hardware con recursos limitados. Este avance en la IA de código abierto funciona en varias plataformas, ofreciendo a los desarrolladores de todo el mundo potentes capacidades en un formato accesible.
En esta guía, le guiaremos a través del ajuste fino de Gemma 3 en un conjunto de datos personalizado de respuesta a preguntas derivado de las reseñas de Trustpilot. Utilizaremos Bright Data para extraer las opiniones de los clientes, procesarlas en pares de QA estructurados y aprovechar Unsloth para un ajuste fino eficiente con un cálculo mínimo. Al final, habrás creado un asistente de IA especializado que entiende preguntas específicas del dominio y está listo para alojarse en Hugging Face Hub.
Sumerjámonos.
Comprender a Gemma 3
La familia Gemma 3 de Google se lanzó en marzo de 2025 con cuatro tamaños de peso abierto-1B, 4B, 12B y 27B parámetros-todos diseñados para ejecutarse en una sola GPU.
- El modelo 1B es sólo texto con una ventana contextual de 32K tokens.
- Los modelos 4B, 12B y 27B añaden entrada multimodal (texto + imagen) y admiten una ventana de 128K tokens.
En la clasificación de preferencias humanas de LMArena, Gemma 3-27B-IT se sitúa por delante de modelos mucho más grandes como Llama 3 405B y DeepSeek-V3, ofreciendo una calidad de vanguardia sin necesidad de utilizar varias GPU.
Fuente de la imagen: Presentación de Gemma 3
Características principales de los modelos Gemma 3
Estas son algunas características destacables de los modelos Gemma 3:
- La entrada multimodal (texto + imagen) está disponible en los modelos 4B, 12B y 27B.
- Contexto largo:hasta 128.000 fichas (32.000 en el modelo 1B).
- Capacidad multilingüe: más de 35 idiomas listos para usar y más de 140 idiomas preformados.
- Quantization-Aware Training – Las versiones oficiales de QAT reducen significativamente el uso de memoria (aproximadamente 3 veces) manteniendo una alta calidad.
- Llamada a funciones y salida estructurada – Incluye soporte integrado para automatizar llamadas y recibir respuestas estructuradas.
- Eficiencia: diseñada para ejecutarse en una sola GPU/TPU o incluso en dispositivos de consumo, desde teléfonos y portátiles hasta estaciones de trabajo.
- Seguridad (ShieldGemma) – Cuenta con un marco integrado de filtrado de contenidos.
¿Por qué afinar Gemma 3?
El ajuste fino toma un modelo preentrenado como Gemma 3 y le enseña nuevos comportamientos para su dominio o tarea específicos, sin el tiempo y el coste de un entrenamiento desde cero. Con su diseño compacto y, en las variantes 4B+, soporte multimodal, Gemma 3 es ligero, asequible y factible de ajustar incluso en hardware con recursos limitados.
Los beneficios de la puesta a punto incluyen:
- Especialización en el dominio: ayuda al modelo a comprender el lenguaje específico del sector y a realizar mejor las tareas especializadas dentro de su dominio.
- Mejora del conocimiento: añade hechos y contextos importantes que no formaban parte de los datos de entrenamiento originales del modelo.
- Perfeccionamiento del comportamiento – Ajusta la forma en que responde el modelo, para que se adapte al tono de su marca o al formato de salida preferido.
- Optimización de recursos: consigue resultados de alta calidad utilizando muchos menos recursos informáticos que si se entrenara un nuevo modelo desde cero.
Requisitos previos
Antes de empezar este tutorial, asegúrate de tener lo siguiente:
- Python 3.9 o superior está instalado en su sistema.
- Conocimientos básicos de programación en Python.
- Acceso a un entorno informático compatible con GPU (por ejemplo, Google Colab, Jupyter Notebook o Kaggle Notebooks).
- Comprensión de los fundamentos del aprendizaje automático y de los modelos lingüísticos amplios (LLM).
- Experiencia en el uso de un IDE como VS Code o similar.
También necesitarás credenciales de acceso a servicios externos:
- Una cuenta de Hugging Face y un token con acceso de Escritura.
👉 Crear un token aquí - Una cuenta de Bright Data y un token de API.
Regístrese y siga las instrucciones para generar un token. - Una cuenta de OpenAI y una clave de API.
👉 Consigue tu clave API aquí.
💡 Asegúrate de que tu cuenta de OpenAI tiene saldo suficiente. Gestiona la facturación aquí y haz un seguimiento del uso aquí.
Creación de un conjunto de datos personalizado para el ajuste fino
El ajuste fino funciona mejor cuando el conjunto de datos refleja fielmente el comportamiento que desea que aprenda el modelo. Al crear un conjunto de datos personalizado y adaptado a su caso de uso específico, puede mejorar drásticamente el rendimiento del modelo. Recuerde la regla clásica: “Basura dentro, basura fuera”. Por eso es tan importante invertir tiempo en la preparación del conjunto de datos.
Un conjunto de datos de alta calidad debería:
- Cuanto másse ajuste el conjunto de datos a la aplicación de destino, más pertinentes serán los resultados del modelo.
- Mantener un formato coherente – Una estructura uniforme (como los pares pregunta-respuesta) ayuda al modelo a aprender patrones con mayor eficacia.
- Incluir diversos ejemplos – Una variedad de escenarios ayuda al modelo a generalizar a través de diferentes entradas.
- Estar limpio y libre de errores – Eliminar las incoherencias y el ruido impide que el modelo capte comportamientos no deseados.
Empezaremos con críticas en bruto como ésta:
Y transfórmalos en pares estructurados de pregunta-respuesta como éste:
Este conjunto de datos enseñará a Gemma 3 a extraer información de las opiniones de los clientes, identificar patrones de sentimiento y ofrecer recomendaciones prácticas.
Pasos de configuración
#1 Instalar Bibliotecas: Abre el entorno de tu proyecto e instala todas las librerías Python necesarias listadas en el archivo requirements.txt. Puedes hacer esto ejecutando el siguiente comando en tu terminal o notebook:
pip install -r requirements.txt
#2 Configure las variables de entorno: Crea un archivo .env
en el directorio raíz de tu proyecto y almacena de forma segura tus claves API.
OPENAI_API_KEY="your_openai_key_here"
HF_TOKEN="your_hugging_face_token_here"
Paso 1: Recogida de datos con Bright Data
El primer paso crucial es la obtención de datos. Para construir nuestro conjunto de datos de ajuste fino, recopilaremos datos de reseñas sin procesar de Trustpilot. Debido a las sólidas medidas anti-bot de Trustpilot, utilizaremos la API Trustpilot Scraper de Bright Data. Esta API gestiona eficazmente la rotación de IP, la resolución de CAPTCHA y el manejo de contenido dinámico, lo que permite una recopilación eficiente de reseñas estructuradas a escala, evitando las complejidades de crear su propia solución de raspado.
He aquí un script en Python que muestra cómo utilizar la API de Bright Data para recopilar reseñas paso a paso:
import time
import json
import requests
from typing import Optional
# --- Configuration ---
API_KEY = "YOUR_API_KEY" # Replace with your Bright Data API key
DATASET_ID = "gd_lm5zmhwd2sni130p" # Replace with your Dataset ID
TARGET_URL = "https://www.trustpilot.com/review/hubspot.com" # Target company page
OUTPUT_FILE = "trustpilot_reviews.json" # Output file name
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
TIMEOUT = 30 # Request timeout in seconds
# --- Functions ---
def trigger_snapshot() -> Optional[str]:
"""Triggers a Bright Data snapshot collection job."""
print(f"Triggering snapshot for: {TARGET_URL}")
try:
resp = requests.post(
"https://api.brightdata.com/datasets/v3/trigger",
headers=HEADERS,
params={"dataset_id": DATASET_ID},
json=[{"url": TARGET_URL}],
timeout=TIMEOUT,
)
resp.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
snapshot_id = resp.json().get("snapshot_id")
print(f"Snapshot triggered successfully. ID: {snapshot_id}")
return snapshot_id
except requests.RequestException as e:
print(f"Error triggering snapshot: {e}")
except json.JSONDecodeError:
print(f"Error decoding trigger response: {resp.text}")
return None
def wait_for_snapshot(snapshot_id: str) -> Optional[list]:
"""Polls the API until snapshot data is ready and returns it."""
check_url = f"https://api.brightdata.com/datasets/v3/snapshot/{snapshot_id}"
print(f"Waiting for snapshot {snapshot_id} to complete...")
while True:
try:
resp = requests.get(
check_url,
headers=HEADERS,
params={"format": "json"},
timeout=TIMEOUT,
)
resp.raise_for_status()
# Check if response is the final data (list) or status info (dict)
if isinstance(resp.json(), list):
print("Snapshot data is ready.")
return resp.json()
else:
pass
except requests.RequestException as e:
print(f"Error checking snapshot status: {e}")
return None # Stop polling on error
except json.JSONDecodeError:
print(f"Error decoding snapshot status response: {resp.text}")
return None # Stop polling on error
print("Data not ready yet. Waiting 30 seconds...")
time.sleep(30)
def save_reviews(reviews: list, output_file: str) -> bool:
"""Saves the collected reviews list to a JSON file."""
try:
with open(output_file, "w", encoding="utf-8") as f:
json.dump(reviews, f, indent=2, ensure_ascii=False)
print(f"Successfully saved {len(reviews)} reviews to {output_file}")
return True
except (IOError, TypeError) as e:
print(f"Error saving reviews to file: {e}")
return False
except Exception as e:
print(f"An unexpected error occurred during saving: {e}")
return False
def main():
"""Main execution flow for collecting Trustpilot reviews."""
print("Starting Trustpilot review collection process...")
snapshot_id = trigger_snapshot()
if not snapshot_id:
print("Failed to trigger snapshot. Exiting.")
return
reviews = wait_for_snapshot(snapshot_id)
if not reviews:
print("Failed to retrieve reviews from snapshot. Exiting.")
return
if not save_reviews(reviews, OUTPUT_FILE):
print("Failed to save the collected reviews.")
else:
print("Review collection process completed.")
if __name__ == "__main__":
main()
Este script realiza los siguientes pasos:
- Autenticación: Utiliza su
API_KEY
para autenticarse con la API de Bright Data a través del encabezadoAutorización
. - Activar recopilación: Envía una solicitud POST para activar una “instantánea” de recopilación de datos para la
TARGET_URL
especificada (la página Trustpilot de HubSpot en este caso), asociada a suDATASET_ID
. - Espera a que se complete: Sondea periódicamente la API utilizando el
snapshot_id
devuelto para comprobar si se ha completado la recopilación de datos. - Recuperar datos: Una vez que la API indica que los datos están listos, el script obtiene los datos de la revisión en formato JSON.
- Guardar salida: Guarda la lista recopilada de objetos de revisión en un archivo JSON estructurado
(trustpilot_reviews.json
).
Cada revisión del archivo JSON resultante proporciona información detallada, como por ejemplo
{
"review_id": "680af52fb0bab688237f75c5",
"review_date": "2025-04-25T04:36:31.000Z",
"review_rating": 1,
"review_title": "Cancel Auto Renewal Doesn't Work",
"review_content": "I was with Hubspot for almost 3 years...",
"reviewer_name": "Steven Barrett",
"reviewer_location": "AU",
"is_verified_review": false,
"review_date_of_experience": "2025-04-19T00:00:00.000Z",
// Additional fields omitted for brevity
}
Aprende a encontrar los mejores datos para la formación LLM con nuestra guía: Las mejores fuentes de datos para la formación LLM.
Paso 2: Conversión de JSON a Markdown
Después de recopilar los datos de revisión en bruto, el siguiente paso es convertirlos a un formato limpio y legible adecuado para su procesamiento. Utilizaremos Markdown, que ofrece una estructura ligera de texto plano que reduce el ruido durante la tokenización, mejora potencialmente el rendimiento del ajuste fino y garantiza una separación coherente entre las distintas secciones de contenido.
Para realizar la conversión, simplemente ejecuta este script 👉 convert-trustpilot-json-to-markdown.py
Este script lee los datos JSON de la salida del Paso 1 y genera un archivo Markdown que contiene un resumen estructurado y las opiniones individuales de los clientes.
He aquí un ejemplo de la estructura de salida Markdown:
# HubSpot Review Summary
[Visit Website](https://www.hubspot.com/)
**Overall Rating**: 2.3
**Total Reviews**: 873
**Location**: United States
**Industry**: Electronics & Technology
> HubSpot is a leading growth platform... Grow Better.
---
### Review by Steven Barrett (AU)
- **Posted on**: April 25, 2025
- **Experience Date**: April 19, 2025
- **Rating**: 1
- **Title**: *Cancel Auto Renewal Doesn't Work*
I was with Hubspot for almost 3 years... Avoid.
[View Full Review](https://www.trustpilot.com/reviews/680af52fb0bab688237f75c5)
---
Aprende por qué los agentes de IA prefieren Markdown a HTML leyendo más en nuestra guía.
Paso 3: Agrupar y procesar el documento
Con el documento Markdown listo, el siguiente paso crucial es dividirlo en trozos más pequeños y manejables. Esto es importante porque los grandes modelos lingüísticos (LLM) tienen límites de tokens de entrada, y el ajuste suele funcionar mejor con ejemplos de una longitud adecuada. Además, el procesamiento de estos fragmentos puede mejorar su claridad y coherencia para el modelo.
Utilizamos RecursiveCharacterTextSplitter
de LangChain para dividir el archivo Markdown. Este método divide el texto de forma recursiva basándose en una lista de separadores, lo que ayuda a mantener juntos los fragmentos de texto relacionados. Para preservar el contexto que podría extenderse a través de los puntos de división, aplicamos un solapamiento entre trozos consecutivos. Para este proceso, utilizamos un tamaño de trozo de 1.024 caracteres con un solapamiento de 256 caracteres.
Tras la división, cada fragmento se pasa opcionalmente a un LLM (como GPT-4o) para mejorar su claridad y coherencia generales, manteniendo estrictamente el significado original del texto de revisión. El objetivo de este paso de mejora es que la estructura de datos y el contenido de cada fragmento queden perfectamente claros para el posterior proceso de ajuste.
A continuación, se asigna a cada trozo procesado un identificador único y se almacena en un archivo con formato JSON Lines (.jsonl
), preparándolo para la siguiente etapa del pipeline.
Aquí está la función Python utilizando el LLM para mejorar la claridad:
def improve_review_chunk(text: str, client: OpenAI, model: str = "gpt-4o") -> str:
prompt = """Improve this review's clarity while preserving its meaning:
{text}
Return only the improved text without additional commentary."""
response = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": prompt},
{"role": "user", "content": text}
]
)
return response.choices[0].message.content
Encuentra el código completo para este paso aquí 👉 split-markdown-into-chunks.py
El resultado es un archivo JSON Lines en el que cada línea representa un fragmento de revisión con un identificador único y el contenido de la revisión potencialmente mejorado:
[
{
"id": "f8a3b1c9-e4d5-4f6a-8b7c-2d9e0a1b3c4d", // Unique chunk ID
"review": "# HubSpot Review Summary\\n\\n[Visit Website](https://www.hubspot.com/)...\\n---\\n\\n### Review by Steven Barrett (AU)\\n- **Posted on**: April 25, 2024...\\n- **Rating**: 1\\n- **Title**: *Cancel Auto Renewal Doesn't Work*\\n\\nI was with Hubspot for almost 3 years... [Text continues - may be improved]" // Chunk content (potentially refined)
},
// ... more chunk objects
]
Paso 4: Generación de pares de control de calidad
El paso final de preparación de datos transforma los fragmentos de revisión procesados en pares estructurados de pregunta-respuesta (QA) adecuados para el ajuste fino de un modelo lingüístico. Utilizamos GPT-4o de OpenAI para generar un par QA por cada fragmento del archivo .jsonl
creado en el paso 3.
Para cada trozo, el script llama a la API de OpenAI utilizando un prompt del sistema cuidadosamente diseñado:
SYSTEM_PROMPT = """
You are an expert at transforming customer reviews into insightful question–answer pairs. For each review, generate exactly 1 high-quality QA pair.
PURPOSE:
These QA pairs will train a customer service AI to understand feedback patterns about HubSpot products and identify actionable insights.
GUIDELINES FOR QUESTIONS:
- Make questions general and applicable to similar situations
- Phrase from a stakeholder perspective (e.g., "What feature gaps are causing customer frustration?")
- Focus on product features, usability, pricing, or service impact
GUIDELINES FOR ANSWERS:
- Provide analytical responses (3–5 sentences)
- Extract insights without quoting verbatim
- Offer actionable recommendations
- Maintain objectivity and clarity
FORMAT REQUIREMENTS:
- Start with "Q: " followed by your question
- Then "A: " followed by a plain-text answer
"""
El script incluye mecanismos integrados de limitación de velocidad y reintento para gestionar las interrupciones temporales de la API y garantizar una ejecución estable. Puedes encontrar la implementación completa en generate-qa-pairs.py.
El resultado se guarda como una matriz JSON, donde cada objeto contiene un par de preguntas y respuestas generadas, vinculadas por el ID del fragmento original:
[
{
"id": "82d53a10-9f37-4d03-8d3b-38812e39ecdc",
"question": "How can pricing and customer support issues impact customer satisfaction and retention for HubSpot?",
"answer": "Pricing concerns, particularly when customers feel they are overpaying for services they find unusable or unsupported, can significantly impact customer satisfaction and retention..."
}
// ... more QA pairs
]
Una vez generado, se recomienda encarecidamente enviar el conjunto de datos de control de calidad resultante al Hugging Face Hub. De este modo, podrá acceder fácilmente a él para ajustarlo y compartirlo. Puedes ver un ejemplo del conjunto de datos publicado aquí: trustpilot-reviews-qa-dataset.
Puesta a punto de Gemma 3 con Unsloth: Paso a paso
Ahora que ya tenemos preparado nuestro conjunto de datos personalizado de preguntas y respuestas, vamos a ajustar el modelo Gemma 3. Utilizaremos Unsloth, una biblioteca de código abierto que proporciona mejoras significativas de memoria y velocidad para el entrenamiento LoRA/QLoRA en comparación con las implementaciones estándar de Hugging Face. Estas optimizaciones hacen que los modelos de ajuste fino como Gemma 3 sean más accesibles en configuraciones de una sola GPU, siempre que la GPU tenga suficiente VRAM.
Talla Gemma 3 | VRAM aproximada requerida* | Plataformas adecuadas |
---|---|---|
4B | ~15 GB | Google Colab gratuito (T4), Kaggle (P100 16 GB) |
12B | ≥24 GB | Colab Pro+ (A100/A10), RTX 4090, A40 |
27B | 22-24 GB (con QLoRA de 4 bits, tamaño de lote = 1); ~40 GB en caso contrario | A100 40 GB, H100, configuraciones multi-GPU |
Nota: Los requisitos de VRAM pueden variar en función del tamaño del lote, la longitud de la secuencia y las técnicas específicas de cuantificación. El requisito para el modelo 27B es con QLoRA de 4 bits y un tamaño de lote pequeño (por ejemplo, 1 o 2); tamaños de lote mayores o una cuantización menos agresiva requerirán mucha más VRAM (~40 GB+).
Para los principiantes, se recomienda empezar con el modelo 4B en un portátil gratuito de Colab, ya que soporta cómodamente la carga, la formación y el despliegue con Unsloth. La actualización a los modelos 12B o 27B solo debería considerarse cuando se disponga de acceso a GPUs con mayor RAM o a niveles de nube de pago.
Para cambiar el tipo de tiempo de ejecución en Google Colab y seleccionar una GPU T4, sigue estos pasos:
- Haga clic en el menú Runtime de la parte superior.
- Seleccione Cambiar tipo de tiempo de ejecución.
- En el cuadro de diálogo que aparece, en Acelerador de hardware, seleccione GPU.
- Haga clic en Guardar para aplicar los cambios.
Paso 1: Configuración del entorno
En primer lugar, instala las bibliotecas necesarias. Si estás en un entorno Colab o Jupyter, puedes ejecutar estos comandos directamente en una celda de código.
%%capture
!pip install --no-deps unsloth vllm
import sys, re, requests; modules = list(sys.modules.keys())
for x in modules: sys.modules.pop(x) if "PIL" in x or "google" in x else None
!pip install --no-deps bitsandbytes accelerate xformers==0.0.29.post3 peft "trl==0.15.2" triton cut_cross_entropy unsloth_zoo
!pip install sentencepiece protobuf datasets huggingface_hub hf_transfer
# vLLM requirements - vLLM breaks Colab due to reinstalling numpy
f = requests.get("https://raw.githubusercontent.com/vllm-project/vllm/refs/heads/main/requirements/common.txt").content
with open("vllm_requirements.txt", "wb") as file:
file.write(re.sub(rb"(transformers|numpy|xformers)[^\n]{1,}\n", b"", f))
!pip install -r vllm_requirements.txt
He aquí una breve explicación de los principales paquetes instalados:
unsloth
: Proporciona las optimizaciones básicas para un entrenamiento y carga LLM más rápidos y eficientes en memoria utilizando técnicas como los núcleos fusionados.peft
: Parameter-Efficient Fine-Tuning methods (como LoRA). Permite entrenar solo un pequeño número de parámetros adicionales en lugar del modelo completo.trl
: Transformer Reinforcement Learning. Incluye elSFTTrainer
, que simplifica el proceso de ajuste fino supervisado.bitsybytes
: Permite la cuantización en k bits (4 y 8 bits), lo que reduce drásticamente el espacio de memoria del modelo.acelerar
: Biblioteca Hugging Face para ejecutar sin problemas el entrenamiento PyTorch a través de diversas configuraciones de hardware (una sola GPU, multi-GPU, etc.).conjuntos de datos
: Biblioteca Hugging Face para cargar, procesar y gestionar conjuntos de datos de forma eficiente.transformers
: biblioteca central de Hugging Face para modelos preformados, tokenizadores y utilidades.huggingface_hub
: Utilidades para interactuar con el Hugging Face Hub (inicio de sesión, descarga, carga).vllm
(Opcional): Un rápido motor de inferencia LLM. Puede instalarse por separado si es necesario para el despliegue.
Paso 2: Autenticación facial mediante abrazo
Tendrás que conectarte al Hugging Face Hub desde tu entorno para descargar el modelo y, potencialmente, cargar el resultado afinado más tarde.
import os
from huggingface_hub import login
from google.colab import userdata
hf_token = userdata.get('HF_TOKEN')
if not hf_token:
raise ValueError("Please set your HF_TOKEN environment variable before running.")
try:
login(hf_token)
print("Successfully logged in to Hugging Face Hub.")
except Exception as e:
print(f"Error logging in to Hugging Face Hub: {e}")
En Google Colab, la forma más segura de gestionar tu token Cara de abrazo es utilizando la pestaña “Secretos”:
Paso 3: Cargar el modelo y el tokenizador
Para comenzar el ajuste fino, cargaremos eficientemente el modelo ajustado a instrucciones Gemma 3 utilizando FastModel
de Unsloth. Para este ejemplo, utilizaremos el modelo unsloth/gemma-3-4b-it
, que es una versión cuantificada de 4 bits optimizada por Unsloth para ajustarse a las limitaciones de memoria de las GPUs Colab típicas.
Echa un vistazo a la colección Gemma 3 de Unsloth en Hugging Face. Incluye modelos en tamaños 1B, 4B, 12B y 27B, disponibles en formatos GGUF, 4 bits y 16 bits.
from unsloth import FastModel
from unsloth.chat_templates import get_chat_template
import torch # Import torch for checking CUDA
# Ensure CUDA is available
if not torch.cuda.is_available():
raise RuntimeError("CUDA is not available. A GPU is required for this tutorial.")
print(f"CUDA available: {torch.cuda.is_available()}")
print(f"CUDA device name: {torch.cuda.get_device_name(0)}")
model, tokenizer = FastModel.from_pretrained(
model_name="unsloth/gemma-3-4b-it", # Using the 4B instruction-tuned model optimized by Unsloth
max_seq_length=2048, # Set max context length
load_in_4bit=True, # Enable 4-bit quantization
full_finetuning=False, # Use PEFT (LoRA)
token=hf_token, # Pass your Hugging Face token
)
# Apply the correct chat template for Gemma 3
tokenizer = get_chat_template(tokenizer, chat_template="gemma-3")
print("Model and Tokenizer loaded successfully.")
Lo que está pasando en este código:
FastModel.from_pretrained()
: El cargador de modelos optimizado de Unsloth.model_name="unsloth/gemma-3-4b-it"
: Especifica la variante del modelo a cargar. Elegimos la versión ajustada a instrucciones 4B(it)
, preoptimizada por Unsloth.max_seq_length=2048
: Establece el número máximo de tokens que el modelo puede procesar a la vez. Ajústalo en función de la longitud de los fragmentos de datos y de la ventana de contexto deseada, equilibrando el uso de memoria y la capacidad de procesar entradas más largas.load_in_4bit=True
: Esencial para entrenar con VRAM limitada. Esto carga los pesos del modelo en precisión de 4 bits usandobitsandbytes
.full_finetuning=False
: Indica a Unsloth que prepare el modelo para el ajuste fino PEFT/LoRA, lo que significa que sólo se entrenarán las capas adaptadoras, no todos los parámetros del modelo.get_chat_template(tokenizer, chat_template="gemma-3")
: Envuelve el tokenizador para formatear automáticamente las instrucciones en el formato de chat esperado de Gemma 3(<start_of_turn>user\n...\n<end_of_turn><start_of_turn>model\n...\n<end_of_turn>
). Esto es crucial para ajustar correctamente los modelos de seguimiento de instrucciones y garantizar que el modelo aprenda a generar respuestas en los turnos de conversación esperados.
Paso 4: Carga y preparación del conjunto de datos para el entrenamiento
Cargamos el conjunto de datos que previamente hemos subido al Hugging Face Hub y lo transformamos en el formato basado en chat que esperan el tokenizador y el entrenador.
from datasets import load_dataset
from unsloth.chat_templates import standardize_data_formats, train_on_responses_only # train_on_responses_only imported earlier
# 1. Load the dataset from Hugging Face Hub
dataset_name = "triposatt/trustpilot-reviews-qa-dataset" # Replace with your dataset name
dataset = load_dataset(dataset_name, split="train")
print(f"Dataset '{dataset_name}' loaded.")
print(dataset)
# 2. Normalize any odd formats (ensure 'question' and 'answer' fields exist)
dataset = standardize_data_formats(dataset)
print("Dataset standardized.")
# 3. Define a function to format examples into chat template
def formatting_prompts_func(examples):
"""Formats question-answer pairs into Gemma 3 chat template."""
questions = examples["question"]
answers = examples["answer"]
texts = []
for q, a in zip(questions, answers):
# Structure the conversation as a list of roles and content
conv = [
{"role": "user", "content": q},
{"role": "assistant", "content": a},
]
# Apply the chat template
txt = tokenizer.apply_chat_template(
conv,
tokenize=False, # Return string, not token IDs
add_generation_prompt=False # Don't add the model's start tag at the end yet
)
# Gemma 3 tokenizer adds <bos> by default, which the trainer will re-add
# We remove it here to avoid double <bos> tokens
txt = txt.removeprefix(tokenizer.bos_token)
texts.append(txt)
return {"text": texts}
# 4. Apply the formatting function to the dataset
dataset = dataset.map(formatting_prompts_func, batched=True, remove_columns=["question", "answer"])
print("Dataset formatted with chat template.")
print(dataset) # Inspect the new 'text' column
En este código:
load_dataset()
: Obtiene nuestro conjunto de datos de preguntas y respuestas del Hugging Face Hub.normalizar_formatos_datos()
: Garantiza la coherencia de los nombres de los campos en los distintos conjuntos de datos, buscando específicamente “pregunta” y “respuesta” en este caso.formatting_prompts_func()
: Esta función crítica procesa lotes de nuestros pares de preguntas y respuestas. Utiliza el métodotokenizer.apply_chat_template()
para convertirlos en cadenas formateadas correctamente para el ajuste fino de instrucciones de Gemma 3. Este formato incluye tokens de turno especiales como<start_of_turn>user\n
y<start_of_turn>model\n
, que son esenciales para que el modelo comprenda la estructura conversacional. Eliminamos el token inicial<bos>
, ya queel SFTTrainer
añade el suyo propio.dataset.map(...)
: Aplica laformatting_prompts_func
a todo el conjunto de datos de forma eficiente, creando una nueva columna ‘text’ que contiene las cadenas formateadas y eliminando las columnas originales.
Paso 5: Configurar LoRA y el Entrenador
Ahora configuramos los ajustes de PEFT (LoRA) y el SFTTrainer
de la biblioteca trl
. LoRA funciona inyectando pequeñas matrices entrenables en capas clave del modelo preentrenado. Sólo estas pequeñas matrices adaptadoras se actualizan durante el ajuste fino, reduciendo drásticamente el número de parámetros a entrenar y minimizando así el uso de memoria.
from trl import SFTTrainer, SFTConfig
import torch
# 1. Configure LoRA
model = FastModel.get_peft_model(
model,
r=8, # LoRA rank (a common value) - lower rank means fewer parameters, higher means more expressive
target_modules=[
"q_proj", "k_proj", "v_proj", "o_proj", # Attention layers
"gate_proj", "up_proj", "down_proj" # MLP layers
],
# Set True if you want to fine-tune language layers (recommended for text tasks)
# and Attention/MLP modules (where LoRA is applied)
finetune_language_layers=True,
finetune_attention_modules=True,
finetune_mlp_modules=True,
# finetune_vision_layers=False, # Only relevant for multimodal models (12B/27B)
lora_alpha=8, # LoRA scaling factor (often set equal to r)
lora_dropout=0, # Dropout for LoRA layers
bias="none", # Don't train bias terms
use_gradient_checkpointing="unsloth", # Memory optimization
random_state=1000, # Seed for reproducibility
use_rslora=False, # Rank-Stabilized LoRA (optional alternative)
# modules_to_save=["embed_tokens", "lm_head"], # Optional: train embedding/output layers
)
print("Model configured for PEFT (LoRA).")
# 2. Configure the SFTTrainer
# Determine a reasonable max_steps based on dataset size and epochs
# For demonstration, a small number of steps is used (e.g., 30)
# For a real use case, calculate steps = (dataset_size / batch_size / grad_accum) * num_epochs
dataset_size = len(dataset)
per_device_train_batch_size = 2 # Adjust based on your GPU VRAM
gradient_accumulation_steps = 4 # Accumulate gradients to simulate larger batch size (batch_size * grad_accum = 8)
num_train_epochs = 3 # Example: 3 epochs
# Calculate total training steps
total_steps = int((dataset_size / per_device_train_batch_size / gradient_accumulation_steps) * num_train_epochs)
# Ensure max_steps is not 0 if dataset is small or calculation results in < 1 step
max_steps = max(30, total_steps) # Set a minimum or calculate properly
print(f"Calculated total training steps for {num_train_epochs} epochs: {total_steps}. Using max_steps={max_steps}")
sft_config = SFTConfig(
dataset_text_field="text", # The column in our dataset containing the formatted chat text
per_device_train_batch_size=per_device_train_batch_size,
gradient_accumulation_steps=gradient_accumulation_steps,
warmup_steps=max(5, int(max_steps * 0.03)), # Warmup for first few steps (e.g., 3% of total steps)
max_steps=max_steps, # Total number of training steps
learning_rate=2e-4, # Learning rate
logging_steps=max(1, int(max_steps * 0.01)), # Log every 1% of total steps (min 1)
optim="adamw_8bit", # 8-bit AdamW optimizer (memory efficient)
weight_decay=0.01, # L2 regularization
lr_scheduler_type="linear", # Linear learning rate decay
seed=3407, # Random seed
report_to="none", # Disable reporting to platforms like W&B unless needed
output_dir="./results", # Directory to save checkpoints and logs
)
# 3. Build the SFTTrainer instance
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=dataset,
eval_dataset=None, # Optional: provide a validation dataset
args=sft_config,
)
print("SFTTrainer built.")
# 4. Mask out the input portion for training
# This teaches the model to only generate the assistant’s response
# It prevents the model from just copying the user’s prompt
# Pass the literal prefixes for instruction and response turns from the chat template
trainer = train_on_responses_only(
trainer,
instruction_part="<start_of_turn>user\n", # Literal string before user content
response_part="<start_of_turn>model\n", # Literal string before model content
)
print("Trainer configured to train only on responses.")
En este código:
FastModel.get_peft_model()
: Configura el modelo cargado para el ajuste fino LoRA con los parámetros especificados.r
es el rango LoRA, que controla el tamaño de las matrices adaptadoras.target_modules
especifica qué capas del modelo (como la atención y las proyecciones MLP) recibirán estos adaptadores.lora_alpha
es un factor de escala.use_gradient_checkpointing
es una técnica de ahorro de memoria proporcionada por Unsloth.SFTConfig()
: Define los hiperparámetros de entrenamiento parael SFTTrainer
.per_device_train_batch_size
ygradient_accumulation_steps
trabajan juntos para determinar el tamaño efectivo del lote utilizado para calcular gradientes.max_steps
establece el total de iteraciones de entrenamiento.learning_rate
,optim
,weight_decay
ylr_scheduler_type
controlan el proceso de optimización.dataset_text_field
indica al entrenador qué columna del conjunto de datos contiene los ejemplos de entrenamiento formateados.SFTTrainer()
: Instancia del entrenador, que reúne el modelo configurado por LoRA, el conjunto de datos preparado, el tokenizador y los argumentos de entrenamiento definidos enSFTConfig
.train_on_responses_only()
: Una función de utilidad (parte detrl
y compatible con Unsloth) que modifica el cálculo de pérdidas del entrenador. Establece que la pérdida se calcule sólo sobre los tokens correspondientes a la respuesta esperada del modelo(
<inicio_de_turno>modelo\n.
.
.), ignorando los tokens de la pregunta del usuario(<inicio_de_turno>usuario\n..
.). Esto es esencial para enseñar al modelo a generar respuestas relevantes en lugar de simplemente repetir o completar la pregunta de entrada. Proporcionamos los prefijos de cadena exactos utilizados en la plantilla de chat para delimitar estas secciones.
Paso 6: Entrenamiento del modelo
Con todo configurado, podemos iniciar el proceso de ajuste. El método trainer.train(
) se encarga del bucle de entrenamiento basado en las configuraciones proporcionadas en SFTConfig
.
# Optional: clear CUDA cache before training
torch.cuda.empty_cache()
print("Starting training...")
# Use mixed precision training for efficiency
# Unsloth automatically handles float16/bf16 based on GPU capabilities and model
with torch.amp.autocast(device_type="cuda", dtype=torch.float16): # Or torch.bfloat16 if supported
trainer.train()
print("Training finished.")
El entrenador mostrará actualizaciones de progreso, incluyendo la pérdida de entrenamiento. Debería observar que la pérdida disminuye a lo largo de los pasos, lo que indica que el modelo está aprendiendo de los datos. El tiempo total de entrenamiento dependerá del tamaño del conjunto de datos, el tamaño del modelo, los hiperparámetros y la GPU utilizada. Para nuestro conjunto de datos de ejemplo y el modelo 4B en una GPU T4, el entrenamiento para 200 pasos debería completarse con relativa rapidez (por ejemplo, menos de 15-30 minutos, dependiendo de la configuración exacta y la longitud de los datos).
Paso 7: Comprobación del modelo ajustado (inferencia)
Después del entrenamiento, vamos a probar nuestro modelo afinado para ver lo bien que responde a las preguntas basadas en los datos de revisión de Trustpilot con los que fue entrenado. Utilizaremos el método model.generate
con un TextStreamer
para obtener un resultado más interactivo.
from transformers import TextStreamer
# Define some test questions related to the dataset content
questions = [
"What are common issues or complaints mentioned in the reviews?",
"What do customers like most about the product/service?",
"How is the customer support perceived?",
"Are there any recurring themes regarding pricing or value?"
# Add more questions here based on your dataset content
]
# Set up a streamer for real-time output
# skip_prompt=True prevents printing the input prompt again
# skip_special_tokens=True removes chat template tokens from output
streamer = TextStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
print("\n--- Testing Fine-Tuned Model ---")
# Iterate through questions and generate answers
for idx, q in enumerate(questions, start=1):
# Build the conversation prompt in the correct Gemma 3 chat format
conv = [{"role": "user", "content": q}]
# Apply the chat template and add the generation prompt token
# add_generation_prompt=True includes the <start_of_turn>model tag
prompt = tokenizer.apply_chat_template(
conv,
add_generation_prompt=True,
tokenize=False
)
# Tokenize the prompt and move to GPU
inputs = tokenizer([prompt], return_tensors="pt", padding=True).to("cuda")
# Display the question
print(f"\n=== Question {idx}: {q}\n")
# Generate the response with streaming
# Pass the tokenized inputs directly to model.generate
_ = model.generate(
**inputs,
streamer=streamer, # Use the streamer for token-by-token output
max_new_tokens=256, # Limit the response length
temperature=0.7, # Control randomness (lower=more deterministic)
top_p=0.95, # Nucleus sampling
top_k=64, # Top-k sampling
use_cache=True, # Use cache for faster generation
# Add stopping criteria if needed, e.g., stopping after <end_of_turn>
# eos_token_id=tokenizer.eos_token_id,
)
# Add a separator after each answer
print("\n" + "="*40)
print("\n--- Testing Complete ---")
Vea las respuestas de la modelo en la imagen inferior:
🔥 ¡Genial, funciona bien!
Si el proceso de ajuste es satisfactorio, el modelo genera respuestas más analíticas y derivadas directamente del contenido de las reseñas con las que se ha ajustado, reflejando el estilo y los conocimientos presentes en el conjunto de datos personalizados, en lugar de respuestas genéricas.
Paso 8: Guardar e impulsar el modelo perfeccionado
Por último, guarda los adaptadores LoRA y el tokenizador ajustados. Puedes guardarlos localmente y también enviarlos al Hugging Face Hub para compartirlos, versionarlos y desplegarlos fácilmente.
# Define local path and Hub repository ID
new_model_local = "gemma-3-4b-trustpilot-qa-adapter" # Local directory name
new_model_online = "YOUR_HF_USERNAME/gemma-3-4b-trustpilot-qa" # Hub repo name
# 1. Save locally
print(f"Saving model adapter and tokenizer locally to '{new_model_local}'...")
model.save_pretrained(new_model_local)
tokenizer.save_pretrained(new_model_local)
print("Saved locally.")
# 2. Push to Hugging Face Hub
print(f"Pushing model adapter and tokenizer to Hugging Face Hub '{new_model_online}'...")
model.push_to_hub(new_model_online, token=hf_token)
tokenizer.push_to_hub(new_model_online, token=hf_token)
El modelo perfeccionado ya está disponible en Hugging Face Hub:
Conclusión
Esta guía muestra un enfoque integral para ajustar Gemma 3 de Google a un caso de uso práctico: generar respuestas analíticas a partir de las opiniones de los clientes. Cubrimos todo el flujo de trabajo, desde la recopilación de datos específicos del dominio de alta calidad a través de la API de raspado web de Bright Data, pasando por su estructuración en un formato de control de calidad mediante el procesamiento basado en LLM, hasta el ajuste fino del modelo Gemma 3 4B mediante la biblioteca Unsloth en un hardware con recursos limitados.
El resultado es un LLM especializado que es experto en extraer información e interpretar los sentimientos a partir de datos de reseñas sin procesar, transformándolos en respuestas estructuradas y procesables. Este método es muy adaptable: se puede aplicar este mismo flujo de trabajo para ajustar Gemma 3 (u otros LLM adecuados) en varios conjuntos de datos de dominios específicos para crear asistentes de IA adaptados a diferentes necesidades.
Si desea profundizar en las estrategias de extracción de datos basadas en IA, consulte estos recursos adicionales:
- Web Scraping con LLaMA 3
- Web Scraping con servidores MCP
- Scraping basado en IA con LLM-Scraper
- ScrapeGraphAI para el raspado web LLM
Para más optimizaciones y ejemplos de uso de Unsloth, consulte la colección de cuadernos de Unsloth.
No se requiere tarjeta de crédito