Обфускация PII с Gemma-3 270M: тонкая настройка на Unsloth | AiManual
AiManual Logo Ai / Manual.
27 Янв 2026 Гайд

Как обучить Gemma-3 270M для обфускации данных: инструкция по финтюну с Unsloth и датасетом на 1700 примеров

Полное руководство по обучению Gemma-3 270M для анонимизации данных на португальском языке с Unsloth. Датасет 1700 примеров, код, ошибки.

Зачем вообще обучать модель для обфускации?

Представьте: у вас есть тонны клиентских данных на бразильском португальском - чеки, переписки, медицинские записи. Всё это нужно передать аналитикам или в AI-сервисы, но нельзя показывать реальные имена, телефоны, адреса. Ручная замена - это ад. Регулярные выражения ломаются на опечатках. GPT-4 API стоит денег и отправляет данные куда-то в облако.

Решение? Обучить маленькую модель, которая будет жить у вас на сервере и заменять PII (персонально идентифицируемую информацию) на реалистичные, но вымышленные аналоги. Gemma-3 270M - идеальный кандидат: весит около 1.5 ГБ, работает на CPU, а с Unsloth её можно дообучить за пару часов даже на дешёвой видеокарте.

Важно: Gemma-3 270M - самая маленькая модель в семействе на январь 2026 года. Она не заменит GPT-4 в сложных задачах, но для детерминированных преобразований вроде обфускации подходит идеально.

Что пойдёт не так, если делать по учебникам

Большинство гайдов по fine-tuning'у показывают общие принципы, но упускают критичные для обфускации детали:

  • Модель начнёт "сочинять" - вместо замены имени "Мария" на "Ана", она может генерировать целые предложения
  • Контекст съест память - если подавать длинные тексты, 270M параметров не хватит
  • Формат данных сломает обучение - неправильный JSONL = потраченные часы тренировки впустую
  • Обфускация станет обратимой - если замена предсказуема, можно восстановить оригинал

Подготовка: что нужно перед стартом

1 Железо и софт

Компонент Минимум Рекомендуется
GPU VRAM 8 ГБ (RTX 3070) 16 ГБ (RTX 4080)
RAM 16 ГБ 32 ГБ
Диск 10 ГБ свободно 20 ГБ свободно
Python 3.10 3.11+
# Установка всего необходимого
pip install "unsloth[cu121] @ git+https://github.com/unslothai/unsloth.git"
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
pip install transformers datasets accelerate trl peft
💡
Unsloth на январь 2026 года ускоряет обучение в 2-3 раза и экономит до 70% памяти. Если у вас мало VRAM, сначала прочитайте статью про Unsloth для эмбеддингов - там те же принципы экономии памяти.

2 Создание датасета

Вот где большинство спотыкаются. Нужно не просто собрать тексты, а создать парные примеры: оригинал → обфусцированная версия. Для бразильского португальского я использовал 1700 примеров такого формата:

{
  "instruction": "Substitua todas as informações pessoais (PII) por dados fictícios realistas, mantendo a estrutura do texto.",
  "input": "O paciente João Silva, CPF 123.456.789-00, residente na Rua das Flores, 123, São Paulo-SP, telefone (11) 99999-9999, foi atendido com queixa de dor abdominal.",
  "output": "O paciente Lucas Oliveira, CPF 987.654.321-00, residente na Avenida Paulista, 456, São Paulo-SP, telefone (11) 98888-8888, foi atendido com queixa de dor abdominal."
}

Ключевые моменты:

  • Одинаковая длина - input и output должны быть примерно одинаковой длины
  • Сохранение форматов - CPF остаётся в формате XXX.XXX.XXX-XX, телефон в формате (XX) XXXXX-XXXX
  • Реалистичность замен - "João Silva" → "Lucas Oliveira" (оба бразильские имена), а не "John Smith"
  • Контекстуальная замена - адрес в Сан-Паулу заменяется на другой адрес в Сан-Паулу

Не делайте так: Создавать датасет, где модель просто удаляет PII. Это проще, но тогда модель научится удалять куски текста, что ломает структуру документов.

Код обучения: от загрузки модели до инференса

