Implementación de agregaciones personalizadas

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHub Descargar cuaderno

En este tutorial, explicamos los principios de diseño detrás de la tff.aggregators módulo y las mejores prácticas para la implementación de agregación personalizada de los valores de los clientes al servidor.

Prerrequisitos. Este tutorial asume que ya está familiarizado con los conceptos básicos de Federated Core tales como colocaciones ( tff.SERVER , tff.CLIENTS ), cómo TFF representa cálculos ( tff.tf_computation , tff.federated_computation ) y sus firmas de tipos.

!pip install --quiet --upgrade tensorflow_federated_nightly
!pip install --quiet --upgrade nest_asyncio

import nest_asyncio
nest_asyncio.apply()

Resumen de diseño

En TFF, "agregación" se refiere al movimiento de un conjunto de valores en tff.CLIENTS para producir un valor agregado del mismo tipo en tff.SERVER . Es decir, no es necesario que el valor de cada cliente individual esté disponible. Por ejemplo, en el aprendizaje federado, las actualizaciones del modelo del cliente se promedian para obtener una actualización del modelo agregado para aplicar al modelo global en el servidor.

Además de los operadores lograr este objetivo, tales como tff.federated_sum , TFF proporciona tff.templates.AggregationProcess (un proceso de estado ) que formaliza la firma de tipo para el cálculo de agregación por lo que se puede generalizar a las formas más complejas que una simple suma.

Los principales componentes de los tff.aggregators módulo son fábricas para la creación de la AggregationProcess , que están diseñados para ser bloques de construcción generalmente útiles y reemplazables de TFF en dos aspectos:

  1. Cálculos parametrizados. La agregación es un bloque de construcción independiente que puede ser enchufado en otros módulos TFF diseñado para trabajar con tff.aggregators para parametrizar su agregación necesario.

Ejemplo:

learning_process = tff.learning.build_federated_averaging_process(
    ...,
    model_update_aggregation_factory=tff.aggregators.MeanFactory())
  1. Composición de agregación. Un bloque de construcción de agregación se puede componer con otros bloques de construcción de agregación para crear agregaciones compuestas más complejas.

Ejemplo:

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

El resto de este tutorial explica cómo se logran estos dos objetivos.

Proceso de agregación

En primer lugar, un resumen de la tff.templates.AggregationProcess , y seguimos con el patrón de la fábrica para su creación.

El tff.templates.AggregationProcess es un tff.templates.MeasuredProcess con las firmas de tipos especificados para la agregación. En particular, la initialize y next funciones tienen las firmas de tipos siguiente:

  • ( -> state_type@SERVER)
  • (<state_type@SERVER, {value_type}@CLIENTS, *> -> <state_type@SERVER, value_type@SERVER, measurements_type@SERVER>)

El estado (de tipo state_type ) debe ser colocado en el servidor. La next función toma como argumento de entrada el estado y un valor a ser agregados (de tipo value_type ) colocado a clientes. Las * medio opcional otros argumentos de entrada, por ejemplo pesos en una media ponderada. Devuelve un objeto de estado actualizado, el valor agregado del mismo tipo colocado en el servidor y algunas medidas.

Tenga en cuenta que tanto el estado que se pasa entre las ejecuciones de la next función, y las mediciones reportadas por objeto informar de cualquier información en función de una ejecución específica de la next función, puede estar vacío. Sin embargo, deben especificarse explícitamente para que otras partes del TFF tengan un contrato claro a seguir.

Otros módulos de TFF, por ejemplo, los cambios de modelo en tff.learning , se espera que utilice el tff.templates.AggregationProcess parametrizar cómo se agregan los valores. Sin embargo, cuáles son exactamente los valores agregados y cuáles son sus firmas de tipo, depende de otros detalles del modelo que se está entrenando y del algoritmo de aprendizaje utilizado para hacerlo.

Para hacer la agregación independiente de los otros aspectos de los cálculos, se utiliza el patrón de la fábrica - creamos el apropiado tff.templates.AggregationProcess una vez que las firmas de tipos relevantes de los objetos a ser agregados están disponibles, invocando al create método de la fábrica. Por lo tanto, el manejo directo del proceso de agregación solo es necesario para los autores de bibliotecas, que son responsables de esta creación.

Fábricas de procesos de agregación

Hay dos clases de fábrica base abstractas para agregación ponderada y no ponderada. Su create método toma las firmas de tipos de valor a ser agregados y devuelve un tff.templates.AggregationProcess para la agregación de tales valores.

El proceso creado por tff.aggregators.UnweightedAggregationFactory toma dos argumentos de entrada: (1) el estado en servidor y (2) el valor de tipo especificado value_type .

Un ejemplo de implementación es tff.aggregators.SumFactory .

El proceso creado por tff.aggregators.WeightedAggregationFactory toma tres argumentos de entrada: (1) el estado en servidor, (2) el valor de tipo especificado value_type y (3) peso de tipo weight_type , tal como se especifica por el usuario de la fábrica cuando se invoca su create método.

Un ejemplo de implementación es tff.aggregators.MeanFactory que calcula una media ponderada.

