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

›

教程

  • 概览

使用 Opacus

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

梯度采样器(grad samplers)指南¶

DP-SGD 保证了训练中使用的每个样本的隐私。为了实现这一点,我们必须限制每个样本的敏感度,而为了做到这一点,我们必须对每个样本的梯度进行裁剪(clipping)。遗憾的是,pytorch 并不保留 batch 中单个样本的梯度,而仅通过 .grad 属性公开 batch 中所有样本的聚合梯度。

获取我们所需内容最简单的方法是按如下方式使用为 1 的 batch size 进行训练

In [ ]
optimizer = torch.optim.SGD(lr=0.01)
for x, y i DataLoader(train_dataset, batch_size=128):
  # Run samples one-by-one to get per-sample gradients
  for x_i, y_i in zip(x, y):
    y_hat_i = model(x_i)
    loss = criterion(y_hat_i, y_i)
    loss.backward()
  
    # Clip each parameter's per-sample gradient
    for p in model.parameters():
      per_sample_grad = p.grad.detach().clone()
      torch.nn.utils.clip_grad_norm(per_sample_grad, max_norm=1.0)
      p.accumulated_grads.append(per_sample_grad)
    model.zero_grad(). # p.grad is accumulative, so we need to manually reset
  
  # Aggregate clipped gradients of all samples in a batch, and add DP noise
  for p in model.parameters():
    p.grad = accumulate_and_noise(p.accumulated_grads, dp_paramters)
  
  optimizer.step()
  optimizer.zero_grad()

然而,这将是对时间和资源的极大浪费,而且我们会把所有向量化优化都束之高阁。

GradSampleModule 是 Opacus 提供的一个 nn.Module 替代品,用于解决上述问题。除了 .grad 属性外,该模块的参数还将拥有一个 .grad_sample 属性。

GradSampleModule 内部机制¶

对于大多数模块,Opacus 提供了一个函数(又名 grad_sampler),该函数基本上通过——或多或少——“手动”执行反向传播来计算 batch 的每样本梯度(per-sample-gradients)。

GradSampleModule 是对现有 nn.Module 的包装。它使用 反向传播钩子(backward hooks) 将上述函数附加到它包装的模块上。它还提供其他辅助方法,如验证、添加/删除/设置/重置 grad_sample 的工具、挂载/移除钩子的工具等。

简而言之:grad_samplers 包含在给定激活值和反向传播梯度的情况下计算梯度的逻辑,而 GradSampleModule 则通过将 grad_samplers 附加到正确的模块上并向用户公开简单/最小化的接口来负责处理其他所有事务。

让我们看一个例子。假设你想获得一个 nn.Linear 的 GradSampleModule 版本。这就是你所需做的

In [ ]
import torch.nn as nn
from opacus.grad_sample import GradSampleModule

lin_mod = nn.Linear(42,2)
print(f"Before wrapping: {lin_mod}")

gs_lin_mod = GradSampleModule(lin_mod)
print(f"After wrapping : {gs_lin_mod}")
Before wrapping: Linear(in_features=42, out_features=2, bias=True)
After wrapping : GradSample(Linear(in_features=42, out_features=2, bias=True))

就这样!GradSampleModule 为你的线性模块包装了所有功能,你可以将此模块作为直接替代品使用。

grad_sampler 内部机制¶

那么,上述 nn.Linear 层的 grad_sampler 长什么样呢?如下所示

In [ ]
def compute_linear_grad_sample(
    layer: nn.Linear, activations: torch.Tensor, backprops: torch.Tensor
) -> Dict[nn.Parameter, torch.Tensor]:
    """
    Computes per sample gradients for ``nn.Linear`` layer
    Args:
        layer: Layer
        activations: Activations
        backprops: Backpropagations
    """
    gs = torch.einsum("n...i,n...j->nij", backprops, activations)
    ret = {layer.weight: gs}
    if layer.bias is not None:
        ret[layer.bias] = torch.einsum("n...k->nk", backprops)

    return ret

上述 grad_sampler 接收激活值和反向传播的梯度,计算相对于模块参数的每样本梯度,并将它们映射到相应的参数。这篇 博客 详细讨论了其实现及背后的数学原理。

注册 grad_sampler¶

但是你如何告诉 Opacus 这是一个 grad_sampler 呢?很简单,你只需用 register_grad_sampler 对其进行装饰

In [ ]
from opacus.grad_sample import register_grad_sampler


@register_grad_sampler(nn.Linear)
def compute_linear_grad_sample(
    layer: nn.Linear, activations: torch.Tensor, backprops: torch.Tensor
) -> Dict[nn.Parameter, torch.Tensor]:
    """
    Computes per sample gradients for ``nn.Linear`` layer
    Args:
        layer: Layer
        activations: Activations
        backprops: Backpropagations
    """
    gs = torch.einsum("n...i,n...j->nij", backprops, activations)
    ret = {layer.weight: gs}
    if layer.bias is not None:
        ret[layer.bias] = torch.einsum("n...k->nk", backprops)

    return ret

再次强调,就这样!真的,查看一下 代码,它字面上就是这样。

在 grad_sample/utils 中定义的 register_grad_sampler 将该函数注册为 nn.Linear(作为装饰器参数传递)的 grad_sampler。GradSampleModule 维护着所有 grad_samplers 及其对应模块的 注册表。

如果你想注册一个自定义 grad_sampler,你只需按照上面所示装饰你的函数。请注意,注册顺序很重要;如果你为某个模块注册了多个 grad_sampler,则以最后一个为准。

受支持的模块¶

Opacus 为大多数常用模块提供 grad_samplers;你可以在 此处 查看完整列表。如你所见,这个列表并不详尽;我们衷心欢迎您的贡献。

根据设计,GradSampleModule 只做这一件事——计算梯度样本。虽然它是为与 Opacus 配合使用而构建的,但它肯定不局限于差分隐私(DP)用例,可以用于任何需要每样本梯度的任务。

如果您有任何问题或评论,请随时发布到我们的 论坛。

每样本梯度正确性工具¶

这里 你可以找到一个简单的工具函数 check_per_sample_gradients_are_correct,用于检查梯度采样器在特定模块上是否工作正确。

In [ ]
x_shape = [N, Z, W]
x = torch.randn(x_shape)
model = nn.Linear(W, W + 2)
assert check_per_sample_gradients_are_correct(
        x,
        model
    ) # This will fail only if the opacus per sample gradients do not match the micro-batch gradients.
下载教程 Jupyter Notebook
Opacus
文档
简介常见问题教程API 参考
Github
opacus
法律
隐私政策条款
Meta Open Source
Copyright © 2025 Meta Platforms, Inc.