Перейти к содержимому

Дообучение с дифференциальной приватностью через Opacus

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

Новое в v2.14.0

Этот учебник обучает классификатор на собственных метках исследования с дифференциальной приватностью, так что выпущенная модель мало раскрывает о любом одном размеченном объекте. Страница предназначена для владельцев исследований, чьи объекты или метки чувствительны (например, медицинские записи или личные сообщения) и кто планирует делиться моделью или публиковать её. Вы добавите блок dp в параметры hf-text-classification, запустите тренер на примере с отзывами о товарах и прочитаете израсходованную приватность в журнале запуска и в карточке модели. Тот же блок работает для hf-image-classification, а другие тренеры dp не принимают.

Эпсилон и дельта в дифференциальной приватности

Дифференциальная приватность это математическая гарантия для вычисления над набором данных. Добавление или удаление одной записи меняет вероятность любого результата не более чем на ограниченную величину, поэтому результат мало говорит о том, присутствовала ли эта запись. Эпсилон (ε) это эта граница. Чем меньше эпсилон, тем труднее отличить результаты с записью и без неё, и поэтому меньший эпсилон даёт более сильную приватность. Дельта (δ) это вероятность того, что граница эпсилон не выполняется, а документация Potato описывает дельту как "the chance the guarantee does not hold" (вероятность того, что гарантия не выполняется).

DP-SGD обучает нейронную сеть с этой гарантией методом Abadi и соавт. (2016). Каждый шаг обучения обрезает градиент каждого объекта до максимальной нормы и добавляет к сумме гауссов шум. Обрезка ограничивает, насколько далеко один объект может сдвинуть модель, а шум маскирует этот ограниченный сдвиг. Opacus это библиотека PyTorch, реализующая DP-SGD, и Potato передаёт цикл обучения Opacus всякий раз, когда запуск несёт dp. Бухгалтер RDP в Opacus суммирует эпсилон, который расходует каждый шаг.

Установка тренера и Opacus

Трансформерным тренерам нужны torch и transformers из дополнительных пакетов обучения Potato. Ни один дополнительный пакет Potato не включает Opacus, потому что Opacus закрепляет определённые версии torch. Установите оба на машине, где обучается запуск:

bash
pip install "potato-annotation[train-text]"   # [train-vision] for hf-image-classification
pip install opacus

Запуск с dp проверяет наличие Opacus на машине обучения до начала обучения. Поэтому на бэкенде ssh, SLURM или агента Opacus ставится на GPU-сервер или вычислительный узел, который обучает, а не на сервер разметки. Удалённое обучение описывает эти бэкенды.

Настройка тренера

Конфигурация ниже это пример с отзывами о товарах из Обучите первую модель, где приватное обучение задано по умолчанию для hf-text-classification. Скопируйте data/reviews.json и data/gold.json из examples/ai-assisted/model-training/ в Potato в папку проекта и сохраните эту конфигурацию там как config.yaml:

yaml
annotation_task_name: "Review Sentiment (private training)"
 
data_files:
  - "data/reviews.json"
 
item_properties:
  id_key: "id"
  text_key: "text"
 
task_dir: "."
output_annotation_dir: "annotation_output"
 
require_password: false
admin_api_key: "demo-admin-key"
 
num_annotators_per_item: 1
 
annotation_schemes:
  - annotation_type: "radio"
    name: "sentiment"
    description: "Is this review positive or negative?"
    labels:
      - "positive"
      - "negative"
    sequential_key_binding: true
 
model_training:
  enabled: true
  trainer_params:
    hf-text-classification:
      epochs: 5
      batch_size: 16
      dp:
        epsilon: 8
        max_grad_norm: 1.0
  writeback:
    schemas: ["sentiment"]
    include_unlabeled: true
    min_confidence: 0.6

trainer_params задаёт значения по умолчанию для каждого запуска hf-text-classification, поэтому поле Parameters на странице Training открывается с уже заполненной записью dp. epsilon: 8 лежит внутри диапазона от 1 до 10, который документация Potato называет обычным. delta опущена, поэтому Potato задаёт дельту равной 1 / (10 × число объектов train). max_grad_norm: 1.0 повторяет обрезку по умолчанию. epochs и batch_size повторяют значения по умолчанию тренера, потому что Opacus рассчитывает шум по обоим. require_password: false и простой ключ администратора подходят только для локальной пробы.

dp принимает три ключа:

КлючПо умолчаниюЗначение
epsilonобязателенБюджет приватности на весь запуск. Меньший эпсилон означает большую приватность и больше шума.
delta1 / (10 × число объектов train)Вероятность того, что гарантия не выполняется. Дельта должна быть меньше 1 / число объектов train.
max_grad_norm1.0Градиент каждого объекта обрезается до этой нормы перед добавлением шума.

Конфигурация проходит python -m potato.validate_cli config.yaml --strict. Валидатор принимает для dp отображение, null или false и отвергает dp: true. Валидатор также проверяет каждый ключ внутри dp и диапазон его значения. Opacus и проверки, зависящие от числа объектов train, выполняются при начале запуска на машине обучения, как перечисляет раздел Ошибки в начале запуска.

