Ajusta Gemini en Vertex AI

1. Introducción

En este lab, aprenderás a realizar el flujo de trabajo completo del ajuste supervisado en un modelo de Google Gemini para adaptarlo a una tarea específica: el resumen de artículos. Si bien los modelos de lenguaje grandes son potentes, su naturaleza de uso general significa que se pueden hacer aún más eficaces para casos de uso específicos a través del ajuste. Si entrenas el modelo en un conjunto de datos de ejemplos de alta calidad, puedes mejorar su coherencia, calidad y eficiencia para la tarea objetivo.

Usarás Gemini 2.5 Flash, un modelo ligero y rentable, y realizarás el ajuste con Vertex AI.

Descripción general de la arquitectura

Esto es lo que compilaremos:

  • Cloud Shell: Tu entorno de desarrollo
  • Cloud Storage: Almacena datos de entrenamiento o validación en formato JSONL
  • Vertex AI Training: Administra el trabajo de ajuste
  • Extremo de Vertex AI: Aloja tu modelo ajustado

Qué aprenderás

  • Prepara conjuntos de datos de alta calidad para el ajuste supervisado.
  • Configura y, luego, inicia trabajos de ajuste con el SDK de Vertex AI para Python.
  • Evalúa modelos con métricas automatizadas (puntuaciones ROUGE).
  • Compara modelos de base y ajustados para cuantificar las mejoras.

2. Configura el proyecto

Cuenta de Google

Si aún no tienes una Cuenta de Google personal, debes crear una Cuenta de Google.

Usa una cuenta personal en lugar de una cuenta de trabajo o de institución educativa.

Accede a la consola de Google Cloud

Accede a la consola de Google Cloud con una Cuenta de Google personal.

Habilitar facturación

Canjea USD 5 en créditos de Google Cloud (opcional)

Para ejecutar este taller, necesitas una cuenta de facturación con algunos créditos. Si planeas usar tu propia facturación, puedes omitir este paso.

  1. Haz clic en este vínculo y accede con una cuenta personal de Google.Verás algo como esto:Haz clic para autorizar Cloud Shell
  2. Haz clic en el botón HAZ CLIC AQUÍ PARA ACCEDER A TUS CRÉDITOS.Se te dirigirá a una página para configurar tu perfil de facturaciónHaz clic para autorizar Cloud Shell
  3. Haz clic en Confirmar.

Ahora estás conectado a una cuenta de facturación de prueba de Google Cloud Platform.

Captura de pantalla de la descripción general de la facturación

Crear un proyecto (opcional)

Si no tienes un proyecto actual que te gustaría usar para este lab, crea uno nuevo aquí.

3. Abre el editor de Cloud Shell

  1. Haz clic en este vínculo para navegar directamente al editor de Cloud Shell.
  2. Si se te solicita autorización en algún momento hoy, haz clic en Autorizar para continuar.Haz clic para autorizar Cloud Shell
  3. Si la terminal no aparece en la parte inferior de la pantalla, ábrela:
    • Haz clic en Ver.
    • Haz clic en TerminalAbre una terminal nueva en el editor de Cloud Shell
  4. En la terminal, configura tu proyecto con este comando:
    gcloud config set project [PROJECT_ID]
    
    • Ejemplo:
      gcloud config set project lab-project-id-example
      
    • Si no recuerdas el ID del proyecto, puedes enumerar todos los IDs de tus proyectos con:
      gcloud projects list
      
      Establece el ID del proyecto en la terminal del editor de Cloud Shell
  5. Deberías ver el siguiente mensaje:
    Updated property [core/project].
    

4. Habilita las APIs

Para usar Vertex AI y otros servicios, debes habilitar las APIs necesarias en tu proyecto de Google Cloud.

  1. En la terminal, habilita las APIs:
    • API de Vertex AI (aiplatform.googleapis.com): Permite el uso de Vertex AI para ajustar y entregar modelos.
    • API de Cloud Storage (storage.googleapis.com): Permite el almacenamiento de conjuntos de datos y artefactos de modelos.
    gcloud services enable aiplatform.googleapis.com \
        storage.googleapis.com
    

