Implementando agregações personalizadas

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:

  1. 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())
  1. 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:

  1. 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, como tff.learning.build_federated_averaging_process .
  2. 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.