Разметка объектов и начало запуска

hf-text-classification отказывается обучаться менее чем на 20 объектах train (min_instances). Запустите сервер, а затем размечайте отзывы вручную или тремя имитируемыми разметчиками из симулятора пользователей, которые отвечают по файлу с эталоном:

bash
potato start config.yaml -p 8000
 
python -m potato.simulator --server http://localhost:8000 --users 3 \
  --strategy gold_standard --competence good=1.0 --max-annotations 40 \
  --gold-file data/gold.json --sequential --fast-mode --no-export

Симулятор размечает 120 из 160 отзывов. При splits по умолчанию (70% train, 15% val, 15% test) и split_seed: 0 в нашем запуске 76 размеченных отзывов попали в train, 19 в val и 25 в test. Поэтому дельта по умолчанию равна 1 / 760, примерно 0.00132, а любая заданная вами дельта должна быть меньше 1 / 76, примерно 0.0132.

Откройте http://localhost:8000/admin/training с ключом администратора, выберите hf-text-classification и схему sentiment, проверьте, что поле Parameters показывает запись dp, и нажмите Train now. Тот же запуск начинается и из API:

bash
curl -X POST -H "X-API-Key: demo-admin-key" -H "Content-Type: application/json" \
  http://localhost:8000/admin/training/api/runs \
  -d '{"trainer": "hf-text-classification", "schemas": ["sentiment"]}'

Ответ содержит run_id. Собственные params запуска переопределяют trainer_params, поэтому "params": {"dp": {"epsilon": 2}} обучает один запуск с меньшим бюджетом, а "params": {"dp": null} или "params": {"dp": false} обучает один запуск без DP. Оставляйте split_seed неизменным между двумя запусками, чтобы оба обучались и оценивались на одних и тех же объектах, а оценки на отложенных объектах сравнивались напрямую.

Строки журнала запуска при DP

Страница запуска /admin/training/runs/<run_id> перечисляет события и метрики запуска. Запуск с DP добавляет к событиям три вида строк.

Первая строка называет таблицы эмбеддингов, которые Potato замораживает. Позиционные эмбеддинги и эмбеддинги типа токена текстового трансформера занимают одну строку, общую для всего пакета, а Opacus не может вычислить градиент для каждого объекта через общую строку. Для модели по умолчанию, distilbert/distilroberta-base, замороженные таблицы это roberta.embeddings.position_embeddings и roberta.embeddings.token_type_embeddings. Замороженные таблицы не обучаются и не расходуют приватность, а эмбеддинги слов и все остальные слои по-прежнему обучаются.

Вторая строка начинается с DP-SGD: и называет целевой эпсилон, дельту, запланированные эпохи, множитель шума, который выбрал Opacus, обрезку на объект и частоту выборки. Каждый шаг формирует пакет пуассоновской выборкой, поэтому каждый объект train попадает в пакет независимо с вероятностью 1 / число пакетов в эпохе. При 76 объектах train пакетами по 16 в эпохе 5 пакетов, и журнал показывает частоту выборки 0.2000. Множитель шума задаёт стандартное отклонение шума, а FAQ Opacus указывает от 0.1 до 2 как обычный диапазон.

После каждой эпохи строка вида Epoch <n>/<epochs>: loss <loss>, epsilon spent <epsilon> сообщает среднюю потерю эпохи и эпсилон, израсходованный к этому моменту. То же значение записывается по эпохам как метрика dp_epsilon, а итоговые метрики запуска несут dp_epsilon для всего запуска. Opacus рассчитывает шум так, чтобы запланированные эпохи расходовали не больше целевого эпсилон. Запуск, который останавливается раньше по patience (по умолчанию две эпохи без лучшей оценки на val), расходует меньше цели.

Запись dp в карточке модели

Запуск записывает model_card.json в artifacts/ в папке запуска, <task_dir>/<output_annotation_dir>/training/runs/<run_id>/. У карточки запуска с DP есть запись dp:

ПолеЗначение
epsilon_targetЗапрошенный вами epsilon
epsilon_spentИзрасходованное запуском, по бухгалтеру RDP из Opacus
deltaДельта, которую использовал запуск, ваша или по умолчанию
max_grad_normОбрезка на объект
noise_multiplierМножитель шума, который выбрал Opacus
accountantrdp
coverstrain split

epsilon_spent учитывает каждую эпоху, которую обучал запуск. Эпохи после сохранённой контрольной точки входят в сумму. Публикация на Hugging Face Hub загружает model_card.json вместе с моделью, заменяя локальные пути именами файлов, так что запись dp путешествует с весами. Карточка модели README.md на Hub тоже содержит строку "Differential privacy" с израсходованным эпсилон в сравнении с целевым, дельтой и выборкой train, которую покрывает гарантия. Строка опускает целевое значение или дельту, если запуск не записал это значение.

Выбор бюджета приватности и обрезки