5. Configura el entorno del proyecto

Crea un directorio de trabajo

  1. En la terminal, crea un directorio para tu proyecto y navega hasta él.
    mkdir gemini-finetuning
    cd gemini-finetuning
    

Configura variables de entorno

  1. En la terminal, define las variables de entorno para tu proyecto. Crearemos un archivo env.sh para almacenar estas variables de modo que se puedan volver a cargar fácilmente si se desconecta tu sesión.
    cat <<EOF > env.sh
    export PROJECT_ID=\$(gcloud config get-value project)
    export REGION="us-central1"
    export BUCKET_NAME="\${PROJECT_ID}-gemini-tuning"
    EOF
    
    source env.sh
    

Crea un bucket de Cloud Storage

  1. En la terminal, crea un bucket para almacenar tu conjunto de datos y artefactos de modelos.
    gcloud storage buckets create gs://$BUCKET_NAME --project=$PROJECT_ID --location=$REGION
    

Configura el entorno virtual

  1. Usaremos uv para administrar nuestro entorno de Python. En la terminal, ejecuta lo siguiente:
    uv venv .venv
    source .venv/bin/activate
    
  2. En la terminal, instala los paquetes de Python necesarios.
    uv pip install google-cloud-aiplatform rouge-score matplotlib pandas tqdm
    

6. Prepara los datos de entrenamiento

Los datos de calidad son la base del ajuste exitoso. Usarás el conjunto de datos de WikiLingua, lo transformarás en el formato JSONL específico que requiere Gemini y lo subirás a tu bucket de almacenamiento.

  1. En la terminal, crea un archivo llamado prepare_data.py.
    cloudshell edit prepare_data.py
    
  2. Pega el siguiente código en prepare_data.py.
    import json
    import os
    import pandas as pd
    from google.cloud import storage
    import subprocess
    
    # Configuration
    BUCKET_NAME = os.environ["BUCKET_NAME"]
    PROJECT_ID = os.environ["PROJECT_ID"]
    
    def download_data():
        print("Downloading WikiLingua dataset...")
        # Using gsutil to copy from public bucket
        subprocess.run(["gsutil", "cp", "gs://github-repo/generative-ai/gemini/tuning/summarization/wikilingua/*", "."], check=True)
    
    def convert_to_gemini_format(input_file, output_file, max_samples=1000):
        print(f"Converting {input_file} to Gemini format (first {max_samples} samples)...")
        converted_data = []
        with open(input_file, 'r') as f:
            for i, line in enumerate(f):
                if i >= max_samples:
                    break
                obj = json.loads(line)
                messages = obj.get("messages", [])
    
                # Convert messages to Gemini 2.5 format
                # Input: {"messages": [{"role": "user", "content": "..."}, {"role": "model", "content": "..."}]}
                # Output: {"contents": [{"role": "user", "parts": [{"text": "..."}]}, {"role": "model", "parts": [{"text": "..."}]}]}
    
                contents = []
                for msg in messages:
                    role = msg["role"]
                    content = msg["content"]
                    contents.append({
                        "role": role,
                        "parts": [{"text": content}]
                    })
    
                converted_data.append({"contents": contents})
    
        with open(output_file, 'w') as f:
            for item in converted_data:
                f.write(json.dumps(item) + "\n")
    
        print(f"Saved {len(converted_data)} examples to {output_file}")
    
    def upload_to_gcs(local_file, destination_blob_name):
        print(f"Uploading {local_file} to gs://{BUCKET_NAME}/{destination_blob_name}...")
        storage_client = storage.Client(project=PROJECT_ID)
        bucket = storage_client.bucket(BUCKET_NAME)
        blob = bucket.blob(destination_blob_name)
        blob.upload_from_filename(local_file)
        print("Upload complete.")
    
    def main():
        download_data()
    
        # Process Training Data
        convert_to_gemini_format("sft_train_samples.jsonl", "train_gemini.jsonl")
        upload_to_gcs("train_gemini.jsonl", "datasets/train/train_gemini.jsonl")
    
        # Process Validation Data
        convert_to_gemini_format("sft_val_samples.jsonl", "val_gemini.jsonl")
        upload_to_gcs("val_gemini.jsonl", "datasets/val/val_gemini.jsonl")
    
        print("Data preparation complete!")
    
    if __name__ == "__main__":
        main()
    
  3. En la terminal, ejecuta la secuencia de comandos de preparación de datos.
    python prepare_data.py
    

