Привет, Хабр!
Сегодня поговорим в коротком формате о защите данных при обучении моделей, а именно в процессе обучения. Никому не понравится, если ваша нейросеть вдруг выдаст чужие паспортные данные или медицинские записи, правда? А ведь модели машинного обучения иногда склонны запоминать кусочки обучающего набора. Бывали случчаи, где из языковой модели вытаскивали строки с номерами телефонов и email тех людей, чьи данные были в тренировочном датасете.
Стоит ли нам вообще кормить модель конфиденциальной информацией, если она потом болтает лишнее? К счастью, есть крутая техника — дифференциальная приватность. Она позволяет обучать ML-модели на реальных данных, но с гарантией, что никакой отдельный пользователь не будет опознан моделью.
Что такое дифференциальная приватность и зачем она нужна
Если говорить по-простому, дифференциальная приватность гарантирует, что вклад любого одного пользователя в итог модели мал. Представьте две почти одинаковые базы: в одной есть ваши данные, в другой нет. Модель обучается то на одной, то на другой и разница в результатах минимальна. Настолько минимальна, что никто, глядя на модель, не поймёт, были ли ваши данные внутри. Формально это выражается параметрами ε (эпсилон) и δ (дельта). Маленький ε значит сильную приватность: модель на наборах с или без конкретного человека выдаёт почти одинаковые распределения ответов. А δ вероятность допустить чуть больший прокол (обычно δ берут микроскопическим, например 1e-5, чтобы практически 0).
Например, приложение обучает модель на пользовательских фото или на покупках клиентов. Дифференциальная приватность даёт математически доказуемую защиту от утечек индивидуальной информации. Даже если злоумышленник получит веса вашей нейросети, он не сумеет выудить из них исходные записи. Более того, DP уже требование законов и этики: регуляторы всё чаще требуют, чтобы данные либо анонимизировались, либо не использовались напрямую.
Как работает обучение модели с дифференциальной приватностью
Основная идея слегка исказить процесс обучения, чтобы модель не запоминала частные детали. Конкретно для нейросетей чаще всего применяют алгоритм DP-SGD. Он почти как обычный SGD, но с двумя важными ингредиентами:
Клиппирование градиентов. Ограничиваем вклад каждого отдельного образца в градиент. Если градиент от одного примера слишком большой (может, этот пример «странный» и сильно влияет на веса), мы его обрубаем до порога C (max_grad_norm). Тем самым любой один объект датасета не сможет слишком сильно сдвинуть веса модели.
Добавление шума. На каждом шаге обучения, после вычисления градиентов на батче, мы добавляем к суммарному градиенту случайный шум (обычно гауссовский). Шум масштабируется так, чтобы спрятать влияние отдельного образца. Даже зная итоговый градиент, нельзя точно сказать, какие объекты в батче были и какие значения их градиентов, шум всё замаскирует.
За счёт этих двух шагов алгоритм как раз и гарантирует дифференциальную приватность. Интуитивно клиппирование задаёт максимум влияния одного примера, а шум размазывает это влияние под завесой случайности. В итоге модель учит общие закономерности, но мелкие детали отдельных пользователей тонут в шуме.
Конечно, бесплатных пирожков не бывает. Шум сам по себе дополнительная ошибка в каждом обновлении. Значит, модель будет сходиться хуже или достигнет меньше точности. Придётся пожертвовать точностью ради приватности. Насколько? Зависит от уровня ε (чем сильнее приватность, тем больше шума мы льём). Или наоборот, если хотим модель поточнее, придётся допустить больший ε (то есть чуть более высокий риск утечки). Есть компромисс. Часто выбирают ε в районе от ~1 до 10 (вспомним, чем меньше, тем приватнее). Например, ε=1 считается очень строгой гарантией, ε=10 – уже слабее, но может приемлемо. Библиотека Opacus по умолчанию даже ругается, если ε выходит за 10, мол, вы слишком слабо защищаете данные, так не годится.
Как влияет шум на метрику? В документации Opacus есть пример: они обучали текстовый классификатор с разными настройками DP. Без приватности модель дала ~85% точности. С сильной приватностью (ε ~0.7, шум огромный) точность упала до ~70%. Если ослабить приватность до ε ~7.5, точность ~74%. То есть потеряли около 11 процентных пунктов по сравнению с обычной моделью. Это, кстати, еще адекватная плата за довольно сильную гарантию приватности. А если еще снизить шум (ε сильно больше 7.5), точность подтянется ближе к 80%, но там уже ε вылетает за сотни (что, по сути, “приватность только на бумаге”). В общем, всегда есть компромисс: больше шума = ниже риск утечки, но хуже качество.
Кстати, помимо шума, на качество влияют и другие нюансы DP-SGD: размер батча, порог клиппирования, скорость обучения. Например, маленький батч даёт сильную приватность при том же шуме, но модель обучается нестабильнее. Порог max_grad_norm тоже важно настроить: слишком маленький – градиенты все обрезаны, модель недоучивается; слишком большой – мало обрезания, но тогда ради приватности приходится лить больше шума. Обычно рекомендуют начать с max_grad_norm = 1.0 и фокусироваться на подборе noise_multiplier (коэффициента шума) и размера батча. Скорость обучения при DP обычно ставят пониже, чем в обычном SGD, потому что наш шумящий градиент итак достаточно шаткий, не стоит делать слишком большие шаги.
В общем сперва нужно добиться, чтобы модель вообще сходилась с каким-то приемлемым quality при включённом DP, а потом уже крутить ручки, больше данных, побольше слоёв в модель (глубокие модели легче уживаются с шумом), тюнить гиперпараметры.
Учим нейросеть с PyTorch Opacus
Будем использовать PyTorch + Opacus. Аналоги для TensorFlow тоже существуют (например, TensorFlow Privacy), но для пайторчников так привычнее. Opacus встраивается в стандартный тренинг PyTorch с минимальными изменениями.
Для примера представим, что мы обучаем простенькую нейросеть на датасете MNIST (распознавание рукописных цифр). Обычно мы бы написали цикл тренировки: берём батч изображений, считаем loss, дифференцируем, делаем optimizer.step(). С Opacus будет почти то же, + инициализация PrivacyEngine.
Код:
import torch
from torch import nn
from torch.utils.data import DataLoader
from opacus import PrivacyEngine
# ... (загружаем датасет MNIST в train_loader) ...
model = nn.Sequential(
nn.Flatten(),
nn.Linear(28*28, 128), nn.ReLU(),
nn.Linear(128, 10)
)
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
# Инициализируем механизм приватности
privacy_engine = PrivacyEngine()
model, optimizer, train_loader = privacy_engine.make_private(
module=model,
optimizer=optimizer,
data_loader=train_loader,
noise_multiplier=1.0, # коэффициент шума
max_grad_norm=1.0 # порог клиппирования градиента
)
Создали PrivacyEngine и вызвали make_private, передав нашу модель, оптимизатор и данные. В результате модель и оптимизатор обёрнуты в приватность: теперь optimizer.step() будет делать нужные вещи (градиенты на каждом шаге будут клиппированы по L2-норме до 1.0 и затем к ним добавится Gaussian шум с σ = 1.0). Еще мы задали noise_multiplier=1.0 – это довольно сильный шум, просто для примера. Можно было вместо noise_multiplier указать целевой target_epsilon и epochs, тогда Opacus сам подберёт нужный шум.
Дальше запускаем учебный цикл как обычно:
num_epochs = 3
criterion = nn.CrossEntropyLoss()
for epoch in range(num_epochs):
for X, y in train_loader:
optimizer.zero_grad()
logits = model(X)
loss = criterion(logits, y)
loss.backward()
optimizer.step()
# После каждой эпохи можно вычислить фактический эпсилон:
epsilon_spent = privacy_engine.get_epsilon(delta=1e-5)
print(f"Эпоха {epoch+1}: текущий ε = {epsilon_spent:.2f}")
Метод get_epsilon(delta) считает накопленный расход приватности после всех градиентных шагов на этой эпохе. В нашем случае ставим δ = 1e-5 (как правило, берут 1/число_образцов). В начале ε будет расти с каждой эпохой (каждый шаг чуть “тратит” бюджет приватности). Можно остановить обучение, когда ε достигнет приемлемого для нас порога. Либо задать сразу epochs и target_epsilon при инициализации PrivacyEngine, чтобы он сам остановился когда надо.
Когда модель обучена, получим итоговый ε. Например, у меня вышло что-то вроде ε = 2.5 после 3 эпох (число для иллюстрации). Значит, модель удовлетворяет (2.5, 1e-5)-дифференциальной приватности. Это довольно хороший уровень защиты. Можно теперь смело публиковать модель, даже открыв все веса, вы не нарушите приватность пользователей (в рамках заданных гарантий).
В итоге
Конечно дифференциальная приватность не серебряная пуля. Она гарантирует, что модель не выдаст конкретные данные человека, но все еще может быть уязвима к другим вещам. Например, модель может по-прежнему быть смещенной или выдавать общие инсайты о группе людей (что уже задача этики и fairness, за пределами DP). Также DP не спасёт, если вы напрямую логируете личные данные или вывели их куда-то помимо модели. Это только про тренировку модели.
Если интересно применять ML там, где каждая ошибка стоит денег, посмотрите в сторону задач на финансовых рядах и торговых стратегиях. На курсе «ML для финансового анализа» вы на реальных данных проходите весь цикл создания торгового робота — от анализа инструментов и оценки риска до запуска и регулярного переобучения в продакшене.
Чтобы узнать больше о формате обучения и познакомиться с преподавателями, приходите на бесплатные демо-уроки:
4 декабря: Введение в библиотеки обработки данных финансовых моделей. Записаться
9 декабря: Yahoo finance и не только — работа с российскими торговыми площадками. Записаться
15 декабря: Частотный портрет временных рядов: от Фурье к вейвлетам и обратно. Записаться