Opacus
  • 简介
  • 常见问题
  • 教程
  • API 参考
  • GitHub

›

教程

  • 概览

使用 Opacus

  • 使用快速梯度裁剪 DP-SGD 构建文本分类器
  • 使用差分隐私构建图像分类器
  • 训练用于姓名分类的差分隐私 LSTM 模型
  • 深入了解 Opacus 的高级功能
  • 模块验证器 (Module Validator) 和修复器 (Fixer) 指南
  • 梯度采样器 (Grad Samplers) 指南
  • 使用 DistributedDataParallel 进行多 GPU 训练

目录

  • 使用差分隐私构建图像分类器
    • 概览
    • 超参数
    • 数据
    • 模型
    • 准备训练
    • 训练网络
    • 在测试数据上测试网络
    • 提示和技巧
    • 私有模型与非私有模型的性能

使用差分隐私构建图像分类器¶

概述¶

在本教程中,我们将学习以下内容

  1. 了解与DP-SGD相关的隐私特定超参数
  2. 了解ModelInspector、不兼容层,并使用模型重写实用程序。
  3. 训练一个差分隐私的ResNet18用于图像分类。

超参数¶

要使用Opacus训练模型,必须调整三个隐私特定超参数以获得更好的性能

  • 最大梯度范数:在平均步骤聚合之前,每个样本梯度的最大L2范数。
  • 噪声乘数:采样并添加到批次梯度平均值中的噪声量。
  • Delta:(ϵ,δ)-差分隐私保证的目标δ。通常,它应设置为小于训练数据集大小的倒数。在本教程中,它设置为$10^{−5}$,因为CIFAR10数据集有50,000个训练点。

我们使用下面的超参数值来获取最后一节中的结果

输入 [1]
import warnings
warnings.simplefilter("ignore")

MAX_GRAD_NORM = 1.2
EPSILON = 50.0
DELTA = 1e-5
EPOCHS = 20

LR = 1e-3

我们还应该注意另一个限制——内存。为了平衡峰值内存需求(与batch_size^2成正比)和训练性能,我们将使用BatchMemoryManager。它将逻辑批大小(定义模型更新频率和添加DP噪声量)与物理批大小(定义我们一次处理多少样本)分开。

使用BatchMemoryManager,您将使用逻辑批大小创建DataLoader,然后将最大物理批大小提供给内存管理器。

输入 [2]
BATCH_SIZE = 512
MAX_PHYSICAL_BATCH_SIZE = 128

数据¶

现在,让我们加载CIFAR10数据集。我们在这里不使用数据增强,因为在我们的实验中,我们发现使用DP训练时,数据增强会降低效用。

输入 [3]
import torch
import torchvision
import torchvision.transforms as transforms

# These values, specific to the CIFAR10 dataset, are assumed to be known.
# If necessary, they can be computed with modest privacy budgets.
CIFAR10_MEAN = (0.4914, 0.4822, 0.4465)
CIFAR10_STD_DEV = (0.2023, 0.1994, 0.2010)

transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(CIFAR10_MEAN, CIFAR10_STD_DEV),
])

使用torchvision数据集,我们可以加载CIFAR10并将PILImage图像转换为标准化范围[-1, 1]的张量

输入 [4]
from torchvision.datasets import CIFAR10

DATA_ROOT = '../cifar10'

train_dataset = CIFAR10(
    root=DATA_ROOT, train=True, download=True, transform=transform)

train_loader = torch.utils.data.DataLoader(
    train_dataset,
    batch_size=BATCH_SIZE,
)

test_dataset = CIFAR10(
    root=DATA_ROOT, train=False, download=True, transform=transform)

test_loader = torch.utils.data.DataLoader(
    test_dataset,
    batch_size=BATCH_SIZE,
    shuffle=False,
)
Files already downloaded and verified
Files already downloaded and verified

模型¶

输入 [5]
from torchvision import models

model = models.resnet18(num_classes=10)

现在,让我们检查模型是否与Opacus兼容。Opacus不支持所有类型的Pytorch层。为了检查您的模型是否与隐私引擎兼容,我们提供了一个实用类来验证您的模型。

当您运行下面的代码时,您会看到一个错误列表,指示哪些模块不兼容。