7. Establece el rendimiento de referencia

Antes del ajuste, necesitas un parámetro de comparación. Medirás el rendimiento del modelo gemini-2.5-flash de base en la tarea de resumen con las puntuaciones ROUGE.

  1. En la terminal, crea un archivo llamado evaluate.py.
    cloudshell edit evaluate.py
    
  2. Pega el siguiente código en evaluate.py.
    import argparse
    import json
    import os
    import pandas as pd
    from google.cloud import aiplatform
    import vertexai
    from vertexai.generative_models import GenerativeModel, GenerationConfig, HarmCategory, HarmBlockThreshold
    from rouge_score import rouge_scorer
    from tqdm import tqdm
    import matplotlib.pyplot as plt
    import time
    
    # Configuration
    PROJECT_ID = os.environ["PROJECT_ID"]
    REGION = os.environ["REGION"]
    
    aiplatform.init(project=PROJECT_ID, location=REGION)
    
    def evaluate(model_name, test_file, max_samples=50, output_json="results.json"):
        print(f"Evaluating model: {model_name}")
    
        # Load Test Data
        test_df = pd.read_csv(test_file)
        test_df = test_df.head(max_samples)
    
        model = GenerativeModel(model_name)
    
        safety_settings = {
            HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_ONLY_HIGH,
            HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_ONLY_HIGH,
            HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_ONLY_HIGH,
            HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_ONLY_HIGH,
        }
    
        generation_config = GenerationConfig(
            temperature=0.1,
            max_output_tokens=1024,
        )
    
        scorer = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=True)
        results = []
    
        for index, row in tqdm(test_df.iterrows(), total=len(test_df)):
            input_text = row['input_text']
            reference_summary = row['output_text']
    
            try:
                response = model.generate_content(
                    input_text,
                    generation_config=generation_config,
                    safety_settings=safety_settings
                )
                generated_summary = response.text
    
                scores = scorer.score(reference_summary, generated_summary)
    
                results.append({
                    "generated": generated_summary,
                    "reference": reference_summary,
                    "rouge1": scores['rouge1'].fmeasure,
                    "rouge2": scores['rouge2'].fmeasure,
                    "rougeL": scores['rougeL'].fmeasure
                })
            except Exception as e:
                print(f"Error processing example {index}: {e}")
                # Sleep briefly to avoid quota issues if hitting limits
                time.sleep(1)
    
        # Save results
        with open(output_json, 'w') as f:
            json.dump(results, f, indent=2)
    
        return pd.DataFrame(results)
    
    def plot_results(df, title, filename):
        os.makedirs("plots", exist_ok=True)
    
        metrics = ['rouge1', 'rouge2', 'rougeL']
        fig, axes = plt.subplots(1, 3, figsize=(15, 5))
    
        for i, metric in enumerate(metrics):
            axes[i].hist(df[metric], bins=10, alpha=0.7, color='skyblue', edgecolor='black')
            axes[i].set_title(f'{metric} Distribution')
            axes[i].set_xlabel('Score')
            axes[i].set_ylabel('Count')
    
        plt.suptitle(title)
        plt.tight_layout()
        plt.savefig(f"plots/{filename}")
        print(f"Plot saved to plots/{filename}")
    
    def compare_results(baseline_file, tuned_file):
        with open(baseline_file, 'r') as f:
            baseline_data = pd.DataFrame(json.load(f))
        with open(tuned_file, 'r') as f:
            tuned_data = pd.DataFrame(json.load(f))
    
        print("\n--- Comparison ---")
        metrics = ['rouge1', 'rouge2', 'rougeL']
        for metric in metrics:
            base_mean = baseline_data[metric].mean()
            tuned_mean = tuned_data[metric].mean()
            diff = tuned_mean - base_mean
            print(f"{metric}: Base={base_mean:.4f}, Tuned={tuned_mean:.4f}, Diff={diff:+.4f}")
    
        # Comparative Plot
        os.makedirs("plots", exist_ok=True)
        comparison_df = pd.DataFrame({
            'Metric': metrics,
            'Baseline': [baseline_data[m].mean() for m in metrics],
            'Tuned': [tuned_data[m].mean() for m in metrics]
        })
    
        comparison_df.plot(x='Metric', y=['Baseline', 'Tuned'], kind='bar', figsize=(10, 6))
        plt.title('Baseline vs Tuned Model Performance')
        plt.ylabel('Average Score')
        plt.xticks(rotation=0)
        plt.tight_layout()
        plt.savefig("plots/comparison.png")
        print("Comparison plot saved to plots/comparison.png")
    
    def main():
        parser = argparse.ArgumentParser()
        parser.add_argument("--model", type=str, default="gemini-2.5-flash", help="Model resource name")
        parser.add_argument("--baseline", type=str, help="Path to baseline results json for comparison")
        parser.add_argument("--output", type=str, default="results.json", help="Output file for results")
        args = parser.parse_args()
    
        # Ensure test data exists (it was downloaded in prepare_data step)
        if not os.path.exists("sft_test_samples.csv"):
            # Fallback download if needed
            subprocess.run(["gsutil", "cp", "gs://github-repo/generative-ai/gemini/tuning/summarization/wikilingua/sft_test_samples.csv", "."], check=True)
    
        df = evaluate(args.model, "sft_test_samples.csv", output_json=args.output)
    
        print("\n--- Results Summary ---")
        print(df.describe())
    
        plot_filename = "baseline_dist.png" if not args.baseline else "tuned_dist.png"
        plot_results(df, f"ROUGE Scores - {args.model}", plot_filename)
    
        if args.baseline:
            compare_results(args.baseline, args.output)
    
    if __name__ == "__main__":
        main()
    
  3. En la terminal, ejecuta la evaluación de referencia.
    python evaluate.py --model "gemini-2.5-flash" --output "baseline.json"
    
    Esto generará un archivo baseline.json y un gráfico en plots/baseline_dist.png.

