Parte 4: Aprendizaje Federado con Promedio de Modelos

Recap: En la Parte 2 de este tutorial entrenamos un modelo utilizando una versión muy simple del Aprendizaje Federado. Esto requirió que cada propietario de los datos confiara en el propietario del modelo para poder ver sus gradientes.

Descripción: En este tutorial mostraremos cómo usar los métodos avanzados de agregación de la Parte 3 para que los pesos sean agregados por un "trabajador seguro" confiable antes de que el modelo final sea enviado al propietario del modelo (nosotros).

De esta manera, sólo el trabajador seguro puede ver los gradientes de cada trabajador involucrado. Podríamos deducir qué partes del modelo cambiaron, pero NO sabríamos cuál trabajador (bob o alice) hizo este cambio, lo cual añade una capa de privacidad.

Autores:

Traductores:


In [ ]:
import torch
import syft as sy
import copy
hook = sy.TorchHook(torch)
from torch import nn, optim

Paso 1: Crear los Propietarios de los Datos

Primero, vamos a crear dos propietarios de datos (Bob y Alice) cada uno con una pequeña porción de los datos. También vamos a inicializar una máquina segura llamada "trabajador seguro" ("secure worker"). En la práctica esto podría ser un hardware seguro (como el SGX de Intel) o simplemente un intermediario confiable.


In [ ]:
# crear un par de trabajadores

bob = sy.VirtualWorker(hook, id="bob")
alice = sy.VirtualWorker(hook, id="alice")
secure_worker = sy.VirtualWorker(hook, id="secure_worker")


# Un conjunto de datos de juguete
data = torch.tensor([[0,0],[0,1],[1,0],[1,1.]], requires_grad=True)
target = torch.tensor([[0],[0],[1],[1.]], requires_grad=True)

# Obtén los punteros para realizar el entrenamiento en cada
# trabajador enviando una parte de los datos de entrenamiento
# a bob y alice.
bobs_data = data[0:2].send(bob)
bobs_target = target[0:2].send(bob)

alices_data = data[2:].send(alice)
alices_target = target[2:].send(alice)

Paso 2: Crear Nuestro Modelo

En este ejemplo entrenaremos un simple modelo lineal. Podemos inicializar el modelo de manera normal utilizando el constructor nn.Linear de PyTorch.


In [ ]:
# Inicializa el modelo de juguete
model = nn.Linear(2,1)

Paso 3: Enviar una Copia del Modelo a Alice y Bob

Ahora, necesitamos enviar una copia del modelo actual a Alice y Bob de tal manera que puedan realizar los pasos de entrenamiento en sus respectivos conjuntos de datos.


In [ ]:
bobs_model = model.copy().send(bob)
alices_model = model.copy().send(alice)

bobs_opt = optim.SGD(params=bobs_model.parameters(),lr=0.1)
alices_opt = optim.SGD(params=alices_model.parameters(),lr=0.1)

In [ ]:

Paso 4: Entrena los Modelos de Bob y Alice (en paralelo).

Como es la convención con Aprendizaje Federado via Agregación Segura, primero cada propietario de los datos entrena su modelo por varias iteraciones de manera local antes de que los modelos se promedien.


In [ ]:
for i in range(10):

    # Entrena el modelo de bob
    bobs_opt.zero_grad()
    bobs_pred = bobs_model(bobs_data)
    bobs_loss = ((bobs_pred - bobs_target)**2).sum()
    bobs_loss.backward()

    bobs_opt.step()
    bobs_loss = bobs_loss.get().data

    # Entrena el modelo de Alice
    alices_opt.zero_grad()
    alices_pred = alices_model(alices_data)
    alices_loss = ((alices_pred - alices_target)**2).sum()
    alices_loss.backward()

    alices_opt.step()
    alices_loss = alices_loss.get().data
    
    print("Bob:" + str(bobs_loss) + " Alice:" + str(alices_loss))

Paso 5: Envía Ambos Modelos Actualizados a un Trabajador Seguro

Ahora que cada propietario de los datos tiene un modelo parcialmente entrenado, es tiempo de promediarlos en una manera segura. Esto lo hacemos instruyendo a Alice y Bob para que manden sus modelos al servidor seguro (confiable).


In [ ]:
alices_model.move(secure_worker)

In [ ]:
bobs_model.move(secure_worker)

Paso 6: Promediar los Modelos

Finalmente, la última etapa de esta iteración (epoch) es promediar el modelo entrenado de Bob y Alice y usar esto para establecer los valores de nuestro modelo global.