输入 [6]
from opacus.validators import ModuleValidator

errors = ModuleValidator.validate(model, strict=False)
errors[-5:]
输出[6]
[opacus.validators.errors.ShouldReplaceModuleError("BatchNorm cannot support training with differential privacy. The reason for it is that BatchNorm makes each sample's normalized value depend on its peers in a batch, ie the same sample x will get normalized to a different value depending on who else is on its batch. Privacy-wise, this means that we would have to put a privacy mechanism there too. While it can in principle be done, there are now multiple normalization layers that do not have this issue: LayerNorm, InstanceNorm and their generalization GroupNorm are all privacy-safe since they don't have this property.We offer utilities to automatically replace BatchNorms to GroupNorms and we will release pretrained models to help transition, such as GN-ResNet ie a ResNet using GroupNorm, pretrained on ImageNet"),
 opacus.validators.errors.ShouldReplaceModuleError("BatchNorm cannot support training with differential privacy. The reason for it is that BatchNorm makes each sample's normalized value depend on its peers in a batch, ie the same sample x will get normalized to a different value depending on who else is on its batch. Privacy-wise, this means that we would have to put a privacy mechanism there too. While it can in principle be done, there are now multiple normalization layers that do not have this issue: LayerNorm, InstanceNorm and their generalization GroupNorm are all privacy-safe since they don't have this property.We offer utilities to automatically replace BatchNorms to GroupNorms and we will release pretrained models to help transition, such as GN-ResNet ie a ResNet using GroupNorm, pretrained on ImageNet"),
 opacus.validators.errors.ShouldReplaceModuleError("BatchNorm cannot support training with differential privacy. The reason for it is that BatchNorm makes each sample's normalized value depend on its peers in a batch, ie the same sample x will get normalized to a different value depending on who else is on its batch. Privacy-wise, this means that we would have to put a privacy mechanism there too. While it can in principle be done, there are now multiple normalization layers that do not have this issue: LayerNorm, InstanceNorm and their generalization GroupNorm are all privacy-safe since they don't have this property.We offer utilities to automatically replace BatchNorms to GroupNorms and we will release pretrained models to help transition, such as GN-ResNet ie a ResNet using GroupNorm, pretrained on ImageNet"),
 opacus.validators.errors.ShouldReplaceModuleError("BatchNorm cannot support training with differential privacy. The reason for it is that BatchNorm makes each sample's normalized value depend on its peers in a batch, ie the same sample x will get normalized to a different value depending on who else is on its batch. Privacy-wise, this means that we would have to put a privacy mechanism there too. While it can in principle be done, there are now multiple normalization layers that do not have this issue: LayerNorm, InstanceNorm and their generalization GroupNorm are all privacy-safe since they don't have this property.We offer utilities to automatically replace BatchNorms to GroupNorms and we will release pretrained models to help transition, such as GN-ResNet ie a ResNet using GroupNorm, pretrained on ImageNet"),
 opacus.validators.errors.ShouldReplaceModuleError("BatchNorm cannot support training with differential privacy. The reason for it is that BatchNorm makes each sample's normalized value depend on its peers in a batch, ie the same sample x will get normalized to a different value depending on who else is on its batch. Privacy-wise, this means that we would have to put a privacy mechanism there too. While it can in principle be done, there are now multiple normalization layers that do not have this issue: LayerNorm, InstanceNorm and their generalization GroupNorm are all privacy-safe since they don't have this property.We offer utilities to automatically replace BatchNorms to GroupNorms and we will release pretrained models to help transition, such as GN-ResNet ie a ResNet using GroupNorm, pretrained on ImageNet")]

让我们修改模型以与Opacus一起使用。从上面的输出中,您可以看到BatchNorm层不受支持,因为它们计算批次的均值和方差,在批次中的样本之间创建了依赖关系,这违反了隐私。

推荐的处理方法是调用ModuleValidator.fix(model)——它会尝试为不兼容的模块找到最佳替代品。例如,对于BatchNorm模块,它将其替换为GroupNorm。您可以看到,在此之后,没有引发异常

输入 [7]
model = ModuleValidator.fix(model)
ModuleValidator.validate(model, strict=False)
输出[7]
[]