El patrón de fábrica es cómo logramos el primer objetivo mencionado anteriormente; esa agregación es un bloque de construcción independiente. Por ejemplo, cuando se cambian las variables del modelo que se pueden entrenar, no es necesario que cambie una agregación compleja; la fábrica que representa será invocado con una firma tipo diferente cuando es utilizado por un método tal como tff.learning.build_federated_averaging_process .

Composiciones

Recuerde que un proceso de agregación general puede encapsular (a) algún procesamiento previo de los valores en los clientes, (b) el movimiento de valores del cliente al servidor y (c) algún procesamiento posterior del valor agregado en el servidor. El segundo objetivo indicado anteriormente, la composición de la agregación, se realiza dentro de las tff.aggregators módulo por estructuración la aplicación de las fábricas de agregación tales que la parte (b) se puede delegar a otra fábrica agregación.

En lugar de implementar toda la lógica necesaria dentro de una sola clase de fábrica, las implementaciones se centran por defecto en un solo aspecto relevante para la agregación. Cuando sea necesario, este patrón nos permitirá reemplazar los bloques de construcción de uno en uno.

Un ejemplo es el ponderado tff.aggregators.MeanFactory . Su implementación multiplica los valores proporcionados y los pesos en los clientes, luego suma los valores ponderados y los pesos de forma independiente y luego divide la suma de los valores ponderados por la suma de los pesos en el servidor. En lugar de implementar las sumas usando directamente la tff.federated_sum operador, la suma se delega a dos instancias de tff.aggregators.SumFactory .

Dicha estructura hace posible que las dos sumas predeterminadas sean reemplazadas por diferentes fábricas, que realizan la suma de manera diferente. Por ejemplo, un tff.aggregators.SecureSumFactory , o una implementación personalizada de la tff.aggregators.UnweightedAggregationFactory . A la inversa, el tiempo, tff.aggregators.MeanFactory puede por sí mismo ser una agregación interior de otra fábrica como tff.aggregators.clipping_factory , si los valores son para ser recortado antes de promediar.

Ver la anterior sintonización agregaciones recomienda para el aprendizaje tutorial para usos receommended del mecanismo de composición usando las fábricas existentes en el tff.aggregators módulo.

Mejores prácticas con el ejemplo

Vamos a ilustrar los tff.aggregators conceptos en detalle mediante la implementación de una tarea simple ejemplo, y lo hacemos cada vez más general. Otra forma de aprender es observar la implementación de las fábricas existentes.

import collections
import tensorflow as tf
import tensorflow_federated as tff

En lugar de sumar value , la tarea ejemplo es sumar value * 2.0 y luego dividir la suma por 2.0 . El resultado de agregación es por lo tanto matemáticamente equivalente a la suma directamente el value , y podría ser considerado como que consta de tres partes: (1) de escala a clientes (2) sumando en todos los clientes (3) unscaling en servidor.

Siguiendo el diseño se ha explicado anteriormente, la lógica será implementado como una subclase de tff.aggregators.UnweightedAggregationFactory , que crea apropiado tff.templates.AggregationProcess cuando se les da un value_type a agregada:

Implementación mínima

Para la tarea de ejemplo, los cálculos necesarios son siempre los mismos, por lo que no es necesario utilizar el estado. Así vaciar, y se representa como tff.federated_value((), tff.SERVER) . Lo mismo se aplica a las medidas, por ahora.

La implementación mínima de la tarea es, por tanto, la siguiente:

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)

Si todo funciona como se esperaba se puede verificar con el siguiente 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 y medidas

Statefulness se usa ampliamente en TFF para representar cálculos que se espera que se ejecuten de forma iterativa y cambien con cada iteración. Por ejemplo, el estado de un cálculo de aprendizaje contiene los pesos del modelo que se está aprendiendo.

Para ilustrar cómo utilizar el estado en un cálculo de agregación, modificamos la tarea de ejemplo. En lugar de multiplicar value por 2.0 , lo multiplicamos por el índice de iteración - el número de veces que la agregación ha sido ejecutado.

Para hacerlo, necesitamos una forma de realizar un seguimiento del índice de iteración, que se logra a través del concepto de estado. En el initialize_fn , en lugar de crear un estado vacío, inicializar el estado de ser un cero escalar. Entonces, el estado se puede utilizar en la next_fn en tres etapas: (1) de incremento por 1.0 , (2) el uso para multiplicar value , y (3) de retorno como el nuevo estado actualizada.

Una vez hecho esto, es posible que tenga en cuenta: Pero exactamente el mismo código que anteriormente se pueden usar para verificar todas las obras como se esperaba. ¿Cómo sé que algo ha cambiado realmente?

¡Buena pregunta! Aquí es donde el concepto de medidas se vuelve útil. En general, las mediciones pueden informar de cualquier valor relevante para una sola ejecución de la next función, que podría ser utilizado para el seguimiento. En este caso, puede ser el summed_value del ejemplo anterior. Es decir, el valor antes del paso de "desescalado", que debería depender del índice de iteración. Nuevamente, esto no es necesariamente útil en la práctica, pero ilustra el mecanismo relevante.

