Demo en la construcción de un modelo para la predicción de casos de dengue con el objetivo de preservar la privacidad de los datos utilizados.
# all_flag

Esta implementación trata de reproducir el paper Prediction of Dengue Cases in Paraguay Using Artificial Neural Networks simulando una situación donde el dataset está distribuido en diferentes entidades y por motivos de privacidad, los mismos no pueden ser compartidos entre entidades.

Motivación

Las tecnicas de machine learning pueden ayudar a mejorar el diagnostico de enfermedades, como detección de tumores en imagenes de MRI, detectar con tiempo retinopatía diabética en imagenes de retina, detección de cancer en imagenes de melanoma, hasta detectar el brotes de enfermedades entre varias otras aplicaciónes más. Pero este tipo de datos son bastante sensibles ya que son datos de los pacientes, una filtración de este tipo de información sería muy grave.

Pero no solo filtraciones, por culpa de varios escandalos respecto al uso de los datos sensibles de usuarios de parte de grandes empresas como Equifax, Facebook y Google generaron gran desconfianza en los mismos sobre como estos manipulan datos de sus usuarios. Un caso reciente es el uso de datos de pacientes para el Proyecto Nightingale de Google, el cual se encuentra bajo examinación por parte del gobierno estadounidense:

Mediante técnicas de preservación de privacidad como Federated Learning, Differential Privacy, Homomorphic Encryption entre otros es posible crear modelos útiles preservando la privacidad de los datos de los usuarios

Obs.: En el notebook de federated learning se dan más detalle sobre federated learning, homomorphic encryption y sus posibles aplicaciónes

import os
import sys
module_path = os.path.abspath(os.path.join('..'))
if module_path not in sys.path:
    sys.path.append(module_path)
    
sys.setrecursionlimit(15000)

Los modelos de este notebook se desarrollaran utilizando el framework desarrollado en los notebooks anteriores.

Se importan las clases necesarias para definir una red neuronal del framework lightdlf además de definirse un método de evaluación del modelo

import numpy as np
import pandas as pd
import copy
import phe

from lightdlf_old.cpu.core import Tensor
from lightdlf_old.cpu.layers import Linear, Relu, Sigmoid, Tanh, MSELoss, Sequential
from lightdlf_old.cpu.optimizers import SGD

np.random.seed(123)
def rmse (pred, y):
    se_sum = 0
    for i in range(len(pred)):
        se = (pred[i] - y[i]) * (pred[i] - y[i])
        se_sum += se
    
    mse = se_sum/len(pred)
    rmse = np.sqrt(mse)
    return rmse

Se carga el dataset con datos epidemiológicos y climatológicos

df = pd.read_csv('datasets/dengue/asu_dengue_dataset.csv')
df.head()
cantidad(-1) cantidad(-2) cantidad(-3) cantidad(-4) cantidad(-5) cantidad(-6) cantidad(-7) cantidad(-8) cantidad(-9) cantidad(-10) ... temperatura_min_media(-2) temperatura_min_media(-3) temperatura_min_media(-4) temperatura_min_media(-5) temperatura_min_media(-6) temperatura_min_media(-7) temperatura_min_media(-8) temperatura_min_media(-9) temperatura_min_media(-10) temperatura_min_media(-11)
0 47 50 19 15 12 8 11 0 3 2 ... 20.714 24.286 22.857 24.143 21.143 20.714 21.000 20.571 21.714 18.714
1 29 47 50 19 15 12 8 11 0 3 ... 19.429 20.714 24.286 22.857 24.143 21.143 20.714 21.000 20.571 21.714
2 27 29 47 50 19 15 12 8 11 0 ... 21.286 19.429 20.714 24.286 22.857 24.143 21.143 20.714 21.000 20.571
3 30 27 29 47 50 19 15 12 8 11 ... 21.143 21.286 19.429 20.714 24.286 22.857 24.143 21.143 20.714 21.000
4 60 30 27 29 47 50 19 15 12 8 ... 17.429 21.143 21.286 19.429 20.714 24.286 22.857 24.143 21.143 20.714

5 rows × 111 columns

# for column in df.columns:
#     print(column)

Se toman las columnas mencionadas en el paper para la creación del modelo

