อัลกอริธึมแบบรวมศูนย์ที่กำหนดเอง ส่วนที่ 2: การนำ Federated Averaging . ไปใช้

ดูบน TensorFlow.org ทำงานใน Google Colab ดูแหล่งที่มาบน GitHub ดาวน์โหลดโน๊ตบุ๊ค

กวดวิชานี้เป็นส่วนที่สองของชุดสองส่วนที่แสดงให้เห็นถึงวิธีการใช้ประเภทที่กำหนดเองของอัลกอริทึมแบบ federated ในฉิบหายใช้ สหพันธ์แกน (เอฟซี) ซึ่งทำหน้าที่เป็นรากฐานสำหรับการที่ สหพันธ์การเรียนรู้ (FL) ชั้น ( tff.learning ) .

เราขอแนะนำให้คุณไปก่อนอ่าน ส่วนแรกของชุดนี้ ซึ่งแนะนำบางส่วนของแนวคิดหลักและแนวคิดการเขียนโปรแกรมใช้ที่นี่

ส่วนที่สองของชุดข้อมูลนี้ใช้กลไกที่นำมาใช้ในส่วนแรกเพื่อนำอัลกอริธึมการฝึกอบรมและการประเมินผลแบบรวมรุ่นที่เรียบง่ายมาใช้

เราขอแนะนำให้คุณทบทวน การจัดหมวดหมู่ภาพ และ รุ่นข้อความ แบบฝึกหัดสำหรับระดับที่สูงขึ้นและการแนะนำอ่อนโยนมากขึ้นที่จะฉิบหายของสหพันธ์การเรียนรู้ APIs ที่พวกเขาจะช่วยให้คุณใส่แนวคิดที่เราจะอธิบายในบริบทที่นี่

ก่อนที่เราจะเริ่มต้น

ก่อนที่เราจะเริ่ม ให้ลองเรียกใช้ตัวอย่าง "Hello World" ต่อไปนี้เพื่อให้แน่ใจว่าสภาพแวดล้อมของคุณได้รับการตั้งค่าอย่างถูกต้อง ถ้ามันไม่ทำงานโปรดดูที่ การติดตั้ง คู่มือสำหรับคำแนะนำ

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

import nest_asyncio
nest_asyncio.apply()
import collections

import numpy as np
import tensorflow as tf
import tensorflow_federated as tff

# Must use the Python context because it
# supports tff.sequence_* intrinsics.
executor_factory = tff.framework.local_executor_factory(
    support_sequence_ops=True)
execution_context = tff.framework.ExecutionContext(
    executor_fn=executor_factory)
tff.framework.set_default_context(execution_context)
@tff.federated_computation
def hello_world():
  return 'Hello, World!'

hello_world()
b'Hello, World!'

การใช้ Federated Averaging

ในขณะที่ การเรียนรู้สหพันธ์สำหรับภาพการจัดจำแนกของ เราจะใช้ตัวอย่างเช่น MNIST แต่ตั้งแต่นี้มีวัตถุประสงค์เพื่อเป็นระดับต่ำกวดวิชา, เราจะไปบายพาส API Keras และ tff.simulation เขียนโค้ดรูปแบบดิบและสร้าง ชุดข้อมูลรวมศูนย์ตั้งแต่เริ่มต้น

การเตรียมชุดข้อมูลแบบรวมศูนย์

เพื่อการสาธิต เราจะจำลองสถานการณ์ที่เรามีข้อมูลจากผู้ใช้ 10 ราย และผู้ใช้แต่ละรายมีส่วนช่วยให้ความรู้ในการจำแนกตัวเลขที่แตกต่างกัน นี้เป็นเรื่องเกี่ยวกับที่ไม่ใช่ IID ตามที่ได้รับ

ขั้นแรก ให้โหลดข้อมูล MNIST มาตรฐาน:

mnist_train, mnist_test = tf.keras.datasets.mnist.load_data()
[(x.dtype, x.shape) for x in mnist_train]
[(dtype('uint8'), (60000, 28, 28)), (dtype('uint8'), (60000,))]

ข้อมูลมาในรูปแบบ Numpy Array อันหนึ่งมีรูปภาพและอีกอันหนึ่งมีป้ายกำกับตัวเลข ทั้งคู่มีมิติแรกข้ามตัวอย่างแต่ละรายการ มาเขียนฟังก์ชันตัวช่วยที่จัดรูปแบบให้สอดคล้องกับวิธีที่เราป้อนลำดับรวมเข้าในการคำนวณ TFF กล่าวคือ เป็นรายการของรายการ - รายการภายนอกตั้งแต่ผู้ใช้ (ตัวเลข) รายการภายในที่มีชุดข้อมูลใน ลำดับของลูกค้าแต่ละราย ในฐานะที่เป็นธรรมเนียมที่เราจะจัดโครงสร้างแต่ละชุดเป็นคู่ของเทนเซอร์ชื่อ x และ y แต่ละคนมีมิติชุดชั้นนำ ในขณะที่มันเราจะแผ่ภาพแต่ละภาพเป็นเวกเตอร์ 784 องค์ประกอบและ rescale พิกเซลในนั้นเข้าไปใน 0..1 ช่วงเพื่อที่เราจะได้ไม่ต้องถ่วงตรรกะรุ่นที่มีการแปลงข้อมูล