8. Configura y, luego, inicia el ajuste

Ahora iniciarás un trabajo de ajuste administrado en Vertex AI.

  1. En la terminal, crea un archivo llamado tune.py.
    cloudshell edit tune.py
    
  2. Pega el siguiente código en tune.py.
    import os
    import time
    from google.cloud import aiplatform
    import vertexai
    from vertexai.preview.tuning import sft
    
    # Configuration
    PROJECT_ID = os.environ["PROJECT_ID"]
    REGION = os.environ["REGION"]
    BUCKET_NAME = os.environ["BUCKET_NAME"]
    
    aiplatform.init(project=PROJECT_ID, location=REGION)
    
    def train():
        print("Launching fine-tuning job...")
    
        sft_tuning_job = sft.train(
            source_model="gemini-2.5-flash", # Using specific version for stability
            train_dataset=f"gs://{BUCKET_NAME}/datasets/train/train_gemini.jsonl",
            validation_dataset=f"gs://{BUCKET_NAME}/datasets/val/val_gemini.jsonl",
            epochs=1, # Keep it short for the lab
            adapter_size=4,
            learning_rate_multiplier=1.0,
            tuned_model_display_name="gemini-2.5-flash-wikilingua",
        )
    
        print(f"Job started: {sft_tuning_job.resource_name}")
        print("Waiting for job to complete... (this may take ~45 minutes)")
    
        # Wait for the job to complete
        while not sft_tuning_job.has_ended:
            time.sleep(60)
            sft_tuning_job.refresh()
            print(f"Status: {sft_tuning_job.state.name}")
    
        print("Job completed!")
        print(f"Tuned Model Endpoint: {sft_tuning_job.tuned_model_endpoint_name}")
        return sft_tuning_job.tuned_model_endpoint_name
    
    if __name__ == "__main__":
        train()
    
  3. En la terminal, ejecuta la secuencia de comandos de ajuste.
    python tune.py
    
    Nota: Este proceso puede tardar unos 45 minutos. Puedes supervisar el trabajo en la consola de Vertex AI.

