本文档介绍了 TFF 的核心层,它是 联邦学习 的基础,以及未来可能出现的非学习联邦算法。
要了解联邦核心的基础知识,请阅读以下教程,因为它们通过示例介绍了一些基本概念,并逐步演示了简单联邦平均算法的构建过程。
我们还建议您熟悉 联邦学习 以及相关的 图像分类 和 文本生成 教程,因为联邦核心 API (FC API) 在联邦学习中的使用为我们设计此层时做出的某些选择提供了重要的背景。
概述
目标、预期用途和范围
联邦核心 (FC) 最好理解为一个用于实现分布式计算的编程环境,即涉及多台计算机(手机、平板电脑、嵌入式设备、台式机、传感器、数据库服务器等)的计算,这些计算机可能各自在本地执行非平凡的处理,并通过网络进行通信以协调其工作。
术语“分布式”非常通用,TFF 并不针对所有可能的分布式算法,因此我们更喜欢使用不太通用的术语“联邦计算”来描述可以在此框架中表达的算法类型。
虽然在本文档中完全正式地定义术语“联邦计算”超出了范围,但请考虑您可能在 研究出版物 中看到的描述新分布式学习算法的伪代码中的算法类型。
简而言之,FC 的目标是能够以类似的紧凑表示,以类似的伪代码抽象级别,来表示程序逻辑,该逻辑不是伪代码,而是可以在各种目标环境中执行的。
FC 设计用于表达的算法类型的关键定义特征是系统参与者的行为以集体方式描述。因此,我们倾向于谈论每个设备在本地转换数据,以及设备通过集中式协调器广播、收集或聚合其结果来协调工作。
虽然 TFF 被设计为能够超越简单的客户端-服务器架构,但集体处理的概念是基础。这是由于 TFF 起源于联邦学习,这项技术最初旨在支持对可能保存在客户端设备控制下且出于隐私原因可能无法简单地下载到集中式位置的敏感数据的计算。虽然此类系统中的每个客户端都贡献数据和处理能力来计算系统结果(我们通常期望该结果对所有参与者都有价值),但我们也努力维护每个客户端的隐私和匿名性。
因此,虽然大多数分布式计算框架旨在从单个参与者的角度表达处理——也就是说,在单个点对点消息交换的级别上,以及参与者本地状态转换与传入和传出消息的相互依赖关系,TFF 的联邦核心旨在从全局系统范围的角度描述系统的行为(类似于例如 MapReduce)。
因此,虽然用于通用目的的分布式框架可能提供诸如发送和接收之类的操作作为构建块,但 FC 提供诸如 tff.federated_sum
、tff.federated_reduce
或 tff.federated_broadcast
之类的构建块,这些构建块封装了简单的分布式协议。
语言
Python 接口
TFF 使用内部语言来表示联邦计算,该语言的语法由 computation.proto 中的可序列化表示定义。FC API 的用户通常不需要直接与这种语言交互。相反,我们提供了一个 Python API(tff
命名空间),它作为一种定义计算的方式围绕它进行包装。
具体来说,TFF 提供了 Python 函数装饰器,例如 tff.federated_computation
,它跟踪装饰函数的主体,并生成 TFF 语言中联邦计算逻辑的可序列化表示。用 tff.federated_computation
装饰的函数充当此类序列化表示的载体,并且可以将其作为构建块嵌入到另一个计算的主体中,或者在调用时按需执行。
这里只是一个例子;更多示例可以在 自定义算法 教程中找到。
@tff.federated_computation(tff.FederatedType(np.float32, tff.CLIENTS))
def get_average_temperature(sensor_readings):
return tff.federated_mean(sensor_readings)
熟悉非急切 TensorFlow 的读者会发现这种方法类似于编写 Python 代码,该代码在定义 TensorFlow 图的 Python 代码部分中使用诸如 tf.add
或 tf.reduce_sum
之类的函数。虽然代码在技术上是用 Python 表达的,但它的目的是在下面构建 tf.Graph
的可序列化表示,并且是图,而不是 Python 代码,由 TensorFlow 运行时内部执行。同样,可以将 tff.federated_mean
视为将联邦操作插入由 get_average_temperature
表示的联邦计算中。
FC 定义语言的部分原因是,如上所述,联邦计算指定了分布式集体行为,因此它们的逻辑是非局部的。例如,TFF 提供了运算符,其输入和输出可能存在于网络中的不同位置。
这需要一种能够捕捉分布式概念的语言和类型系统。
类型系统
联邦核心提供以下类型的类别。在描述这些类型时,我们指出了类型构造函数,并引入了一种紧凑的表示法,因为它是一种描述计算和运算符类型的便捷方式。
首先,以下是与现有主流语言中找到的类型在概念上相似的类型类别。
张量类型 (
tff.TensorType
)。就像在 TensorFlow 中一样,这些类型具有dtype
和shape
。唯一的区别是,此类型的对象不仅限于tf.Tensor
实例,这些实例在 Python 中表示 TensorFlow 图中 TensorFlow 操作的输出,还可能包括可以生成的单位数据,例如,作为分布式聚合协议的输出。因此,TFF 张量类型只是这种类型在 Python 或 TensorFlow 中的具体物理表示的抽象版本。TFF 的
TensorTypes
在其(静态)形状处理方面可能比 TensorFlow 更严格。例如,TFF 的类型系统将具有未知秩的张量视为可从任何其他具有相同dtype
的张量分配,但不能分配给任何具有固定秩的张量。这种处理可以防止某些运行时错误(例如,尝试将未知秩的张量重塑为具有不正确元素数量的形状),但代价是 TFF 接受为有效的计算的严格性更高。张量类型的紧凑表示法是
dtype
或dtype[shape]
。例如,int32
和int32[10]
分别是整数和 int 向量类型。序列类型 (
tff.SequenceType
)。这些是 TFF 对 TensorFlow 的tf.data.Dataset
的具体概念的抽象等效项。序列的元素可以以顺序方式使用,并且可以包含复杂类型。序列类型的紧凑表示法是
T*
,其中T
是元素的类型。例如int32*
表示一个整数序列。命名元组类型 (
tff.StructType
)。这些是 TFF 用于构建元组和类似字典的结构的方式,这些结构具有预定义数量的具有特定类型的元素,命名或未命名。重要的是,TFF 的命名元组概念包含了 Python 的参数元组的抽象等效项,即,一些元素有名称,而另一些元素没有名称,还有一些元素是位置性的。命名元组的紧凑表示法是
<n_1=T_1, ..., n_k=T_k>
,其中n_k
是可选的元素名称,而T_k
是元素类型。例如,<int32,int32>
是两个未命名整数的紧凑表示法,而<X=float32,Y=float32>
是两个名为X
和Y
的浮点数的紧凑表示法,它们可能代表平面上的一个点。元组可以嵌套,也可以与其他类型混合,例如,<X=float32,Y=float32>*
将是点序列的紧凑表示法。函数类型 (
tff.FunctionType
)。TFF 是一个函数式编程框架,函数被视为 一等公民。函数最多有一个参数,并且只有一个结果。函数的紧凑表示法是
(T -> U)
,其中T
是参数的类型,而U
是结果的类型,或者( -> U)
如果没有参数(尽管无参数函数是一个退化的概念,它主要存在于 Python 级别)。例如(int32* -> int32)
是将整数序列减少为单个整数值的函数类型的表示法。
以下类型解决了 TFF 计算的分布式系统方面。由于这些概念在某种程度上是 TFF 独有的,我们建议您参考 自定义算法 教程以获取更多评论和示例。
放置类型。此类型尚未在公共 API 中公开,除了以 2 个文字
tff.SERVER
和tff.CLIENTS
的形式,您可以将它们视为此类型的常量。但是,它在内部使用,并将将在未来的版本中引入公共 API。此类型的紧凑表示法是placement
。放置表示一组以特定角色扮演的系统参与者。初始版本针对的是客户端-服务器计算,其中有两组参与者:客户端和服务器(您可以将后者视为一个单例组)。但是,在更复杂的架构中,可能存在其他角色,例如多层系统中的中间聚合器,它们可能执行不同类型的聚合,或者使用与服务器或客户端使用的类型不同的数据压缩/解压缩类型。
定义放置概念的主要目的是作为定义联邦类型的基础。
联邦类型 (
tff.FederatedType
)。联邦类型的值为由特定放置(例如tff.SERVER
或tff.CLIENTS
)定义的一组系统参与者托管的值。联邦类型由放置值(因此,它是一个 依赖类型)、成员成分的类型(每个参与者在本地托管的内容类型)以及附加位all_equal
定义,该位指定所有参与者是否都在本地托管相同的项目。包含类型为
T
的项目(成员成分)的联邦类型的紧凑表示法,每个项目由组(放置)G
托管,是T@G
或{T}@G
,分别设置或未设置all_equal
位。例如
{int32}@CLIENTS
表示一个联邦值,它包含一组可能不同的整数,每个客户端设备一个。请注意,我们正在谈论一个包含多个数据项的单个联邦值,这些数据项出现在网络中的多个位置。一种理解它的方式是将其视为具有“网络”维度的张量,尽管这种类比并不完美,因为 TFF 不允许对联邦值的成员成分进行 随机访问。{<X=float32,Y=float32>*}@CLIENTS
表示一个联邦数据集,它包含多个XY
坐标序列,每个客户端设备一个序列。<weights=float32[10,5],bias=float32[5]>@SERVER
表示服务器上权重和偏差张量的命名元组。由于我们删除了花括号,这表示all_equal
位已设置,即,只有一个元组(无论在托管此值的集群中可能存在多少个服务器副本)。
构建块
联邦核心的语言是 lambda 演算 的一种形式,它包含一些额外的元素。
它目前在公共 API 中公开了以下编程抽象
TensorFlow 计算 (
tff.tensorflow.computation
)。这些是使用tff.tensorflow.computation
装饰器作为 TFF 中可重用组件包装的 TensorFlow 代码段。它们始终具有函数类型,并且与 TensorFlow 中的函数不同,它们可以接受结构化参数或返回序列类型的结构化结果。以下是一个示例,一个类型为
(int32* -> int)
的 TF 计算,它使用tf.data.Dataset.reduce
运算符来计算整数的总和@tff.tensorflow.computation(tff.SequenceType(np.int32)) def add_up_integers(x): return x.reduce(np.int32(0), lambda x, y: x + y)
内在函数 或联邦运算符 (
tff.federated_...
)。这是一个函数库,例如tff.federated_sum
或tff.federated_broadcast
,它们构成了 FC API 的大部分,其中大部分代表用于 TFF 的分布式通信运算符。我们将这些称为内在函数,因为它们有点像 内在函数,它们是 TFF 理解并编译成更低级代码的开放式、可扩展的运算符集。
这些运算符中的大多数具有联邦类型的参数和结果,并且大多数是可应用于各种数据的模板。
例如,
tff.federated_broadcast
可以被认为是函数类型为T@SERVER -> T@CLIENTS
的模板运算符。Lambda 表达式 (
tff.federated_computation
)。TFF 中的 lambda 表达式等效于 Python 中的lambda
或def
;它包含参数名称和包含对该参数的引用的主体(表达式)。在 Python 代码中,这些可以通过用
tff.federated_computation
装饰 Python 函数并定义一个参数来创建。以下是一个我们之前已经提到的 lambda 表达式的示例
@tff.federated_computation(tff.FederatedType(np.float32, tff.CLIENTS)) def get_average_temperature(sensor_readings): return tff.federated_mean(sensor_readings)
放置文字。目前,只有
tff.SERVER
和tff.CLIENTS
允许定义简单的客户端-服务器计算。函数调用 (
__call__
)。任何具有函数类型的东西都可以使用标准 Python__call__
语法调用。调用是一个表达式,其类型与被调用函数的结果类型相同。例如
add_up_integers(x)
表示对之前定义的 TensorFlow 计算在参数x
上的调用。此表达式的类型是int32
。tff.federated_mean(sensor_readings)
表示对sensor_readings
上的联邦平均运算符的调用。此表达式的类型是float32@SERVER
(假设来自上面示例的上下文)。
形成元组和选择它们的元素。形式为
[x, y]
、x[y]
或x.y
的 Python 表达式出现在用tff.federated_computation
装饰的函数的主体中。