Ver no TensorFlow.org | Executar no Google Colab | Ver fonte no GitHub | Baixar caderno |
Neste tutorial, vamos explicar os princípios de design por trás do tff.aggregators
módulo e as melhores práticas para a implementação de agregação personalizada de valores de clientes ao servidor.
Pré-requisitos. Este tutorial assume que você já está familiarizado com os conceitos básicos da Federated Núcleo tais como posicionamentos ( tff.SERVER
, tff.CLIENTS
), como TFF representa cálculos ( tff.tf_computation
, tff.federated_computation
) e seu tipo de assinaturas.
!pip install --quiet --upgrade tensorflow_federated_nightly
!pip install --quiet --upgrade nest_asyncio
import nest_asyncio
nest_asyncio.apply()
Resumo do projeto
Em TFF, "agregação" refere-se ao movimento de um conjunto de valores em tff.CLIENTS
para produzir um valor agregado do mesmo tipo em tff.SERVER
. Ou seja, o valor de cada cliente individual não precisa estar disponível. Por exemplo, no aprendizado federado, as atualizações do modelo do cliente são calculadas para obter uma atualização do modelo agregado para aplicar ao modelo global no servidor.
Em adição aos operadores realização deste objectivo, tais como tff.federated_sum
, TFF fornece tff.templates.AggregationProcess
(um processo com estado ) que formalizar a assinatura de tipo para a computação agregação de modo que se pode generalizar a formas mais complexas do que uma simples soma.
Os principais componentes dos tff.aggregators
módulo são fábricas para criação do AggregationProcess
, que são projetados para serem blocos de construção geralmente úteis e substituíveis de TFF em dois aspectos:
- Cálculos parametrizados. A agregação é um bloco de construção independente que pode ser conectado a outros módulos TFF projetado para trabalhar com
tff.aggregators
para parametrizar a sua agregação necessário.
Exemplo:
learning_process = tff.learning.build_federated_averaging_process(
...,
model_update_aggregation_factory=tff.aggregators.MeanFactory())
- Composição de agregação. Um bloco de construção de agregação pode ser composto com outros blocos de construção de agregação para criar agregações compostas mais complexas.
Exemplo:
secure_mean = tff.aggregators.MeanFactory(
value_sum_factory=tff.aggregators.SecureSumFactory(...))
O restante deste tutorial explica como esses dois objetivos são alcançados.
Processo de agregação
Nós primeiro resumir o tff.templates.AggregationProcess
, e seguir com o padrão de fábrica para a sua criação.
O tff.templates.AggregationProcess
é um tff.templates.MeasuredProcess
com assinaturas tipo especificado para a agregação. Em particular, o initialize
e next
funções têm as assinaturas seguinte tipo:
-
( -> state_type@SERVER)
-
(<state_type@SERVER, {value_type}@CLIENTS, *> -> <state_type@SERVER, value_type@SERVER, measurements_type@SERVER>)
O Estado (do tipo state_type
) deve ser colocado no servidor. A next
função toma como argumento de entrada do estado e um valor a ser agregados (do tipo value_type
) colocado em clientes. Os *
meios opcionais outros parâmetros de entrada, por exemplo, pesos em uma média ponderada. Ele retorna um objeto de estado atualizado, o valor agregado do mesmo tipo colocado no servidor e algumas medidas.
Note-se que tanto o estado a ser passado entre execuções da next
função, e as medições reportadas destina-se a relatar qualquer informação dependendo de uma execução específica da next
função, pode estar vazia. No entanto, eles devem ser explicitamente especificados para que outras partes da TFF tenham um contrato claro a seguir.
Outros módulos TFF, por exemplo, as atualizações do modelo em tff.learning
, são esperados para usar o tff.templates.AggregationProcess
para parametrizar como os valores são agregados. No entanto, quais são exatamente os valores agregados e quais são suas assinaturas de tipo, depende de outros detalhes do modelo que está sendo treinado e do algoritmo de aprendizagem usado para fazê-lo.
Para fazer a agregação independente dos outros aspectos de cálculos, usamos o padrão de fábrica - criamos o apropriado tff.templates.AggregationProcess
uma vez que as assinaturas correspondente tipo de objetos a serem agregadas estão disponíveis, invocando a create
método da fábrica. O manuseio direto do processo de agregação é, portanto, necessário apenas para os autores da biblioteca, que são responsáveis por esta criação.
Fábricas de processo de agregação
Existem duas classes de fábrica de base abstratas para agregação não ponderada e ponderada. Sua create
método leva assinaturas de tipo de valor a ser agregado e retorna um tff.templates.AggregationProcess
para a agregação de tais valores.
O processo criado pelo tff.aggregators.UnweightedAggregationFactory
leva dois argumentos de entrada: (1) estado, no servidor e valor (2) do tipo especificado value_type
.
Um exemplo de implementação é tff.aggregators.SumFactory
.
O processo criado por tff.aggregators.WeightedAggregationFactory
leva três argumentos de entrada: (1) estado no servidor, o valor (2) do tipo especificado value_type
e (3) o peso do tipo weight_type
, conforme especificado pelo usuário da fábrica ao invocar a sua create
método.
Um exemplo de implementação é tff.aggregators.MeanFactory
que calcula uma média ponderada.
O padrão de fábrica é como alcançamos a primeira meta declarada acima; essa agregação é um bloco de construção independente. Por exemplo, ao alterar quais variáveis do modelo são treináveis, uma agregação complexa não precisa necessariamente ser alterada; a fábrica representando ele será chamado com uma assinatura de tipo diferente quando usado por um método como tff.learning.build_federated_averaging_process
.
Composições
Lembre-se de que um processo de agregação geral pode encapsular (a) algum pré-processamento dos valores nos clientes, (b) movimento de valores do cliente para o servidor e (c) algum pós-processamento do valor agregado no servidor. O segundo objectivo acima referido, a composição de agregação, é realizada dentro dos tff.aggregators
módulo por estruturação a implementação das fábricas de agregação tais que a parte (b) pode ser delegada a outra fábrica agregação.
Em vez de implementar toda a lógica necessária em uma única classe de fábrica, as implementações são, por padrão, focadas em um único aspecto relevante para agregação. Quando necessário, esse padrão nos permite substituir os blocos de construção um de cada vez.
Um exemplo é o ponderado tff.aggregators.MeanFactory
. Sua implementação multiplica os valores e pesos fornecidos nos clientes, depois soma os valores ponderados e os pesos independentemente e, a seguir, divide a soma dos valores ponderados pela soma dos pesos no servidor. Em vez de aplicar as somas usando directamente a tff.federated_sum
operador, o somatório é delegada a dois exemplos de tff.aggregators.SumFactory
.
Tal estrutura permite que as duas somas padrão sejam substituídas por fábricas diferentes, que realizam a soma de forma diferente. Por exemplo, um tff.aggregators.SecureSumFactory
, ou uma implementação personalizada do tff.aggregators.UnweightedAggregationFactory
. Por outro lado, o tempo, tff.aggregators.MeanFactory
pode ela própria ser uma agregação interior de outra fábrica tais como tff.aggregators.clipping_factory
, se os valores são para ser cortada antes da média.
Veja o anterior Sintonia agregações recomendado para a aprendizagem tutorial para usos receommended do mecanismo de composição usando fábricas existentes na tff.aggregators
módulo.
Melhores práticas por exemplo
Vamos ilustrar os tff.aggregators
conceitos em detalhe através da implementação de uma tarefa simples exemplo, e torná-lo cada vez mais geral. Outra forma de aprender é observar a implementação das fábricas existentes.
import collections
import tensorflow as tf
import tensorflow_federated as tff
Em vez de somar value
, a tarefa de exemplo é a soma value * 2.0
e, em seguida, dividir a soma de 2.0
. O resultado da agregação é, assim, matematicamente equivalente à soma diretamente o value
, e poderia ser pensado como consistindo em três partes: (1) de escala para clientes (2) soma todos os clientes (3) unscaling no servidor.
Seguindo o desenho explicado acima, a lógica será implementado como uma subclasse de tff.aggregators.UnweightedAggregationFactory
, que cria apropriado tff.templates.AggregationProcess
quando é dada uma value_type
ao agregado:
Implementação mínima
Para a tarefa de exemplo, os cálculos necessários são sempre os mesmos, portanto, não há necessidade de usar o estado. É, portanto, vazio, e representada como tff.federated_value((), tff.SERVER)
. O mesmo vale para as medições, por enquanto.
A implementação mínima da tarefa é, portanto, a seguinte:
class ExampleTaskFactory(tff.aggregators.UnweightedAggregationFactory):
def create(self, value_type):
@tff.federated_computation()
def initialize_fn():
return tff.federated_value((), tff.SERVER)
@tff.federated_computation(initialize_fn.type_signature.result,
tff.type_at_clients(value_type))
def next_fn(state, value):
scaled_value = tff.federated_map(
tff.tf_computation(lambda x: x * 2.0), value)
summed_value = tff.federated_sum(scaled_value)
unscaled_value = tff.federated_map(
tff.tf_computation(lambda x: x / 2.0), summed_value)
measurements = tff.federated_value((), tff.SERVER)
return tff.templates.MeasuredProcessOutput(
state=state, result=unscaled_value, measurements=measurements)
return tff.templates.AggregationProcess(initialize_fn, next_fn)
Se tudo funciona conforme o esperado, pode ser verificado com o seguinte código:
client_data = [1.0, 2.0, 5.0]
factory = ExampleTaskFactory()
aggregation_process = factory.create(tff.TensorType(tf.float32))
print(f'Type signatures of the created aggregation process:\n'
f' - initialize: {aggregation_process.initialize.type_signature}\n'
f' - next: {aggregation_process.next.type_signature}\n')
state = aggregation_process.initialize()
output = aggregation_process.next(state, client_data)
print(f'Aggregation result: {output.result} (expected 8.0)')
Type signatures of the created aggregation process: - initialize: ( -> <>@SERVER) - next: (<state=<>@SERVER,value={float32}@CLIENTS> -> <state=<>@SERVER,result=float32@SERVER,measurements=<>@SERVER>) Aggregation result: 8.0 (expected 8.0)
Statefulness e medições
Statefulness é amplamente usado no TFF para representar cálculos que devem ser executados iterativamente e mudar a cada iteração. Por exemplo, o estado de uma computação de aprendizado contém os pesos do modelo que está sendo aprendido.
Para ilustrar como usar o estado em um cálculo de agregação, modificamos a tarefa de exemplo. Em vez de multiplicar value
por 2.0
, que multiplicá-lo pelo índice de iteração - o número de vezes que a agregação foi executado.
Para fazer isso, precisamos de uma maneira de rastrear o índice de iteração, que é obtido por meio do conceito de estado. No initialize_fn
, em vez de criar um estado vazio, nós inicializar o estado de ser um zero escalar. Em seguida, o estado pode ser utilizado na next_fn
em três passos: (1) de incremento por 1.0
, (2) uso de multiplicar value
, e (3) de retorno como o novo estado actualizada.
Uma vez feito isso, você pode notar: Mas exatamente o mesmo código acima pode ser usado para verificar todas as obras como esperado. Como posso saber se algo realmente mudou?
Boa pergunta! É aqui que o conceito de medições se torna útil. Em geral, as medições podem denunciar qualquer valor relevante para uma única execução da next
função, que poderia ser usado para monitoramento. Neste caso, ele pode ser o summed_value
do exemplo anterior. Ou seja, o valor antes da etapa de "desescalonamento", que deve depender do índice de iteração. Novamente, isso não é necessariamente útil na prática, mas ilustra o mecanismo relevante.
A resposta com estado para a tarefa, portanto, é a seguinte:
class ExampleTaskFactory(tff.aggregators.UnweightedAggregationFactory):
def create(self, value_type):
@tff.federated_computation()
def initialize_fn():
return tff.federated_value(0.0, tff.SERVER)
@tff.federated_computation(initialize_fn.type_signature.result,
tff.type_at_clients(value_type))
def next_fn(state, value):
new_state = tff.federated_map(
tff.tf_computation(lambda x: x + 1.0), state)
state_at_clients = tff.federated_broadcast(new_state)
scaled_value = tff.federated_map(
tff.tf_computation(lambda x, y: x * y), (value, state_at_clients))
summed_value = tff.federated_sum(scaled_value)
unscaled_value = tff.federated_map(
tff.tf_computation(lambda x, y: x / y), (summed_value, new_state))
return tff.templates.MeasuredProcessOutput(
state=new_state, result=unscaled_value, measurements=summed_value)
return tff.templates.AggregationProcess(initialize_fn, next_fn)
Note que o state
que entra em next_fn
como entrada é colocado no servidor. A fim de usá-lo em clientes, ele primeiro precisa ser comunicada, o que é conseguido usando o tff.federated_broadcast
operador.
Para verificar todas as obras como esperado, podemos agora olhar para os relatados measurements
, que deve ser diferente com cada rodada de execução, mesmo que correr com o mesmo client_data
.
client_data = [1.0, 2.0, 5.0]
factory = ExampleTaskFactory()
aggregation_process = factory.create(tff.TensorType(tf.float32))
print(f'Type signatures of the created aggregation process:\n'
f' - initialize: {aggregation_process.initialize.type_signature}\n'
f' - next: {aggregation_process.next.type_signature}\n')
state = aggregation_process.initialize()
output = aggregation_process.next(state, client_data)
print('| Round #1')
print(f'| Aggregation result: {output.result} (expected 8.0)')
print(f'| Aggregation measurements: {output.measurements} (expected 8.0 * 1)')
output = aggregation_process.next(output.state, client_data)
print('\n| Round #2')
print(f'| Aggregation result: {output.result} (expected 8.0)')
print(f'| Aggregation measurements: {output.measurements} (expected 8.0 * 2)')
output = aggregation_process.next(output.state, client_data)
print('\n| Round #3')
print(f'| Aggregation result: {output.result} (expected 8.0)')
print(f'| Aggregation measurements: {output.measurements} (expected 8.0 * 3)')
Type signatures of the created aggregation process: - initialize: ( -> float32@SERVER) - next: (<state=float32@SERVER,value={float32}@CLIENTS> -> <state=float32@SERVER,result=float32@SERVER,measurements=float32@SERVER>) | Round #1 | Aggregation result: 8.0 (expected 8.0) | Aggregation measurements: 8.0 (expected 8.0 * 1) | Round #2 | Aggregation result: 8.0 (expected 8.0) | Aggregation measurements: 16.0 (expected 8.0 * 2) | Round #3 | Aggregation result: 8.0 (expected 8.0) | Aggregation measurements: 24.0 (expected 8.0 * 3)
Tipos estruturados
Os pesos do modelo de um modelo treinado em aprendizado federado são geralmente representados como uma coleção de tensores, em vez de um único tensor. Em TFF, este é representado como tff.StructType
e fábricas de agregação de utilidade geral precisa ser capaz de aceitar os tipos estruturados.
No entanto, nos exemplos acima, nós só trabalhamos com um tff.TensorType
objeto. Se tentarmos usar a fábrica anterior para criar o processo de agregação com um tff.StructType([(tf.float32, (2,)), (tf.float32, (3,))])
, obtemos um erro estranho porque TensorFlow vai tentar multiplicar um tf.Tensor
e uma list
.
O problema é que, em vez de multiplicar a estrutura de tensores por uma constante, precisamos multiplicar cada tensor na estrutura por uma constante. A solução usual para este problema é usar o tf.nest
módulo dentro dos criados tff.tf_computation
s.
A versão do anterior ExampleTaskFactory
compatível com tipos estruturados parece, assim, como segue:
@tff.tf_computation()
def scale(value, factor):
return tf.nest.map_structure(lambda x: x * factor, value)
@tff.tf_computation()
def unscale(value, factor):
return tf.nest.map_structure(lambda x: x / factor, value)
@tff.tf_computation()
def add_one(value):
return value + 1.0
class ExampleTaskFactory(tff.aggregators.UnweightedAggregationFactory):
def create(self, value_type):
@tff.federated_computation()
def initialize_fn():
return tff.federated_value(0.0, tff.SERVER)
@tff.federated_computation(initialize_fn.type_signature.result,
tff.type_at_clients(value_type))
def next_fn(state, value):
new_state = tff.federated_map(add_one, state)
state_at_clients = tff.federated_broadcast(new_state)
scaled_value = tff.federated_map(scale, (value, state_at_clients))
summed_value = tff.federated_sum(scaled_value)
unscaled_value = tff.federated_map(unscale, (summed_value, new_state))
return tff.templates.MeasuredProcessOutput(
state=new_state, result=unscaled_value, measurements=summed_value)
return tff.templates.AggregationProcess(initialize_fn, next_fn)
Este exemplo destaca um padrão que pode ser útil seguir ao estruturar o código TFF. Quando não está lidando com operações muito simples, o código torna-se mais legível quando os tff.tf_computation
s que serão usados como blocos de construção dentro de um tff.federated_computation
são criados em um local separado. Dentro do tff.federated_computation
, esses blocos de construção só são conectados usando os operadores intrínsecas.
Para verificar se funciona como esperado:
client_data = [[[1.0, 2.0], [3.0, 4.0, 5.0]],
[[1.0, 1.0], [3.0, 0.0, -5.0]]]
factory = ExampleTaskFactory()
aggregation_process = factory.create(
tff.to_type([(tf.float32, (2,)), (tf.float32, (3,))]))
print(f'Type signatures of the created aggregation process:\n'
f' - initialize: {aggregation_process.initialize.type_signature}\n'
f' - next: {aggregation_process.next.type_signature}\n')
state = aggregation_process.initialize()
output = aggregation_process.next(state, client_data)
print(f'Aggregation result: [{output.result[0]}, {output.result[1]}]\n'
f' Expected: [[2. 3.], [6. 4. 0.]]')
Type signatures of the created aggregation process: - initialize: ( -> float32@SERVER) - next: (<state=float32@SERVER,value={<float32[2],float32[3]>}@CLIENTS> -> <state=float32@SERVER,result=<float32[2],float32[3]>@SERVER,measurements=<float32[2],float32[3]>@SERVER>) Aggregation result: [[2. 3.], [6. 4. 0.]] Expected: [[2. 3.], [6. 4. 0.]]
Agregações internas
A etapa final é habilitar opcionalmente a delegação da agregação real a outras fábricas, a fim de permitir a fácil composição de diferentes técnicas de agregação.
Isto é conseguido através da criação de um opcional inner_factory
argumento no construtor da nossa ExampleTaskFactory
. Se não for especificado, tff.aggregators.SumFactory
é usado, que aplica o tff.federated_sum
operador utilizado directamente na secção anterior.
Ao create
é chamado, podemos chamar primeiro create
do inner_factory
para criar o processo de agregação interna com o mesmo value_type
.
O estado do nosso processo retornado por initialize_fn
é uma composição de duas partes: o estado criado por "este" processo e o estado do processo interno acabou de criar.
A aplicação das next_fn
difere em que a agregação real é delegada a next
função do processo interior, e na forma como o produto final é composto. O estado é novamente composto por "este" e estado "interna", e as medições são compostos de um modo semelhante como uma OrderedDict
.
A seguir está uma implementação de tal padrão.
@tff.tf_computation()
def scale(value, factor):
return tf.nest.map_structure(lambda x: x * factor, value)
@tff.tf_computation()
def unscale(value, factor):
return tf.nest.map_structure(lambda x: x / factor, value)
@tff.tf_computation()
def add_one(value):
return value + 1.0
class ExampleTaskFactory(tff.aggregators.UnweightedAggregationFactory):
def __init__(self, inner_factory=None):
if inner_factory is None:
inner_factory = tff.aggregators.SumFactory()
self._inner_factory = inner_factory
def create(self, value_type):
inner_process = self._inner_factory.create(value_type)
@tff.federated_computation()
def initialize_fn():
my_state = tff.federated_value(0.0, tff.SERVER)
inner_state = inner_process.initialize()
return tff.federated_zip((my_state, inner_state))
@tff.federated_computation(initialize_fn.type_signature.result,
tff.type_at_clients(value_type))
def next_fn(state, value):
my_state, inner_state = state
my_new_state = tff.federated_map(add_one, my_state)
my_state_at_clients = tff.federated_broadcast(my_new_state)
scaled_value = tff.federated_map(scale, (value, my_state_at_clients))
# Delegation to an inner factory, returning values placed at SERVER.
inner_output = inner_process.next(inner_state, scaled_value)
unscaled_value = tff.federated_map(unscale, (inner_output.result, my_new_state))
new_state = tff.federated_zip((my_new_state, inner_output.state))
measurements = tff.federated_zip(
collections.OrderedDict(
scaled_value=inner_output.result,
example_task=inner_output.measurements))
return tff.templates.MeasuredProcessOutput(
state=new_state, result=unscaled_value, measurements=measurements)
return tff.templates.AggregationProcess(initialize_fn, next_fn)
Ao delegar ao inner_process.next
função, a estrutura de retorno que temos é um tff.templates.MeasuredProcessOutput
, com os mesmos três campos - state
, result
e measurements
. Ao criar a estrutura de retorno total do processo de agregação composto, os state
e measurements
campos deve ser geralmente compostos e devolvido juntos. Em contraste, os result
corresponde campo para o valor a ser agregada e em vez "flui através de" a agregação composto.
O state
objecto deve ser visto como um detalhe de implementação da fábrica, e assim a composição pode ser de qualquer estrutura. No entanto, measurements
correspondem a valores de ser comunicados para o utilizador, em algum ponto. Portanto, recomendamos a utilização OrderedDict
, com compôs nomear tal que seria claro onde em uma composição que um relatado métrica vem.
Note-se também o uso do tff.federated_zip
operador. O state
objeto contolled pelo processo criado deve ser um tff.FederatedType
. Se tivéssemos vez devolvidos (this_state, inner_state)
na initialize_fn
, sua assinatura tipo de retorno seria um tff.StructType
contendo uma 2-tupla de tff.FederatedType
s. O uso de tff.federated_zip
"elevadores" o tff.FederatedType
para o nível superior. Isto é semelhante ao utilizado na next_fn
quando se prepara o estado e as medições a serem devolvidos.
Finalmente, podemos ver como isso pode ser usado com a agregação interna padrão:
client_data = [1.0, 2.0, 5.0]
factory = ExampleTaskFactory()
aggregation_process = factory.create(tff.TensorType(tf.float32))
state = aggregation_process.initialize()
output = aggregation_process.next(state, client_data)
print('| Round #1')
print(f'| Aggregation result: {output.result} (expected 8.0)')
print(f'| measurements[\'scaled_value\']: {output.measurements["scaled_value"]}')
print(f'| measurements[\'example_task\']: {output.measurements["example_task"]}')
output = aggregation_process.next(output.state, client_data)
print('\n| Round #2')
print(f'| Aggregation result: {output.result} (expected 8.0)')
print(f'| measurements[\'scaled_value\']: {output.measurements["scaled_value"]}')
print(f'| measurements[\'example_task\']: {output.measurements["example_task"]}')
| Round #1 | Aggregation result: 8.0 (expected 8.0) | measurements['scaled_value']: 8.0 | measurements['example_task']: () | Round #2 | Aggregation result: 8.0 (expected 8.0) | measurements['scaled_value']: 16.0 | measurements['example_task']: ()
... e com uma agregação interna diferente. Por exemplo, um ExampleTaskFactory
:
client_data = [1.0, 2.0, 5.0]
# Note the inner delegation can be to any UnweightedAggregaionFactory.
# In this case, each factory creates process that multiplies by the iteration
# index (1, 2, 3, ...), thus their combination multiplies by (1, 4, 9, ...).
factory = ExampleTaskFactory(ExampleTaskFactory())
aggregation_process = factory.create(tff.TensorType(tf.float32))
state = aggregation_process.initialize()
output = aggregation_process.next(state, client_data)
print('| Round #1')
print(f'| Aggregation result: {output.result} (expected 8.0)')
print(f'| measurements[\'scaled_value\']: {output.measurements["scaled_value"]}')
print(f'| measurements[\'example_task\']: {output.measurements["example_task"]}')
output = aggregation_process.next(output.state, client_data)
print('\n| Round #2')
print(f'| Aggregation result: {output.result} (expected 8.0)')
print(f'| measurements[\'scaled_value\']: {output.measurements["scaled_value"]}')
print(f'| measurements[\'example_task\']: {output.measurements["example_task"]}')
| Round #1 | Aggregation result: 8.0 (expected 8.0) | measurements['scaled_value']: 8.0 | measurements['example_task']: OrderedDict([('scaled_value', 8.0), ('example_task', ())]) | Round #2 | Aggregation result: 8.0 (expected 8.0) | measurements['scaled_value']: 16.0 | measurements['example_task']: OrderedDict([('scaled_value', 32.0), ('example_task', ())])
Resumo
Neste tutorial, explicamos as práticas recomendadas a seguir para criar um bloco de construção de agregação de uso geral, representado como uma fábrica de agregação. A generalidade vem por meio da intenção do projeto de duas maneiras:
- Cálculos parametrizados. A agregação é um bloco de construção independente que pode ser conectado a outros módulos TFF projetado para trabalhar com
tff.aggregators
para parametrizar a sua agregação necessário, comotff.learning.build_federated_averaging_process
. - Composição de agregação. Um bloco de construção de agregação pode ser composto com outros blocos de construção de agregação para criar agregações compostas mais complexas.