La respuesta con estado a la tarea tiene el siguiente aspecto:

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)

Tenga en cuenta que el state que entra en next_fn como entrada se coloca en el servidor. Con el fin de utilizarlo en los clientes, primero debe ser comunicada, que se logra mediante el tff.federated_broadcast operador.

Para verificar todas las obras como era de esperar, ahora podemos mirar a los reportados measurements , que debería ser diferente con cada ronda de ejecución, aunque esté funcionando con la misma 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 estructurados

Los pesos del modelo de un modelo entrenado en aprendizaje federado generalmente se representan como una colección de tensores, en lugar de un solo tensor. En TFF, esto se representa como tff.StructType y fábricas de agregación generalmente útiles necesita ser capaz de aceptar los tipos estructurados.

Sin embargo, en los ejemplos anteriores, sólo trabajamos con un tff.TensorType objeto. Si tratamos de utilizar la fábrica anterior para crear el proceso de agregación con un tff.StructType([(tf.float32, (2,)), (tf.float32, (3,))]) , obtenemos un error extraño porque TensorFlow intentará multiplicar un tf.Tensor y una list .

El problema es que en lugar de multiplicar la estructura de tensores por una constante, tenemos que multiplicar cada tensor en la estructura por una constante. La solución habitual a este problema es utilizar el tf.nest en el interior del módulo de las creadas tff.tf_computation s.

La versión anterior de la ExampleTaskFactory compatible con los tipos estructurados por lo tanto se ve de la siguiente manera:

@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 ejemplo destaca un patrón que puede ser útil seguir al estructurar el código TFF. Cuando no se trata de operaciones muy simples, el código se vuelve más legible cuando los tff.tf_computation s que serán utilizados como bloques de construcción dentro de un tff.federated_computation se crean en un lugar separado. En el interior de la tff.federated_computation , estos bloques de construcción solamente se conectan mediante los operadores intrínsecas.

Para verificar que funciona como se esperaba:

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.]]

Agregaciones internas

El paso final es habilitar opcionalmente la delegación de la agregación real a otras fábricas, para permitir una fácil composición de diferentes técnicas de agregación.

Esto se logra mediante la creación de una opción inner_factory argumento en el constructor de nuestra ExampleTaskFactory . Si no se especifica, tff.aggregators.SumFactory se utiliza, que aplica el tff.federated_sum operador que se utiliza directamente en la sección anterior.

Cuando create que se llama, lo primero que puede llamar a create de la inner_factory para crear el proceso de agregación interior con el mismo value_type .

El estado de nuestro proceso devuelto por initialize_fn es una composición de dos partes: el estado creado por "este" proceso, y el estado del proceso interno que acaba de crear.

La aplicación de las next_fn difiere en que la agregación real se delega a la next función del proceso interno, y en cómo se compone el producto final. El estado se compone de nuevo de "este" estado y "interior", y las mediciones se compone de una manera similar como un OrderedDict .

La siguiente es una implementación de dicho patrón.

@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)

Al delegar a la inner_process.next función, la estructura de retorno que obtenemos es un tff.templates.MeasuredProcessOutput , con los mismos tres campos - state , result y measurements . Al crear la estructura general de retorno del proceso de agregación compuesta, los state y measurements campos se deben generalmente compuestas y volvieron juntos. En contraste, los result corresponde campo al valor que se agregan y en lugar de "fluye a través de" la agregación compuesta.

El state objeto debe ser visto como un detalle de implementación de la fábrica, y por lo tanto la composición podría ser de cualquier estructura. Sin embargo, measurements corresponden a los valores que se informe al usuario en algún momento. Por lo tanto, se recomienda el uso OrderedDict , con nombrando compuesta de tal manera que quedara claro, donde en una composición informó hace un métrica viene.

Tenga en cuenta también el uso de la tff.federated_zip operador. El state objeto contolled por el proceso creado debe ser un tff.FederatedType . Si por el contrario nos volvimos (this_state, inner_state) en el initialize_fn , el tipo que devuelve la firma sería un tff.StructType que contiene un 2-tupla de tff.FederatedType s. El uso de tff.federated_zip "ascensores" la tff.FederatedType al nivel superior. Esto se utiliza de manera similar en la next_fn cuando se prepara el estado y las mediciones a ser devueltos.

Finalmente, podemos ver cómo se puede usar esto con la agregación interna predeterminada:

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']: ()

... y con una agregación interior diferente. Por ejemplo, un 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', ())])

Resumen

En este tutorial, explicamos las mejores prácticas a seguir para crear un bloque de construcción de agregación de propósito general, representado como una fábrica de agregación. La generalidad viene a través de la intención del diseño de dos maneras:

  1. Cálculos parametrizados. La agregación es un bloque de construcción independiente que puede ser enchufado en otros módulos TFF diseñado para trabajar con tff.aggregators para parametrizar su agregación necesarios, tales como tff.learning.build_federated_averaging_process .
  2. Composición de agregación. Un bloque de construcción de agregación se puede componer con otros bloques de construcción de agregación para crear agregaciones compuestas más complejas.