3 Загрузка модели и токенизатора

from unsloth import FastLanguageModel
import torch
from transformers import TrainingArguments
from trl import SFTTrainer
from datasets import load_dataset

# Загружаем Gemma-3 270M через Unsloth
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="google/gemma-3-270m",
    max_seq_length=2048,  # Увеличиваем для длинных документов
    dtype=torch.float16,  # Экономия памяти
    load_in_4bit=True,    # QLoRA 4-bit
    device_map="auto",
)

# Добавляем адаптеры LoRA
model = FastLanguageModel.get_peft_model(
    model,
    r=16,                 # Ранг LoRA
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
                    "gate_proj", "up_proj", "down_proj"],
    lora_alpha=16,
    lora_dropout=0.1,
    bias="none",
    use_gradient_checkpointing=True,
    random_state=42,
    max_seq_length=2048,
)
💡
Почему max_seq_length=2048, а не 1024? Потому что медицинские записи и юридические документы часто длинные. Если ужать контекст, модель будет обфусцировать только первые 1024 токена, а остальное пропустит.

4 Подготовка датасета

# Загружаем наш JSONL датасет
dataset = load_dataset("json", data_files="pii_obfuscation_ptbr.jsonl", split="train")

# Форматируем промпты
def format_instruction(example):
    text = f"""Abaixo está uma instrução que descreve uma tarefa, juntamente com uma entrada que fornece mais contexto. Escreva uma resposta que complete adequadamente a solicitação.

### Instrução:
{example['instruction']}

### Entrada:
{example['input']}

### Resposta:
{example['output']}"""
    return {"text": text}

dataset = dataset.map(format_instruction)

# Токенизация
def tokenize_function(examples):
    return tokenizer(
        examples["text"],
        truncation=True,
        padding="max_length",
        max_length=2048,
    )

tokenized_dataset = dataset.map(tokenize_function, batched=True)

Ошибка, которую делают 90% людей: они токенизируют input и output отдельно. Так модель не учится связи между ними. Надо токенизировать весь промпт целиком.

5 Конфигурация обучения

training_args = TrainingArguments(
    output_dir="./gemma3-270m-pii-obfuscator",
    num_train_epochs=3,              # 3 эпохи достаточно для 1700 примеров
    per_device_train_batch_size=4,   # Увеличиваем если VRAM позволяет
    gradient_accumulation_steps=4,   # Эффективный batch size = 16
    warmup_steps=50,
    logging_steps=10,
    save_steps=500,
    eval_steps=500,
    evaluation_strategy="steps",
    save_total_limit=3,
    load_best_model_at_end=True,
    metric_for_best_model="eval_loss",
    greater_is_better=False,
    fp16=True,                       # Обязательно для экономии памяти
    learning_rate=2e-4,               # Gemma-3 любит высокий LR
    optim="adamw_8bit",              # 8-bit AdamW из bitsandbytes
    report_to="none",                # Не отправляем в WandB/TensorBoard
)

# Создаём тренера
trainer = SFTTrainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset,
    dataset_text_field="text",
    max_seq_length=2048,
    tokenizer=tokenizer,
    packing=False,                   # Не пакуем - у нас структурированные промпты
)

Внимание на learning_rate: Для Gemma-3 270M ставлю 2e-4, а не стандартные 5e-5. Маленькие модели требуют более агрессивного обучения, иначе они просто запоминают датасет без обобщения.

6 Запуск обучения и сохранение

# Запускаем обучение
trainer.train()

# Сохраняем адаптеры LoRA
model.save_pretrained("./gemma3-270m-pii-obfuscator-lora")
tokenizer.save_pretrained("./gemma3-270m-pii-obfuscator-lora")

# Сохраняем полную модель для инференса (опционально)
merged_model = model.merge_and_unload()
merged_model.save_pretrained("./gemma3-270m-pii-obfuscator-merged", 
                             safe_serialization=True)

Инференс: как использовать обученную модель

from transformers import AutoTokenizer, AutoModelForCausalLM
import torch