为了最大速度,我们可以检查CUDA是否可用并且受PyTorch安装支持。如果GPU可用,请将device变量设置为您的CUDA兼容设备。然后我们可以将神经网络传输到该设备上。

输入 [8]
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

model = model.to(device)

然后我们定义我们的优化器和损失函数。Opacus的隐私引擎可以附加到任何(一阶)优化器。您可以使用您喜欢的——Adam、Adagrad、RMSprop——只要它有派生自torch.optim.Optimizer的实现。在本教程中,我们将使用RMSprop。

输入 [9]
import torch.nn as nn
import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.RMSprop(model.parameters(), lr=LR)

准备训练¶

我们将定义一个实用函数来计算准确性

输入 [10]
def accuracy(preds, labels):
    return (preds == labels).mean()

我们现在附加用之前定义的隐私超参数初始化的隐私引擎。

输入 [11]
from opacus import PrivacyEngine

privacy_engine = PrivacyEngine()

model, optimizer, train_loader = privacy_engine.make_private_with_epsilon(
    module=model,
    optimizer=optimizer,
    data_loader=train_loader,
    epochs=EPOCHS,
    target_epsilon=EPSILON,
    target_delta=DELTA,
    max_grad_norm=MAX_GRAD_NORM,
)

print(f"Using sigma={optimizer.noise_multiplier} and C={MAX_GRAD_NORM}")
Using sigma=0.39066894531249996 and C=1.2

然后我们将定义我们的训练函数。此函数将训练模型一个 epoch。

输入 [12]
import numpy as np
from opacus.utils.batch_memory_manager import BatchMemoryManager


def train(model, train_loader, optimizer, epoch, device):
    model.train()
    criterion = nn.CrossEntropyLoss()

    losses = []
    top1_acc = []
    
    with BatchMemoryManager(
        data_loader=train_loader, 
        max_physical_batch_size=MAX_PHYSICAL_BATCH_SIZE, 
        optimizer=optimizer
    ) as memory_safe_data_loader:

        for i, (images, target) in enumerate(memory_safe_data_loader):   
            optimizer.zero_grad()
            images = images.to(device)
            target = target.to(device)

            # compute output
            output = model(images)
            loss = criterion(output, target)

            preds = np.argmax(output.detach().cpu().numpy(), axis=1)
            labels = target.detach().cpu().numpy()

            # measure accuracy and record loss
            acc = accuracy(preds, labels)

            losses.append(loss.item())
            top1_acc.append(acc)

            loss.backward()
            optimizer.step()

            if (i+1) % 200 == 0:
                epsilon = privacy_engine.get_epsilon(DELTA)
                print(
                    f"\tTrain Epoch: {epoch} \t"
                    f"Loss: {np.mean(losses):.6f} "
                    f"Acc@1: {np.mean(top1_acc) * 100:.6f} "
                    f"(ε = {epsilon:.2f}, δ = {DELTA})"
                )

接下来,我们将定义我们的测试函数来验证我们的模型在测试数据集上的表现。

输入 [13]
def test(model, test_loader, device):
    model.eval()
    criterion = nn.CrossEntropyLoss()
    losses = []
    top1_acc = []

    with torch.no_grad():
        for images, target in test_loader:
            images = images.to(device)
            target = target.to(device)

            output = model(images)
            loss = criterion(output, target)
            preds = np.argmax(output.detach().cpu().numpy(), axis=1)
            labels = target.detach().cpu().numpy()
            acc = accuracy(preds, labels)

            losses.append(loss.item())
            top1_acc.append(acc)

    top1_avg = np.mean(top1_acc)

    print(
        f"\tTest set:"
        f"Loss: {np.mean(losses):.6f} "
        f"Acc: {top1_avg * 100:.6f} "
    )
    return np.mean(top1_acc)

训练网络¶

输入 [14]
from tqdm.notebook import tqdm

for epoch in tqdm(range(EPOCHS), desc="Epoch", unit="epoch"):
    train(model, train_loader, optimizer, epoch + 1, device)
