Path: blob/master/site/es-419/federated/openmined2020/openmined_conference_2020.ipynb
25118 views
Copyright 2020 The TensorFlow Authors.
Antes de empezar
Para editar el bloc de notas de colaboración, vaya a "File" -> "Save a copy in Drive" ("Archivo" -> "Guardar una copia en Drive") y edite su copia.
Antes de empezar, ejecute lo que se encuentra a continuación, para asegurarse de que el entorno esté preparado correctamente. Si no ve un mensaje de inicio, para más instrucciones, consulte la guía de instalación.
TensorFlow federado para clasificación de imágenes
Experimentemos con el aprendizaje federado en una simulación. En este tutorial usamos el ejemplo de entrenamiento clásico MNIST para presentar la capa de API de aprendizaje federado (FL, por sus siglas en inglés) de TFF, tff.learning
; un conjunto de interfaces que se puede utilizar para realizar distintos tipos de tareas de aprendizaje federado, como un entrenamiento federado, con respecto a los modelos implementados por TensorFlow provistos por usuarios.
Estructura del tutorial
Entrenaremos un modelo para clasificar imágenes con el conjunto de datos clásico MNIST y aplicaremos el aprendizaje de red neuronal para clasificar dígitos de imágenes. En este caso, simularemos aprendizaje federado con los datos de entrenamiento distribuidos en diferentes dispositivos.
Secciones
Carga de las bibliotecas de TFF.
Exploración y preprocesamiento del conjunto de datos EMNIST federado.
Creación de un modelo.
Configuración del proceso del cálculo de promedio federado para entrenamiento.
Análisis de las métricas de entrenamiento.
Configuración del cálculo de evaluación federada.
Análisis de las métricas de evaluación.
Preparación de los datos de entrada
Empecemos con los datos. Para poner en práctica el aprendizaje federado es necesario contar con un conjunto de datos federados; es decir, una colección de datos de múltiples usuarios. Los datos federados normalmente son no i.i.d., lo que presenta un grupo de problemas particulares. Normalmente, los usuarios tienen diferentes distribuciones de datos que dependen de los patrones de uso.
A fin de facilitar la experimentación, sembramos el repositorio de TFF con algunos conjuntos de datos.
A continuación, compartimos cómo podemos cargar nuestro conjunto de datos de muestra.
Los conjuntos de datos devueltos por load_data()
son instancias de tff.simulation.datasets.ClientData
, una interfaz que permite enumerar los conjuntos de usuarios para construir un tf.data.Dataset
que representa los datos de un usuario en particular y para consultar la estructura de elementos individuales.
Exploremos el conjunto de datos.
Exploración de los datos que no tienen una distribución iid
Preprocesamiento de los datos
Como los datos ya son un tf.data.Dataset
, el preprocesamiento se puede cumplir con transformaciones de conjuntos de datos. Consulte aquí por más detalle sobre estas transformaciones.
Verifiquemos si funcionó.
Aquí, presentamos una función ayudante simple que construirá una lista de conjuntos de datos (a partir de un conjunto dado de usuarios) como entrada a una ronda de entrenamiento o evaluación.
Ahora, ¿cómo elegimos a los clientes?
Creación de un modelo con Keras
Si usa Keras, probablemente ya tenga el código que construye un modelo Keras. A continuación, mostramos un ejemplo de un modelo simple que bastará para nuestro propósito.
Entrenamiento centralizado con Keras
Entrenamiento federado con un modelo Keras
A fin de usar cualquier modelo con TFF, hay que encapsularlo (wrap) en una instancia de la interfaz del tff.learning.Model
.
Aquí hallará más métricas Keras para agregar.
Entrenamiento del modelo sobre datos federados
Ahora que tenemos un modelo encapsulado como tff.learning.Model
para usarlo con TFF, podemos dejar que TFF construya un algoritmo de promedio federado "Federated Averaging" si invocamos la función ayudante tff.learning.build_federated_averaging_process
, como se muestra a continuación.
¿Qué acaba de suceder? TFF ha construido un par de cálculos federados y los empaquetó en un tff.templates.IterativeProcess
en los cuales estos cálculos se encuentran en forma de un par de propiedades initialize
y next
.
Por lo general, un proceso iterativo será provocado por un bucle de control como el siguiente:
Invoquemos el cálculo initialize
para construir el estado del servidor.
El segundo par de cálculos federados, next
, representa a una ronda simple de cálculo de promedio federado de un modelo nuevo actualizado en el servidor, que está compuesta por el envío del estado del servidor (incluidos los parámetros del modelo) a los clientes, el entrenamiento en el dispositivo sobre sus datos locales, las actualizaciones del modelo de recolección y el cálculo del promedio y la producción.
Ejecutemos una ronda simple de entrenamiento y observemos los resultados. Podemos usar los datos federados que ya hemos generado (arriba) para una muestra de usuarios.
Ejecutemos algunas rondas más. Tal como lo señalamos antes, normalmente en esta instancia, elegiríamos un subconjunto de los datos de simulación a partir de una muestra de usuarios seleccionada de forma aleatoria para cada ronda, a fin de simular una implementación realista en la cual los usuarios vienen y van continuamente. Pero en estas notas interactivas, con propósito demostrativo, simplemente reutilizaremos los mismos usuarios, para que el sistema converja rápidamente.
La pérdida de entrenamiento disminuye después de cada ronda de entrenamiento federado. Es señal de que el modelo está convergiendo. Hay algunas salvedades importantes relacionadas con estas métricas de entrenamiento, pero para conocerlas, consulte más adelante la sección Evaluación en este tutorial.
##Se muestran las métricas del modelo en TensorBoard. Luego, observemos las métricas de estos cálculos federados en TensorBoard.
Comencemos por crear un directorio y el escritor de resúmenes correspondiente en el que se redactarán las métricas.
Grafiquemos las métricas escalares relevantes con el mismo escritor de resúmenes.
Comencemos por TensorBoard, con el directorio de registros raíz especificado arriba. La carga de los datos puede demorar algunos segundos.
A fin de ver las métricas de evaluación del mismo modo, se puede crear una carpeta de evaluación por separado, como "logs/scalars/eval", para escribir en TensorBoard.
Evaluación
Para llevar a cabo la evaluación sobre los datos federados, se puede construir otro cálculo federado diseñado para este propósito, con la función tff.learning.build_federated_evaluation
y pasar el constructor del modelo como un argumento.
Ahora compilemos una muestra de prueba de datos federados y volvamos a ejecutar la evaluación de los datos de prueba. Los datos provendrán de una muestra diferente de usuarios y de un conjunto de datos retenidos (held-out) distintivos.
De este modo, se concluye con el tutorial. Le aconsejamos jugar con distintos parámetros (p. ej., los tamaños de los lotes, la cantidad de usuarios, las épocas, las tasas de aprendizaje, etc.), para modificar el código que figura arriba a fin de simular el entrenamiento con muestras aleatorias de usuarios en cada ronda. También le recomendamos explorar los otros tutoriales que hemos desarrollado.
Creación de los propios algoritmos de aprendizaje federado
En los tutoriales anteriores aprendimos a configurar las canalizaciones de los datos y del modelo. Además las usamos para realizar entrenamientos federados con la API tff.learning
API.
Por supuesto, esto es solamente la punta del iceberg en la investigación sobre el aprendizaje federado. En este tutorial analizaremos cómo implementar los algoritmos de aprendizaje federado sin delegar a la API tff.learning
. Con este tutorial, pretendemos lograr lo siguiente:
Objetivos:
Entender la estructura general de los algoritmos de aprendizaje federado.
Explorar el núcleo federado de TFF.
Usar el núcleo federado para implementar directamente el cálculo del promedio federado.
Preparación de los datos de entrada
Primero, cargamos y preprocesamos el conjunto de datos EMNIST incluido en TFF. Básicamente usamos el mismo código que se utilizó en el primer tutorial.
Preparación del modelo
Usamos el mismo modelo del primer tutorial, que tiene una sola capa oculta, seguida por una capa softmax.
Encapsulamos (wrap) este modelo Keras como un tff.learning.Model
.
Personalización del algoritmo de aprendizaje federado
Si bien la API tff.learning
permite que uno cree muchas variantes del cálculo del promedio federado, hay otros algoritmos federados que no se adaptan perfectamente a este marco de trabajo. Por ejemplo, tal vez le convenga agregar algoritmos de regularización, recorte (clipping) u otros más complicados como el entrenamiento GAN federado. Probablemente, por otra parte, lo que le resulte interesante sea el análisis federado.
Para estos algoritmos más avanzados, deberemos escribir nuestro propio algoritmo personalizado de aprendizaje federado.
En general, los algoritmos de aprendizaje federado están compuestos por cuatro partes principales:
Un paso para la emisión (broadcast) del servidor al cliente.
Un paso para la actualización del cliente local.
Un paso para la carga del cliente al servidor.
Un paso para la actualización del servidor.
En TFF, un algoritmo federado, normalmente, está representado por un IterativeProcess
. Simplemente, es una clase que contiene las funciones initialize_fn
y next_fn
. initialize_fn
se usará para inicializar el servidor y next_fn
realizará una ronda de comunicación del cálculo de promedio federado. Escribamos un esquema sobre cómo debería lucir de nuestro proceso iterativo para FedAvg.
Primero, hay una función para inicializar que simplemente crea tff.learning.Model
y devuelve sus pesos entrenables.
Esta función tiene buen aspecto, pero como verá más adelante, deberemos hacerle una pequeña modificación para convertirla en un cálculo de TFF.
También nos convendrá realizar el esquema de next_fn
.
Centrémonos en implementar estos cuatro componentes por separado. Primero, enfoquémonos en las partes que se pueden implementar en TensorFlow puro, a saber, los pasos relacionados con el cliente y el servidor.
Bloques de TensorFlow
Actualización del cliente
Usaremos nuestro tff.learning.Model
para hacer el entrenamiento del cliente, esencialmente, del mismo modo en que se entrenaría un modelo de TF. En particular, usaremos tf.GradientTape
para calcular el gradiente en lotes de datos y luego aplicarlo con un client_optimizer
.
Tenga en cuenta que cada instancia de tff.learning.Model
tiene un atributo de weights
con dos subatributos:
trainable
: una lista de tensores correspondientes a las capas entrenables.non_trainable
: una lista de tensores correspondientes a las capas no entrenables.
Para cumplir con nuestro objetivo, solamente usaremos pesos entrenables (ya que nuestro modelo únicamente tiene los de este tipo).
Actualización del servidor
Para la actualización del servidor se necesitará incluso menos esfuerzo. Implementaremos el cálculo de promedios federados "vainilla", en el que los pesos del modelo del servidor se reemplazan por el promedio de los pesos del modelo del cliente. Una vez más, solamente nos centraremos en los pesos entrenables.
Tenga en cuenta que para el fragmento anterior es claramente un exceso; ya que, sencillamente, se podría simplificar con la devolución de mean_client_weights
. Sin embargo, en las implementaciones más avanzadas del cálculo de promedio federado se usa mean_client_weights
con técnicas más sofisticadas, como momentum o adaptabilidad.
Hasta el momento, solamente hemos escrito código puro de TensorFlow. El motivo es el diseño, ya que TFF permite usar gran parte del código de TensorFlow con el que ya estamos familiarizados. Sin embargo, deberá especificar la lógica de orquestación; es decir, la que dicta lo que el servidor emite (broadcast) al cliente y que el cliente carga en el servidor.
El núcleo federado de TFF será indispensable.
Introducción al núcleo federado
El núcleo federado (FC, por sus siglas en inglés) es un conjunto de interfaces de bajo nivel que sirve como base para la API tff.learning
. Sin embargo, estas interfaces no se limitan al aprendizaje. De hecho, se pueden usar para análisis y muchos otros cálculos de datos distribuidos.
A un alto nivel, el núcleo federado es un entorno de desarrollo que permite expresar de manera compacta la lógica de programación para combinar código de TensorFlow con los operadores de comunicación distribuidos (como las sumas y las emisiones distribuidas). El objetivo es brindarles a los investigadores y especialistas el control explícito de la comunicación distribuida en sus sistemas, sin requerir de otros detalles para la implementación (tales como la especificación de los intercambios de mensajes de red punto a punto).
Un punto clave es que TFF está diseñado para la preservación de la privacidad. Por lo tanto, permite el control explícito del sitio donde residen los datos, para prevenir la acumulación indeseada de datos en el lugar del servidor centralizado.
Datos federados
Del mismo modo que el concepto de "tensor" en TensorFlow es fundamental, el concepto de los "datos federados" es clave en TFF. Se refiere a una colección de elementos de datos alojados en un grupo de dispositivos en un sistema distribuido (p. ej., las bases de datos de clientes o los pesos del modelo del servidor). La colección entera de valores de todos los dispositivos se representa con un solo valor federado.
Por ejemplo, supongamos que hay dispositivos clientes y que cada uno tiene un flotante que representa la temperatura de un sensor. Esos flotantes se pueden representar como flotante federado de la siguiente manera:
Los tipos federados son especificados por un tipo de T
de los miembros que lo componen (p. ej., tf.float32
) y un grupo de dispositivos G
. Nos centraremos en aquellos casos en que G
es tff.CLIENTS
o tff.SERVER
. Un tipo federado como tal se representa con {T}@G
, como se muestra a continuación.
¿Por qué nos interesan tanto las ubicaciones? El objetivo clave de TFF es el de facilitar la escritura de código que se podría implementar en un sistema distribuido real. Significa que es vital razonar con respecto a qué subconjuntos de dispositivos ejecutan qué códigos y dónde residen las diferentes porciones de datos.
TFF se centra en tres cosas: en los datos, en dónde se ubican los datos y en cómo se transforman esos datos. Las primeras dos se encuentran encapsuladas dentro de los tipos federados, mientras que la última, en cálculos federados.
Cálculos federados
TFF es un entorno de programación funcional fuertemente tipado cuyas unidades básicas son cálculos federados. Son porciones de lógica que aceptan valores federados como entrada y devuelven valores federados como salida.
Por ejemplo, supongamos que quisiéramos calcular el promedio de temperaturas en los sensores de nuestro cliente. Podríamos definir lo siguiente (con nuestro flotante federado):
Uno podría preguntarse, en qué difiere esto del decorador tf.function
de TensorFlow. La respuesta determinante es que el código generado por tff.federated_computation
no es un código de TensorFlow ni de Python. Es una especificación de un sistema distribuido en un lenguaje pegamento interno independiente de plataformas.
Si bien es cierto que puede sonar complicado, se puede pensar en los cálculos TFF como funciones con firmas de tipo bien definidas. Estas firmas de tipo se pueden consultar directamente.
Este tff.federated_computation
acepta argumentos del tipo federado <float32>@CLIENTS
y devuelve valores del mismo tipo <float32>@SERVER
. Los cálculos federados también pueden ir de servidor a cliente, de cliente a cliente o de servidor a servidor. Los cálculos federados además se pueden componer como las funciones normales, siempre y cuando haya coincidencia entre las firmas de tipo.
Para facilitar el desarrollo, TFF permite invocar un tff.federated_computation
como una función Python. Por ejemplo, podemos llamar lo siguiente:
Los cálculos sin ejecución eager y con TensorFlow
Hay dos restricciones fundamentales para tener en cuenta. La primera, es que cuando un intérprete Python encuentra un decorador tff.federated_computation
, la función se rastrea una vez y se serializa para futuros usos. Por lo tanto, los cálculos TFF son fundamentalmente non-eager (no utilizan ejecución eager). Este comportamiento es, en cierto modo, análogo al del decorador tf.function
en TensorFlow.
La segunda, es que un cálculo federado solamente puede estar compuesto por operadores federados ( como tff.federated_mean
), no puede contener operaciones de TensorFlow. Hay que confinar el código de TensorFlow a bloques decorados con tff.tf_computation
. El código TensorFlow más común, directamente, se puede decorar, como la siguiente función que toma un número y le agrega 0.5
.
Estas también son firmas de tipo, pero sin ubicaciones. Por ejemplo, se puede llamar lo siguiente:
Observamos entonces, la gran diferencia que hay entre tff.federated_computation
y tff.tf_computation
. El primero tiene ubicaciones explícitas, mientras que el segundo no.
Podemos usar bloques tff.tf_computation
en cálculos federados para ubicaciones específicas. Creemos una función que agregue un medio (add half), pero solamente a flotantes federados de clientes. Podemos hacerlo con tff.federated_map
, que aplica un tff.tf_computation
dado y, a la vez, preserva la ubicación.
Esta función es casi idéntica a add_half
, excepto porque solamente acepta valores con ubicación en tff.CLIENTS
y devuelve valores con la misma ubicación. Podemos observarlo en su firma de tipo:
En resumen:
TFF opera sobre valores federados.
Cada valor federado tiene un tipo federado, con un tipo (p. ej.,
tf.float32
) y una ubicación (p. ej.,tff.CLIENTS
).Los valores federados se pueden transformar con cálculos federados, que se deben decorar con
tff.federated_computation
y una firma de tipo federado.El código TensorFlow debe estar contenido en bloques con decoradores
tff.tf_computation
.Estos bloques, después se pueden incorporar en cálculos federados.
Creación de los propios algoritmos de aprendizaje federado (parte 2)
Ahora que ya tenemos una idea de lo que es el núcleo federado, podemos crear un algoritmo propio de aprendizaje federado. Recordemos que antes (arriba) ya definimos un initialize_fn
y next_fn
para nuestro algoritmo. El next_fn
usará client_update
y server_update
que ya definimos con código de TensorFlow puro.
Sin embargo, para hacer nuestro algoritmo con un cálculo federado, necesitaremos que tanto next_fn
como initialize_fn
sean tff.federated_computation
.
Bloques de TensorFlow federado
Creación del cálculo de inicialización
La función de inicializar será bastante simple: deberemos crear un modelo con model_fn
. Sin embargo, recuerde que debemos separar nuestro código de TensorFlow con tff.tf_computation
.
Entonces, ahora, podemos pasarlo directamente a cálculo federado con tff.federated_value
.
Creación de next_fn
El código de actualización de cliente y servidor ahora se puede usar para escribir el algoritmo real. Primero, se transformará el client_update
en un tff.tf_computation
que acepta un conjunto de datos del cliente y los pesos del servidor, y sale un tensor de pesos del cliente actualizado.
Necesitaremos los tipos correspondientes que decoren adecuadamente nuestra función. Afortunadamente, el tipo de pesos del servidor se puede extraer directamente desde nuestro modelo.
Observemos la firma de tipo del conjunto de datos. Recordemos que tomamos imágenes de 28 por 28 (con etiquetas de enteros) y las aplanamos.
También podemos extraer el tipo de pesos del modelo con nuestra función server_init
, que figura más arriba.
Al examinar la firma de tipo, podrá ver la arquitectura del modelo.
Ahora podemos crear nuestro propio tff.tf_computation
para la actualización del cliente.
La versión tff.tf_computation
de la actualización del servidor se puede definir de un modo similar, con los tipos que ya hemos extraído.
Por último, pero no menos importante, deberemos crear el tff.federated_computation
que une todo. Esta función aceptará dos valores federados, uno correspondiente a los pesos del servidor (con la ubicación tff.SERVER
) y otro correspondiente a los conjuntos de datos del cliente (con la ubicación tff.CLIENTS
).
Tenga en cuenta que ambos tipos ya han sido definidos más arriba. Simplemente debemos darles la ubicación adecuada con tff.FederatedType
.
¿Recuerda los 4 elementos de un algoritmo de aprendizaje federado?
Un paso para la emisión (broadcast) del servidor al cliente.
Un paso para la actualización del cliente local.
Un paso para la carga del cliente al servidor.
Un paso para la actualización del servidor.
Ahora que hemos creado lo anterior, cada parte se puede representar de forma compacta como una sola línea de código TFF. Esta simplicidad es el motivo por el cual hemos debido prestar suma atención a la especificación de cosas como los tipos federados.
Ahora tenemos un tff.federated_computation
tanto para la inicialización del algoritmo como para la ejecución de un paso del algoritmo. Para terminarlo, pasaremos estos elementos a tff.templates.IterativeProcess
.
Observemos la *firma de tipo * de las funciones initialize
y next
de nuestro proceso iterativo.
Refleja el hecho de que federated_algorithm.initialize
es una función no argumentativa que devuelve un modelo de una sola capa (con una matriz de peso de 784 por 10, y 10 unidades de sesgo).
Aquí, podemos ver que federated_algorithm.next
acepta un modelo de servidor y datos del cliente, y devuelve un modelo de servidor actualizado.
Evaluación del algoritmo
Ejecutemos algunas rondas y veamos cómo cambia la pérdida. Primero, definiremos una función de evaluación con el modo centralizado referido en el segundo tutorial.
En primer lugar, creamos un conjunto de datos de evaluación centralizado y luego aplicamos el mismo preprocesamiento que usamos para los datos de entrenamiento.
Tenga en cuenta que solamente take
(tomamos) los primeros 1000 elementos, para eficiencia en los cálculos. Pero que normalmente usaríamos el conjunto completo de los datos de prueba.
A continuación, escribiremos una función que acepte un estado del servidor y usaremos Keras para evaluar el conjunto de datos de prueba. Si está familiarizado con tf.Keras
, todo esto le resultará conocido; de todos modos, preste particular atención al uso de set_weights
.
Ahora, inicialicemos nuestro algoritmo y evaluemos el conjunto de prueba.
Entrenemos durante algunas rondas y veamos si cambia algo.
Observamos una disminución leve en la función de pérdida. Si bien el salto es pequeño, solamente hemos realizado 15 rondas de entrenamiento y sobre un subconjunto reducido de clientes. Para ver mejores resultados, probablemente debamos hacer cientos o miles de rondas.
Modificación del algoritmo
En este punto, detengámonos a pensar sobre lo que hemos logrado. Hemos implementado el cálculo promedio federado directamente mediante la combinación de código de TensorFlow puro (para las actualizaciones del cliente y del servidor) con cálculos federados del núcleo federado de TFF.
Para realizar un aprendizaje más sofisticado, simplemente podemos alterar lo que hicimos arriba. En particular, editando el código de TF puro mencionado podemos cambiar la manera en que el cliente realiza el entrenamiento o cómo el servidor actualiza su modelo.
Desafío: agregar recorte (clipping) de gradiente a la función client_update
.