调整推荐聚合以进行学习

在 TensorFlow.org 上查看 在 Google Colab 中运行 在 GitHub 上查看源代码 下载笔记本

tff.learning 模块包含多种方法,可以使用推荐的默认配置聚合模型更新

在本教程中,我们将解释其背后的动机,它们是如何实现的,并提供有关如何自定义其配置的建议。


pip install --quiet --upgrade tensorflow-federated
import math
import tensorflow_federated as tff
tff.federated_computation(lambda: 'Hello, World!')()
b'Hello, World!'

聚合方法由可以传递给 tff.learning.algorithms.build_weighted_fed_avg(以及 build_unweighted_fed_avg)的 model_aggregator 关键字参数的对象表示。因此,此处讨论的聚合器可以直接用于修改有关联合学习的 先前 教程

来自 FedAvg 算法的基线加权平均值可以使用 tff.aggregators.MeanFactory 表示如下

mean = tff.aggregators.MeanFactory()
iterative_process = tff.learning.algorithms.build_weighted_fed_avg(
    ...,
    model_aggregator=mean)

本教程中介绍的可以用来扩展加权平均值的技巧有

  • 归零
  • 裁剪
  • 差分隐私
  • 压缩
  • 安全聚合

扩展是通过组合完成的,其中 MeanFactory 包装了一个内部工厂,它将部分聚合委托给该工厂,或者它本身被另一个聚合工厂包装。有关设计的更多详细信息,请参阅 实现自定义聚合器 教程。

首先,我们将解释如何单独启用和配置这些技巧,然后展示如何将它们组合在一起。

技巧

在深入研究各个技巧之前,我们首先介绍分位数匹配算法,它将有助于配置下面的技巧。

分位数匹配

下面的几种聚合技巧需要使用一个范数边界来控制聚合的某些方面。此类边界可以作为常数提供,但通常最好在训练过程中调整边界。推荐的方法是使用 Andrew 等人 (2019) 的分位数匹配算法,该算法最初是为与差分隐私兼容而提出的,但更广泛地适用。要估计给定分位数的值,可以使用 tff.aggregators.PrivateQuantileEstimationProcess。例如,要适应分布的中位数,可以使用

median_estimate = tff.aggregators.PrivateQuantileEstimationProcess.no_noise(
    initial_estimate=1.0, target_quantile=0.5, learning_rate=0.2)

使用分位数估计算法的不同技巧将需要算法参数的不同值,正如我们将看到的。一般来说,增加 learning_rate 参数意味着更快地适应正确分位数,但方差更大。 no_noise 类方法构建一个分位数匹配过程,该过程不会为差分隐私添加噪声。

归零

归零是指用零替换异常大的值。这里,“异常大”可能意味着大于预定义的阈值,或者相对于先前计算轮次的数值而言很大。归零可以提高系统对故障客户端数据损坏的鲁棒性。

为了计算 L-infinity 范数大于 ZEROING_CONSTANT 的值的平均值,我们将一个 tff.aggregators.MeanFactory 包裹在一个 tff.aggregators.zeroing_factory 中,该工厂执行清零操作。

zeroing_mean = tff.aggregators.zeroing_factory(
    zeroing_norm=MY_ZEROING_CONSTANT,
    inner_agg_factory=tff.aggregators.MeanFactory())

在这里,我们将 MeanFactory 包裹在一个 zeroing_factory 中,因为我们希望 zeroing_factory 的(预聚合)效果在客户端的值传递给内部 MeanFactory 进行平均聚合之前应用于这些值。

但是,对于大多数应用程序,我们建议使用分位数估计器的自适应清零。为此,我们使用分位数匹配算法,如下所示。

zeroing_norm = tff.aggregators.PrivateQuantileEstimationProcess.no_noise(
    initial_estimate=10.0,
    target_quantile=0.98,
    learning_rate=math.log(10),
    multiplier=2.0,
    increment=1.0)
zeroing_mean = tff.aggregators.zeroing_factory(
    zeroing_norm=zeroing_norm,
    inner_agg_factory=tff.aggregators.MeanFactory())

# Equivalent to:
# zeroing_mean = tff.learning.robust_aggregator(clipping=False)