NUM_EXAMPLES_PER_USER = 1000
BATCH_SIZE = 100


def get_data_for_digit(source, digit):
  output_sequence = []
  all_samples = [i for i, d in enumerate(source[1]) if d == digit]
  for i in range(0, min(len(all_samples), NUM_EXAMPLES_PER_USER), BATCH_SIZE):
    batch_samples = all_samples[i:i + BATCH_SIZE]
    output_sequence.append({
        'x':
            np.array([source[0][i].flatten() / 255.0 for i in batch_samples],
                     dtype=np.float32),
        'y':
            np.array([source[1][i] for i in batch_samples], dtype=np.int32)
    })
  return output_sequence


federated_train_data = [get_data_for_digit(mnist_train, d) for d in range(10)]

federated_test_data = [get_data_for_digit(mnist_test, d) for d in range(10)]

ขณะที่การตรวจสอบสติอย่างรวดเร็วดูให้ของที่ Y เมตริกซ์ในชุดสุดท้ายของข้อมูลสนับสนุนโดยไคลเอ็นต์ห้า (หนึ่งที่สอดคล้องกับหลัก 5 )

federated_train_data[5][-1]['y']
array([5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5], dtype=int32)

เพื่อความแน่ใจ เรามาดูภาพที่สอดคล้องกับองค์ประกอบสุดท้ายของชุดงานนั้นกัน

from matplotlib import pyplot as plt

plt.imshow(federated_train_data[5][-1]['x'][-1].reshape(28, 28), cmap='gray')
plt.grid(False)
plt.show()

png

ในการรวม TensorFlow และ TFF

ในการกวดวิชานี้แน่นเราได้ทันทีตกแต่งฟังก์ชั่นที่แนะนำตรรกะ TensorFlow กับ tff.tf_computation อย่างไรก็ตาม สำหรับตรรกะที่ซับซ้อนกว่านี้ นี่ไม่ใช่รูปแบบที่เราแนะนำ การดีบัก TensorFlow อาจเป็นเรื่องท้าทาย และการดีบัก TensorFlow หลังจากที่ได้รับการซีเรียลไลซ์โดยสมบูรณ์แล้วนำเข้าใหม่อีกครั้งจะสูญเสียข้อมูลเมตาบางส่วนและจำกัดการโต้ตอบ ทำให้การดีบั๊กมีความท้าทายมากขึ้น

ดังนั้นเราจึงขอแนะนำการเขียนตรรกะ TF ซับซ้อนเท่าที่ยืนอยู่คนเดียวฟังก์ชั่นหลาม (นั่นคือโดยไม่ต้อง tff.tf_computation ตกแต่ง) วิธีนี้ตรรกะ TensorFlow สามารถที่จะพัฒนาและทดสอบโดยใช้ TF ปฏิบัติที่ดีที่สุดและเครื่องมือ (เช่นโหมดกระตือรือร้น) ก่อน serializing คำนวณสำหรับฉิบหาย (เช่นโดยการเรียก tff.tf_computation กับฟังก์ชั่นหลามเป็นอาร์กิวเมนต์)

การกำหนดฟังก์ชันการสูญเสีย

เมื่อได้ข้อมูลแล้ว มากำหนดฟังก์ชันการสูญเสียที่เราสามารถใช้สำหรับการฝึกกัน ขั้นแรก ให้กำหนดประเภทของอินพุตเป็น TFF ชื่อทูเพิล เนื่องจากขนาดของแบตช์ข้อมูลที่อาจแตกต่างกันเราตั้งมิติชุดที่จะ None การระบุว่าขนาดของมิตินี้ไม่เป็นที่รู้จัก

BATCH_SPEC = collections.OrderedDict(
    x=tf.TensorSpec(shape=[None, 784], dtype=tf.float32),
    y=tf.TensorSpec(shape=[None], dtype=tf.int32))
BATCH_TYPE = tff.to_type(BATCH_SPEC)

str(BATCH_TYPE)
'<x=float32[?,784],y=int32[?]>'

