تولید نویز تصادفی در TFF

این آموزش بهترین شیوه های توصیه شده برای ایجاد نویز تصادفی در TFF را مورد بحث قرار می دهد. تولید سر و صدا تصادفی جزء مهمی از بسیاری از تکنیک های حفاظت از حریم خصوصی در الگوریتم های یادگیری متمرکز است ، به عنوان مثال ، حریم خصوصی متفاوت.

مشاهده در TensorFlow.org در Google Colab اجرا کنید مشاهده منبع در GitHub دانلود دفترچه یادداشت

قبل از اینکه شروع کنیم

ابتدا ، اجازه دهید مطمئن شویم که نوت بوک به پشتیبان متصل است که اجزای مربوطه کامپایل شده است.

!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

مثال "Hello World" زیر را اجرا کنید تا مطمئن شوید محیط TFF به درستی تنظیم شده است. اگر آن کار نمی کند، لطفا به مراجعه نصب و راه اندازی راهنمای دستورالعمل.

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

hello_world()
b'Hello, World!'

سر و صدای تصادفی برای مشتریان

به طور کلی نیاز به سر و صدا برای مشتریان به دو صورت تقسیم می شود: نویز یکسان و نویز iid.

  • برای سر و صدا یکسان، الگوی توصیه می شود برای حفظ یک دانه بر روی سرور، پخش آن را به مشتریان، و استفاده از tf.random.stateless توابع برای تولید سر و صدا.
  • برای سر و صدای iid ، از یک tf.random.Generator استفاده کنید که با کلمه client از

رفتار مشتری با سرور متفاوت است (مشکلی که بعداً مورد بحث قرار گرفت) وجود ندارد زیرا هر کلاینت نمودار محاسبه خود را ایجاد کرده و دانه پیش فرض خود را مقداردهی می کند.

سر و صدای یکسان برای مشتریان

# 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.

سر و صدای مستقل بر روی مشتریان

@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.

سر و صدای تصادفی روی سرور

استفاده دلسرد: به طور مستقیم با استفاده از tf.random.normal

TF1.x مانند رابط های برنامه کاربردی tf.random.normal برای نسل سر و صدا به صورت تصادفی به شدت در TF2 با توجه به دلسرد تصادفی آموزش نسل سر و صدا در TF . رفتار شگفت انگیز ممکن است رخ دهد که این API ها همراه با استفاده tf.function و tf.random.set_seed . به عنوان مثال ، کد زیر با هر تماس مقدار یکسانی تولید می کند. این رفتار شگفت انگیز برای TF انتظار می رود، و توضیح می توان در یافت اسناد و مدارک از 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

در TFF ، اوضاع کمی متفاوت است. اگر ما نسل سر و صدا به عنوان بسته بندی tff.tf_computation جای tf.function ، سر و صدا تصادفی غیر قطعی ایجاد خواهد شد. با این حال، اگر ما این تکه کد را چندین بار اجرا شود، مجموعه ای متفاوت از (n1, n2) تولید خواهد شد در هر زمان. هیچ راهی آسان برای تنظیم بذر تصادفی جهانی برای 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

علاوه بر این ، سر و صدای قطعی را می توان در TFF بدون تنظیم صریح دانه ایجاد کرد. تابع return_two_noise در کد زیر قطعه بازده دو مقدار سر و صدا یکسان است. این رفتار مورد انتظار است زیرا TFF قبل از اجرا نمودار محاسبه را از قبل ایجاد می کند. با این حال، این نشان می دهد تا کاربران را به توجه دارند در مورد استفاده از tf.random.normal در 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

طریقه استفاده با دقت: tf.random.Generator

ما می توانید استفاده کنید tf.random.Generator در پیشنهاد آموزش 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

با این حال ، ممکن است کاربران در استفاده از آن احتیاط کنند

  • tf.random.Generator با استفاده از tf.Variable برای حفظ کشور برای الگوریتم RNG. در TFF، توصیه می شود به contruct ژنراتور در داخل یک tff.tf_computation ؛ و آن را دشوار است به تصویب ژنراتور و دولت خود را بین tff.tf_computation توابع.
  • قطعه کد قبلی همچنین به تنظیم دقیق دانه ها در ژنراتورها متکی است. ما خواهد شد انتظار می رود اما این نتایج تعجب آور (قطعی n1==n2 ) اگر ما با استفاده tf.random.Generator.from_non_deterministic_state() به جای آن.

به طور کلی، TFF ترجیح عملیات کاربردی و ما را به استفاده از نمایش گذاشتن tf.random.stateless_* توابع در بخش های زیر.

در TFF برای یادگیری فدرال ، ما اغلب به جای مقیاس بندی با ساختارهای تو در تو کار می کنیم و قطعه کد قبلی را می توان به طور طبیعی به ساختارهای تو در تو توسعه داد.

@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)]

یک توصیه عمومی در TFF است که استفاده از کاربردی tf.random.stateless_* توابع برای نسل سر و صدا به صورت تصادفی. این توابع را seed (یک تانسور با شکل [2] یا یک tuple از دو تانسورها اسکالر) به عنوان یک آرگومان ورودی صریح برای تولید نویز تصادفی. ما ابتدا یک کلاس کمکی برای حفظ بذر به عنوان حالت شبه تعریف می کنیم. یاور RandomSeedGenerator است اپراتور کاربردی در مد دولت در دولت است. این منطقی است به استفاده از یک شمارنده به عنوان حالت شبه برای tf.random.stateless_* عنوان این توابع تقلا دانه قبل از استفاده از آن را به سر و صدا تولید شده توسط دانه همبسته آماری ناهمبسته.

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)

در حال حاضر ما با استفاده از کلاس کمکی و tf.random.stateless_normal برای تولید (ساختار تو در تو از) سر و صدا تصادفی در TFF. قطعه کد زیر به نظر می رسد بسیاری مانند یک فرآیند تکرار شونده TFF، مشاهده simple_fedavg به عنوان مثال بیان الگوریتم یادگیری فدرال به عنوان TFF فرآیند تکرار شونده. دولت دانه شبه در اینجا برای نسل سر و صدا تصادفی است tf.Tensor است که می تواند به راحتی در توابع TFF و 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)]