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
-
tf.random.Generator
menggunakantf.Variable
untuk mempertahankan negara untuk algoritma RNG. Dalam TFF, dianjurkan untuk contruct generator di dalamtff.tf_computation
; dan sulit untuk lulus generator dan keadaan antaratff.tf_computation
fungsi. - cuplikan kode sebelumnya juga bergantung pada pengaturan seed dengan hati-hati di generator. Kami akan mendapatkan diharapkan tapi hasil yang mengejutkan (deterministik
n1==n2
) jika kita menggunakantf.random.Generator.from_non_deterministic_state()
sebagai gantinya.
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)]
Rekomendasi penggunaan: tf.random.stateless_*
dengan pembantu
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)]