df_reduced = df[['cantidad',
                'cantidad(-1)',
                'temperatura_max_media(-1)',
                'temperatura_max_media(-2)',
                'temperatura_max_media(-3)',
                'temperatura_max_media(-4)',
                'temperatura_max_media(-5)',
                'temperatura_max_media(-6)',
                'temperatura_max_media(-7)',
                'temperatura_max_media(-8)',
                'temperatura_max_media(-9)',
                'temperatura_max_media(-10)',
                'temperatura_max_media(-11)',
                'lluvia_mm(-1)',
                'lluvia_mm(-2)',
                'humedad_min_media_porc(-1)',
                'humedad_min_media_porc(-2)',
                'humedad_min_media_porc(-3)',
                'humedad_min_media_porc(-4)',
                'humedad_min_media_porc(-5)',
                'humedad_min_media_porc(-6)',
                'humedad_min_media_porc(-7)',
                'humedad_min_media_porc(-8)',
                'humedad_min_media_porc(-9)',
                'humedad_min_media_porc(-10)',
                'humedad_min_media_porc(-11)']]
df_reduced.dtypes
cantidad                         int64
cantidad(-1)                     int64
temperatura_max_media(-1)      float64
temperatura_max_media(-2)      float64
temperatura_max_media(-3)      float64
temperatura_max_media(-4)      float64
temperatura_max_media(-5)      float64
temperatura_max_media(-6)      float64
temperatura_max_media(-7)      float64
temperatura_max_media(-8)      float64
temperatura_max_media(-9)      float64
temperatura_max_media(-10)     float64
temperatura_max_media(-11)     float64
lluvia_mm(-1)                  float64
lluvia_mm(-2)                  float64
humedad_min_media_porc(-1)     float64
humedad_min_media_porc(-2)     float64
humedad_min_media_porc(-3)     float64
humedad_min_media_porc(-4)     float64
humedad_min_media_porc(-5)     float64
humedad_min_media_porc(-6)     float64
humedad_min_media_porc(-7)     float64
humedad_min_media_porc(-8)     float64
humedad_min_media_porc(-9)     float64
humedad_min_media_porc(-10)    float64
humedad_min_media_porc(-11)    float64
dtype: object
df_reduced.head()
cantidad cantidad(-1) temperatura_max_media(-1) temperatura_max_media(-2) temperatura_max_media(-3) temperatura_max_media(-4) temperatura_max_media(-5) temperatura_max_media(-6) temperatura_max_media(-7) temperatura_max_media(-8) ... humedad_min_media_porc(-2) humedad_min_media_porc(-3) humedad_min_media_porc(-4) humedad_min_media_porc(-5) humedad_min_media_porc(-6) humedad_min_media_porc(-7) humedad_min_media_porc(-8) humedad_min_media_porc(-9) humedad_min_media_porc(-10) humedad_min_media_porc(-11)
0 29 47 33.286 31.857 36.000 31.857 34.429 31.143 33.000 33.857 ... 36.000 34.429 48.429 43.000 46.857 48.571 40.429 39.571 38.857 20.714
1 27 29 34.857 33.286 31.857 36.000 31.857 34.429 31.143 33.000 ... 31.857 36.000 34.429 48.429 43.000 46.857 48.571 40.429 39.571 38.857
2 30 27 35.571 34.857 33.286 31.857 36.000 31.857 34.429 31.143 ... 34.143 31.857 36.000 34.429 48.429 43.000 46.857 48.571 40.429 39.571
3 60 30 31.000 35.571 34.857 33.286 31.857 36.000 31.857 34.429 ... 33.286 34.143 31.857 36.000 34.429 48.429 43.000 46.857 48.571 40.429
4 79 60 34.429 31.000 35.571 34.857 33.286 31.857 36.000 31.857 ... 32.429 33.286 34.143 31.857 36.000 34.429 48.429 43.000 46.857 48.571

5 rows × 26 columns

