Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/es-419/federated/tutorials/random_noise_generation.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

Generación de ruido aleatorio en TFF

En este tutorial se analizan las prácticas recomendadas para la generación de ruido aleatorio en TFF. La generación de ruido aleatorio es un componente importante de muchas técnicas de protección de la privacidad en algoritmos de aprendizaje federados, por ejemplo, privacidad diferencial.

Antes de empezar

Primero, asegurémonos de que este notebook esté conectado a un servidor que tenga los componentes relevantes compilados.

#@test {"skip": true} !pip install --quiet --upgrade tensorflow-federated
import numpy as np import tensorflow as tf import tensorflow_federated as tff

Intente ejecutar el siguiente ejemplo de "Hola mundo" para asegurarse de que su entorno esté configurado correctamente. Si no funciona, consulte la guía Instalación para acceder a las instrucciones.

@tff.federated_computation def hello_world(): return 'Hello, World!' hello_world()
b'Hello, World!'

Ruido aleatorio en los clientes

La necesidad de ruido en los clientes generalmente se divide en dos casos: ruido idéntico y ruido i.i.d.

  • Para ruido idéntico, el patrón recomendado es mantener una semilla en el servidor, difundirla a los clientes y usar las funciones tf.random.stateless para generar ruido.

  • Para ruido i.i.d., use un tf.random.Generator inicializado en el cliente con from_non_deterministic_state, de acuerdo con la recomendación de TF de evitar las funciones tf.random.<distribution>.

El cliente se comporta de forma diferente al servidor (no sufre los inconvenientes que se analizan más adelante) porque cada cliente creará su propio gráfico de cálculo e inicializará su propia semilla predeterminada.

Ruido idéntico en los clientes

# Set to use 10 clients. tff.backends.native.set_sync_local_cpp_execution_context(default_num_clients=10) @tff.tf_computation def noise_from_seed(seed): return tf.random.stateless_normal((), seed=seed) seed_type_at_server = tff.type_at_server(tff.to_type((tf.int64, [2]))) @tff.federated_computation(seed_type_at_server) def get_random_min_and_max_deterministic(seed): # Broadcast seed to all clients. seed_on_clients = tff.federated_broadcast(seed) # Clients generate noise from seed deterministicly. noise_on_clients = tff.federated_map(noise_from_seed, seed_on_clients) # Aggregate and return the min and max of the values generated on clients. min = tff.aggregators.federated_min(noise_on_clients) max = tff.aggregators.federated_max(noise_on_clients) return min, max seed = tf.constant([1, 1], dtype=tf.int64) min, max = get_random_min_and_max_deterministic(seed) assert min == max print(f'Seed: {seed.numpy()}. All clients sampled value {min:8.3f}.') seed += 1 min, max = get_random_min_and_max_deterministic(seed) assert min == max print(f'Seed: {seed.numpy()}. All clients sampled value {min:8.3f}.')
Seed: [1 1]. All clients sampled value 1.665. Seed: [2 2]. All clients sampled value -0.219.

Ruido independiente en los clientes

@tff.tf_computation def nondeterministic_noise(): gen = tf.random.Generator.from_non_deterministic_state() return gen.normal(()) @tff.federated_computation def get_random_min_and_max_nondeterministic(): noise_on_clients = tff.federated_eval(nondeterministic_noise, tff.CLIENTS) min = tff.aggregators.federated_min(noise_on_clients) max = tff.aggregators.federated_max(noise_on_clients) return min, max min, max = get_random_min_and_max_nondeterministic() assert min != max print(f'Values differ across clients. {min:8.3f},{max:8.3f}.') new_min, new_max = get_random_min_and_max_nondeterministic() assert new_min != new_max assert new_min != min and new_max != max print(f'Values differ across rounds. {new_min:8.3f},{new_max:8.3f}.')
Values differ across clients. -1.490, 1.172. Values differ across rounds. -1.358, 1.208.

Inicializador del modelo en los clientes

