要使用Opacus训练模型,必须调整三个隐私特定超参数以获得更好的性能
我们使用下面的超参数值来获取最后一节中的结果
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,然后将最大物理批大小提供给内存管理器。
BATCH_SIZE = 512
MAX_PHYSICAL_BATCH_SIZE = 128
现在,让我们加载CIFAR10数据集。我们在这里不使用数据增强,因为在我们的实验中,我们发现使用DP训练时,数据增强会降低效用。
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]的张量
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
from torchvision import models
model = models.resnet18(num_classes=10)
现在,让我们检查模型是否与Opacus兼容。Opacus不支持所有类型的Pytorch层。为了检查您的模型是否与隐私引擎兼容,我们提供了一个实用类来验证您的模型。
当您运行下面的代码时,您会看到一个错误列表,指示哪些模块不兼容。
from opacus.validators import ModuleValidator
errors = ModuleValidator.validate(model, strict=False)
errors[-5:]
[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。您可以看到,在此之后,没有引发异常
model = ModuleValidator.fix(model)
ModuleValidator.validate(model, strict=False)
[]
为了最大速度,我们可以检查CUDA是否可用并且受PyTorch安装支持。如果GPU可用,请将device变量设置为您的CUDA兼容设备。然后我们可以将神经网络传输到该设备上。
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
然后我们定义我们的优化器和损失函数。Opacus的隐私引擎可以附加到任何(一阶)优化器。您可以使用您喜欢的——Adam、Adagrad、RMSprop——只要它有派生自torch.optim.Optimizer的实现。在本教程中,我们将使用RMSprop。
import torch.nn as nn
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.RMSprop(model.parameters(), lr=LR)
我们将定义一个实用函数来计算准确性
def accuracy(preds, labels):
return (preds == labels).mean()
我们现在附加用之前定义的隐私超参数初始化的隐私引擎。
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。
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})"
)
接下来,我们将定义我们的测试函数来验证我们的模型在测试数据集上的表现。
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)
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)
top1_acc = test(model, test_loader, device)
Test set:Loss: 1.711833 Acc: 60.753676
现在让我们比较我们的私有模型与非私有ResNet18的性能。
我们使用与上述相同的超参数,并将BatchNorm替换为GroupNorm,训练了一个非私有ResNet18模型20个epoch。该训练以及本教程中讨论的训练结果总结在下表中
| 模型 | Top 1 准确率 (%) | ϵ |
|---|---|---|
| ResNet | 76 | ∞ |
| 私有 ResNet | 61 | 47.21 |