Invio di dati diversi a clienti particolari con tff.federated_select

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza la fonte su GitHub Scarica taccuino

Questo tutorial mostra come implementare algoritmi federati personalizzati in TFF che richiedono l'invio di dati diversi a client diversi. Si può già avere familiarità con tff.federated_broadcast che invia un singolo valore server collocato a tutti i clienti. Questa esercitazione si concentra sui casi in cui parti diverse di un valore basato su server vengono inviate a client diversi. Ciò può essere utile per suddividere parti di un modello tra diversi client per evitare di inviare l'intero modello a un singolo client.

Cominciamo importando sia tensorflow e 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()

Invio di valori diversi in base ai dati del cliente

Considera il caso in cui abbiamo una lista posizionata sul server da cui vogliamo inviare alcuni elementi a ciascun client in base ad alcuni dati posizionati sul client. Ad esempio, un elenco di stringhe sul server e sui client un elenco di indici da scaricare separati da virgole. Possiamo implementarlo come segue:

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

Quindi possiamo simulare il nostro calcolo fornendo l'elenco di stringhe posizionato sul server e i dati di stringa per ciascun client:

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'>]

Invio di un elemento casuale a ciascun cliente

In alternativa, può essere utile inviare una porzione casuale dei dati del server a ciascun client. Possiamo implementarlo generando prima una chiave casuale su ciascun client e quindi seguendo un processo di selezione simile a quello utilizzato sopra:

@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

Dato che il nostro broadcast_random_element funzione non prendere in tutti i dati dei clienti in classifica, dobbiamo configurare il TFF Simulazione Runtime con un numero predefinito di clienti di utilizzare:

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

Quindi possiamo simulare la selezione. Possiamo cambiare default_num_clients sopra e l'elenco delle stringhe di seguito per generare risultati diversi, o semplicemente rieseguire il calcolo per generare differenti uscite casuali.

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