def _keras_model(): inputs = tf.keras.Input(shape=(1,)) outputs = tf.keras.layers.Dense(1)(inputs) return tf.keras.Model(inputs=inputs, outputs=outputs) @tff.tf_computation def tff_return_model_init(): model = _keras_model() # return the initialized single weight value of the dense layer return tf.reshape( tff.learning.models.ModelWeights.from_model(model).trainable[0], [-1])[0] @tff.federated_computation def get_random_min_and_max_nondeterministic(): noise_on_clients = tff.federated_eval(tff_return_model_init, tff.CLIENTS) min = tff.aggregators.federated_min(noise_on_clients) max = tff.aggregators.federated_max(noise_on_clients) return min, max min, max = get_random_min_and_max_nondeterministic() assert min != max print(f'Values differ across clients. {min:8.3f},{max:8.3f}.') new_min, new_max = get_random_min_and_max_nondeterministic() assert new_min != new_max assert new_min != min and new_max != max print(f'Values differ across rounds. {new_min:8.3f},{new_max:8.3f}.')
Values differ across clients. -1.022, 1.567. Values differ across rounds. -1.675, 1.550.

Ruido aleatorio en el servidor

Uso desaconsejado: uso directo de tf.random.normal

TF1.x como las API tf.random.normal para la generación de ruido aleatorio se desaconsejan en TF2 de acuerdo con el tutorial de generación de ruido aleatorio en TF. Cuando estas API se usan junto con tf.function y tf.random.set_seed se puede generar un comportamiento sorprendente. Por ejemplo, el siguiente código generará el mismo valor con cada llamada. Este comportamiento sorprendente es esperado en TF y la explicación se puede encontrar en la documentación de tf.random.set_seed.

tf.random.set_seed(1) @tf.function def return_one_noise(_): return tf.random.normal([]) n1=return_one_noise(1) n2=return_one_noise(2) assert n1 == n2 print(n1.numpy(), n2.numpy())
0.3052047 0.3052047

En TFF, las cosas son ligeramente diferentes. Si ajustamos la generación de ruido como tff.tf_computation en lugar de como tf.function, se generará ruido aleatorio no determinista. Sin embargo, si ejecutamos este fragmento de código varias veces, cada vez se generará un conjunto diferente de (n1, n2). No existe una manera fácil de establecer una semilla aleatoria global para TFF.

tf.random.set_seed(1) @tff.tf_computation def return_one_noise(_): return tf.random.normal([]) n1=return_one_noise(1) n2=return_one_noise(2) assert n1 != n2 print(n1, n2)
0.11990704 1.9185987

Además, se puede generar ruido determinista en TFF sin que se establezca explícitamente una semilla. La función return_two_noise en el siguiente fragmento de código devuelve dos valores de ruido idénticos. Este es el comportamiento esperado porque TFF creará un gráfico de cálculo antes de la ejecución. Sin embargo, esto sugiere que los usuarios deben prestar atención al uso de tf.random.normal en TFF.

Uso prudente: tf.random.Generator

Podemos usar tf.random.Generator como se sugiere en el tutorial de TF.

@tff.tf_computation def tff_return_one_noise(i): g=tf.random.Generator.from_seed(i) @tf.function def tf_return_one_noise(): return g.normal([]) return tf_return_one_noise() @tff.federated_computation def return_two_noise(): return (tff_return_one_noise(1), tff_return_one_noise(2)) n1, n2 = return_two_noise() assert n1 != n2 print(n1, n2)
0.3052047 -0.38260335

Sin embargo, es posible que los usuarios deban tener cuidado con su uso.

  • tf.random.Generator usa tf.Variable para mantener los estados de los algoritmos RNG. En TFF, se recomienda construir el generador dentro de tff.tf_computation; y es difícil pasar el generador y su estado entre funciones tff.tf_computation.

  • El fragmento de código anterior también se basa en la cuidadosa colocación de semillas en los generadores. Es posible que obtengamos resultados esperados pero sorprendentes (n1==n2 deterministas) si en su lugar usamos tf.random.Generator.from_non_deterministic_state().

En general, TFF prefiere las operaciones funcionales y mostraremos el uso de las funciones tf.random.stateless_* en las siguientes secciones.

En TFF para el aprendizaje federado, a menudo trabajamos con estructuras anidadas en lugar de escalares y el fragmento de código anterior se puede extender sin problemas a estructuras anidadas.

