Se usó la API de Cloud Translation para traducir esta página.
Switch to English

Aprendizaje federado para la clasificación de imágenes

Ver en TensorFlow.org Ejecutar en Google Colab Ver código fuente en GitHub

En este tutorial, usamos el clásico ejemplo de capacitación MNIST para presentar la capa API de Aprendizaje Federado (FL) de TFF, tff.learning , un conjunto de interfaces de nivel superior que se pueden usar para realizar tipos comunes de tareas de aprendizaje federado, como capacitación federada, contra modelos proporcionados por el usuario implementados en TensorFlow.

Este tutorial, y la API de aprendizaje federado, están destinados principalmente a usuarios que desean conectar sus propios modelos TensorFlow en TFF, tratando a estos últimos principalmente como una caja negra. Para una comprensión más profunda de TFF y cómo implementar sus propios algoritmos de aprendizaje federados, vea los tutoriales en FC Core API - Algoritmos federados personalizados Parte 1 y Parte 2 .

Para obtener más información sobre tff.learning , continúe con el Aprendizaje federado para la generación de texto , tutorial que además de cubrir modelos recurrentes, también demuestra la carga de un modelo de Keras serializado previamente entrenado para el refinamiento con el aprendizaje federado combinado con la evaluación usando Keras.

Antes que empecemos

Antes de comenzar, ejecute lo siguiente para asegurarse de que su entorno esté configurado correctamente. Si no ve un saludo, consulte la guía de instalación para obtener instrucciones.

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

import nest_asyncio
nest_asyncio.apply()

%load_ext tensorboard
 
 import collections

import numpy as np
import tensorflow as tf
import tensorflow_federated as tff

np.random.seed(0)

tff.federated_computation(lambda: 'Hello, World!')()
 
b'Hello, World!'

Preparando los datos de entrada

Comencemos con los datos. El aprendizaje federado requiere un conjunto de datos federados, es decir, una recopilación de datos de múltiples usuarios. Los datos federados generalmente no son iid , lo que plantea un conjunto único de desafíos.

Con el fin de facilitar la experimentación, sembró el repositorio TFF con algunos conjuntos de datos, incluida una versión federada de MNIST que contiene una versión del conjunto de datos NIST original que se ha vuelto a procesar utilizando Leaf para que el escritor original de los dígitos Dado que cada escritor tiene un estilo único, este conjunto de datos exhibe el tipo de comportamiento no iid que se espera de los conjuntos de datos federados.

Así es como podemos cargarlo.

 emnist_train, emnist_test = tff.simulation.datasets.emnist.load_data()
 

Los conjuntos de datos devueltos por load_data() son instancias de tff.simulation.ClientData , una interfaz que le permite enumerar el conjunto de usuarios, construir un tf.data.Dataset que representa los datos de un usuario en particular y consultar el estructura de elementos individuales. Aquí le mostramos cómo puede usar esta interfaz para explorar el contenido del conjunto de datos. Tenga en cuenta que si bien esta interfaz le permite iterar sobre los identificadores de los clientes, esta es solo una característica de los datos de simulación. Como verá en breve, el marco de aprendizaje federado no utiliza las identidades de los clientes; su único propósito es permitirle seleccionar subconjuntos de datos para simulaciones.

 len(emnist_train.client_ids)
 
3383
 emnist_train.element_type_structure
 
OrderedDict([('pixels', TensorSpec(shape=(28, 28), dtype=tf.float32, name=None)), ('label', TensorSpec(shape=(), dtype=tf.int32, name=None))])
 example_dataset = emnist_train.create_tf_dataset_for_client(
    emnist_train.client_ids[0])

example_element = next(iter(example_dataset))

example_element['label'].numpy()
 
1
 from matplotlib import pyplot as plt
plt.imshow(example_element['pixels'].numpy(), cmap='gray', aspect='equal')
plt.grid(False)
_ = plt.show()
 

png

