לשמור את התאריך! קלט / פלט של Google חוזר 18-20 במאי הירשם עכשיו
דף זה תורגם על ידי Cloud Translation API.
Switch to English

הפקת רעש אקראית ב- TFF

הדרכה זו תדון בהמלצה ליצירת רעש אקראי ב- TFF. יצירת רעש אקראית היא מרכיב חשוב בטכניקות הגנה על פרטיות רבות באלגוריתמי למידה מאוחדים, למשל פרטיות דיפרנציאלית.

צפה ב- TensorFlow.org הפעל בגוגל קולאב צפה במקור ב- GitHub הורד מחברת

לפני שנתחיל

ראשית, בואו נוודא שהמחברת מחוברת ל- backend שמרכיבים את הרכיבים הרלוונטיים.

!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

הפעל את הדוגמה הבאה "שלום עולם" כדי לוודא שסביבת TFF מוגדרת כהלכה. אם זה לא עובד, עיין במדריך ההתקנה לקבלת הוראות.

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

hello_world()
b'Hello, World!'

שימוש מיואש: ישירות באמצעות tf.random.normal

TF1.x כמו ממשקי API tf.random.normal לייצור רעשים אקראיים מיואשים מאוד ב- TF2 על פי tf.random.normal לייצור tf.random.normal אקראי ב- 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 , 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, מומלץ לבנות את הגנרטור בתוך tff.tf_computation ; וקשה להעביר את הגנרטור ואת מצבו בין פונקציות tff.tf_computation .
  • קטע הקוד הקודם מסתמך גם על הגדרת זרעים בקפידה בגנרטורים. נקבל תוצאות צפויות אך מפתיעות ( n1==n2 דטרמיניסטי n1==n2 ) אם נשתמש ב- tf.random.Generator.from_non_deterministic_state() במקום זאת.

באופן כללי, TFF מעדיף פעולות פונקציונליות tf.random.stateless_* את השימוש 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 כטיעון קלט מפורש ליצירת רעש אקראי. ראשית אנו מגדירים מעמד עוזר לשמור על הזרע כמצב פסאודו. לעוזר RandomSeedGenerator יש מפעילים פונקציונליים בצורה של מדינה-ב-מדינה. סביר להשתמש במונה כמצב tf.random.stateless_* עבור tf.random.stateless_* שכן פונקציות אלה tf.random.stateless_* את הזרע לפני השימוש בו כדי להשמיע רעשים שנוצרו על ידי זרעים מתואמים מבחינה סטטיסטית.

class RandomSeedGenerator():

  def initialize(self, seed=None):
    if seed is None:
      return tf.cast(tf.stack(
          [tf.math.floor(tf.timestamp()*1e6),
            tf.math.floor(tf.math.log(tf.timestamp()*1e6))]), dtype=tf.int64)
    else:
      return tf.constant(self.seed, dtype=tf.int64, shape=(2,))

  def next(self, state):
    return state + 1

  def structure_next(self, state, nest_structure):
    "Returns seed in nexted structure and the next state seed."
    flat_structure = tf.nest.flatten(nest_structure)
    flat_seeds = [state+i for i in range(len(flat_structure))]
    nest_seeds = tf.nest.pack_sequence_as(nest_structure, flat_seeds)
    return nest_seeds, flat_seeds[-1] + 1

עכשיו בואו נשתמש בכיתת העזר וב- 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)]