שליחת נתונים שונים ללקוחות מסוימים באמצעות tff.federated_select

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

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

בואו נתחיל באמצעות יבוא הוא tensorflow ו tensorflow_federated .

!pip install --quiet --upgrade tensorflow-federated-nightly
!pip install --quiet --upgrade nest-asyncio

import nest_asyncio
nest_asyncio.apply()
import tensorflow as tf
import tensorflow_federated as tff
tff.backends.native.set_local_python_execution_context()

שליחת ערכים שונים על סמך נתוני לקוח

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

list_of_strings_type = tff.TensorType(tf.string, [None])
# We only ever send exactly two values to each client. The number of keys per
# client must be a fixed number across all clients.
number_of_keys_per_client = 2
keys_type = tff.TensorType(tf.int32, [number_of_keys_per_client])
get_size = tff.tf_computation(lambda x: tf.size(x))
select_fn = tff.tf_computation(lambda val, index: tf.gather(val, index))
client_data_type = tf.string

# A function from our client data to the indices of the values we'd like to
# select from the server.
@tff.tf_computation(client_data_type)
@tff.check_returns_type(keys_type)
def keys_for_client(client_string):
  # We assume our client data is a single string consisting of exactly three
  # comma-separated integers indicating which values to grab from the server.
  split = tf.strings.split([client_string], sep=',')[0]
  return tf.strings.to_number([split[0], split[1]], tf.int32)

@tff.tf_computation(tff.SequenceType(tf.string))
@tff.check_returns_type(tf.string)
def concatenate(values):
  def reduce_fn(acc, item):
    return tf.cond(tf.math.equal(acc, ''),
                   lambda: item,
                   lambda: tf.strings.join([acc, item], ','))
  return values.reduce('', reduce_fn)

@tff.federated_computation(tff.type_at_server(list_of_strings_type), tff.type_at_clients(client_data_type))
def broadcast_based_on_client_data(list_of_strings_at_server, client_data):
  keys_at_clients = tff.federated_map(keys_for_client, client_data)
  max_key = tff.federated_map(get_size, list_of_strings_at_server)
  values_at_clients = tff.federated_select(keys_at_clients, max_key, list_of_strings_at_server, select_fn)
  value_at_clients = tff.federated_map(concatenate, values_at_clients)
  return value_at_clients

לאחר מכן נוכל לדמות את החישוב שלנו על ידי אספקת רשימת המחרוזות הממוקמת בשרת, כמו גם נתוני מחרוזות עבור כל לקוח:

client_data = ['0,1', '1,2', '2,0']
broadcast_based_on_client_data(['a', 'b', 'c'], client_data)
[<tf.Tensor: shape=(), dtype=string, numpy=b'a,b'>,
 <tf.Tensor: shape=(), dtype=string, numpy=b'b,c'>,
 <tf.Tensor: shape=(), dtype=string, numpy=b'c,a'>]

שליחת אלמנט אקראי לכל לקוח

לחלופין, זה עשוי להיות שימושי לשלוח חלק אקראי מנתוני השרת לכל לקוח. אנו יכולים ליישם זאת על ידי יצירת מפתח אקראי על כל לקוח ולאחר מכן ביצוע תהליך בחירה דומה לזה ששימש לעיל:

@tff.tf_computation(tf.int32)
@tff.check_returns_type(tff.TensorType(tf.int32, [1]))
def get_random_key(max_key):
  return tf.random.uniform(shape=[1], minval=0, maxval=max_key, dtype=tf.int32)

list_of_strings_type = tff.TensorType(tf.string, [None])
get_size = tff.tf_computation(lambda x: tf.size(x))
select_fn = tff.tf_computation(lambda val, index: tf.gather(val, index))

@tff.tf_computation(tff.SequenceType(tf.string))
@tff.check_returns_type(tf.string)
def get_last_element(sequence):
  return sequence.reduce('', lambda _initial_state, val: val)

@tff.federated_computation(tff.type_at_server(list_of_strings_type))
def broadcast_random_element(list_of_strings_at_server):
  max_key_at_server = tff.federated_map(get_size, list_of_strings_at_server)
  max_key_at_clients = tff.federated_broadcast(max_key_at_server)
  key_at_clients = tff.federated_map(get_random_key, max_key_at_clients)
  random_string_sequence_at_clients = tff.federated_select(
      key_at_clients, max_key_at_server, list_of_strings_at_server, select_fn)
  # Even though we only passed in a single key, `federated_select` returns a
  # sequence for each client. We only care about the last (and only) element.
  random_string_at_clients = tff.federated_map(get_last_element, random_string_sequence_at_clients)
  return random_string_at_clients

מאז שלנו broadcast_random_element פונקציה לא לוקח בכל נתוני להציב הלקוח, עלינו להגדיר את Runtime סימולציה TFF עם מספר ברירת מחדל של לקוחות לשימוש:

tff.backends.native.set_local_python_execution_context(default_num_clients=3)

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

broadcast_random_element(tf.convert_to_tensor(['foo', 'bar', 'baz']))