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

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

نویز یکسان روی مشتریان

# 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 بدون تنظیم صریح یک seed تولید کرد. تابع 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 از دو تانسورها اسکالر) به عنوان یک آرگومان ورودی صریح برای تولید نویز تصادفی. ابتدا یک کلاس کمکی برای حفظ seed به صورت شبه حالت تعریف می کنیم. یاور 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)]