Dado que los datos ya son un tf.data.Dataset datos tf.data.Dataset , el preprocesamiento se puede lograr utilizando transformaciones de conjunto de datos. Aquí, 28x28 imágenes de 28x28 en matrices de 784 elementos, mezclamos los ejemplos individuales, los organizamos en lotes y cambiamos el nombre de las características de pixels y label a x e y para usar con Keras. También incluimos una repeat sobre el conjunto de datos para ejecutar varias épocas.

 NUM_CLIENTS = 10
NUM_EPOCHS = 5
BATCH_SIZE = 20
SHUFFLE_BUFFER = 100
PREFETCH_BUFFER=10

def preprocess(dataset):

  def batch_format_fn(element):
    """Flatten a batch `pixels` and return the features as an `OrderedDict`."""
    return collections.OrderedDict(
        x=tf.reshape(element['pixels'], [-1, 784]),
        y=tf.reshape(element['label'], [-1, 1]))

  return dataset.repeat(NUM_EPOCHS).shuffle(SHUFFLE_BUFFER).batch(
      BATCH_SIZE).map(batch_format_fn).prefetch(PREFETCH_BUFFER)
 

Verifiquemos que esto funcionó.

 preprocessed_example_dataset = preprocess(example_dataset)

sample_batch = tf.nest.map_structure(lambda x: x.numpy(),
                                     next(iter(preprocessed_example_dataset)))

sample_batch
 
OrderedDict([('x', array([[1., 1., 1., ..., 1., 1., 1.],
       [1., 1., 1., ..., 1., 1., 1.],
       [1., 1., 1., ..., 1., 1., 1.],
       ...,
       [1., 1., 1., ..., 1., 1., 1.],
       [1., 1., 1., ..., 1., 1., 1.],
       [1., 1., 1., ..., 1., 1., 1.]], dtype=float32)), ('y', array([[2],
       [1],
       [2],
       [3],
       [6],
       [0],
       [1],
       [4],
       [1],
       [0],
       [6],
       [9],
       [9],
       [3],
       [6],
       [1],
       [4],
       [8],
       [0],
       [2]], dtype=int32))])

Tenemos casi todos los componentes básicos para construir conjuntos de datos federados.

Una de las formas de alimentar datos federados a TFF en una simulación es simplemente como una lista de Python, con cada elemento de la lista que contiene los datos de un usuario individual, ya sea como una lista o como un tf.data.Dataset . Como ya tenemos una interfaz que proporciona este último, usémoslo.

Aquí hay una función auxiliar simple que construirá una lista de conjuntos de datos del conjunto de usuarios dado como entrada para una ronda de capacitación o evaluación.

 def make_federated_data(client_data, client_ids):
  return [
      preprocess(client_data.create_tf_dataset_for_client(x))
      for x in client_ids
  ]
 

Ahora, ¿cómo elegimos clientes?

En un escenario típico de capacitación federada, estamos tratando con una población potencialmente muy grande de dispositivos de usuario, de los cuales solo una fracción puede estar disponible para capacitación en un momento dado. Este es el caso, por ejemplo, cuando los dispositivos del cliente son teléfonos móviles que participan en la capacitación solo cuando están enchufados a una fuente de alimentación, fuera de una red medida y de otra manera inactivos.

Por supuesto, estamos en un entorno de simulación y todos los datos están disponibles localmente. Por lo general, cuando ejecutamos simulaciones, simplemente tomamos muestras de un subconjunto aleatorio de los clientes que participarán en cada ronda de capacitación, generalmente diferente en cada ronda.

Dicho esto, como puede descubrir al estudiar el documento sobre el algoritmo de Promedio Federado , lograr la convergencia en un sistema con subconjuntos de clientes muestreados al azar en cada ronda puede llevar un tiempo, y sería poco práctico tener que ejecutar cientos de rondas en este tutorial interactivo

Lo que haremos en su lugar es probar el conjunto de clientes una vez y reutilizar el mismo conjunto en todas las rondas para acelerar la convergencia (ajuste excesivo intencional a los datos de estos pocos usuarios). Lo dejamos como un ejercicio para que el lector modifique este tutorial para simular un muestreo aleatorio: es bastante fácil de hacer (una vez que lo haga, tenga en cuenta que hacer que el modelo converja puede llevar un tiempo).

 sample_clients = emnist_train.client_ids[0:NUM_CLIENTS]