df_reduced.describe()
cantidad cantidad(-1) temperatura_max_media(-1) temperatura_max_media(-2) temperatura_max_media(-3) temperatura_max_media(-4) temperatura_max_media(-5) temperatura_max_media(-6) temperatura_max_media(-7) temperatura_max_media(-8) ... humedad_min_media_porc(-2) humedad_min_media_porc(-3) humedad_min_media_porc(-4) humedad_min_media_porc(-5) humedad_min_media_porc(-6) humedad_min_media_porc(-7) humedad_min_media_porc(-8) humedad_min_media_porc(-9) humedad_min_media_porc(-10) humedad_min_media_porc(-11)
count 328.000000 328.000000 328.000000 328.000000 328.000000 328.000000 328.000000 328.000000 328.000000 328.000000 ... 328.000000 328.000000 328.000000 328.000000 328.000000 328.000000 328.000000 328.000000 328.00000 328.000000
mean 111.030488 110.945122 28.538134 28.558604 28.608692 28.623064 28.648326 28.675765 28.695366 28.727595 ... 42.243988 42.214372 42.209146 42.182143 42.117247 42.088500 42.038851 42.026655 41.99878 41.912979
std 247.165693 247.183010 4.833589 4.833383 4.825080 4.827720 4.836375 4.824871 4.829351 4.828414 ... 11.584123 11.591668 11.588468 11.576231 11.489590 11.461491 11.433179 11.433673 11.43021 11.484073
min 0.000000 0.000000 16.143000 16.143000 16.143000 16.143000 16.143000 16.143000 16.143000 16.143000 ... 15.429000 15.429000 15.429000 15.429000 15.429000 15.429000 15.429000 15.429000 15.42900 15.429000
25% 2.750000 2.750000 25.429000 25.429000 25.535500 25.535500 25.535500 25.571000 25.571000 25.678250 ... 34.107250 34.107250 34.107250 34.107250 34.107250 34.107250 34.107250 34.107250 34.10725 34.000000
50% 18.500000 18.500000 29.357500 29.429000 29.500000 29.571000 29.571000 29.571000 29.571000 29.571000 ... 42.214500 42.071500 42.071500 42.071500 42.071500 42.071500 41.928500 41.857000 41.78550 41.714000
75% 76.500000 76.500000 32.429000 32.429000 32.429000 32.429000 32.429000 32.429000 32.429000 32.464500 ... 48.857000 48.857000 48.749750 48.606750 48.464500 48.464500 48.321750 48.321750 48.32175 48.286000
max 1691.000000 1691.000000 39.714000 39.714000 39.714000 39.714000 39.714000 39.714000 39.714000 39.714000 ... 78.857000 78.857000 78.857000 78.857000 78.857000 78.857000 78.857000 78.857000 78.85700 78.857000

8 rows × 26 columns

max_values = df_reduced.max()
min_values = df_reduced.min()

# Normalización del dataset
df_normalizado = (df_reduced - df_reduced.min())/(df_reduced.max() - df_reduced.min())
df_normalizado.head()
cantidad cantidad(-1) temperatura_max_media(-1) temperatura_max_media(-2) temperatura_max_media(-3) temperatura_max_media(-4) temperatura_max_media(-5) temperatura_max_media(-6) temperatura_max_media(-7) temperatura_max_media(-8) ... humedad_min_media_porc(-2) humedad_min_media_porc(-3) humedad_min_media_porc(-4) humedad_min_media_porc(-5) humedad_min_media_porc(-6) humedad_min_media_porc(-7) humedad_min_media_porc(-8) humedad_min_media_porc(-9) humedad_min_media_porc(-10) humedad_min_media_porc(-11)
0 0.017150 0.027794 0.727292 0.666667 0.842433 0.666667 0.775784 0.636375 0.715158 0.751517 ... 0.324320 0.299552 0.520275 0.434682 0.495491 0.522514 0.394148 0.380621 0.369364 0.083323
1 0.015967 0.017150 0.793942 0.727292 0.666667 0.842433 0.666667 0.775784 0.636375 0.715158 ... 0.259002 0.324320 0.299552 0.520275 0.434682 0.495491 0.522514 0.394148 0.380621 0.369364
2 0.017741 0.015967 0.824233 0.793942 0.727292 0.666667 0.842433 0.666667 0.775784 0.636375 ... 0.295043 0.259002 0.324320 0.299552 0.520275 0.434682 0.495491 0.522514 0.394148 0.380621
3 0.035482 0.017741 0.630308 0.824233 0.793942 0.727292 0.666667 0.842433 0.666667 0.775784 ... 0.281532 0.295043 0.259002 0.324320 0.299552 0.520275 0.434682 0.495491 0.522514 0.394148
4 0.046718 0.035482 0.775784 0.630308 0.824233 0.793942 0.727292 0.666667 0.842433 0.666667 ... 0.268020 0.281532 0.295043 0.259002 0.324320 0.299552 0.520275 0.434682 0.495491 0.522514

5 rows × 26 columns

Definición del conjunto de entrenamiento y de prueba

Y = df_normalizado[['cantidad']].to_numpy()
X = df_normalizado.drop(['cantidad'], axis=1).to_numpy()
Y[0], X[0]
(array([0.01714962]),
 array([0.0277942 , 0.72729201, 0.66666667, 0.8424335 , 0.66666667,
        0.7757838 , 0.63637521, 0.71515846, 0.75151669, 0.64244198,
        0.76971703, 0.72729201, 0.        , 0.11750881, 0.25900233,
        0.32432049, 0.29955225, 0.52027496, 0.43468184, 0.49549095,
        0.52251372, 0.3941477 , 0.38062055, 0.36936369, 0.08332282]))
len(X[0])
25
bunch_size = int(len(Y)/4)
bunch_size
82
x_train = X[0:len(Y)-bunch_size]
x_test = X[-bunch_size:]

