Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/pt-br/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.

Geração de ruído aleatório no TFF

Este tutorial discute as práticas recomendadas para geração de ruído aleatório no TFF. A geração de ruído aleatório é um componente importante de diversas técnicas de proteção de privacidade em algoritmos de aprendizado federado, como a privacidade diferencial.

Antes de começarmos

Primeiro, vamos garantir que o notebook esteja conectado a um back-end que tenha os 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

Execute o exemplo "Olá, mundo" abaixo para garantir que o ambiente do TFF esteja configurado corretamente. Se não funcionar, consulte as instruções no guia de instalação.

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

Ruído aleatório nos clientes

Em geral, há dois casos de necessidade de ruído em clientes: ruído idêntico e ruído independente e identicamente distribuído.

  • Para ruído idêntico, o padrão recomendado é manter uma semente no servidor, enviá-la aos clientes e usar funções tf.random.stateless para gerar ruído.

  • Para ruído independente e identicamente distribuído, use um tf.random.Generator iniciado no cliente com from_non_deterministic_state, de acordo com a recomendação do TF para evitar as funções de tf.random.

O comportamento do cliente é diferente do comportamento do servidor (ele não sofre das desvantagens discutidas mais adiante), pois cada cliente construirá seu próprio grafo de computações e inicializará sua própria semente padrão.

Ruído idêntico nos 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.

Ruído independente nos 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 do modelo nos 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.

Ruído aleatório no servidor

Não é recomendado usar diretamente tf.random.normal

As APIs do TF1.x tf.random.normal para geração de ruído aleatório são fortemente desencorajadas no TF2 de acordo com o tutorial de geração de ruído aleatório no TF. Pode ocorrer um comportamento surpreendente quando essas APIs são usadas em conjunto com tf.function e tf.random.set_seed. Por exemplo: o código abaixo gerará o mesmo valor em cada chamada. Esse comportamento surpreendente é esperado pelo TF, e a explicação está disponível na documentação 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

No TFF, as coisas são um pouco diferentes. Se encapsularmos a geração de ruído como tff.tf_computation em vez de tf.function, será gerado ruído aleatório não determinístico. Porém, se executarmos esse trecho de código diversas vezes, será gerado um conjunto diferente de (n1, n2) a cada vez. Não existe uma maneira fácil de definir uma semente aleatória global para o 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

Além disso, é possível gerar ruído determinístico no TFF sem definir explicitamente uma semente. A função return_two_noise no trecho de código abaixo retorna dois valores de ruído idênticos. Esse é o comportamento esperado, pois o TFF construirá o grafo de computações antecipadamente, antes da execução. Porém, isso indica que os usuários precisam prestar atenção ao uso de tf.random.normal no TFF.

Use com cuidado: tf.random.Generator

Podemos usar tf.random.Generator conforme sugerido no tutorial do 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

Entretanto, os usuários precisam ter cuidado com o uso.

  • tf.random.Generator usa tf.Variable para manter os estados dos algoritmos de RNG. No TFF, é recomendável construir o gerador dentro de uma tff.tf_computation, e é difícil passar o gerador e seu estado entre funções de tff.tf_computation.

  • O trecho de código anterior também depende da definição cuidadosa de sementes nos geradores. Podemos obter resultados esperados, mas surpreendentes (n1==n2 determinístico) se usarmos tf.random.Generator.from_non_deterministic_state() no lugar.

De forma geral, o TFF prefere operações funcionais, e demonstraremos o uso de funções tf.random.stateless_* nas próximas seções.

No TFF para aprendizado federado, costumamos trabalhar com estruturas aninhadas em vez de escalares, e o trecho de código anterior pode ser estendido naturalmente para estruturas aninhadas.

@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_* com um helper

Uma recomendação geral no TFF é usar as funções tf.random.stateless_* funcionais para gerar ruído aleatório. Essas funções recebem seed (um Tensor com formato [2] ou uma tuple de dois tensores escalares) como um argumento de entrada explícito para gerar ruído aleatório. Primeiro, definimos uma classe helper para manter a semente como pseudoestado. O helper RandomSeedGenerator tem operadores funcionais de última geração. É razoável usar um contador como pseudoestado para tf.random.stateless_*, pois essas funções embaralham a semente antes de usá-la para fazer ruídos gerados por sementes correlacionadas serem estatisticamente não 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)

Agora, vamos usar a classe helper e tf.random.stateless_normal para gerar ruído aleatório no TFF (uma estrutura aninhada de ruídos aleatórios). O seguinte trecho de código parece muito com um processo iterativo do TFF (confira um exemplo de como expressar um algoritmo de aprendizado federado como processo iterativo do TFF em simple_fedavg). Aqui, o pseudoestado da semente para geração de ruído aleatório é tf.Tensor, que pode ser facilmente transformado para funções do TFF e 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)]