federated_train_data = make_federated_data(emnist_train, sample_clients)

print('Number of client datasets: {l}'.format(l=len(federated_train_data)))
print('First dataset: {d}'.format(d=federated_train_data[0]))
 
Number of client datasets: 10
First dataset: <DatasetV1Adapter shapes: OrderedDict([(x, (None, 784)), (y, (None, 1))]), types: OrderedDict([(x, tf.float32), (y, tf.int32)])>

Creando un modelo con Keras

Si está utilizando Keras, es probable que ya tenga un código que construya un modelo Keras. Aquí hay un ejemplo de un modelo simple que será suficiente para nuestras necesidades.

 def create_keras_model():
  return tf.keras.models.Sequential([
      tf.keras.layers.Input(shape=(784,)),
      tf.keras.layers.Dense(10, kernel_initializer='zeros'),
      tf.keras.layers.Softmax(),
  ])
 

Para usar cualquier modelo con TFF, debe estar envuelto en una instancia de la interfaz tff.learning.Model , que expone métodos para sellar el pase de avance del modelo, las propiedades de metadatos, etc., de manera similar a Keras, pero también introduce más elementos, como las formas de controlar el proceso de cálculo de métricas federadas. No nos preocupemos por esto por ahora; Si tiene un modelo de Keras como el que acabamos de definir anteriormente, puede hacer que TFF lo tff.learning.from_keras_model invocando tff.learning.from_keras_model , pasando el modelo y un lote de datos de muestra como argumentos, como se muestra a continuación.

 def model_fn():
  # We _must_ create a new model here, and _not_ capture it from an external
  # scope. TFF will call this within different graph contexts.
  keras_model = create_keras_model()
  return tff.learning.from_keras_model(
      keras_model,
      input_spec=preprocessed_example_dataset.element_spec,
      loss=tf.keras.losses.SparseCategoricalCrossentropy(),
      metrics=[tf.keras.metrics.SparseCategoricalAccuracy()])
 

Entrenando el modelo en datos federados

Ahora que tenemos un modelo envuelto como tff.learning.Model para usar con TFF, podemos dejar que TFF construya un algoritmo de Promedio Federado invocando la función auxiliar tff.learning.build_federated_averaging_process , de la siguiente manera.

Tenga en cuenta que el argumento debe ser un constructor (como model_fn arriba), no una instancia ya construida, para que la construcción de su modelo pueda ocurrir en un contexto controlado por TFF (si tiene curiosidad sobre las razones de esto, le recomendamos que lea el tutorial de seguimiento sobre algoritmos personalizados ).

Una nota crítica sobre el algoritmo de promedio Federados de abajo, hay 2 optimizadores: un optimizador _client y un optimizador _SERVER. El optimizador _client solo se usa para calcular actualizaciones de modelos locales en cada cliente. El optimizador _server aplica la actualización promediada al modelo global en el servidor. En particular, esto significa que la elección del optimizador y la tasa de aprendizaje utilizada pueden ser diferentes de las que ha utilizado para entrenar el modelo en un conjunto de datos iid estándar. Recomendamos comenzar con SGD regular, posiblemente con una tasa de aprendizaje más pequeña de lo habitual. La tasa de aprendizaje que utilizamos no se ha ajustado cuidadosamente, no dude en experimentar.

 iterative_process = tff.learning.build_federated_averaging_process(
    model_fn,
    client_optimizer_fn=lambda: tf.keras.optimizers.SGD(learning_rate=0.02),
    server_optimizer_fn=lambda: tf.keras.optimizers.SGD(learning_rate=1.0))
 

¿Lo que acaba de suceder? TFF ha construido un par de cálculos federados y los ha empaquetado en un tff.templates.IterativeProcess en el que estos cálculos están disponibles a medida que se initialize y next un par de propiedades.

En pocas palabras, los cálculos federados son programas en el lenguaje interno de TFF que pueden expresar varios algoritmos federados (puede encontrar más información sobre esto en el tutorial de algoritmos personalizados ). En este caso, los dos cálculos generados y empaquetados en iterative_process implementan el Promedio Federado .

El objetivo de TFF es definir los cálculos de manera que puedan ejecutarse en entornos de aprendizaje federados reales, pero actualmente solo se implementa el tiempo de ejecución de la simulación de ejecución local. Para ejecutar un cálculo en un simulador, simplemente invoca como una función de Python. Este entorno interpretado predeterminado no está diseñado para un alto rendimiento, pero será suficiente para este tutorial; esperamos proporcionar tiempos de ejecución de simulación de mayor rendimiento para facilitar la investigación a mayor escala en futuras versiones.

Comencemos con el cálculo de initialize . Como es el caso de todos los cálculos federados, puede considerarlo como una función. El cálculo no toma argumentos y devuelve un resultado: la representación del estado del proceso de Promedio Federado en el servidor. Si bien no queremos profundizar en los detalles de TFF, puede ser instructivo ver cómo se ve este estado. Puede visualizarlo de la siguiente manera.

 str(iterative_process.initialize.type_signature)
 
'( -> <model=<trainable=<float32[784,10],float32[10]>,non_trainable=<>>,optimizer_state=<int64>,delta_aggregate_state=<>,model_broadcast_state=<>>@SERVER)'

Si bien la firma de tipo anterior puede parecer un poco críptica al principio, puede reconocer que el estado del servidor consta de un model (los parámetros del modelo inicial para MNIST que se distribuirán a todos los dispositivos) y optimizer_state (información adicional mantenida por el servidor, como el número de rondas a usar para los horarios de hiperparámetros, etc.).

Invoquemos el cálculo de initialize para construir el estado del servidor.

 state = iterative_process.initialize()
 

El segundo del par de cálculos federados, a next , representa una sola ronda de Promedio federado, que consiste en enviar el estado del servidor (incluidos los parámetros del modelo) a los clientes, capacitación en el dispositivo sobre sus datos locales, recopilar y promediar actualizaciones del modelo , y produciendo un nuevo modelo actualizado en el servidor.

Conceptualmente, puede pensar que el next tiene una firma de tipo funcional que se ve de la siguiente manera.

 SERVER_STATE, FEDERATED_DATA -> SERVER_STATE, TRAINING_METRICS
 

En particular, uno debería pensar que next() no es una función que se ejecuta en un servidor, sino una representación funcional declarativa de todo el cálculo descentralizado: algunas de las entradas son proporcionadas por el servidor ( SERVER_STATE ), pero cada una participa El dispositivo contribuye con su propio conjunto de datos local.

Realicemos una sola ronda de entrenamiento y visualicemos los resultados. Podemos usar los datos federados que ya generamos anteriormente para una muestra de usuarios.

 state, metrics = iterative_process.next(state, federated_train_data)
print('round  1, metrics={}'.format(metrics))
 
round  1, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.12037037312984467,loss=3.0108425617218018>>

Vamos a correr algunas rondas más. Como se señaló anteriormente, generalmente en este punto elegiría un subconjunto de sus datos de simulación de una nueva muestra de usuarios seleccionados al azar para cada ronda para simular una implementación realista en la que los usuarios van y vienen continuamente, pero en este cuaderno interactivo, para En aras de la demostración, reutilizaremos a los mismos usuarios para que el sistema converja rápidamente.

 NUM_ROUNDS = 11
for round_num in range(2, NUM_ROUNDS):
  state, metrics = iterative_process.next(state, federated_train_data)
  print('round {:2d}, metrics={}'.format(round_num, metrics))
 