y_train = Y[0:len(Y)-bunch_size]
y_test = Y[-bunch_size:]
len(y_train), len(y_test)
(246, 82)

Definicion y Entrenamiento del Modelo

A modo de prueba se entrena y evalua un modelo centralizado

np.random.seed(0)

data = Tensor(x_train, autograd=True)
target = Tensor(y_train, autograd=True)

# model = Sequential([Linear(25,4), Relu(), Linear(4,3), Relu(), Linear(3,1), Sigmoid()])
# model = Sequential([Linear(25,4), Sigmoid(), Linear(4,5), Sigmoid(), Linear(5,1), Sigmoid()])
# model = Sequential([Linear(25,4), Relu(), Linear(4,6), Relu(), Linear(6,1), Sigmoid()])
# model = Sequential([Linear(25,4), Sigmoid(), Linear(4,6), Sigmoid(), Linear(6,1), Sigmoid()])
model = Sequential([Linear(25,4), Tanh(), Linear(4,6), Tanh(), Linear(6,1), Sigmoid()])
criterion = MSELoss()
# optim = SGD(parameters=model.get_parameters(), alpha=0.01)
optim = SGD(parameters=model.get_parameters(), alpha=0.01)

# 500
for i in range(500):
    # Predecir
    pred = model.forward(data)
    
    # Comparar
    loss = criterion.forward(pred, target)
    
    # Aprender
    loss.backward(Tensor(np.ones_like(loss.data)))
    optim.step()
    if (i%100 == 0):
        print(loss)
[66.59855077]
[1.0714176]
[0.91269979]
[0.8268145]
[0.79791048]
test_data = Tensor(x_test)
test_target = Tensor(y_test)
pred = model.forward(test_data)

pred_list = [x[0] for x in pred.data]
test_target_list = [x[0] for x in test_target.data]
comparison = pd.DataFrame({'actual':test_target_list, 'predicted':pred_list})
comparison.head()
actual predicted
0 0.014193 0.021984
1 0.010645 0.017447
2 0.011827 0.020254
3 0.062093 0.017199
4 0.021881 0.031740
denormalized_pred_list = [(x[0] * (max_values['cantidad'] - min_values['cantidad'])) + min_values['cantidad'] for x in pred.data]
denormalized_test_target_list = [(x[0] * (max_values['cantidad'] - min_values['cantidad'])) + min_values['cantidad'] for x in test_target.data]
denormalized_comparison = pd.DataFrame({'actual':denormalized_test_target_list, 'predicted':denormalized_pred_list})
denormalized_comparison.head()
actual predicted
0 24.0 37.174131
1 18.0 29.503143
2 20.0 34.249939
3 105.0 29.084327
4 37.0 53.671524
print('RMSE:',rmse(pred_list, test_target_list))
RMSE: 0.022350545248195304

Modelo de Aprendizaje Federado con Cifrado Homomorfico

Como se dijo al inicio, vamos hacer las siguientes suposiciones sobre nuestro dataset:

  • Se encuentra distribuido entre 3 instituciones
  • Contienen datos confidenciales que no pueden ser compartidos entre sí ni a un tercero

Con estas condiciónes, el siguiente codigo pretende mostrar como podemos obtener un modelo capaz de predecir casos de dengue sin la necesidad de que el dueño del modelo o (model owner) tenga que acceder directamente a los datos de las instituciones (data owners)

Definición de la arquitectura y del metodo de entrenamiento del modelo

np.random.seed(0)

data = Tensor(x_train, autograd=True)
target = Tensor(y_train, autograd=True)

layers = [Linear(25,4), Relu(), Linear(4,3), Relu(), Linear(3,1), Sigmoid()]
model = Sequential(layers)

def train(model, data, target, iterations=5, alpha=0.01, print_loss=True):
    criterion = MSELoss()
    optim = SGD(parameters=model.get_parameters(), alpha=alpha)

    for i in range(iterations):
        # Predecir
        pred = model.forward(data)

        # Comparar
        loss = criterion.forward(pred, target)

        # Aprender
        loss.backward(Tensor(np.ones_like(loss.data)))
        optim.step()
        if (i%100 == 0 and print_loss):
            sys.stdout.write("\r\tLoss:" + str(loss))
    return model

Definicion de funciones auxiliares para manipular modelos encryptados

def encrypt_tensor(matrix, pubkey):
    encrypt_weights = list()
    for vector in matrix:
        # print(vector)
        for val in vector:
            # print(val)
            encrypt_weights.append(pubkey.encrypt(val))
    restore = np.array(encrypt_weights).reshape(matrix.shape)
    # print(restore)
    return restore