คุณอาจสงสัยว่าทำไมเราไม่สามารถกำหนดประเภท Python ธรรมดาได้ จำการอภิปรายใน ส่วน 1 ที่เราอธิบายว่าในขณะที่เราสามารถแสดงเหตุผลของการคำนวณฉิบหายโดยใช้งูใหญ่ภายใต้ประทุนคำนวณฉิบหายไม่ได้หลาม สัญลักษณ์ BATCH_TYPE ที่กำหนดไว้ข้างต้นแสดงให้เห็นถึงสเปคชนิดฉิบหายนามธรรม มันเป็นสิ่งสำคัญที่จะแยกแยะประเภทนี้ฉิบหายนามธรรมจากคอนกรีตหลามประเภทการเป็นตัวแทนเช่นภาชนะบรรจุเช่น dict หรือ collections.namedtuple ที่อาจถูกใช้เพื่อเป็นตัวแทนของประเภทฉิบหายในร่างกายของฟังก์ชันหลาม ซึ่งแตกต่างจากงูหลามฉิบหายมีนามธรรมประเภทคอนสตรัคเดียว tff.StructType สำหรับ tuple เหมือนภาชนะที่มีองค์ประกอบที่สามารถตั้งชื่อหรือซ้ายชื่อเป็นรายบุคคล ประเภทนี้ยังใช้ในการสร้างแบบจำลองพารามิเตอร์ที่เป็นทางการของการคำนวณ เนื่องจากการคำนวณ TFF สามารถประกาศพารามิเตอร์ได้เพียงตัวเดียวและผลลัพธ์เดียวเท่านั้น - คุณจะเห็นตัวอย่างของสิ่งนี้ในไม่ช้า

ตอนนี้ขอให้กำหนดประเภทฉิบหายของพารามิเตอร์แบบอีกครั้งเป็นฉิบหายชื่อ tuple ของน้ำหนักและอคติ

MODEL_SPEC = collections.OrderedDict(
    weights=tf.TensorSpec(shape=[784, 10], dtype=tf.float32),
    bias=tf.TensorSpec(shape=[10], dtype=tf.float32))
MODEL_TYPE = tff.to_type(MODEL_SPEC)
print(MODEL_TYPE)
<weights=float32[784,10],bias=float32[10]>

ด้วยคำจำกัดความเหล่านี้ ตอนนี้เราสามารถกำหนดการสูญเสียสำหรับแบบจำลองที่กำหนด ในชุดเดียวได้ หมายเหตุการใช้งานของ @tf.function มัณฑนากรภายใน @tff.tf_computation มัณฑนากร นี้จะช่วยให้เราสามารถเขียน TF ใช้งูใหญ่เช่นความหมายแม้อยู่ภายใน tf.Graph บริบทที่สร้างขึ้นโดย tff.tf_computation มัณฑนากร

# NOTE: `forward_pass` is defined separately from `batch_loss` so that it can 
# be later called from within another tf.function. Necessary because a
# @tf.function  decorated method cannot invoke a @tff.tf_computation.

@tf.function
def forward_pass(model, batch):
  predicted_y = tf.nn.softmax(
      tf.matmul(batch['x'], model['weights']) + model['bias'])
  return -tf.reduce_mean(
      tf.reduce_sum(
          tf.one_hot(batch['y'], 10) * tf.math.log(predicted_y), axis=[1]))

@tff.tf_computation(MODEL_TYPE, BATCH_TYPE)
def batch_loss(model, batch):
  return forward_pass(model, batch)

เป็นที่คาดหวังการคำนวณ batch_loss ผลตอบแทน float32 การสูญเสียที่กำหนดรูปแบบและชุดข้อมูลเดียว หมายเหตุวิธีการ MODEL_TYPE และ BATCH_TYPE ได้รับการล้างโลกเข้าด้วยกันเป็น 2 tuple ของพารามิเตอร์อย่างเป็นทางการ; คุณสามารถรับรู้ประเภทของ batch_loss เป็น (<MODEL_TYPE,BATCH_TYPE> -> float32)

str(batch_loss.type_signature)
'(<model=<weights=float32[784,10],bias=float32[10]>,batch=<x=float32[?,784],y=int32[?]>> -> float32)'

เพื่อเป็นการตรวจสอบสติ เรามาสร้างแบบจำลองเริ่มต้นซึ่งเต็มไปด้วยศูนย์และคำนวณการสูญเสียของชุดข้อมูลที่เราแสดงไว้ด้านบน

initial_model = collections.OrderedDict(
    weights=np.zeros([784, 10], dtype=np.float32),
    bias=np.zeros([10], dtype=np.float32))

sample_batch = federated_train_data[5][-1]

batch_loss(initial_model, sample_batch)
2.3025851

โปรดทราบว่าเรากินคำนวณฉิบหายที่มีรูปแบบเริ่มต้นกำหนดเป็น dict แม้ว่าร่างกายของฟังก์ชั่นหลามที่กำหนดมันกินพารามิเตอร์แบบเป็น model['weight'] และ model['bias'] ข้อโต้แย้งของการเรียกร้องให้ batch_loss ไม่ได้เพียงแค่ส่งผ่านไปยังร่างของฟังก์ชั่นว่า

