¿Tengo una pregunta? Conéctese con la comunidad en el Foro de visita del foro de TensorFlow

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 del módulo tff.aggregators y las mejores prácticas para implementar la agregación personalizada de valores de los clientes al servidor.

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

!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 de cliente se promedian para obtener una actualización de modelo agregada para aplicar al modelo global en el servidor.

Además de los operadores que logran este objetivo, como tff.federated_sum , TFF proporciona tff.templates.AggregationProcess (un proceso con estado ) que formaliza la firma de tipo para el cálculo de agregación para que pueda generalizarse a formas más complejas que una simple suma.

Los componentes principales del módulo tff.aggregators son fábricas para la creación del proceso de AggregationProcess , que están diseñadas 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 se puede conectar a otros módulos TFF diseñados para trabajar con tff.aggregators para parametrizar su agregación necesaria.

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

Primero resumimos el tff.templates.AggregationProcess y seguimos con el patrón de fábrica para su creación.

tff.templates.AggregationProcess es un tff.templates.MeasuredProcess con firmas de tipo especificadas para agregación. En particular, las funciones initialize y next tienen las siguientes firmas de tipo:

  • ( -> 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 colocarse en el servidor. La next función toma como argumento de entrada el estado y un valor a agregar (de tipo value_type ) colocado en los clientes. El * significa otros argumentos de entrada opcionales, por ejemplo, ponderaciones 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 pasará entre las ejecuciones de la next función como las mediciones informadas destinadas a informar cualquier información que dependa de una ejecución específica de la next función pueden estar vacías. Sin embargo, deben especificarse explícitamente para que otras partes del TFF tengan un contrato claro a seguir.

Se espera que otros módulos TFF, por ejemplo, las actualizaciones del modelo en tff.learning , utilicen tff.templates.AggregationProcess para 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 que la agregación sea independiente de los otros aspectos de los cálculos, usamos el patrón de fábrica: creamos los tff.templates.AggregationProcess apropiados una vez que las firmas de tipo relevantes de los objetos que se agregarán están disponibles, invocando el método de create de la fábrica. Por 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 método de create toma firmas de tipo de valor para agregar y devuelve un tff.templates.AggregationProcess para la agregación de dichos valores.

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

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

El proceso creado por tff.aggregators.WeightedAggregationFactory toma tres argumentos de entrada: (1) estado en el servidor, (2) valor del tipo especificado value_type y (3) peso del tipo weight_type , según lo especificado por el usuario de la fábrica al invocar su método de create .

Una implementación de ejemplo 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 lo representa se invocará con una firma de tipo diferente cuando se utilice con un método 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 mencionado anteriormente, la composición de la agregación, se realiza dentro del módulo tff.aggregators al estructurar la implementación de las fábricas de agregación de manera que la parte (b) se pueda delegar a otra fábrica de 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 uno a la vez.

Un ejemplo son los tff.aggregators.MeanFactory ponderados. Su implementación multiplica los valores proporcionados y las ponderaciones en los clientes, luego suma tanto los valores ponderados como las ponderaciones de forma independiente y luego divide la suma de los valores ponderados por la suma de las ponderaciones en el servidor. En lugar de implementar las sumas mediante el uso directo del operador tff.federated_sum , 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 tff.aggregators.UnweightedAggregationFactory . Por el contrario, time, tff.aggregators.MeanFactory puede ser en sí mismo una agregación interna de otra fábrica como tff.aggregators.clipping_factory , si los valores deben recortarse antes de promediar.

Consulte el tutorial de aprendizaje de agregaciones recomendadas de Tuning para conocer los usos recomendados del mecanismo de composición utilizando fábricas existentes en el módulo tff.aggregators .

Mejores prácticas con el ejemplo

Vamos a ilustrar los conceptos de tff.aggregators en detalle implementando una tarea de ejemplo simple y haciéndola progresivamente 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 de ejemplo es sumar value * 2.0 y luego dividir la suma por 2.0 . Por lo tanto, el resultado de la agregación es matemáticamente equivalente a sumar directamente el value , y podría considerarse que consta de tres partes: (1) escalado en los clientes (2) sumando entre clientes (3) desescalado en el servidor.

Siguiendo el diseño explicado anteriormente, la lógica se implementará como una subclase de tff.aggregators.UnweightedAggregationFactory , que crea tff.templates.AggregationProcess apropiado cuando se le da un value_type para agregar:

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. Por lo tanto, está vacío 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 lo 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 el value por 2.0 , lo multiplicamos por el índice de iteración, la cantidad de veces que se ha ejecutado la agregación.

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 initialize_fn , en lugar de crear un estado vacío, inicializamos el estado para que sea un cero escalar. Luego, el estado se puede usar en next_fn en tres pasos: (1) incrementar en 1.0 , (2) usar para multiplicar el value y (3) regresar como el nuevo estado actualizado.

Una vez hecho esto, puede notar: Pero se puede usar exactamente el mismo código que el anterior para verificar todos los trabajos 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 reportar cualquier valor relevante para una sola ejecución de la next función, que podría usarse para monitoreo. En este caso, puede ser el valor 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. Para usarlo en los clientes, primero debe comunicarse, lo que se logra utilizando el operador tff.federated_broadcast .

Para verificar todos los trabajos como se esperaba, ahora podemos ver las measurements informadas, que deberían ser diferentes con cada ronda de ejecución, incluso si se ejecutan con el mismo 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 las fábricas de agregación generalmente útiles deben poder aceptar los tipos estructurados.

Sin embargo, en los ejemplos anteriores, solo trabajamos con un objeto tff.TensorType . Si intentamos usar 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 los tensores por una constante, necesitamos multiplicar cada tensor en la estructura por una constante. La solución habitual a este problema es utilizar el módulo tf.nest dentro de los tff.tf_computation creados.

La versión anterior de ExampleTaskFactory compatible con tipos estructurados tiene el siguiente aspecto:

@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 que se usarán como bloques de construcción dentro de un tff.federated_computation se crean en un lugar separado. Dentro de tff.federated_computation , estos bloques de construcción solo están conectados usando los operadores intrínsecos.

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 creando un argumento inner_factory opcional en el constructor de nuestro ExampleTaskFactory . Si no se especifica, se usa tff.aggregators.SumFactory , que aplica el operador tff.federated_sum usado directamente en la sección anterior.

Cuando se llama a create , primero podemos llamar a create de inner_factory para crear el proceso de agregación interna 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 recién creado.

La implementación de 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 la salida final. El estado se compone de nuevo de "este" y el estado "interno", y las medidas se componen de manera similar a 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 en la función inner_process.next , la estructura de retorno que obtenemos es tff.templates.MeasuredProcessOutput , con los mismos tres campos: state , result y measurements . Al crear la estructura de retorno general del proceso de agregación compuesto, los campos de state y measurements generalmente deben componerse y devolverse juntos. Por el contrario, el campo de result corresponde al valor que se está agregando y, en cambio, "fluye a través" de la agregación compuesta.

El objeto de state debe verse 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, las measurements corresponden a valores que se informarán al usuario en algún momento. Por lo tanto, recomendamos usar OrderedDict , con nombres compuestos de manera que quede claro de dónde proviene una métrica informada en una composición.

Tenga en cuenta también el uso del operador tff.federated_zip . El objeto de state controlado por el proceso creado debe ser un tff.FederatedType . Si en cambio hubiéramos devuelto (this_state, inner_state) en initialize_fn , su firma de tipo de retorno sería un tff.StructType contiene una tupla 2 de tff.FederatedType s. El uso de tff.federated_zip "eleva" el tff.FederatedType al nivel superior. Esto se usa de manera similar en next_fn cuando se prepara el estado y las mediciones que se devolverán.

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, una 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 se puede conectar a otros módulos TFF diseñados para trabajar con tff.aggregators para parametrizar su agregación necesaria, 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.