def decrypt_tensor(matrix, privkey):
    decrypted_weights = list()
    for vector in matrix:
        # print(vector)
        for val in vector.flatten():
            # print(val)
            decrypted_weights.append(privkey.decrypt(val))
    restore = np.array(decrypted_weights).reshape(matrix.shape)
    # print(restore)
    return restore
    
def encrypt_sequential_model(model, pubkey):
    for layer in model.layers:
        if type(layer) == Linear:
            layer.weight.data = encrypt_tensor(layer.weight.data, pubkey)
    return model

def decrypt_sequential_model(model, n_models, privkey):
    for layer in model.layers:
        if type(layer) == Linear:
            layer.weight.data = decrypt_tensor(layer.weight.data, privkey)/n_models
    return model

def zero_sequential_model(model):
    for layer in model.layers:
        if type(layer) == Linear:
            layer.weight.data = np.zeros_like(layer.weight.data)
    return model

def aggregate_models(list_of_models):
    aggregated_model = zero_sequential_model(copy.deepcopy(list_of_models[0]))
    # print(list_of_models)
    for model in list_of_models:
        # print(model)
        for i in range(len(model.layers)):
            if type(model.layers[i]) == Linear:
                aggregated_model.layers[i].weight.data += model.layers[i].weight.data
            
    return aggregated_model

def train_and_encrypt(model, data, target, pubkey, iterations=50, alpha=0.01, print_loss=True):
    new_model = train(copy.deepcopy(model), data, target, iterations, print_loss=print_loss)
    encrypted_model = encrypt_sequential_model(new_model, pubkey)
    return encrypted_model

Prueba de la funcion de entrenamiento

new = train(model, data, target, iterations=500)
	Loss:[0.8324166]

Pruebas de creación de un modelo encriptado

public_key, private_key = phe.generate_paillier_keypair(n_length=128)
np.random.seed(0)

data = Tensor(x_train, autograd=True)
target = Tensor(y_train, autograd=True)

layers = [Linear(25,4), Relu(), Linear(4,3), Relu(), Linear(3,1), Sigmoid()]
model = Sequential(layers)

for i in range(9):
    model = train_and_encrypt(model, data, target, public_key)
    model = aggregate_models([model])
    model = decrypt_sequential_model(model, 1, private_key)
	Loss:[0.8324166]
pred = model.forward(test_data)

pred_list = [x[0] for x in pred.data]
test_target_list = [x[0] for x in test_target.data]

print('RMSE:',rmse(pred_list, test_target_list))
RMSE: 0.023673897541489297

Distrubución del dataset en las diferentes Instituciones

Inicializamos las entidades distribuyendo el dataset entre las tres y definimos un perceptron multicapa para realizar una regresión (predecir el numero de casos futuros)

np.random.seed(0)
rangos = list()
for i in range(4):
    rangos.append(int((len(x_train)/3)*i))
# print(rangos)

data_entidad_01 = Tensor(x_train[rangos[0]:rangos[1]], autograd=True)
target_entidad_01 = Tensor(y_train[rangos[0]:rangos[1]], autograd=True)

data_entidad_02 = Tensor(x_train[rangos[1]:rangos[2]], autograd=True)
target_entidad_02 = Tensor(y_train[rangos[1]:rangos[2]], autograd=True)

data_entidad_03 = Tensor(x_train[rangos[2]:rangos[3]], autograd=True)
target_entidad_03 = Tensor(y_train[rangos[2]:rangos[3]], autograd=True)

layers = [Linear(25,4), Relu(), Linear(4,3), Relu(), Linear(3,1), Sigmoid()]
model = Sequential(layers)
# print(len(data_entidad_01.data))
# print(len(data_entidad_02.data))
# print(len(data_entidad_03.data))

federated learning diagram

np.random.seed(0)

layers = [Linear(25,4), Relu(), Linear(4,3), Relu(), Linear(3,1), Sigmoid()]
# layers = [Linear(25,4), Relu(), Linear(4,4), Relu(), Linear(4,1), Sigmoid()]
# layers = [Linear(25,4), Relu(), Linear(4,5), Relu(), Linear(5,1), Sigmoid()]
# layers = [Linear(25,4), Tanh(), Linear(4,3), Tanh(), Linear(3,1), Sigmoid()]
# layers = [Linear(25,4), Relu(), Linear(4,1), Sigmoid()]
# model = Sequential([Linear(25,4), Tanh(), Linear(4,6), Tanh(), Linear(6,1), Sigmoid()])

model = Sequential(layers)