จะเกิดอะไรขึ้นเมื่อเราวิงวอน batch_loss ? ร่างกายของงูหลาม batch_loss ได้รับการตรวจสอบและต่อเนื่องในเซลล์ดังกล่าวข้างต้นที่มันถูกกำหนดไว้ ฉิบหายทำหน้าที่เป็นโทรไป batch_loss ในเวลาที่นิยามการคำนวณและเป็นเป้าหมายของการภาวนาในเวลา batch_loss ถูกเรียก ในทั้งสองบทบาท TFF ทำหน้าที่เป็นสะพานเชื่อมระหว่างระบบประเภทนามธรรมของ TFF และประเภทการแสดงแทน Python ในขณะที่การภาวนา, ฉิบหายจะยอมรับมาตรฐานส่วนใหญ่ประเภทภาชนะหลาม ( dict , list , tuple , collections.namedtuple ฯลฯ ) ในฐานะตัวแทนที่เป็นรูปธรรมของ tuples ฉิบหายนามธรรม นอกจากนี้ แม้ว่าตามที่กล่าวไว้ข้างต้น การคำนวณ TFF อย่างเป็นทางการยอมรับเพียงพารามิเตอร์เดียวเท่านั้น คุณสามารถใช้ไวยากรณ์การเรียก Python ที่คุ้นเคยพร้อมอาร์กิวเมนต์ตำแหน่งและ/หรือคีย์เวิร์ด ในกรณีที่ประเภทของพารามิเตอร์เป็นทูเพิล - มันทำงานได้ตามที่คาดไว้

การไล่สีแบบไล่ระดับในชุดเดียว

ตอนนี้ มากำหนดการคำนวณที่ใช้ฟังก์ชันการสูญเสียนี้เพื่อดำเนินการไล่ระดับขั้นเดียว หมายเหตุวิธีการในการกำหนดฟังก์ชั่นนี้เราจะใช้ batch_loss เป็นคอมโพเนนต์ย่อย คุณสามารถเรียกการคำนวณสร้างด้วย tff.tf_computation ภายในร่างกายของการคำนวณอีกแม้ว่าโดยทั่วไปนี้ไม่จำเป็น - ตามที่ระบุไว้ข้างต้นเพราะเป็นอันดับ looses ข้อมูลการแก้จุดบกพร่องบางอย่างก็มักจะเป็นที่นิยมสำหรับการคำนวณที่ซับซ้อนมากขึ้นในการเขียนและทดสอบทั้งหมด TensorFlow โดยไม่ต้อง tff.tf_computation มัณฑนากร

@tff.tf_computation(MODEL_TYPE, BATCH_TYPE, tf.float32)
def batch_train(initial_model, batch, learning_rate):
  # Define a group of model variables and set them to `initial_model`. Must
  # be defined outside the @tf.function.
  model_vars = collections.OrderedDict([
      (name, tf.Variable(name=name, initial_value=value))
      for name, value in initial_model.items()
  ])
  optimizer = tf.keras.optimizers.SGD(learning_rate)

  @tf.function
  def _train_on_batch(model_vars, batch):
    # Perform one step of gradient descent using loss from `batch_loss`.
    with tf.GradientTape() as tape:
      loss = forward_pass(model_vars, batch)
    grads = tape.gradient(loss, model_vars)
    optimizer.apply_gradients(
        zip(tf.nest.flatten(grads), tf.nest.flatten(model_vars)))
    return model_vars

  return _train_on_batch(model_vars, batch)
str(batch_train.type_signature)
'(<initial_model=<weights=float32[784,10],bias=float32[10]>,batch=<x=float32[?,784],y=int32[?]>,learning_rate=float32> -> <weights=float32[784,10],bias=float32[10]>)'

เมื่อคุณเรียกใช้ฟังก์ชั่นการตกแต่งด้วยงูหลาม tff.tf_computation ภายในร่างกายของฟังก์ชั่นดังกล่าวอีกตรรกะของการคำนวณฉิบหายภายในจะถูกฝังอยู่ (หลัก inlined) ในตรรกะของนอกอย่างใดอย่างหนึ่ง ตามที่ระบุไว้ข้างต้นถ้าคุณเขียนทั้งการคำนวณก็มีโอกาสที่ดีกว่าที่จะทำให้ฟังก์ชั่นภายใน ( batch_loss ในกรณีนี้) ปกติงูหลามหรือ tf.function มากกว่า tff.tf_computation แต่ที่นี่เราแสดงให้เห็นว่าการเรียกหนึ่ง tff.tf_computation ภายในอื่นทำงานโดยทั่วไปตามที่คาดไว้ ซึ่งอาจจำเป็นตัวอย่างเช่นถ้าคุณไม่ได้มีการกำหนดรหัสหลาม batch_loss แต่มันเป็นตัวแทนฉิบหายต่อเนื่อง

ตอนนี้ ลองใช้ฟังก์ชันนี้สองสามครั้งกับโมเดลเริ่มต้นเพื่อดูว่าการสูญเสียลดลงหรือไม่

model = initial_model
losses = []
for _ in range(5):
  model = batch_train(model, sample_batch, 0.1)
  losses.append(batch_loss(model, sample_batch))
losses
[0.19690023, 0.13176313, 0.10113225, 0.08273812, 0.070301384]

การไล่ระดับสีตามลำดับของข้อมูลในเครื่อง

ตอนนี้ตั้งแต่ batch_train ปรากฏในการทำงานขอเขียนการฝึกอบรมที่คล้ายกันฟังก์ชั่น local_train ที่กินลำดับทั้งหมดสำหรับกระบวนการทั้งหมดจากผู้แทนเพียงชุดเดียว การคำนวณใหม่จะต้องใช้ในขณะนี้ tff.SequenceType(BATCH_TYPE) แทน BATCH_TYPE

