Wysyłanie różnych danych do określonych klientów za pomocą tff.federated_select

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik

W tym samouczku pokazano, jak zaimplementować niestandardowe algorytmy federacyjne w TFF, które wymagają wysyłania różnych danych do różnych klientów. Być może już być zaznajomieni z tff.federated_broadcast który przesyła pojedynczą wartość serwera przygotowana do wszystkich klientów. Ten samouczek koncentruje się na przypadkach, w których różne części wartości opartej na serwerze są wysyłane do różnych klientów. Może to być przydatne do dzielenia części modelu na różnych klientów, aby uniknąć wysyłania całego modelu do pojedynczego klienta.

Zaczynajmy importując zarówno tensorflow i 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

Wysyłanie różnych wartości na podstawie danych klienta

Rozważmy przypadek, w którym mamy jakąś listę umieszczoną na serwerze, z której chcemy wysłać kilka elementów do każdego klienta na podstawie pewnych danych umieszczonych przez klienta. Na przykład lista ciągów na serwerze i na klientach, oddzielona przecinkami lista indeksów do pobrania. Możemy to zrealizować w następujący sposób:

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

Następnie możemy zasymulować nasze obliczenia, dostarczając listę ciągów umieszczonych na serwerze, a także dane ciągów dla każdego klienta:

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

Wysyłanie losowego elementu do każdego klienta

Alternatywnie może być przydatne wysłanie losowej części danych serwera do każdego klienta. Możemy to zaimplementować, najpierw generując losowy klucz na każdym kliencie, a następnie postępując zgodnie z procesem selekcji podobnym do tego użytego powyżej:

@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

Ponieważ nasza broadcast_random_element funkcja nie ma w żadnych danych klienta w tabeli, musimy skonfigurować TFF Symulacje Runtime z domyślną liczbą klientów do używania:

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

Następnie możemy zasymulować wybór. Możemy zmienić default_num_clients powyżej i poniżej listę ciągów do generują różne wyniki, lub po prostu ponownie uruchomić obliczenia generować różne wyjścia losowych.

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