for i in range(9):
    print('\nIniciando la ronda de entrenamiento Nro:', i+1)
    print('\tPaso 1: enviamos el modelo a Institucion 01')
    entidad_01_encrypted_model = train_and_encrypt(model,
                                                   data_entidad_01, 
                                                   target_entidad_01, 
                                                   public_key, iterations=50, alpha=0.007)
    
    print('\n\tPaso 2: enviamos el modelo a Institucion 02')
    entidad_02_encrypted_model = train_and_encrypt(model,
                                                   data_entidad_02, 
                                                   target_entidad_02, 
                                                   public_key, iterations=50, alpha=0.007)
    
    print('\n\tPaso 3: enviamos el modelo a Institucion 03')
    entidad_03_encrypted_model = train_and_encrypt(model,
                                                   data_entidad_03, 
                                                   target_entidad_03, 
                                                   public_key, iterations=50, alpha=0.007)
    
    print('\n\tPaso 4: Institucion 01, Institucion 02 y Institucion 03 envian')
    print('\ty agregan sus modelos encriptados ente sí')
    models_list = [entidad_01_encrypted_model, 
                   entidad_02_encrypted_model, 
                   entidad_03_encrypted_model]    
    encrypted_model = aggregate_models(models_list)
    
    print('\n\tPaso 5: Solo el modelo agregado')
    print('\tse envia devuelta al dueño del modelo')
    print('\tque puede desencriptarlo')
    model = decrypt_sequential_model(encrypted_model, len(models_list), private_key)
Iniciando la ronda de entrenamiento Nro: 1
	Paso 1: enviamos el modelo a Institucion 01
	Loss:[19.47686279]
	Paso 2: enviamos el modelo a Institucion 02
	Loss:[16.14403127]
	Paso 3: enviamos el modelo a Institucion 03
	Loss:[13.94539182]
	Paso 4: Institucion 01, Institucion 02 y Institucion 03 envian
	y agregan sus modelos encriptados ente sí

	Paso 5: Solo el modelo agregado
	se envia devuelta al dueño del modelo
	que puede desencriptarlo

Iniciando la ronda de entrenamiento Nro: 2
	Paso 1: enviamos el modelo a Institucion 01
	Loss:[0.10396935]
	Paso 2: enviamos el modelo a Institucion 02
	Loss:[1.33104586]
	Paso 3: enviamos el modelo a Institucion 03
	Loss:[5.38636908]
	Paso 4: Institucion 01, Institucion 02 y Institucion 03 envian
	y agregan sus modelos encriptados ente sí

	Paso 5: Solo el modelo agregado
	se envia devuelta al dueño del modelo
	que puede desencriptarlo

Iniciando la ronda de entrenamiento Nro: 3
	Paso 1: enviamos el modelo a Institucion 01
	Loss:[0.09768313]
	Paso 2: enviamos el modelo a Institucion 02
	Loss:[1.14368389]
	Paso 3: enviamos el modelo a Institucion 03
	Loss:[3.29253546]
	Paso 4: Institucion 01, Institucion 02 y Institucion 03 envian
	y agregan sus modelos encriptados ente sí

	Paso 5: Solo el modelo agregado
	se envia devuelta al dueño del modelo
	que puede desencriptarlo

Iniciando la ronda de entrenamiento Nro: 4
	Paso 1: enviamos el modelo a Institucion 01
	Loss:[0.1188239]
	Paso 2: enviamos el modelo a Institucion 02
	Loss:[0.81915177]
	Paso 3: enviamos el modelo a Institucion 03
	Loss:[2.09582635]
	Paso 4: Institucion 01, Institucion 02 y Institucion 03 envian
	y agregan sus modelos encriptados ente sí

	Paso 5: Solo el modelo agregado
	se envia devuelta al dueño del modelo
	que puede desencriptarlo

Iniciando la ronda de entrenamiento Nro: 5
	Paso 1: enviamos el modelo a Institucion 01
	Loss:[0.10003674]
	Paso 2: enviamos el modelo a Institucion 02
	Loss:[0.62974628]
	Paso 3: enviamos el modelo a Institucion 03
	Loss:[1.95877656]
	Paso 4: Institucion 01, Institucion 02 y Institucion 03 envian
	y agregan sus modelos encriptados ente sí

	Paso 5: Solo el modelo agregado
	se envia devuelta al dueño del modelo
	que puede desencriptarlo

Iniciando la ronda de entrenamiento Nro: 6
	Paso 1: enviamos el modelo a Institucion 01
	Loss:[0.08355167]
	Paso 2: enviamos el modelo a Institucion 02
	Loss:[0.5142198]
	Paso 3: enviamos el modelo a Institucion 03
	Loss:[1.91234242]
	Paso 4: Institucion 01, Institucion 02 y Institucion 03 envian
	y agregan sus modelos encriptados ente sí

	Paso 5: Solo el modelo agregado
	se envia devuelta al dueño del modelo
	que puede desencriptarlo

