การส่งข้อมูลที่แตกต่างกันไปยังลูกค้าเฉพาะรายด้วย tff.federated_select

ดูบน TensorFlow.org ทำงานใน Google Colab ดูแหล่งที่มาบน 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.backends.native.set_local_python_execution_context(default_num_clients=3)

จากนั้นเราสามารถจำลองการเลือกได้ เราสามารถเปลี่ยน default_num_clients ข้างต้นและรายการของสตริงด้านล่างเพื่อสร้างผลลัพธ์ที่แตกต่างกันหรือเพียงแค่ re-run คำนวณในการสร้างผลที่แตกต่างกันแบบสุ่ม

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