round  2, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.14814814925193787,loss=2.8865506649017334>>
round  3, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.148765429854393,loss=2.9079062938690186>>
round  4, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.17633745074272156,loss=2.724686622619629>>
round  5, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.20226337015628815,loss=2.6334855556488037>>
round  6, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.22427983582019806,loss=2.5482592582702637>>
round  7, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.24094650149345398,loss=2.4472343921661377>>
round  8, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.259876549243927,loss=2.3809611797332764>>
round  9, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.29814815521240234,loss=2.156442403793335>>
round 10, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.31687241792678833,loss=2.122845411300659>>

La pérdida de capacitación disminuye después de cada ronda de capacitación federada, lo que indica que el modelo está convergiendo. Hay algunas advertencias importantes con estas métricas de entrenamiento, sin embargo, consulte la sección Evaluación más adelante en este tutorial.

Mostrar métricas del modelo en TensorBoard

A continuación, visualicemos las métricas de estos cálculos federados utilizando Tensorboard.

Comencemos creando el directorio y el escritor de resumen correspondiente para escribir las métricas.

 
logdir = "/tmp/logs/scalars/training/"
summary_writer = tf.summary.create_file_writer(logdir)
state = iterative_process.initialize()
 

Trace las métricas escalares relevantes con el mismo escritor de resumen.

 
with summary_writer.as_default():
  for round_num in range(1, NUM_ROUNDS):
    state, metrics = iterative_process.next(state, federated_train_data)
    for name, value in metrics.train._asdict().items():
      tf.summary.scalar(name, value, step=round_num)
 

