Generasi kebisingan acak di TFF

Tutorial ini akan membahas praktik terbaik yang direkomendasikan untuk pembangkitan derau acak di TFF. Kebisingan yang dihasilkan secara acak merupakan komponen penting dari banyak teknik perlindungan privasi dalam algoritme pembelajaran gabungan, misalnya, privasi diferensial.

Lihat di TensorFlow.org Jalankan di Google Colab Lihat sumber di GitHub Unduh buku catatan

Sebelum kita mulai

Pertama, mari kita pastikan notebook terhubung ke backend yang memiliki komponen yang relevan dikompilasi.

!pip install --quiet --upgrade tensorflow_federated_nightly
!pip install --quiet --upgrade nest_asyncio

import nest_asyncio
nest_asyncio.apply()
import numpy as np
import tensorflow as tf
import tensorflow_federated as tff

Jalankan contoh "Hello World" berikut untuk memastikan lingkungan TFF telah diatur dengan benar. Jika tidak bekerja, silakan merujuk ke Instalasi panduan untuk petunjuk.

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

hello_world()
b'Hello, World!'

Kebisingan acak pada klien

Kebutuhan kebisingan pada klien umumnya jatuh ke dalam dua kasus: kebisingan identik dan kebisingan iid.

  • Untuk kebisingan identik, pola direkomendasikan adalah untuk mempertahankan benih pada server, disiarkan ke klien, dan menggunakan tf.random.stateless fungsi untuk menghasilkan suara.
  • Untuk noise iid, gunakan tf.random.Generator yang diinisialisasi pada klien dengan from_non_deterministic_state, sesuai dengan rekomendasi TF untuk menghindari fungsi tf.random.<distribution>.

Perilaku klien berbeda dari server (tidak mengalami jebakan yang dibahas nanti) karena setiap klien akan membangun grafik komputasi mereka sendiri dan menginisialisasi seed default mereka sendiri.

Kebisingan identik pada klien

# Set to use 10 clients.
tff.backends.native.set_local_python_execution_context(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.

Kebisingan independen pada klien

@tff.tf_computation
def nondeterministic_noise():
  gen = tf.random.Generator.from_non_deterministic_state()
  return gen.normal(())

@tff.federated_computation(seed_type_at_server)
def get_random_min_and_max_nondeterministic(seed):
  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(seed)
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(seed)
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.810,   1.079.
Values differ across rounds.    -1.205,   0.851.

Kebisingan acak di server

Penggunaan berkecil: langsung menggunakan tf.random.normal

TF1.x seperti API tf.random.normal untuk pembangkit random noise yang sangat tidak dianjurkan di TF2 menurut acak generasi kebisingan tutorial di TF . Perilaku mengejutkan mungkin terjadi ketika API ini digunakan bersama-sama dengan tf.function dan tf.random.set_seed . Misalnya, kode berikut akan menghasilkan nilai yang sama dengan setiap panggilan. Perilaku mengejutkan ini diharapkan untuk TF, dan penjelasan dapat ditemukan dalam dokumentasi 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

Di TFF, semuanya sedikit berbeda. Jika kita membungkus generasi kebisingan sebagai tff.tf_computation bukan tf.function , non-deterministik gangguan acak akan dihasilkan. Namun, jika kita menjalankan kode ini potongan beberapa kali, yang berbeda (n1, n2) akan dihasilkan setiap kali. Tidak ada cara mudah untuk menyetel benih acak global untuk 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)
1.3283143 0.45740178

Selain itu, kebisingan deterministik dapat dihasilkan di TFF tanpa secara eksplisit mengatur benih. Fungsi return_two_noise dalam kode berikut potongan pengembalian dua nilai kebisingan yang identik. Ini adalah perilaku yang diharapkan karena TFF akan membangun grafik komputasi terlebih dahulu sebelum dieksekusi. Namun, ini menunjukkan pengguna harus memperhatikan pada penggunaan tf.random.normal di TFF.

@tff.tf_computation
def tff_return_one_noise():
  return tf.random.normal([])

@tff.federated_computation
def return_two_noise():
  return (tff_return_one_noise(), tff_return_one_noise())

n1, n2=return_two_noise() 
assert n1 == n2
print(n1, n2)
-0.15665223 -0.15665223

Penggunaan dengan hati-hati: tf.random.Generator

Kita dapat menggunakan tf.random.Generator seperti yang disarankan dalam tutorial 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.38260338

Namun, pengguna mungkin harus berhati-hati dalam penggunaannya

Secara umum, TFF lebih suka operasi fungsional dan kami akan menampilkan penggunaan tf.random.stateless_* fungsi dalam bagian berikut.

Di TFF untuk pembelajaran gabungan, kami sering bekerja dengan struktur bersarang alih-alih skalar dan cuplikan kode sebelumnya dapat diperluas secara alami ke struktur bersarang.

@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.38260338, -0.47804865],
       [-0.5187485 , -1.8471988 ]], dtype=float32), array([-0.77835274], dtype=float32)]

Sebuah rekomendasi umum di TFF adalah dengan menggunakan fungsional tf.random.stateless_* fungsi untuk generasi gangguan acak. Fungsi-fungsi ini mengambil seed (a Tensor dengan bentuk [2] atau tuple dari dua tensor skalar) sebagai argumen masukan eksplisit untuk menghasilkan suara acak. Kami pertama-tama mendefinisikan kelas pembantu untuk mempertahankan benih sebagai status semu. Penolong RandomSeedGenerator memiliki operator fungsional secara negara-di-negara-out. Adalah wajar untuk menggunakan counter sebagai negara semu untuk tf.random.stateless_* sebagai fungsi-fungsi ini berebut benih sebelum menggunakannya untuk membuat suara yang dihasilkan oleh biji berkorelasi statistik berkorelasi.

def timestamp_seed():
  # tf.timestamp returns microseconds as decimal places, thus scaling by 1e6.
  return tf.math.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)

Sekarang mari kita menggunakan kelas pembantu dan tf.random.stateless_normal untuk menghasilkan (struktur bersarang) gangguan acak di TFF. Potongan kode berikut terlihat banyak seperti proses berulang TFF, lihat simple_fedavg sebagai contoh mengekspresikan algoritma pembelajaran federasi sebagai TFF proses berulang. Negara benih palsu di sini untuk pembangkit noise acak tf.Tensor yang dapat dengan mudah diangkut dalam fungsi TFF dan 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.21598858, -0.30700883],
       [ 0.7562299 , -0.21218438]], dtype=float32), array([-1.0359321], dtype=float32)]
n2 [array([[ 1.0722181 ,  0.81287116],
       [-0.7140338 ,  0.5896157 ]], dtype=float32), array([0.44190162], dtype=float32)]