LOCAL_DATA_TYPE = tff.SequenceType(BATCH_TYPE)

@tff.federated_computation(MODEL_TYPE, tf.float32, LOCAL_DATA_TYPE)
def local_train(initial_model, learning_rate, all_batches):

  @tff.tf_computation(LOCAL_DATA_TYPE, tf.float32)
  def _insert_learning_rate_to_sequence(dataset, learning_rate):
    return dataset.map(lambda x: (x, learning_rate))

  batches_with_learning_rate = _insert_learning_rate_to_sequence(all_batches, learning_rate)

  # Mapping function to apply to each batch.
  @tff.federated_computation(MODEL_TYPE, batches_with_learning_rate.type_signature.element)
  def batch_fn(model, batch_with_lr):
    batch, lr = batch_with_lr
    return batch_train(model, batch, lr)

  return tff.sequence_reduce(batches_with_learning_rate, initial_model, batch_fn)
str(local_train.type_signature)
'(<initial_model=<weights=float32[784,10],bias=float32[10]>,learning_rate=float32,all_batches=<x=float32[?,784],y=int32[?]>*> -> <weights=float32[784,10],bias=float32[10]>)'

มีรายละเอียดค่อนข้างน้อยที่ฝังอยู่ในโค้ดส่วนสั้นๆ นี้ มาดูรายละเอียดกันทีละรายการ

แรกในขณะที่เราจะได้ดำเนินการตรรกะนี้ทั้งหมดใน TensorFlow อาศัย tf.data.Dataset.reduce ที่จะดำเนินการตามลำดับเช่นเดียวกับวิธีการที่เราได้ทำมันก่อนหน้านี้เราได้เลือกใช้เวลานี้ในการแสดงตรรกะในภาษากาว เป็น tff.federated_computation เราได้ใช้ประกอบการ federated tff.sequence_reduce ในการดำเนินการลดลง

ผู้ประกอบการ tff.sequence_reduce ถูกนำมาใช้ในทำนองเดียวกันกับ tf.data.Dataset.reduce คุณสามารถคิดว่ามันเป็นหลักเช่นเดียวกับ tf.data.Dataset.reduce แต่สำหรับการใช้งานภายในคำนวณ federated ซึ่งในขณะที่คุณอาจจำไม่สามารถมีรหัส TensorFlow มันเป็นผู้ประกอบการที่มีแม่แบบพารามิเตอร์อย่างเป็นทางการ 3 tuple ที่ประกอบด้วยลำดับของ T -typed องค์ประกอบสถานะเริ่มต้นของการลด (เราจะเรียกมันเป็นนามธรรมเป็นศูนย์) บางชนิด U และผู้ประกอบการลดลงของ พิมพ์ (<U,T> -> U) ที่ alters รัฐของการลดโดยการประมวลผลองค์ประกอบเดียว ผลลัพธ์คือสถานะสุดท้ายของการลดลง หลังจากประมวลผลองค์ประกอบทั้งหมดตามลำดับ ในตัวอย่างของเรา สถานะของการลดลงคือโมเดลที่ได้รับการฝึกจากคำนำหน้าของข้อมูล และองค์ประกอบต่างๆ เป็นแบทช์ข้อมูล

ประการที่สองทราบว่าเราได้ใช้อีกครั้งหนึ่งในการคำนวณ ( batch_train ) เป็นส่วนประกอบภายในอื่น (ก local_train ) แต่ไม่ได้โดยตรง เราไม่สามารถใช้เป็นตัวดำเนินการลดได้เนื่องจากต้องใช้พารามิเตอร์เพิ่มเติม - อัตราการเรียนรู้ เพื่อแก้ปัญหานี้เรากำหนดฝังคำนวณ federated batch_fn ที่ผูกกับ local_train 's พารามิเตอร์ learning_rate ในร่างกายของตน อนุญาตให้ใช้การคำนวณย่อยที่กำหนดวิธีนี้เพื่อจับพารามิเตอร์ที่เป็นทางการของพาเรนต์ ตราบใดที่การคำนวณย่อยไม่ถูกเรียกใช้นอกเนื้อหาของพาเรนต์ คุณสามารถคิดว่ารูปแบบนี้เป็นเทียบเท่าของ functools.partial ในหลาม

ความหมายในทางปฏิบัติของการจับ learning_rate วิธีนี้เป็นของแน่นอนว่าค่าอัตราการเรียนรู้เดียวกันจะใช้ข้ามสำหรับกระบวนการทั้งหมด

ตอนนี้ขอลองฟังก์ชั่นการฝึกอบรมในท้องถิ่นที่กำหนดขึ้นใหม่ในลำดับทั้งหมดของข้อมูลจากผู้ใช้คนเดียวกันที่มีส่วนร่วมชุดตัวอย่าง (หลัก 5 )

locally_trained_model = local_train(initial_model, 0.1, federated_train_data[5])