Epoch:   0%|          | 0/20 [00:00<?, ?epoch/s]
	Train Epoch: 1 	Loss: 2.771490 Acc@1: 15.429688 (ε = 13.64, δ = 1e-05)
	Train Epoch: 2 	Loss: 1.755194 Acc@1: 38.804688 (ε = 17.68, δ = 1e-05)
	Train Epoch: 3 	Loss: 1.724797 Acc@1: 45.769531 (ε = 20.62, δ = 1e-05)
	Train Epoch: 4 	Loss: 1.706076 Acc@1: 48.921875 (ε = 22.94, δ = 1e-05)
	Train Epoch: 5 	Loss: 1.682414 Acc@1: 51.664062 (ε = 25.25, δ = 1e-05)
	Train Epoch: 6 	Loss: 1.671187 Acc@1: 53.234375 (ε = 26.99, δ = 1e-05)
	Train Epoch: 7 	Loss: 1.657112 Acc@1: 55.324219 (ε = 28.73, δ = 1e-05)
	Train Epoch: 8 	Loss: 1.633768 Acc@1: 56.277344 (ε = 30.46, δ = 1e-05)
	Train Epoch: 9 	Loss: 1.647288 Acc@1: 57.203125 (ε = 32.20, δ = 1e-05)
	Train Epoch: 10 	Loss: 1.639933 Acc@1: 58.191406 (ε = 33.83, δ = 1e-05)
	Train Epoch: 11 	Loss: 1.639214 Acc@1: 59.140625 (ε = 35.17, δ = 1e-05)
	Train Epoch: 12 	Loss: 1.629374 Acc@1: 59.613281 (ε = 36.51, δ = 1e-05)
	Train Epoch: 13 	Loss: 1.636143 Acc@1: 60.246094 (ε = 37.85, δ = 1e-05)
	Train Epoch: 14 	Loss: 1.634575 Acc@1: 60.550781 (ε = 39.18, δ = 1e-05)
	Train Epoch: 15 	Loss: 1.611133 Acc@1: 61.378906 (ε = 40.52, δ = 1e-05)
	Train Epoch: 16 	Loss: 1.604075 Acc@1: 62.015625 (ε = 41.86, δ = 1e-05)
	Train Epoch: 17 	Loss: 1.601270 Acc@1: 62.140625 (ε = 43.20, δ = 1e-05)
	Train Epoch: 18 	Loss: 1.599596 Acc@1: 62.437500 (ε = 44.53, δ = 1e-05)
	Train Epoch: 19 	Loss: 1.587946 Acc@1: 63.097656 (ε = 45.87, δ = 1e-05)
	Train Epoch: 20 	Loss: 1.583897 Acc@1: 63.250000 (ε = 47.21, δ = 1e-05)

在测试数据上测试网络¶

输入 [15]
top1_acc = test(model, test_loader, device)
	Test set:Loss: 1.711833 Acc: 60.753676 

提示和技巧¶

  1. 一般来说,差分隐私训练本身就是足够的正则化。添加任何额外的正则化(如 dropout 或数据增强)都是不必要的,通常会损害性能。
  2. 调整 MAX_GRAD_NORM 非常重要。从低噪声乘数(如 0.1)开始,这应该能提供与非隐私模型相当的性能。然后对最优的 MAX_GRAD_NORM 值进行网格搜索。网格范围可以在 [0.1, 10] 内。
  3. 你可以调整隐私级别 EPSILON。更小的 EPSILON 意味着更高的隐私、更多的噪声——因此准确性更低。将 EPSILON 降低到 5.0 会将 Top 1 准确性降低到大约 53%。一个有用的技术是,在私有训练数据上完成训练之前,先在公共(非私有)数据上预训练模型。有关示例,请参阅工作簿bit.ly/opacus-dev-day。

私有模型与非私有模型的性能¶

现在让我们比较我们的私有模型与非私有ResNet18的性能。

我们使用与上述相同的超参数,并将BatchNorm替换为GroupNorm,训练了一个非私有ResNet18模型20个epoch。该训练以及本教程中讨论的训练结果总结在下表中

模型 Top 1 准确率 (%) ϵ
ResNet 76 ∞
私有 ResNet 61 47.21
下载教程 Jupyter Notebook
Opacus
文档
简介常见问题教程API 参考
Github
opacus
法律
隐私政策条款
Meta Open Source
Copyright © 2025 Meta Platforms, Inc.