@tff.tf_computation def tff_return_one_noise(i): g=tf.random.Generator.from_seed(i) weights = [ tf.ones([2, 2], dtype=tf.float32), tf.constant([2], dtype=tf.float32) ] @tf.function def tf_return_one_noise(): return tf.nest.map_structure(lambda x: g.normal(tf.shape(x)), weights) return tf_return_one_noise() @tff.federated_computation def return_two_noise(): return (tff_return_one_noise(1), tff_return_one_noise(2)) n1, n2 = return_two_noise() assert n1[1] != n2[1] print('n1', n1) print('n2', n2)
n1 [array([[0.3052047 , 0.5671378 ], [0.41852272, 0.2326421 ]], dtype=float32), array([1.1675092], dtype=float32)] n2 [array([[-0.38260335, -0.4780486 ], [-0.5187485 , -1.8471988 ]], dtype=float32), array([-0.77835274], dtype=float32)]

Uso recomendado: tf.random.stateless_* con un ayudante

En TFF se suele recomendar el uso de las funciones funcionales tf.random.stateless_* para la generación de ruido aleatorio. Estas funciones toman seed (un tensor con forma [2] o una tuple de dos tensores escalares) como argumento de entrada explícito para generar ruido aleatorio. Primero definimos una clase ayudante para mantener la semilla como pseudoestado. El ayudante RandomSeedGenerator tiene operadores funcionales en estado dentro y fuera. Es razonable utilizar un contador como pseudoestado para tf.random.stateless_* ya que estas funciones codifican la semilla antes de usarla para hacer que los ruidos generados por semillas correlacionadas no estén estadísticamente correlacionados.

def timestamp_seed(): # tf.timestamp returns microseconds as decimal places, thus scaling by 1e6. return tf.cast(tf.timestamp() * 1e6, tf.int64) class RandomSeedGenerator(): def initialize(self, seed=None): if seed is None: return tf.stack([timestamp_seed(), 0]) else: return tf.constant(self.seed, dtype=tf.int64, shape=(2,)) def next(self, state): return state + tf.constant([0, 1], tf.int64) def structure_next(self, state, nest_structure): "Returns seed in nested structure and the next state seed." flat_structure = tf.nest.flatten(nest_structure) flat_seeds = [state + tf.constant([0, i], tf.int64) for i in range(len(flat_structure))] nest_seeds = tf.nest.pack_sequence_as(nest_structure, flat_seeds) return nest_seeds, flat_seeds[-1] + tf.constant([0, 1], tf.int64)

Ahora usemos la clase ayudante y tf.random.stateless_normal para generar (estructura anidada de) ruido aleatorio en TFF. El siguiente fragmento de código se parece mucho a un proceso iterativo de TFF; consulte simple_fedavg como ejemplo de expresión del algoritmo de aprendizaje federado como un proceso iterativo de TFF. Aquí, el pseudoestado semilla para la generación de ruido aleatorio es tf.Tensor que se puede transportar fácilmente en funciones de TFF y de TF.

@tff.tf_computation def tff_return_one_noise(seed_state): g=RandomSeedGenerator() weights = [ tf.ones([2, 2], dtype=tf.float32), tf.constant([2], dtype=tf.float32) ] @tf.function def tf_return_one_noise(): nest_seeds, updated_state = g.structure_next(seed_state, weights) nest_noise = tf.nest.map_structure(lambda x,s: tf.random.stateless_normal( shape=tf.shape(x), seed=s), weights, nest_seeds) return nest_noise, updated_state return tf_return_one_noise() @tff.tf_computation def tff_init_state(): g=RandomSeedGenerator() return g.initialize() @tff.federated_computation def return_two_noise(): seed_state = tff_init_state() n1, seed_state = tff_return_one_noise(seed_state) n2, seed_state = tff_return_one_noise(seed_state) return (n1, n2) n1, n2 = return_two_noise() assert n1[1] != n2[1] print('n1', n1) print('n2', n2)
n1 [array([[ 0.86828816, 0.8535084 ], [ 1.0053564 , -0.42096713]], dtype=float32), array([0.18048067], dtype=float32)] n2 [array([[-1.1973879 , -0.2974589 ], [ 1.8309833 , 0.17024393]], dtype=float32), array([0.68991095], dtype=float32)]