Inicie TensorBoard con el directorio de registro raíz especificado anteriormente. La carga de los datos puede demorar unos segundos.

 
%tensorboard --logdir /tmp/logs/scalars/ --port=0
 
 
# Run this this cell to clean your directory of old output for future graphs from this directory.
!rm -R /tmp/logs/scalars/*
 

Para ver las métricas de evaluación de la misma manera, puede crear una carpeta de evaluación separada, como "logs / scalars / eval", para escribir en TensorBoard.

Personalizando la implementación del modelo

Keras es la API de modelo de alto nivel recomendada para TensorFlow , y recomendamos usar modelos Keras (a través de tff.learning.from_keras_model ) en TFF siempre que sea posible.

Sin embargo, tff.learning proporciona una interfaz de modelo de nivel inferior, tff.learning.Model , que expone la funcionalidad mínima necesaria para usar un modelo para el aprendizaje federado. La implementación directa de esta interfaz (posiblemente aún usando bloques de construcción como tf.keras.layers ) permite la máxima personalización sin modificar las tf.keras.layers internas de los algoritmos de aprendizaje federados.

Así que hagámoslo de nuevo desde cero.

Definición de variables de modelo, avance y métricas

El primer paso es identificar las variables TensorFlow con las que vamos a trabajar. Para que el siguiente código sea más legible, definamos una estructura de datos para representar el conjunto completo. Esto incluirá variables como weights y bias que entrenaremos, así como variables que contendrán varias estadísticas acumulativas y contadores que actualizaremos durante el entrenamiento, como loss_sum , accuracy_sum y num_examples .

 MnistVariables = collections.namedtuple(
    'MnistVariables', 'weights bias num_examples loss_sum accuracy_sum')
 

Aquí hay un método que crea las variables. En aras de la simplicidad, representamos todas las estadísticas como tf.float32 , ya que eso eliminará la necesidad de conversiones de tipo en una etapa posterior. El ajuste de los inicializadores de variables como lambdas es un requisito impuesto por las variables de recursos .

 def create_mnist_variables():
  return MnistVariables(
      weights=tf.Variable(
          lambda: tf.zeros(dtype=tf.float32, shape=(784, 10)),
          name='weights',
          trainable=True),
      bias=tf.Variable(
          lambda: tf.zeros(dtype=tf.float32, shape=(10)),
          name='bias',
          trainable=True),
      num_examples=tf.Variable(0.0, name='num_examples', trainable=False),
      loss_sum=tf.Variable(0.0, name='loss_sum', trainable=False),
      accuracy_sum=tf.Variable(0.0, name='accuracy_sum', trainable=False))
 

Con las variables para los parámetros del modelo y las estadísticas acumulativas en su lugar, ahora podemos definir el método de transferencia directa que calcula la pérdida, emite predicciones y actualiza las estadísticas acumulativas para un solo lote de datos de entrada, de la siguiente manera.

 def mnist_forward_pass(variables, batch):
  y = tf.nn.softmax(tf.matmul(batch['x'], variables.weights) + variables.bias)
  predictions = tf.cast(tf.argmax(y, 1), tf.int32)

  flat_labels = tf.reshape(batch['y'], [-1])
  loss = -tf.reduce_mean(
      tf.reduce_sum(tf.one_hot(flat_labels, 10) * tf.math.log(y), axis=[1]))
  accuracy = tf.reduce_mean(
      tf.cast(tf.equal(predictions, flat_labels), tf.float32))

  num_examples = tf.cast(tf.size(batch['y']), tf.float32)

  variables.num_examples.assign_add(num_examples)
  variables.loss_sum.assign_add(loss * num_examples)
  variables.accuracy_sum.assign_add(accuracy * num_examples)

  return loss, predictions
 

A continuación, definimos una función que devuelve un conjunto de métricas locales, nuevamente utilizando TensorFlow. Estos son los valores (además de las actualizaciones del modelo, que se manejan automáticamente) que son elegibles para agregarse al servidor en un proceso federado de aprendizaje o evaluación.

Aquí, simplemente devolvemos la loss y la accuracy promedio, así como los num_examples , que necesitaremos para ponderar correctamente las contribuciones de diferentes usuarios al calcular los agregados federados.

 def get_local_mnist_metrics(variables):
  return collections.OrderedDict(
      num_examples=variables.num_examples,
      loss=variables.loss_sum / variables.num_examples,
      accuracy=variables.accuracy_sum / variables.num_examples)
 

Finalmente, necesitamos determinar cómo agregar las métricas locales emitidas por cada dispositivo a través de get_local_mnist_metrics . Esta es la única parte del código que no está escrita en TensorFlow: es un cálculo federado expresado en TFF. Si desea profundizar, hojee el tutorial de algoritmos personalizados , pero en la mayoría de las aplicaciones, realmente no necesitará hacerlo; variantes del patrón que se muestra a continuación deberían ser suficientes. Así es como se ve:

 @tff.federated_computation
def aggregate_mnist_metrics_across_clients(metrics):
  return collections.OrderedDict(
      num_examples=tff.federated_sum(metrics.num_examples),
      loss=tff.federated_mean(metrics.loss, metrics.num_examples),
      accuracy=tff.federated_mean(metrics.accuracy, metrics.num_examples))
  
 

El argumento de la metrics entrada corresponde al OrderedDict devuelto por get_local_mnist_metrics arriba, pero críticamente los valores ya no son tf.Tensors . tf.Tensors : están " tff.Value " como tff.Value . tff.Value s, para dejar en claro que ya no puede manipularlos usando TensorFlow, pero solo utilizando operadores federados de TFF como tff.federated_mean y tff.federated_sum . El diccionario devuelto de agregados globales define el conjunto de métricas que estarán disponibles en el servidor.

Construyendo una instancia de tff.learning.Model

Con todo lo anterior en su lugar, estamos listos para construir una representación de modelo para usar con TFF similar a la que se genera para usted cuando deja que TFF ingiera un modelo Keras.

 class MnistModel(tff.learning.Model):

  def __init__(self):
    self._variables = create_mnist_variables()

  @property
  def trainable_variables(self):
    return [self._variables.weights, self._variables.bias]

  @property
  def non_trainable_variables(self):
    return []

  @property
  def local_variables(self):
    return [
        self._variables.num_examples, self._variables.loss_sum,
        self._variables.accuracy_sum
    ]

  @property
  def input_spec(self):
    return collections.OrderedDict(
        x=tf.TensorSpec([None, 784], tf.float32),
        y=tf.TensorSpec([None, 1], tf.int32))

  @tf.function
  def forward_pass(self, batch, training=True):
    del training
    loss, predictions = mnist_forward_pass(self._variables, batch)
    num_exmaples = tf.shape(batch['x'])[0]
    return tff.learning.BatchOutput(
        loss=loss, predictions=predictions, num_examples=num_exmaples)

  @tf.function
  def report_local_outputs(self):
    return get_local_mnist_metrics(self._variables)

  @property
  def federated_output_computation(self):
    return aggregate_mnist_metrics_across_clients
 

Como puede ver, los métodos abstractos y las propiedades definidas por tff.learning.Model corresponden a los fragmentos de código en la sección anterior que introdujo las variables y definió las pérdidas y las estadísticas.

Aquí hay algunos puntos que vale la pena destacar:

  • Todos los estados que usará su modelo deben capturarse como variables TensorFlow, ya que TFF no usa Python en tiempo de ejecución (recuerde que su código debe escribirse de manera que pueda implementarse en dispositivos móviles; consulte el tutorial de algoritmos personalizados para obtener más información comentario sobre los motivos).
  • Su modelo debe describir qué forma de datos acepta ( input_spec ), ya que, en general, TFF es un entorno fuertemente tipado y desea determinar las firmas de tipo para todos los componentes. Declarar el formato de entrada de su modelo es una parte esencial de la misma.
  • Aunque técnicamente no es necesario, recomendamos envolver toda la lógica de TensorFlow (paso directo, cálculos métricos, etc.) como tf.function , ya que esto ayuda a garantizar que TensorFlow se pueda serializar y elimina la necesidad de dependencias de control explícitas.

Lo anterior es suficiente para la evaluación y algoritmos como el SGD federado. Sin embargo, para el Promedio Federado, necesitamos especificar cómo el modelo debe entrenarse localmente en cada lote. Especificaremos un optimizador local al construir el algoritmo de Promedio Federado.

Simulando entrenamiento federado con el nuevo modelo

Con todo lo anterior en su lugar, el resto del proceso se parece a lo que ya hemos visto: simplemente reemplace el constructor del modelo con el constructor de nuestra nueva clase de modelo y use los dos cálculos federados en el proceso iterativo que creó para recorrer rondas de entrenamiento.

 iterative_process = tff.learning.build_federated_averaging_process(
    MnistModel,
    client_optimizer_fn=lambda: tf.keras.optimizers.SGD(learning_rate=0.02))
 
 state = iterative_process.initialize()
 
 state, metrics = iterative_process.next(state, federated_train_data)
print('round  1, metrics={}'.format(metrics))
 
round  1, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.9713594913482666,accuracy=0.13518518209457397>>

 for round_num in range(2, 11):
  state, metrics = iterative_process.next(state, federated_train_data)
  print('round {:2d}, metrics={}'.format(round_num, metrics))
 
round  2, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.975412607192993,accuracy=0.14032921195030212>>
round  3, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.9395227432250977,accuracy=0.1594650149345398>>
round  4, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.710164785385132,accuracy=0.17139917612075806>>
round  5, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.5891618728637695,accuracy=0.20267489552497864>>
round  6, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.5148487091064453,accuracy=0.21666666865348816>>
round  7, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.2816808223724365,accuracy=0.2580246925354004>>
round  8, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.3656885623931885,accuracy=0.25884774327278137>>
round  9, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.23549222946167,accuracy=0.28477364778518677>>
round 10, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=1.974222183227539,accuracy=0.35329216718673706>>

Para ver estas métricas en TensorBoard, consulte los pasos enumerados anteriormente en "Visualización de métricas del modelo en TensorBoard".

Evaluación

Hasta ahora, todos nuestros experimentos presentaron solo métricas de entrenamiento federado: las métricas promedio de todos los lotes de datos capacitados en todos los clientes de la ronda. Esto introduce las preocupaciones normales sobre el sobreajuste, especialmente porque utilizamos el mismo conjunto de clientes en cada ronda por simplicidad, pero existe una noción adicional de sobreajuste en las métricas de entrenamiento específicas del algoritmo de Promedio Federado. Esto es más fácil de ver si imaginamos que cada cliente tenía un solo lote de datos, y entrenamos en ese lote para muchas iteraciones (épocas). En este caso, el modelo local se ajustará rápidamente exactamente a ese lote y, por lo tanto, la métrica de precisión local que promediamos se acercará a 1.0. Por lo tanto, estas métricas de entrenamiento se pueden tomar como una señal de que el entrenamiento está progresando, pero no mucho más.

Para realizar una evaluación de datos federados, puede construir otro cálculo federado diseñado para este propósito, utilizando la función tff.learning.build_federated_evaluation y pasando el constructor de su modelo como argumento. Tenga en cuenta que, a diferencia del Promedio federado, donde usamos MnistTrainableModel , es suficiente pasar el MnistModel . La evaluación no realiza el descenso del gradiente, y no hay necesidad de construir optimizadores.

Para la experimentación y la investigación, cuando hay un conjunto de datos de prueba centralizado, Federated Learning for Text Generation muestra otra opción de evaluación: tomar los pesos entrenados del aprendizaje federado, aplicarlos a un modelo estándar de Keras y luego simplemente llamar a tf.keras.models.Model.evaluate() en un conjunto de datos centralizado.

 evaluation = tff.learning.build_federated_evaluation(MnistModel)
 

Puede inspeccionar la firma de tipo abstracto de la función de evaluación de la siguiente manera.

 str(evaluation.type_signature)
 
'(<<trainable=<float32[784,10],float32[10]>,non_trainable=<>>@SERVER,{<x=float32[?,784],y=int32[?,1]>*}@CLIENTS> -> <num_examples=float32@SERVER,loss=float32@SERVER,accuracy=float32@SERVER>)'

No necesita preocuparse por los detalles en este punto, solo tenga en cuenta que toma la siguiente forma general, similar a tff.templates.IterativeProcess.next pero con dos diferencias importantes. Primero, no devolvemos el estado del servidor, ya que la evaluación no modifica el modelo ni ningún otro aspecto del estado; puede considerarlo sin estado. En segundo lugar, la evaluación solo necesita el modelo y no requiere ninguna otra parte del estado del servidor que pueda estar asociada con la capacitación, como las variables del optimizador.

 SERVER_MODEL, FEDERATED_DATA -> TRAINING_METRICS
 

Invoquemos una evaluación del último estado al que llegamos durante el entrenamiento. Para extraer el último modelo entrenado del estado del servidor, simplemente acceda al miembro .model , de la siguiente manera.

 train_metrics = evaluation(state.model, federated_train_data)
 

Esto es lo que tenemos. Tenga en cuenta que los números se ven marginalmente mejor de lo que se informó en la última ronda de entrenamiento anterior. Por convención, las métricas de capacitación informadas por el proceso de capacitación iterativa generalmente reflejan el desempeño del modelo al comienzo de la ronda de capacitación, por lo que las métricas de evaluación siempre estarán un paso adelante.

 str(train_metrics)
 
'<num_examples=4860.0,loss=1.7142657041549683,accuracy=0.38683128356933594>'

Ahora, compilemos una muestra de prueba de datos federados y vuelva a ejecutar la evaluación en los datos de prueba. Los datos provendrán de la misma muestra de usuarios reales, pero de un conjunto de datos distintos.

 federated_test_data = make_federated_data(emnist_test, sample_clients)

len(federated_test_data), federated_test_data[0]
 
(10,
 <DatasetV1Adapter shapes: OrderedDict([(x, (None, 784)), (y, (None, 1))]), types: OrderedDict([(x, tf.float32), (y, tf.int32)])>)
 test_metrics = evaluation(state.model, federated_test_data)
 
 str(test_metrics)
 
'<num_examples=580.0,loss=1.861915111541748,accuracy=0.3362068831920624>'

Esto concluye el tutorial. Le recomendamos que juegue con los parámetros (por ejemplo, tamaños de lote, número de usuarios, épocas, tasas de aprendizaje, etc.), que modifique el código anterior para simular la capacitación en muestras aleatorias de usuarios en cada ronda, y que explore los otros tutoriales. hemos desarrollado