Iniciando la ronda de entrenamiento Nro: 7
	Paso 1: enviamos el modelo a Institucion 01
	Loss:[0.06705228]
	Paso 2: enviamos el modelo a Institucion 02
	Loss:[0.44322799]
	Paso 3: enviamos el modelo a Institucion 03
	Loss:[1.89337013]
	Paso 4: Institucion 01, Institucion 02 y Institucion 03 envian
	y agregan sus modelos encriptados ente sí

	Paso 5: Solo el modelo agregado
	se envia devuelta al dueño del modelo
	que puede desencriptarlo

Iniciando la ronda de entrenamiento Nro: 8
	Paso 1: enviamos el modelo a Institucion 01
	Loss:[0.05572313]
	Paso 2: enviamos el modelo a Institucion 02
	Loss:[0.40201215]
	Paso 3: enviamos el modelo a Institucion 03
	Loss:[1.87859799]
	Paso 4: Institucion 01, Institucion 02 y Institucion 03 envian
	y agregan sus modelos encriptados ente sí

	Paso 5: Solo el modelo agregado
	se envia devuelta al dueño del modelo
	que puede desencriptarlo

Iniciando la ronda de entrenamiento Nro: 9
	Paso 1: enviamos el modelo a Institucion 01
	Loss:[0.04391783]
	Paso 2: enviamos el modelo a Institucion 02
	Loss:[0.38376874]
	Paso 3: enviamos el modelo a Institucion 03
	Loss:[1.88579166]
	Paso 4: Institucion 01, Institucion 02 y Institucion 03 envian
	y agregan sus modelos encriptados ente sí

	Paso 5: Solo el modelo agregado
	se envia devuelta al dueño del modelo
	que puede desencriptarlo
pred = model.forward(test_data)

pred_list = [x[0] for x in pred.data]
test_target_list = [x[0] for x in test_target.data]

print('RMSE:',rmse(pred_list, test_target_list))
RMSE: 0.026521617670045593
comparison = pd.DataFrame({'actual':test_target_list, 'predicted':pred_list})
comparison.head()
actual predicted
0 0.014193 0.017758
1 0.010645 0.013831
2 0.011827 0.032553
3 0.062093 0.032866
4 0.021881 0.027633

Como se puede ver en los resultados, el modelo obtenido es casi tan bueno que el modelo entrenado de forma centralizada.

Notas Finales

Si bien los resultados no logran ser tan buenos como los resultados obtenidos en el paper, se logra demostrar que es posible entrenar un modelo con un performance relativamente bueno sin necesidad de acceder directamente a los datos. Con una mejora en la busqueda de los hiperparametros se podría mejorar aún más el performance del modelo.

Prueba para encontrar un mejor modelo usando Grid Search

np.random.seed(0)

alphas = [0.001, 
          0.003, 
          0.005, 
          0.007, 
          0.01, 
          0.01, 
          0.03, 
          0.05]

architectures = [[Linear(25,1), Sigmoid()],
                 [Linear(25,4), Sigmoid(), Linear(4,3), Sigmoid(), Linear(3,1), Sigmoid()], 
                 [Linear(25,4), Tanh(), Linear(4,3), Tanh(), Linear(3,1), Sigmoid()],
                 [Linear(25,4), Relu(), Linear(4,3), Relu(), Linear(3,1), Sigmoid()],
                 
                 [Linear(25,4), Sigmoid(), Linear(4,5), Sigmoid(), Linear(5,1), Sigmoid()],
                 [Linear(25,4), Tanh(), Linear(4,5), Tanh(), Linear(5,1), Sigmoid()],
                 [Linear(25,4), Relu(), Linear(4,5), Relu(), Linear(5,1), Sigmoid()], 
                 
                 [Linear(25,5), Sigmoid(), Linear(5,6), Sigmoid(), Linear(6,1), Sigmoid()],
                 [Linear(25,5), Tanh(), Linear(5,6), Tanh(), Linear(6,1), Sigmoid()],
                 [Linear(25,5), Relu(), Linear(5,6), Relu(), Linear(6,1), Sigmoid()]]
