توليد الضوضاء العشوائية في TFF

سيناقش هذا البرنامج التعليمي أفضل الممارسات الموصى بها لتوليد الضوضاء العشوائية في TFF. يعد توليد الضوضاء العشوائية مكونًا مهمًا للعديد من تقنيات حماية الخصوصية في خوارزميات التعلم الموحد ، على سبيل المثال ، الخصوصية التفاضلية.

عرض على TensorFlow.org تشغيل في Google Colab عرض المصدر على جيثب تحميل دفتر

قبل أن نبدأ

أولاً ، دعونا نتأكد من توصيل الكمبيوتر الدفتري بالواجهة الخلفية التي تم تجميع المكونات ذات الصلة بها.

!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 . قد يحدث السلوك المستغرب عندما تستخدم واجهات برمجة التطبيقات جنبا إلى جنب مع 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)]