In [ ]:
with torch.no_grad():
    model.weight.set_(((alices_model.weight.data + bobs_model.weight.data) / 2).get())
    model.bias.set_(((alices_model.bias.data + bobs_model.bias.data) / 2).get())

Enjuaga y Repite

¡Ahora sólo necesitamos iterar esto múltiples veces!


In [ ]:
iterations = 10
worker_iters = 5

for a_iter in range(iterations):
    
    bobs_model = model.copy().send(bob)
    alices_model = model.copy().send(alice)

    bobs_opt = optim.SGD(params=bobs_model.parameters(),lr=0.1)
    alices_opt = optim.SGD(params=alices_model.parameters(),lr=0.1)

    for wi in range(worker_iters):

        # Entrena el modelo de Bob
        bobs_opt.zero_grad()
        bobs_pred = bobs_model(bobs_data)
        bobs_loss = ((bobs_pred - bobs_target)**2).sum()
        bobs_loss.backward()

        bobs_opt.step()
        bobs_loss = bobs_loss.get().data

        # Entrena el modelo de Alice
        alices_opt.zero_grad()
        alices_pred = alices_model(alices_data)
        alices_loss = ((alices_pred - alices_target)**2).sum()
        alices_loss.backward()

        alices_opt.step()
        alices_loss = alices_loss.get().data
    
    alices_model.move(secure_worker)
    bobs_model.move(secure_worker)
    with torch.no_grad():
        model.weight.set_(((alices_model.weight.data + bobs_model.weight.data) / 2).get())
        model.bias.set_(((alices_model.bias.data + bobs_model.bias.data) / 2).get())
    
    print("Bob:" + str(bobs_loss) + " Alice:" + str(alices_loss))

Finalmente queremos asegurarnos que nuestro modelo final haya aprendido correctamente, así que lo evaluaremos en un conjunto de datos de prueba. En este ejemplo de juguete utilizaremos los datos originales, pero en la práctica queremos usar nuevos datos para entender bien cómo el modelo se generaliza a nuevos ejemplos.


In [ ]:
preds = model(data)
loss = ((preds - target) ** 2).sum()

In [ ]:
print(preds)
print(target)
print(loss.data)

En este ejemplo de juguete el modelo promediado se está subajustando con relación al comportamiento de un modelo con texto sin formato ("plaintext"). Sin embargo, pudimos entrenarlo sin exponer los datos de los trabajadores. También pudimos agregar los modelos actualizados de cada trabajador con un agregador confiable para prevenir la fuga de datos al propietario del modelo.

En un tutorial futuro intentaremos realizar nuestra agregación confiable directamente con los gradientes, de tal manera que podremos actualizar el modelo con mejores estimaciones de los gradientes y tener un mejor modelo.


In [ ]:

!Felicitaciones! - !Es hora de unirte a la comunidad!

¡Felicitaciones por completar esta parte del tutorial! Si te gustó y quieres unirte al movimiento para preservar la privacidad, propiedad descentralizada de IA y la cadena de suministro de IA (datos), puedes hacerlo de las ¡siguientes formas!

Dale una estrella a PySyft en GitHub

La forma más fácil de ayudar a nuestra comunidad es por darle estrellas a ¡los repositorios de Github! Esto ayuda a crear consciencia de las interesantes herramientas que estamos construyendo.

¡Únete a nuestro Slack!

La mejor manera de mantenerte actualizado con los últimos avances es ¡unirte a la comunidad! Tú lo puedes hacer llenando el formulario en http://slack.openmined.org

¡Únete a un proyecto de código!

La mejor manera de contribuir a nuestra comunidad es convertirte en un ¡contribuidor de código! En cualquier momento puedes ir al Github Issues de PySyft y filtrar por "Proyectos". Esto mostrará todos los tiquetes de nivel superior dando un resumen de los proyectos a los que ¡te puedes unir! Si no te quieres unir a un proyecto, pero quieres hacer un poco de código, también puedes mirar más mini-proyectos "de una persona" buscando por Github Issues con la etiqueta "good first issue".

Donar

Si no tienes tiempo para contribuir a nuestra base de código, pero quieres ofrecer tu ayuda, también puedes aportar a nuestro Open Collective". Todas las donaciones van a nuestro web hosting y otros gastos de nuestra comunidad como ¡hackathons y meetups!

OpenMined's Open Collective Page


In [ ]: