Доработайте Gemini на Vertex AI.

1. Введение

В этой лабораторной работе вы научитесь выполнять полный рабочий процесс контролируемой тонкой настройки модели Google Gemini, чтобы адаптировать ее для конкретной задачи: составления кратких обзоров статей. Хотя большие языковые модели обладают большой мощностью, их универсальность позволяет сделать их еще более эффективными для конкретных задач с помощью тонкой настройки. Обучив модель на высококачественном наборе примеров, вы сможете улучшить ее согласованность, качество и эффективность для вашей целевой задачи.

Вы будете использовать Gemini 2.5 Flash , легкую и экономичную модель, а тонкую настройку будете выполнять с помощью Vertex AI .

Обзор архитектуры

Вот что мы построим:

  • Cloud Shell : Ваша среда разработки.
  • Облачное хранилище : хранит данные для обучения/проверки в формате JSONL.
  • Vertex AI Training : управляет процессом тонкой настройки.
  • Vertex AI Endpoint : размещает вашу точно настроенную модель.

Что вы узнаете

  • Подготовьте высококачественные наборы данных для контролируемой тонкой настройки.
  • Настройте и запустите задачи тонкой настройки с помощью Vertex AI SDK для Python .
  • Оцените модели, используя автоматизированные метрики (оценки ROUGE).
  • Сравните базовую и доработанную модели, чтобы количественно оценить улучшения.

2. Настройка проекта

Аккаунт Google

Если у вас еще нет личного аккаунта Google, вам необходимо его создать .

Используйте личный аккаунт вместо рабочего или учебного.

Войдите в консоль Google Cloud.

Войдите в консоль Google Cloud, используя личную учетную запись Google.

Включить выставление счетов

Обменяйте 5 долларов США на кредиты Google Cloud (по желанию)

Для проведения этого мастер-класса вам потребуется платежный аккаунт с достаточным балансом. Если вы планируете использовать собственную платежную систему, этот шаг можно пропустить.

  1. Перейдите по этой ссылке и войдите в систему, используя свой личный аккаунт Google. Вы увидите примерно следующее: Нажмите, чтобы авторизовать Cloud Shell.
  2. Нажмите кнопку «НАЖМИТЕ ЗДЕСЬ ДЛЯ ДОСТУПА К ВАШИМ КРЕДИТАМ». Это переведет вас на страницу настройки вашего платежного профиля. Нажмите, чтобы авторизовать Cloud Shell.
  3. Нажмите «Подтвердить».

Теперь вы подключены к пробному платёжному аккаунту Google Cloud Platform.

Скриншот обзора выставления счетов

Создать проект (необязательно)

Если у вас нет текущего проекта, который вы хотели бы использовать для этой лабораторной работы, создайте новый проект здесь .

3. Откройте редактор Cloud Shell.

  1. Нажмите на эту ссылку, чтобы перейти непосредственно в редактор Cloud Shell.
  2. Если сегодня вам будет предложено авторизоваться, нажмите «Авторизовать» , чтобы продолжить. Нажмите, чтобы авторизовать Cloud Shell.
  3. Если терминал не отображается внизу экрана, откройте его:
    • Нажмите «Просмотреть».
    • Нажмите «Терминал» Откройте новый терминал в редакторе Cloud Shell.
  4. В терминале настройте свой проект с помощью этой команды:
    gcloud config set project [PROJECT_ID]
    
    • Пример:
      gcloud config set project lab-project-id-example
      
    • Если вы не помните идентификатор своего проекта, вы можете перечислить все идентификаторы своих проектов с помощью следующей команды:
      gcloud projects list
      
      Установите идентификатор проекта в терминале редактора Cloud Shell.
  5. Вы должны увидеть следующее сообщение:
    Updated property [core/project].
    

4. Включите API.

Для использования Vertex AI и других сервисов необходимо включить необходимые API в вашем проекте Google Cloud.

  1. В терминале включите API:
    • API Vertex AI ( aiplatform.googleapis.com ): позволяет использовать Vertex AI для тонкой настройки и запуска моделей.
    • API облачного хранилища ( storage.googleapis.com ): позволяет хранить наборы данных и артефакты моделей.
    gcloud services enable aiplatform.googleapis.com \
        storage.googleapis.com
    

5. Настройка среды проекта

Создайте рабочую директорию

  1. В терминале создайте директорию для вашего проекта и перейдите в неё.
    mkdir gemini-finetuning
    cd gemini-finetuning
    

Настройте переменные среды

  1. В терминале определите переменные окружения для вашего проекта. Мы создадим файл env.sh для хранения этих переменных, чтобы их можно было легко перезагрузить в случае разрыва сессии.
    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
    

Создайте корзину облачного хранилища.

  1. В терминале создайте хранилище (bucket) для хранения вашего набора данных и артефактов модели.
    gcloud storage buckets create gs://$BUCKET_NAME --project=$PROJECT_ID --location=$REGION
    