参数的选择是为了使该过程能够非常快速地适应(相对较大的 learning_rate)到略大于迄今为止看到的最大值的值。对于分位数估计 Q,用于清零的阈值将为 Q * multiplier + increment

裁剪以限制 L2 范数

裁剪客户端更新(投影到 L2 球体上)可以提高对异常值的鲁棒性。一个 tff.aggregators.clipping_factory 的结构与上面讨论的 tff.aggregators.zeroing_factory 完全相同,并且可以接受一个常数或一个 tff.templates.EstimationProcess 作为其 clipping_norm 参数。推荐的最佳实践是使用适度快速地适应适度高范数的裁剪,如下所示。

clipping_norm = tff.aggregators.PrivateQuantileEstimationProcess.no_noise(
    initial_estimate=1.0,
    target_quantile=0.8,
    learning_rate=0.2)
clipping_mean = tff.aggregators.clipping_factory(
    clipping_norm=clipping_norm,
    inner_agg_factory=tff.aggregators.MeanFactory())

# Equivalent to:
# clipping_mean = tff.learning.robust_aggregator(zeroing=False)

根据我们在许多问题上的经验,target_quantile 的精确值似乎并不重要,只要学习率调整得当。但是,将其设置得非常低可能需要提高服务器学习率以获得最佳性能,相对于不使用裁剪而言,这就是我们默认推荐 0.8 的原因。

差分隐私

TFF 也支持使用自适应裁剪和高斯噪声进行差分隐私聚合。可以构建一个聚合器来执行差分隐私平均,如下所示。

dp_mean = tff.aggregators.DifferentiallyPrivateFactory.gaussian_adaptive(
    noise_multiplier=0.1, clients_per_round=100)

# Equivalent to:
# dp_mean = tff.learning.dp_aggregator(
#   noise_multiplier=0.1, clients_per_round=100, zeroing=False)

有关如何设置 noise_multiplier 参数的指南,请参阅 TFF DP 教程

有损压缩

与 gzip 等无损压缩相比,有损压缩通常会产生更高的压缩率,并且仍然可以与无损压缩组合使用。由于在客户端到服务器的通信上花费的时间更少,因此训练轮次完成得更快。由于学习算法的固有随机性,在一定阈值内,有损压缩带来的不准确性不会对整体性能产生负面影响。

默认建议是使用简单的均匀量化(例如,参见 Suresh 等人),由两个值参数化:张量大小压缩 thresholdquantization_bits 的数量。对于每个张量 t,如果 t 的元素数量小于或等于 threshold,则不进行压缩。如果它更大,则使用随机舍入将 t 的元素量化为 quantizaton_bits 位。也就是说,我们应用以下操作:

t = round((t - min(t)) / (max(t) - min(t)) * (2**quantizaton_bits - 1)),

从而产生 [0, 2**quantizaton_bits-1] 范围内的整数值。量化后的值直接打包到整数类型中进行传输,然后应用逆变换。

我们建议将 quantizaton_bits 设置为 8,并将 threshold 设置为 20000。

compressed_mean = tff.aggregators.MeanFactory(
    tff.aggregators.EncodedSumFactory.quantize_above_threshold(
        quantization_bits=8, threshold=20000))

# Equivalent to:
# compressed_mean = tff.learning.compression_aggregator(zeroing=False, clipping=False)

调整建议

两个参数 quantization_bitsthreshold 都可以调整,参与每个训练轮次的客户端数量也会影响压缩的有效性。

阈值。 选择 20000 的默认值是因为我们观察到,具有少量元素的变量(例如常见层类型中的偏差)对引入的噪声更敏感。此外,在实践中,从压缩具有少量元素的变量中几乎没有收获,因为它们的未压缩大小本来就相对较小。

在某些应用程序中,更改阈值的选择可能是有意义的。例如,分类模型输出层的偏差可能对噪声更敏感。如果您正在训练一个词汇量为 20004 的语言模型,您可能希望将 threshold 设置为 20004。

量化位。 quantization_bits 的默认值为 8,对于大多数用户来说应该没问题。如果 8 运行良好,并且您想挤出更多性能,可以尝试将其降低到 7 或 6。如果资源允许进行少量网格搜索,我们建议您确定训练变得不稳定或最终模型质量开始下降的值,然后将该值增加 2。例如,如果将 quantization_bits 设置为 5 可以工作,但将其设置为 4 会降低模型,我们建议默认值为 6,以“安全起见”。