9. Comprende el código de entrenamiento

Mientras se ejecuta tu trabajo, echemos un vistazo más de cerca a la secuencia de comandos tune.py para comprender cómo funciona el ajuste.

Ajuste supervisado administrado

La secuencia de comandos usa el método vertexai.tuning.sft.train para enviar un trabajo de ajuste administrado. Esto abstrae la complejidad del aprovisionamiento de la infraestructura, la distribución del entrenamiento y la administración de los puntos de control.

sft_tuning_job = sft.train(
    source_model="gemini-2.5-flash",
    train_dataset=f"gs://{BUCKET_NAME}/datasets/train/train_gemini.jsonl",
    # ...
)

Configuración de LoRA

En lugar de definir manualmente un LoraConfig como lo harías en frameworks de código abierto, Vertex AI simplifica esto en algunos parámetros clave:

  • adapter_size: Este parámetro (establecido en 4 en nuestra secuencia de comandos) controla la clasificación de los adaptadores de LoRA. Un tamaño más grande permite que el modelo aprenda adaptaciones más complejas, pero aumenta la cantidad de parámetros entrenables.
  • epochs: Establecimos este valor en 1 para este lab para mantener el tiempo de entrenamiento corto (aproximadamente 20 minutos). En una situación de producción, puedes aumentar este valor para permitir que el modelo aprenda más profundamente de tus datos, aunque debes tener cuidado con el sobreajuste.

Selección de modelo

Especificamos explícitamente source_model="gemini-2.5-flash". Vertex AI admite varias versiones de Gemini, y fijar una versión específica garantiza que tu canalización permanezca estable y reproducible.

10. Comparar modelos

Una vez que se complete el trabajo de ajuste, puedes comparar el rendimiento de tu modelo nuevo con el de referencia.

  1. Obtén el extremo del modelo ajustado. Se imprimió al final de la secuencia de comandos tune.py. Se verá como projects/.../locations/.../endpoints/....
  2. Vuelve a ejecutar la secuencia de comandos de evaluación y, esta vez, pasa tu modelo ajustado y los resultados de referencia para la comparación.
    # Replace [YOUR_TUNED_MODEL_ENDPOINT] with the actual endpoint name
    export TUNED_MODEL="projects/[YOUR_PROJECT_ID]/locations/[YOUR_REGION]/endpoints/[YOUR_ENDPOINT_ID]"
    
    python evaluate.py --model "$TUNED_MODEL" --baseline "baseline.json" --output "tuned.json"
    
  3. Consulta los resultados. La secuencia de comandos generará una comparación de las puntuaciones ROUGE y un gráfico plots/comparison.png que muestra la mejora.Para ver los gráficos, abre la carpeta plots en el editor de Cloud Shell.

11. Limpia

Para evitar que se generen cobros, borra los recursos que creaste.

  1. En la terminal, borra el bucket de Cloud Storage y el modelo ajustado.
    gcloud storage rm -r gs://$BUCKET_NAME
    # Note: You can delete the model endpoint from the Vertex AI Console
    

12. ¡Felicitaciones!

Ajustaste correctamente Gemini 2.5 Flash en Vertex AI.

Resumen

En este lab, aprenderás a hacer lo siguiente:

  • Preparar un conjunto de datos en formato JSONL para el ajuste de Gemini
  • Establecer un modelo de referencia con el modelo Gemini 2.5 Flash de base
  • Iniciar un trabajo de ajuste supervisado en Vertex AI
  • Evaluar y comparar el modelo ajustado con el de referencia

¿Qué sigue?

Este lab forma parte de la ruta de aprendizaje de IA lista para producción con Google Cloud.

Explora el plan de estudios completo para cerrar la brecha entre el prototipo y la producción.

Comparte tu progreso con el hashtag #ProductionReadyAI.