best_model = {}
actual_rmse = 100.0
for architecture in architectures:
    for alpha in alphas:   
        model = Sequential(copy.deepcopy(architecture))
        for i in range(10):
            entidad_01_encrypted_model = train_and_encrypt(model,
                                                           data_entidad_01, 
                                                           target_entidad_01, 
                                                           public_key, iterations=50, alpha=alpha)

            # print('\n\tPaso 2: enviamos el modelo a Institucion 02')
            entidad_02_encrypted_model = train_and_encrypt(model,
                                                           data_entidad_02, 
                                                           target_entidad_02, 
                                                           public_key, iterations=25, alpha=alpha)

            # print('\n\tPaso 3: enviamos el modelo a Institucion 03')
            entidad_03_encrypted_model = train_and_encrypt(model,
                                                           data_entidad_03, 
                                                           target_entidad_03, 
                                                           public_key, iterations=25, alpha=alpha)

            # print('\n\tPaso 4: Institucion 01, Institucion 02 y Institucion 03 envian')
            # print('\ty agregan sus modelos encriptados ente sí')
            models_list = [entidad_01_encrypted_model, 
                           entidad_02_encrypted_model, 
                           entidad_03_encrypted_model]    
            encrypted_model = aggregate_models(models_list)

            # print('\n\tPaso 5: Solo el modelo agregado')
            # print('\tse envia devuelta al dueño del modelo')
            # print('\tque puede desencriptarlo')
            model = decrypt_sequential_model(encrypted_model, len(models_list), private_key)
        
        pred = model.forward(test_data)

        pred_list = [x[0] for x in pred.data]
        # test_target_list = [x[0] for x in test_target.data]
        new_rmse = rmse(pred_list, test_target_list)
        if (new_rmse < actual_rmse):
            print('\tNuevo mejor RMSE:',new_rmse)
            actual_rmse = new_rmse
            best_model['model'] = model
            best_model['architecture'] = architecture
            best_model['alpha'] = alpha
            best_model['rmse'] = actual_rmse
	Loss:[3.61176773]	Nuevo mejor RMSE: 0.05850939720417443
	Loss:[5.68172953]	Nuevo mejor RMSE: 0.033539178763380834
	Loss:[2.54063782]	Nuevo mejor RMSE: 0.027510551234677393
	Loss:[1.31393351]
print(best_model['rmse'])
print(best_model['alpha'])
print(best_model['architecture'])
0.027510551234677393
0.001
[<lightdlf.cpu.layers.Linear object at 0x122fee278>, <lightdlf.cpu.layers.Relu object at 0x122fee748>, <lightdlf.cpu.layers.Linear object at 0x122fee898>, <lightdlf.cpu.layers.Relu object at 0x122feeda0>, <lightdlf.cpu.layers.Linear object at 0x122feef60>, <lightdlf.cpu.layers.Sigmoid object at 0x12285acc0>]

Apartado de pruebas de las funciones de Cifrado Homomorfico (Homomorphic Encryption)

Pruebas relizadas para las metodos de encriptado, agregación y desencriptado de un modelo

aux = Sequential([Linear(3,2)])
aux.layers[0].weight.data
array([[-0.14882243,  0.12089122],
       [-0.66242815, -0.76576489],
       [ 0.51999856,  0.51318502]])
encripted_tensor = encrypt_tensor(aux.layers[0].weight.data, pubkey=public_key)
decrypt_tensor(encripted_tensor, privkey=private_key)
array([[-0.14882243,  0.12089122],
       [-0.66242815, -0.76576489],
       [ 0.51999856,  0.51318502]])
seq_aux = Sequential([Linear(2,3), Linear(3,2)])
print(seq_aux.layers[0].weight.data)
print()
encrypted_model = encrypt_sequential_model(seq_aux, pubkey=public_key)
print(encrypted_model.layers[0].weight.data)
print()
decrypted_model = decrypt_sequential_model(encrypted_model, n_models=1, privkey=private_key)
print(decrypted_model.layers[0].weight.data)
[[-0.45233024  1.52973627  0.71918349]
 [-0.76205292  0.76146533 -0.99324104]]

[[<phe.paillier.EncryptedNumber object at 0x11a77cba8>
  <phe.paillier.EncryptedNumber object at 0x11a77c860>
  <phe.paillier.EncryptedNumber object at 0x121448be0>]
 [<phe.paillier.EncryptedNumber object at 0x1217e2898>
  <phe.paillier.EncryptedNumber object at 0x11a77c668>
  <phe.paillier.EncryptedNumber object at 0x1218e1c18>]]

[[-0.45233024  1.52973627  0.71918349]
 [-0.76205292  0.76146533 -0.99324104]]
zero_seq = zero_sequential_model(seq_aux)
print(zero_seq.layers[1].weight)
[[0. 0.]
 [0. 0.]
 [0. 0.]]
new_model = aggregate_models([aux, aux])
print(aux.layers[0].weight.data)
print(new_model.layers[0].weight.data)
[[-0.14882243  0.12089122]
 [-0.66242815 -0.76576489]
 [ 0.51999856  0.51318502]]
[[-0.29764486  0.24178245]
 [-1.3248563  -1.53152978]
 [ 1.03999713  1.02637003]]