หน้านี้ได้รับการแปลโดย Cloud Translation API
Switch to English

อัลกอริทึมแบบรวมที่กำหนดเองส่วนที่ 2: การใช้งานการหาค่าเฉลี่ยแบบรวมศูนย์

ดูใน TensorFlow.org เรียกใช้ใน Google Colab ดูแหล่งที่มาบน GitHub

บทช่วยสอนนี้เป็นส่วนที่สองของชุดข้อมูลสองส่วนที่สาธิตวิธีการใช้อัลกอริทึมแบบรวมที่กำหนดเองใน TFF โดยใช้ Federated Core (FC) ซึ่งทำหน้าที่เป็นพื้นฐานสำหรับเลเยอร์ Federated Learning (FL) ( tff.learning ) .

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

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

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

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

ก่อนที่เราจะเริ่มลองเรียกใช้ตัวอย่าง "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

# TODO(b/148678573,b/148685415): must use the reference context because it
# supports unbounded references and tff.sequence_* intrinsics.
tff.backends.reference.set_reference_context()
@tff.federated_computation
def hello_world():
  return 'Hello, World!'

hello_world()
'Hello, World!'

การใช้งานค่าเฉลี่ยรวม

เช่นเดียวกับใน Federated Learning for Image Classification เราจะใช้ตัวอย่าง MNIST แต่เนื่องจากสิ่งนี้มีไว้เพื่อการสอนระดับต่ำเราจะข้าม Keras API และ tff.simulation เขียนโค้ดโมเดลดิบและสร้าง ชุดข้อมูลแบบรวมศูนย์ตั้งแต่เริ่มต้น

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

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

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

mnist_train, mnist_test = tf.keras.datasets.mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
11501568/11490434 [==============================] - 0s 0us/step

[(x.dtype, x.shape) for x in mnist_train]
[(dtype('uint8'), (60000, 28, 28)), (dtype('uint8'), (60000,))]

ข้อมูลมาในรูปแบบ Numpy อาร์เรย์หนึ่งที่มีรูปภาพและอีกอันมีป้ายกำกับตัวเลขโดยทั้งสองมิติจะอยู่เหนือตัวอย่างแต่ละตัวอย่าง ลองเขียนฟังก์ชันตัวช่วยที่จัดรูปแบบให้เข้ากันได้กับวิธีที่เราป้อนลำดับแบบรวมเข้ากับการคำนวณ TFF เช่นรายการของรายการ - รายการด้านนอกที่มีจำนวนมากกว่าผู้ใช้ (ตัวเลข) ซึ่งเป็นรายการภายในที่มีข้อมูลมากกว่ากลุ่มใน ลำดับของลูกค้าแต่ละราย ตามธรรมเนียมแล้วเราจะจัดโครงสร้างแต่ละชุดเป็นคู่ของเทนเซอร์ชื่อ x และ y โดยแต่ละชุดจะมีมิติข้อมูลชุดงานนำหน้า ในขณะนั้นเราจะทำให้ภาพแต่ละภาพแบนเป็นเวกเตอร์ 784 องค์ประกอบและปรับขนาดพิกเซลในภาพให้เป็นช่วง 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

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

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

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

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

ตอนนี้เรามากำหนดพารามิเตอร์แบบจำลองประเภท TFF อีกครั้งเป็น TFF ที่มีชื่อว่า tuple of weights and bias

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 โดยใช้ Python เหมือนความหมายแม้ว่าจะอยู่ในบริบท 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 ทูเพิลได้อย่างไร คุณสามารถจำประเภทของ batch_loss เป็น (<MODEL_TYPE,BATCH_TYPE> -> float32)

str(batch_loss.type_signature)
'(<<weights=float32[784,10],bias=float32[10]>,<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.3025854

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

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

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

ตอนนี้เรามากำหนดการคำนวณที่ใช้ฟังก์ชันการสูญเสียนี้เพื่อดำเนินการขั้นตอนเดียวของการไล่ระดับสี สังเกตว่าในการกำหนดฟังก์ชันนี้เราใช้ batch_loss เป็นส่วนประกอบย่อยอย่างไร คุณสามารถเรียกใช้การคำนวณที่สร้างขึ้นด้วย tff.tf_computation ภายในเนื้อหาของการคำนวณอื่นได้แม้ว่าโดยทั่วไปแล้วจะไม่จำเป็น - ตามที่ระบุไว้ข้างต้นเนื่องจากการทำให้เป็นอนุกรมจะสูญเสียข้อมูลการดีบักไปบางส่วนจึงมักนิยมใช้การคำนวณที่ซับซ้อนกว่าในการเขียนและทดสอบ 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)
'(<<weights=float32[784,10],bias=float32[10]>,<x=float32[?,784],y=int32[?]>,float32> -> <weights=float32[784,10],bias=float32[10]>)'

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

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

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.19690022, 0.13176313, 0.10113226, 0.082738124, 0.0703014]

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

ตอนนี้เนื่องจาก 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):

  # Mapping function to apply to each batch.
  @tff.federated_computation(MODEL_TYPE, BATCH_TYPE)
  def batch_fn(model, batch):
    return batch_train(model, batch, learning_rate)

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

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

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

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

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

ความหมายในทางปฏิบัติของการจับ 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):
  # TODO(b/120157713): Replace with `tff.sequence_average()` once implemented.
  return tff.sequence_sum(
      tff.sequence_map(
          tff.federated_computation(lambda b: batch_loss(model, b), BATCH_TYPE),
          all_batches))
str(local_eval.type_signature)
'(<<weights=float32[784,10],bias=float32[10]>,<x=float32[?,784],y=int32[?]>*> -> float32)'

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

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

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

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

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

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.4348469

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

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)

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

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

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

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

จากที่กล่าวมาข้างต้นคุณจะสามารถรับรู้นิพจน์ tff.federated_broadcast(model) โดยแสดงค่าของประเภท TFF {MODEL_TYPE}@CLIENTS และ data เป็นค่าของประเภท TFF {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
      ]))

โปรดทราบว่าในการใช้งานแบบเต็มรูปแบบของ Federated 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.60552406311035
round 1, loss=20.365678787231445
round 2, loss=19.27480125427246
round 3, loss=18.31110954284668
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 เป็นตัวอย่างที่สมบูรณ์ยิ่งขึ้นและเพื่อเป็นแนวทางในการแสดงแนวทางปฏิบัติในการเขียนโค้ดบางอย่างที่เราต้องการสนับสนุน