Документация Potato указывает от 1 до 10 как обычный диапазон для эпсилон и требует, чтобы дельта была меньше 1 / число объектов train. FAQ Opacus даёт то же практическое правило для дельты и рекомендует небольшую константу для эпсилон при крошечной дельте. Дельта по умолчанию, 1 / (10 × число объектов train), лежит ниже потолка 1 / число объектов train, поэтому в большинстве исследований дельту можно опустить.

Обрезка max_grad_norm обменивает одну ошибку на другую. Обрезка меняет каждый градиент больше порога, а FAQ Opacus говорит, что обрезка ухудшает сходимость. Шум масштабируется по обрезке, поэтому более высокая обрезка добавляет больше шума. FAQ добавляет, что неверный порог вредит линейным моделям сильнее, чем глубоким. Начните со значения по умолчанию 1.0 и сравните оценки на отложенных объектах, прежде чем подбирать обрезку.

Opacus рассчитывает шум для запланированных epochs, поэтому тот же эпсилон, распределённый на меньшее число эпох, означает меньше шума на каждом шаге. FAQ Opacus отмечает, что больший пакет повышает частоту выборки, а вместе с ней и стоимость приватности. FAQ предлагает более высокую скорость обучения и более раннюю остановку, чтобы компенсировать эту стоимость. Сравните оценку на отложенных объектах запуска с DP с оценкой запуска без dp на той же выборке. Разрыв между двумя оценками измеряет, чего гарантия стоит на ваших данных.

Область действия гарантии

Гарантия покрывает выборку train. Изменение одного объекта train меняет то, какой может быть выпущенная модель, не более чем на заявленный эпсилон. Объект считается вместе с меткой, сведённой из ответов его разметчиков. Potato формулирует гарантию на объект, а документация не формулирует гарантии на разметчика.

Выборка val лежит вне гарантии. После каждой эпохи тренер оценивает модель на объектах val и сохраняет контрольную точку лучшей по оценке эпохи. Этот выбор читает метки объектов val без добавленного шума, поэтому эпсилон в карточке модели ничего не обещает об объектах val. Когда объектам val нужна та же защита, что и объектам train, задайте долю val равной 0 и перенесите её в test, например splits: {train: 0.7, val: 0, test: 0.3}. Без объектов val тренер сохраняет последнюю эпоху, patience никогда не останавливает запуск раньше, а оценка на отложенных объектах берётся из test. Неизменная доля train оставляет в train те же объекты, потому что Potato назначает выборки по хешу с seed от идентификатора каждого объекта.

Список меток модели лежит вне гарантии. Potato читает список меток из меток, которые встречаются в данных, поэтому метка, которую разметчики использовали на одном объекте, всё равно появляется среди выходов модели.

Отличия обучения при DP

DP-SGD нужен градиент для каждого объекта, поэтому запуск с DP отличается от обычного запуска того же тренера следующим:

  • Обучение идёт с полной точностью, что бы ни говорил precision, потому что смешанная точность при DP отключена.
  • gradient_accumulation должен быть равен 1. Каждый шаг обучает один пакет, выбранный по Пуассону.
  • Квантованная базовая модель (lora.quantize) отвергается.
  • Таблицы позиционных эмбеддингов и эмбеддингов типа токена текстового трансформера заморожены, как сообщает журнал запуска.
  • Модель со слоем, который Opacus не поддерживает, например BatchNorm, завершается ошибкой в начале обучения с названием слоя.

Параметр dp принимают только hf-text-classification и hf-image-classification. Каждый другой тренер отвергает параметр, который он не принимает, поэтому dp у sklearn-text, setfit или hf-token-classification приводит к ошибке при загрузке в trainer_params и к ответу 409 при начале запуска.

Ошибки в начале запуска

potato validate сообщает о ключе, который dp не принимает, и о значении вне диапазона до запуска сервера. Запуск проверяет блок dp ещё раз при начале, вместе с Opacus и ограничениями, зависящими от выборки train. Запуск, не прошедший эти проверки, заканчивается ошибкой до обучения с кодом validation_failed и всеми проблемами в одном сообщении. Сообщения приходят из проверок Potato:

ПроблемаСообщение
Opacus не установленdp needs opacus, which is not installed (pip install opacus).
Нет epsilondp needs epsilon, the privacy budget for the run (1 to 10 is usual).
Ключ, который dp не принимаетdp does not take bogus; it takes epsilon, delta and max_grad_norm.
delta не меньше 1 / число объектов traindp.delta is 0.05, at least 1 / 76 train items: at that delta one item's data could be released outright. Use less than 0.0131579.
gradient_accumulation больше 1dp trains one sampled batch per step; set gradient_accumulation to 1.
Задан lora.quantizedp cannot train a quantized (4bit or 8bit) base model; drop lora.quantize.

Пустое отображение dp: {} просит DP без бюджета и завершается сообщением об отсутствующем epsilon. Когда trainer_params задаёт dp, значение "dp": null или "dp": false в запуске отключает DP для этого запуска. Маршрут состояния GET /admin/training/api/status перечисляет те же сообщения в errors.

Дополнительные материалы

Литература