มันทำงาน? เพื่อตอบคำถามนี้ เราจำเป็นต้องใช้การประเมิน

การประเมินท้องถิ่น

นี่เป็นวิธีหนึ่งในการใช้การประเมินในพื้นที่โดยเพิ่มการสูญเสียในชุดข้อมูลทั้งหมด (เราสามารถคำนวณค่าเฉลี่ยได้เช่นกัน เราจะปล่อยให้มันเป็นแบบฝึกหัดสำหรับผู้อ่าน)

@tff.federated_computation(MODEL_TYPE, LOCAL_DATA_TYPE)
def local_eval(model, all_batches):

  @tff.tf_computation(MODEL_TYPE, LOCAL_DATA_TYPE)
  def _insert_model_to_sequence(model, dataset):
    return dataset.map(lambda x: (model, x))

  model_plus_data = _insert_model_to_sequence(model, all_batches)

  @tff.tf_computation(tf.float32, batch_loss.type_signature.result)
  def tff_add(accumulator, arg):
    return accumulator + arg

  return tff.sequence_reduce(
      tff.sequence_map(
          batch_loss,
          model_plus_data), 0., tff_add)
str(local_eval.type_signature)
'(<model=<weights=float32[784,10],bias=float32[10]>,all_batches=<x=float32[?,784],y=int32[?]>*> -> float32)'

อีกครั้ง มีองค์ประกอบใหม่สองสามอย่างที่แสดงโดยโค้ดนี้ มาดูทีละองค์ประกอบกัน

ครั้งแรกที่เราได้ใช้สองผู้ประกอบการ federated ใหม่สำหรับการประมวลผลลำดับ: tff.sequence_map ที่ใช้ฟังก์ชั่นการทำแผนที่ T->U และลำดับของ T และส่งเสียงลำดับของ U ที่ได้รับจากการใช้ฟังก์ชั่นการทำแผนที่ pointwise และ tff.sequence_sum ว่า เพียงเพิ่มองค์ประกอบทั้งหมด ที่นี่ เราจับคู่ชุดข้อมูลแต่ละชุดกับค่าการสูญเสีย แล้วเพิ่มค่าการสูญเสียที่เป็นผลลัพธ์เพื่อคำนวณการสูญเสียทั้งหมด

โปรดทราบว่าเราจะได้ใช้อีกครั้ง tff.sequence_reduce แต่นี้จะไม่เป็นตัวเลือกที่ดีที่สุด - การลดขั้นตอนคือโดยความหมายตามลำดับในขณะที่การทำแผนที่และผลรวมสามารถคำนวณได้ในแบบคู่ขนาน เมื่อได้รับตัวเลือก วิธีที่ดีที่สุดคือยึดติดกับโอเปอเรเตอร์ที่ไม่จำกัดตัวเลือกการใช้งาน เพื่อที่ว่าเมื่อการคำนวณ TFF ของเราถูกคอมไพล์ในอนาคตเพื่อนำไปใช้กับสภาพแวดล้อมเฉพาะ เราสามารถใช้ประโยชน์จากโอกาสที่เป็นไปได้ทั้งหมดได้เร็วขึ้น , ปรับขนาดได้มากขึ้น, การดำเนินการที่มีประสิทธิภาพของทรัพยากรมากขึ้น

ประการที่สองทราบว่าเช่นเดียวกับใน local_train ฟังก์ชั่นที่เราต้องการส่วนประกอบ ( batch_loss ) ใช้พารามิเตอร์มากกว่าสิ่งที่ผู้ประกอบการ federated ( tff.sequence_map ) คาดว่าเพื่อให้เราอีกครั้งกำหนดบางส่วนเวลาในบรรทัดนี้โดยตรงโดยห่อ lambda เป็น tff.federated_computation ใช้ห่อใกล้เคียงกับฟังก์ชั่นเป็นอาร์กิวเมนต์เป็นวิธีที่แนะนำให้ใช้ tff.tf_computation ฝัง TensorFlow ตรรกะในฉิบหาย

ตอนนี้เรามาดูกันว่าการฝึกอบรมของเราได้ผลหรือไม่

print('initial_model loss =', local_eval(initial_model,
                                         federated_train_data[5]))
print('locally_trained_model loss =',
      local_eval(locally_trained_model, federated_train_data[5]))
initial_model loss = 23.025854
locally_trained_model loss = 0.43484688

อันที่จริงความสูญเสียลดลง แต่จะเกิดอะไรขึ้นถ้าเราประเมินมันในข้อมูลของผู้ใช้รายอื่น

print('initial_model loss =', local_eval(initial_model,
                                         federated_train_data[0]))
print('locally_trained_model loss =',
      local_eval(locally_trained_model, federated_train_data[0]))
initial_model loss = 23.025854
locally_trained_model loss = 74.50075

อย่างที่คาดไว้ สิ่งต่างๆ แย่ลง รูปแบบที่ได้รับการฝึกฝนที่จะรับรู้ 5 และได้เคยเห็น 0 สิ่งนี้ทำให้เกิดคำถาม - การฝึกอบรมในพื้นที่ส่งผลต่อคุณภาพของแบบจำลองจากมุมมองทั่วโลกอย่างไร