每轮客户端。 请注意,显着增加每轮客户端的数量可以使 quantization_bits 的较小值也能正常工作,因为量化引入的随机不准确性可以通过对更多客户端更新进行平均来消除。

安全聚合

通过安全聚合 (SecAgg),我们指的是一种加密协议,其中客户端更新以一种方式加密,服务器只能解密它们的总和。如果报告回来的客户端数量不足,服务器将一无所获——在任何情况下,服务器都无法检查单个更新。这是通过使用 tff.federated_secure_sum_bitwidth 运算符实现的。

模型更新是浮点值,但 SecAgg 在整数上运行。因此,我们需要在离散化为整数类型之前将任何大值裁剪到某个边界。裁剪边界可以是常数,也可以是自适应确定的(推荐的默认值)。然后对整数进行安全求和,并将总和映射回浮点域。

要使用 SecAgg 计算具有加权值的平均值,其中加权值使用 MY_SECAGG_BOUND 作为裁剪边界进行求和,将 SecureSumFactory 传递给 MeanFactory,如下所示:

secure_mean = tff.aggregators.MeanFactory(
    tff.aggregators.SecureSumFactory(MY_SECAGG_BOUND))

要执行相同的操作,同时自适应地确定边界,请执行以下操作:

secagg_bound = tff.aggregators.PrivateQuantileEstimationProcess.no_noise(
    initial_estimate=50.0,
    target_quantile=0.95,
    learning_rate=1.0,
    multiplier=2.0)
secure_mean = tff.aggregators.MeanFactory(
    tff.aggregators.SecureSumFactory(secagg_bound))

# Equivalent to:
# secure_mean = tff.learning.secure_aggregator(zeroing=Fasle, clipping=False)

调整建议

自适应参数的选择是为了使边界紧密(我们在离散化中不会损失太多精度),但裁剪很少发生。

如果调整参数,请记住 SecAgg 协议是在平均值中加权后对加权模型更新进行求和。权重通常是本地处理的数据点数量,因此在不同的任务之间,正确的边界可能取决于此数量。

我们不建议在创建自适应 secagg_bound 时使用 increment 关键字参数,因为这会导致较大的相对精度损失,如果实际估计值最终很小。

上面的代码片段将仅对加权值使用 SecAgg。如果 SecAgg 也应该用于权重之和,我们建议将边界设置为常数,因为在常见的训练设置中,最大可能权重将在预先知道。

secure_mean = tff.aggregators.MeanFactory(
    value_sum_factory=tff.aggregators.SecureSumFactory(secagg_bound),
    weight_sum_factory=tff.aggregators.SecureSumFactory(
        upper_bound_threshold=MAX_WEIGHT, lower_bound_threshold=0.0))

组合技术

可以将上面介绍的扩展平均值的各个技术组合在一起。

我们建议在客户端应用这些技术的顺序为:

  1. 归零
  2. 裁剪
  3. 其他技术

中的聚合器 tff.aggregators 模块是通过将“内部聚合器”(其预聚合效果最后发生,后聚合效果首先发生)包装在“外部聚合器”中来组合的。例如,要执行清零、裁剪和压缩(按此顺序),可以编写以下代码:

# Compression is innermost because its pre-aggregation effects are last.
compressed_mean = tff.aggregators.MeanFactory(
    tff.aggregators.EncodedSumFactory.quantize_above_threshold(
        quantization_bits=8, threshold=20000))
# Compressed mean is inner aggregator to clipping...
clipped_compressed_mean = tff.aggregators.clipping_factory(
    clipping_norm=MY_CLIPPING_CONSTANT,
    inner_agg_factory=compressed_mean)
# ...which is inner aggregator to zeroing, since zeroing happens first.
final_aggregator = tff.aggregators.zeroing_factory(
    zeroing_norm=MY_ZEROING_CONSTANT,
    inner_agg_factory=clipped_compressed_mean)

请注意,此结构与学习算法的 默认聚合器 相匹配。

其他组合也是可能的。当我们确信可以提供在多个不同应用程序中有效的默认配置时,我们会扩展本文档。有关实现新想法,请参阅 实现自定义聚合器 教程。