Настройка виртуальной среды

  1. Для управления средой Python мы будем использовать uv . В терминале выполните следующую команду:
    uv venv .venv
    source .venv/bin/activate
    
  2. В терминале установите необходимые пакеты Python.
    uv pip install google-cloud-aiplatform rouge-score matplotlib pandas tqdm
    

6. Подготовьте обучающие данные.

Качественные данные — основа успешной тонкой настройки. Вы будете использовать набор данных WikiLingua , преобразовывать его в специфический формат JSONL, требуемый Gemini, и загружать в свой накопитель.

  1. В терминале создайте файл с именем prepare_data.py .
    cloudshell edit prepare_data.py
    
  2. Вставьте следующий код в 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. В терминале запустите скрипт подготовки данных.
    python prepare_data.py
    

7. Определите базовый уровень производительности.

Перед тонкой настройкой вам потребуется эталонный показатель. Вы измерите, насколько хорошо базовая модель gemini-2.5-flash справляется с задачей суммаризации, используя показатели ROUGE.

  1. В терминале создайте файл с именем evaluate.py .
    cloudshell edit evaluate.py
    
  2. Вставьте следующий код в 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. В терминале запустите базовую оценку.
    python evaluate.py --model "gemini-2.5-flash" --output "baseline.json"
    
    В результате будет создан файл baseline.json и график в plots/baseline_dist.png .

8. Настройте и запустите тонкую настройку.

Теперь вы запустите управляемую задачу тонкой настройки в Vertex AI.

  1. В терминале создайте файл с именем tune.py
    cloudshell edit tune.py
    
  2. Вставьте следующий код в 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. В терминале запустите скрипт тонкой настройки.
    python tune.py
    
    Примечание : Этот процесс может занять около 45 минут. Вы можете отслеживать ход выполнения задания в консоли Vertex AI .

9. Разберитесь в коде обучения.

Пока ваша задача выполняется, давайте подробнее рассмотрим скрипт tune.py , чтобы понять, как работает тонкая настройка.

Управляемая контролируемая тонкая настройка

Скрипт использует метод vertexai.tuning.sft.train для отправки управляемого задания на настройку. Это позволяет избежать сложностей, связанных с выделением инфраструктуры, распределением обучающих данных и управлением контрольными точками.

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

Конфигурация LoRA

Вместо того чтобы вручную задавать LoraConfig , как это можно делать в фреймворках с открытым исходным кодом, Vertex AI упрощает этот процесс, сводя его к нескольким ключевым параметрам:

  • adapter_size : Этот параметр (в нашем скрипте установлен на 4 ) управляет рангом адаптеров LoRA. Больший размер позволяет модели изучать более сложные адаптации, но увеличивает количество обучаемых параметров.
  • epochs : В этой лабораторной работе мы установили это значение равным 1 , чтобы сократить время обучения (~20 минут). В реальных условиях вы можете увеличить это значение, чтобы модель могла глубже обучаться на ваших данных, хотя следует остерегаться переобучения.

Выбор модели

Мы явно указываем source_model="gemini-2.5-flash" . Vertex AI поддерживает различные версии Gemini, и указание конкретной версии гарантирует стабильность и воспроизводимость вашего конвейера.

10. Сравните модели

После завершения тонкой настройки вы можете сравнить производительность вашей новой модели с базовым показателем.

  1. Получите конечную точку вашей настроенной модели. Она была выведена в конце скрипта tune.py Она будет выглядеть примерно так: projects/.../locations/.../endpoints/... .
  2. Запустите скрипт оценки еще раз, на этот раз передав для сравнения вашу настроенную модель и результаты базовой версии.
    # 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. Просмотрите результаты. Скрипт выведет сравнение оценок ROUGE и сгенерирует диаграмму plots/comparison.png , демонстрирующую улучшение. Вы можете просмотреть графики, открыв папку plots в редакторе Cloud Shell.

11. Уборка

Чтобы избежать дополнительных расходов, удалите созданные вами ресурсы.

  1. В терминале удалите сегмент Cloud Storage и настроенную модель.
    gcloud storage rm -r gs://$BUCKET_NAME
    # Note: You can delete the model endpoint from the Vertex AI Console
    

12. Поздравляем!

Вы успешно выполнили тонкую настройку Gemini 2.5 Flash на Vertex AI!

Краткий обзор

В этой лаборатории вы:

  • Для тонкой настройки Gemini был подготовлен набор данных в формате JSONL.
  • Установлены базовые параметры с использованием базовой модели Gemini 2.5 Flash.
  • Запущена задача контролируемой тонкой настройки в Vertex AI.
  • Была проведена оценка и сравнение доработанной модели с базовой.

Что дальше?

Данная лабораторная работа является частью учебного курса "Готовый к внедрению ИИ в производство с использованием Google Cloud" .

Изучите полный учебный план, чтобы преодолеть разрыв между прототипом и серийным производством.

Делитесь своими успехами, используя хэштег #ProductionReadyAI .