# Загружаем модель и адаптеры
tokenizer = AutoTokenizer.from_pretrained("./gemma3-270m-pii-obfuscator-lora")
model = AutoModelForCausalLM.from_pretrained(
    "google/gemma-3-270m",
    torch_dtype=torch.float16,
    device_map="auto",
)
model.load_adapter("./gemma3-270m-pii-obfuscator-lora")

# Функция для обфускации
def obfuscate_pii(text, max_length=1024):
    prompt = f"""Abaixo está uma instrução que descreve uma tarefa, juntamente com uma entrada que fornece mais contexto. Escreva uma resposta que complete adequadamente a solicitação.

### Instrução:
Substitua todas as informações pessoais (PII) por dados fictícios realistas, mantendo a estrutura do texto.

### Entrada:
{text}

### Resposta:
"""
    
    inputs = tokenizer(prompt, return_tensors="pt", truncation=True, 
                      max_length=max_length).to("cuda")
    
    with torch.no_grad():
        outputs = model.generate(
            **inputs,
            max_new_tokens=len(text) + 100,  # Немного больше оригинала
            temperature=0.1,                 # Низкая температура для детерминированности
            do_sample=False,
            pad_token_id=tokenizer.eos_token_id,
        )
    
    response = tokenizer.decode(outputs[0], skip_special_tokens=True)
    # Вырезаем только ответ после "### Resposta:"
    resposta_start = response.find("### Resposta:") + len("### Resposta:")
    return response[resposta_start:].strip()

# Пример использования
texto_original = "Cliente: Maria Santos, email: maria.santos@email.com, telefone: (21) 98765-4321"
texto_obfuscado = obfuscate_pii(texto_original)
print(f"Original: {texto_original}")
print(f"Obfuscado: {texto_obfuscado}")

Типичные проблемы и их решения

Проблема Причина Решение
Модель генерирует мусор Слишком высокая температура temperature=0.1 или do_sample=False
Out of memory ошибка Слишком большой max_length Уменьшить до 1024 или использовать gradient checkpointing
Модель повторяет input Переобучение на датасете Уменьшить эпохи до 2, добавить dropout
Не заменяет все PII Недостаточно разнообразных примеров Добавить больше вариаций в датасет
Медленный инференс Модель на CPU Использовать квантование GGUF для CPU

Что дальше? Улучшения и оптимизации

Обученная модель работает, но можно лучше:

  1. Добавление типов PII - научить модель различать типы данных: CPF → CPF, email → email, адрес → адрес
  2. Контекстуальная замена - если в тексте "доктор Карлос" и "пациент Карлос", заменять их на разные имена
  3. Мультиязычность - дообучить на английских и испанских примерах
  4. Специализация - отдельные модели для медицинских записей, финансовых документов, переписок

Если нужно ускорить инференс в 2-3 раза, смотрите гайд по квантованию Gemma-3. Для сложных задач, где нужна логика поверх обфускации, может помочь настройка вызова процедур.

💡
Самый неочевидный лайфхак: добавьте в датасет 5% "пустых" примеров, где нет PII. Так модель научится пропускать тексты без изменений, а не пытаться что-то найти и заменить.

Сколько это стоит и стоит ли вообще?

Давайте считать:

  • Электричество - 3 часа обучения на RTX 4070 ≈ 0.5 кВт·ч ≈ 5 рублей
  • Время - подготовка датасета (8 часов) + обучение (3 часа) + тестирование (2 часа)
  • Альтернатива - GPT-4 API: 1700 примеров × 500 токенов × 0.03$/1K токенов ≈ 25$ за один прогон

Выгода появляется после 1000 документов. Плюс:

  • Данные не уходят в облако
  • Нет лимитов на запросы
  • Можно дообучать под специфику ваших данных
  • Работает оффлайн

Главный подводный камень? Модель на 270M параметров иногда "забывает" редкие форматы данных. Если у вас встречаются нестандартные номера паспортов или специализированные идентификаторы - добавьте их в датасет с запасом. Лучше 50 примеров редкого формата, чем надеяться, что модель догадается.

И последнее: не экономьте на валидационной выборке. Отложите 200 примеров из 1700 и не смотрите на них во время обучения. Только так поймёте, действительно ли модель обобщает, а не заучивает.