การประเมินโดยสหพันธ์

นี่คือจุดสำคัญในการเดินทางของเรา ซึ่งในที่สุดเราก็วนกลับมาที่ประเภทรวมและการคำนวณแบบรวมศูนย์ ซึ่งเป็นหัวข้อที่เราเริ่มต้น ต่อไปนี้คือคำจำกัดความประเภท TFF คู่สำหรับโมเดลที่เริ่มต้นที่เซิร์ฟเวอร์ และข้อมูลที่เหลืออยู่บนไคลเอ็นต์

SERVER_MODEL_TYPE = tff.type_at_server(MODEL_TYPE)
CLIENT_DATA_TYPE = tff.type_at_clients(LOCAL_DATA_TYPE)

ด้วยคำจำกัดความทั้งหมดที่นำมาใช้จนถึงตอนนี้ การแสดงการประเมินแบบรวมศูนย์ใน TFF เป็นแบบแผนเดียว - เราแจกจ่ายแบบจำลองไปยังลูกค้า ให้ลูกค้าแต่ละรายเรียกใช้การประเมินในพื้นที่ในส่วนข้อมูลภายในเครื่อง จากนั้นจึงหาค่าเฉลี่ยของการสูญเสีย นี่เป็นวิธีหนึ่งในการเขียนสิ่งนี้

@tff.federated_computation(SERVER_MODEL_TYPE, CLIENT_DATA_TYPE)
def federated_eval(model, data):
  return tff.federated_mean(
      tff.federated_map(local_eval, [tff.federated_broadcast(model),  data]))

เราได้เห็นแล้วตัวอย่างของ tff.federated_mean และ tff.federated_map ในสถานการณ์ที่เรียบง่ายและในระดับที่ใช้งานง่ายที่พวกเขาทำงานตามคาด แต่มีมากขึ้นในส่วนนี้ของรหัสกว่าตรงตาจึงขอไปกว่านั้นอย่างรอบคอบ

อันดับแรกให้แบ่งลงให้ลูกค้าเรียกใช้การประเมินผลแต่ละท้องถิ่นในส่วนของท้องถิ่นเป็นส่วนหนึ่งของข้อมูล ในขณะที่คุณอาจจำได้จากส่วนที่ก่อนหน้านี้ local_eval มีลายเซ็นชนิดของแบบฟอร์ม (<MODEL_TYPE, LOCAL_DATA_TYPE> -> float32)

ผู้ประกอบการ federated tff.federated_map เป็นแม่แบบที่ยอมรับเป็นพารามิเตอร์ 2 tuple ที่ประกอบด้วยฟังก์ชั่นการทำแผนที่บางประเภท T->U และค่า federated ประเภท {T}@CLIENTS (กล่าวคือเป็นคนละเรื่องกับสมาชิกของ ประเภทเดียวกันเป็นพารามิเตอร์ของฟังก์ชั่นการทำแผนที่) และผลตอบแทนที่เป็นผลมาจากประเภท {U}@CLIENTS

เนื่องจากเราให้อาหารกำลัง local_eval เป็นฟังก์ชั่นการทำแผนที่จะนำไปใช้บนพื้นฐานต่อลูกค้าอาร์กิวเมนต์ที่สองควรจะมีประเภท federated {<MODEL_TYPE, LOCAL_DATA_TYPE>}@CLIENTS คือในศัพท์ในส่วนที่ก่อนหน้านี้ที่ควร เป็นทูเพิลสหพันธรัฐ ลูกค้าแต่ละคนควรถือชุดเต็มของข้อโต้แย้ง local_eval เป็น consituent สมาชิก แต่เรากำลังให้อาหารมัน 2 องค์ประกอบหลาม list เกิดอะไรขึ้นที่นี่?

แท้จริงนี้คือตัวอย่างของประเภทหล่อนัยในฉิบหายคล้ายกับปลดเปลื้องประเภทนัยคุณอาจพบที่อื่นเช่นเมื่อคุณกินอาหาร int ฟังก์ชั่นที่ยอมรับ float ณ จุดนี้มีการใช้การหล่อโดยนัยเพียงเล็กน้อย แต่เราวางแผนที่จะทำให้มันแพร่หลายมากขึ้นใน TFF เพื่อลดความซับซ้อนของต้นแบบ

หล่อนัยที่นำมาใช้ในกรณีนี้คือความเท่าเทียมกันระหว่าง tuples federated ของฟอร์ม {<X,Y>}@Z และ tuples ของ federated ค่า <{X}@Z,{Y}@Z> ในขณะที่อย่างเป็นทางการทั้งสองมีลายเซ็นชนิดที่แตกต่างกันมองมันจากมุมมองของโปรแกรมเมอร์ของอุปกรณ์ในแต่ละ Z ถือสองหน่วยของข้อมูล X และ Y จะเกิดอะไรขึ้นที่นี่ไม่ได้เป็นเหมือน zip ในหลามและแน่นอนเรามีผู้ประกอบการ tff.federated_zip ที่ช่วยให้คุณดำเนินการแปลงอย่างชัดเจนเช่น เมื่อ tff.federated_map พบ tuple เป็นอาร์กิวเมนต์ที่สองมันก็จะเรียก tff.federated_zip สำหรับคุณ

ป.ร. ให้ไว้ข้างต้นแล้วคุณควรจะสามารถที่จะรับรู้การแสดงออก tff.federated_broadcast(model) เป็นคิดเป็นค่าฉิบหายประเภท {MODEL_TYPE}@CLIENTS และ data เป็นค่าของชนิดฉิบหาย {LOCAL_DATA_TYPE}@CLIENTS (หรือเพียงแค่ CLIENT_DATA_TYPE ) ทั้งสองได้รับการกรองร่วมกันผ่านนัย tff.federated_zip ในรูปแบบอาร์กิวเมนต์ที่สองที่จะ tff.federated_map

ผู้ประกอบการ tff.federated_broadcast ตามที่คุณคาดหวังเพียงแค่ถ่ายโอนข้อมูลจากเซิร์ฟเวอร์ไปยังลูกค้า

ตอนนี้เรามาดูกันว่าการฝึกอบรมในพื้นที่ของเราส่งผลต่อการสูญเสียโดยเฉลี่ยในระบบอย่างไร

print('initial_model loss =', federated_eval(initial_model,
                                             federated_train_data))
print('locally_trained_model loss =',
      federated_eval(locally_trained_model, federated_train_data))
initial_model loss = 23.025852
locally_trained_model loss = 54.432625

เป็นไปตามคาด ขาดทุนเพิ่มขึ้น เพื่อปรับปรุงโมเดลสำหรับผู้ใช้ทุกคน เราจะต้องฝึกอบรมเกี่ยวกับข้อมูลของทุกคน

สหพันธ์การฝึกอบรม

วิธีที่ง่ายที่สุดในการนำการฝึกอบรมแบบสหพันธรัฐไปใช้คือการฝึกอบรมในพื้นที่ จากนั้นจึงหาค่าเฉลี่ยของแบบจำลอง สิ่งนี้ใช้การสร้างบล็อคและรูปแบบเดียวกันกับที่เราได้พูดคุยกันไปแล้วดังที่คุณเห็นด้านล่าง

SERVER_FLOAT_TYPE = tff.type_at_server(tf.float32)


@tff.federated_computation(SERVER_MODEL_TYPE, SERVER_FLOAT_TYPE,
                           CLIENT_DATA_TYPE)
def federated_train(model, learning_rate, data):
  return tff.federated_mean(
      tff.federated_map(local_train, [
          tff.federated_broadcast(model),
          tff.federated_broadcast(learning_rate), data
      ]))

ทราบว่าในการดำเนินการเต็มรูปแบบที่โดดเด่นของสหพันธ์ Averaging ให้โดย tff.learning มากกว่าค่าเฉลี่ยรุ่นที่เราชอบที่จะเฉลี่ยสันดอนรุ่นสำหรับจำนวนของเหตุผลเช่นความสามารถในการตัดบรรทัดฐานการปรับปรุงสำหรับการบีบอัด ฯลฯ .

มาดูกันว่าการฝึกได้ผลหรือไม่โดยรันการฝึกสองสามรอบและเปรียบเทียบผลเสียเฉลี่ยก่อนและหลัง

model = initial_model
learning_rate = 0.1
for round_num in range(5):
  model = federated_train(model, learning_rate, federated_train_data)
  learning_rate = learning_rate * 0.9
  loss = federated_eval(model, federated_train_data)
  print('round {}, loss={}'.format(round_num, loss))
round 0, loss=21.60552215576172
round 1, loss=20.365678787231445
round 2, loss=19.27480125427246
round 3, loss=18.311111450195312
round 4, loss=17.45725440979004

เพื่อความสมบูรณ์ ตอนนี้ ให้เรียกใช้ข้อมูลการทดสอบเพื่อยืนยันว่าแบบจำลองของเรามีภาพรวมที่ดี

print('initial_model test loss =',
      federated_eval(initial_model, federated_test_data))
print('trained_model test loss =', federated_eval(model, federated_test_data))
initial_model test loss = 22.795593
trained_model test loss = 17.278767

นี่เป็นการสรุปบทช่วยสอนของเรา

แน่นอน ตัวอย่างที่เข้าใจง่ายของเราไม่ได้สะท้อนถึงสิ่งที่คุณต้องทำในสถานการณ์ที่สมจริงมากขึ้น ตัวอย่างเช่น เราไม่ได้คำนวณเมตริกอื่นนอกจากการสูญเสีย เราขอแนะนำให้คุณเพื่อการศึกษา การดำเนินงาน ของสหพันธ์เฉลี่ย tff.learning เป็นตัวอย่างที่สมบูรณ์มากขึ้นและเป็นวิธีที่จะแสดงให้เห็นถึงบางส่วนของการเขียนโค้ดที่เราต้องการที่จะส่งเสริมให้