Thuật toán liên kết tùy chỉnh, Phần 2: Triển khai tính trung bình liên kết

Xem trên TensorFlow.org Chạy trong Google Colab Xem nguồn trên GitHub Tải xuống sổ ghi chép

Hướng dẫn này là phần thứ hai của một loạt bài gồm hai phần đó cho thấy làm thế nào để thực hiện các loại tùy chỉnh các thuật toán liên trong TFF sử dụng Federated Core (FC) , phục vụ như là một nền tảng cho sự (FL) Federated Học lớp ( tff.learning ) .

Chúng tôi khuyến khích bạn đầu tiên đọc phần đầu tiên của loạt bài này , trong đó giới thiệu một số khái niệm quan trọng và trừu tượng lập trình sử dụng ở đây.

Phần thứ hai của loạt bài này sử dụng các cơ chế được giới thiệu trong phần đầu để triển khai một phiên bản đơn giản của các thuật toán đánh giá và đào tạo liên hợp.

Chúng tôi khuyến khích bạn xem lại những phân loại hình ảnhthế hệ văn bản hướng dẫn cho một cấp cao hơn và giới thiệu nhẹ nhàng hơn để Federated Learning API của TFF, vì chúng sẽ giúp bạn đưa các khái niệm chúng tôi mô tả ở đây trong bối cảnh.

Trước khi chúng ta bắt đầu

Trước khi chúng tôi bắt đầu, hãy thử chạy ví dụ "Hello World" sau đây để đảm bảo rằng môi trường của bạn được thiết lập chính xác. Nếu nó không hoạt động, vui lòng tham khảo các cài đặt hướng dẫn để được hướng dẫn.

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

Triển khai Trung bình Liên kết

Như trong Federated Learning cho hình ảnh Phân loại , chúng ta sẽ sử dụng ví dụ MNIST, nhưng vì đây là dự định như là một hướng dẫn ở mức độ thấp, chúng ta sẽ bỏ qua các API Keras và tff.simulation , viết mã mô hình thô, và xây dựng một tập hợp dữ liệu liên kết từ đầu.

Chuẩn bị tập dữ liệu được liên kết

Để minh họa, chúng tôi sẽ mô phỏng một tình huống trong đó chúng tôi có dữ liệu từ 10 người dùng và mỗi người dùng đóng góp kiến ​​thức về cách nhận ra một chữ số khác nhau. Đây là khoảng như phi iid như nó được.

Đầu tiên, hãy tải dữ liệu MNIST chuẩn:

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,))]

Dữ liệu đến dưới dạng mảng Numpy, một mảng có hình ảnh và một mảng khác có nhãn chữ số, cả hai đều có kích thước đầu tiên đi qua các ví dụ riêng lẻ. Hãy viết một hàm trợ giúp định dạng nó theo cách tương thích với cách chúng tôi cung cấp các chuỗi liên kết vào tính toán TFF, tức là, dưới dạng danh sách các danh sách - danh sách bên ngoài bao gồm người dùng (chữ số), danh sách bên trong bao gồm các lô dữ liệu trong trình tự của từng khách hàng. Như thường lệ, chúng tôi sẽ cấu trúc từng lô như một cặp tensors tên xy , đều có kích thước hàng loạt hàng đầu. Trong khi ở đó, chúng tôi cũng sẽ làm phẳng từng hình ảnh vào một vector 784 phần tử và rescale các điểm ảnh trong nó vào 0..1 phạm vi, vì vậy mà chúng ta không cần phải lộn xộn logic mô hình với các chuyển đổi dữ liệu.

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

Là một kiểm tra sanity nhanh, nhìn let của tại Y tensor trong đợt cuối cùng của dữ liệu góp của khách hàng thứ năm (một trong những tương ứng với chữ số 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)

Để chắc chắn, chúng ta hãy cũng xem hình ảnh tương ứng với phần tử cuối cùng của lô đó.

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

Về việc kết hợp TensorFlow và TFF

Trong hướng dẫn này, cho chặt, chúng tôi ngay lập tức trang trí chức năng mà giới thiệu TensorFlow logic với tff.tf_computation . Tuy nhiên, đối với logic phức tạp hơn, đây không phải là mô hình mà chúng tôi đề xuất. Gỡ lỗi TensorFlow đã có thể là một thách thức và việc gỡ lỗi TensorFlow sau khi nó đã được tuần tự hóa đầy đủ và sau đó được nhập lại nhất thiết sẽ mất một số siêu dữ liệu và giới hạn khả năng tương tác, khiến việc gỡ lỗi càng trở nên khó khăn hơn.

Do đó, chúng tôi đề nghị bằng văn bản luận TF phức tạp như các chức năng Python độc lập (có nghĩa là, không tff.tf_computation trang trí). Bằng cách này logic TensorFlow thể được phát triển và thử nghiệm sử dụng thực hành tốt nhất và các công cụ TF (như chế độ háo hức), trước khi serializing việc tính toán cho TFF (ví dụ, bằng cách gọi tff.tf_computation với một hàm Python như là đối số).

Xác định một hàm mất mát

Bây giờ chúng ta đã có dữ liệu, hãy xác định một hàm mất mát mà chúng ta có thể sử dụng để đào tạo. Đầu tiên, hãy xác định kiểu đầu vào là TFF có tên là tuple. Kể từ khi kích thước của lô dữ liệu có thể thay đổi, chúng tôi thiết lập kích thước hàng loạt để None để chỉ ra rằng kích thước của không gian này là không rõ.

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

Bạn có thể tự hỏi tại sao chúng ta không thể chỉ định nghĩa một kiểu Python thông thường. Nhớ lại những cuộc thảo luận trong phần 1 , nơi mà chúng tôi đã giải thích rằng trong khi chúng ta có thể diễn tả logic của tính toán TFF sử dụng Python, dưới sự tính toán mui xe TFF không Python. Biểu tượng BATCH_TYPE định nghĩa ở trên đại diện cho một loại TFF đặc điểm kỹ thuật trừu tượng. Điều quan trọng là để phân biệt loại này TFF trừu tượng từ bê tông các loại Python đại diện, ví dụ như, container như dict hoặc collections.namedtuple mà có thể được sử dụng để thể hiện kiểu TFF trong cơ thể của một hàm Python. Không giống như Python, TFF có trừu tượng loại nhà xây dựng đơn tff.StructType cho tuple như container, với các yếu tố có thể được đặt tên riêng hoặc trái giấu tên. Kiểu này cũng được sử dụng để mô hình hóa các tham số chính thức của tính toán, vì tính toán TFF chính thức chỉ có thể khai báo một tham số và một kết quả - bạn sẽ thấy các ví dụ về điều này ngay sau đây.

Hãy là bây giờ xác định loại TFF các thông số mô hình, một lần nữa như một TFF tên tuple của trọng lượngthiên vị.

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

Với những định nghĩa đó, bây giờ chúng ta có thể xác định tổn thất cho một mô hình nhất định, trên một lô đơn lẻ. Lưu ý việc sử dụng @tf.function trang trí bên trong @tff.tf_computation trang trí. Điều này cho phép chúng tôi viết TF sử dụng Python như ngữ nghĩa mặc dù là bên trong một tf.Graph bối cảnh được tạo ra bởi tff.tf_computation trang trí.

# 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)

Đúng như dự đoán, tính toán batch_loss lợi nhuận float32 mất cho mô hình và một loạt dữ liệu duy nhất. Lưu ý cách MODEL_TYPEBATCH_TYPE đã được gộp lại với nhau thành một 2-tuple các thông số chính thức; bạn có thể nhận ra các loại batch_loss như (<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)'

Để kiểm tra sự tỉnh táo, hãy xây dựng một mô hình ban đầu chứa đầy các số không và tính toán sự mất mát của lô dữ liệu mà chúng tôi đã hình dung ở trên.

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

Lưu ý rằng chúng tôi ăn việc tính toán TFF với mô hình ban đầu được xác định như một dict , mặc dù các cơ quan chức năng Python rằng định nghĩa nó tiêu thụ thông số mô hình như model['weight']model['bias'] . Những lập luận của cuộc gọi để batch_loss không chỉ đơn giản là truyền cho cơ thể của chức năng đó.

Chuyện gì xảy ra khi chúng ta invoke batch_loss ? Cơ thể Python của batch_loss đã được truy tìm và đăng trên các tế bào trên, nơi nó được xác định. TFF đóng vai trò như người gọi để batch_loss lúc định nghĩa tính toán, và là mục tiêu của sự thỉnh nguyện lúc batch_loss được gọi. Trong cả hai vai trò, TFF đóng vai trò là cầu nối giữa hệ thống kiểu trừu tượng của TFF và các kiểu biểu diễn Python. Đồng thời gọi, TFF sẽ chấp nhận các loại Python container tiêu chuẩn nhất ( dict , list , tuple , collections.namedtuple , vv) như là cơ quan đại diện cụ thể của các bộ TFF trừu tượng. Ngoài ra, mặc dù như đã lưu ý ở trên, các phép tính TFF chính thức chỉ chấp nhận một tham số duy nhất, bạn có thể sử dụng cú pháp gọi Python quen thuộc với các đối số vị trí và / hoặc từ khóa trong trường hợp loại tham số là một bộ - nó hoạt động như mong đợi.

Giảm dần độ dốc trên một lô duy nhất

Bây giờ, hãy xác định một phép tính sử dụng hàm mất mát này để thực hiện một bước giảm độ dốc duy nhất. Lưu ý cách trong việc xác định chức năng này, chúng tôi sử dụng batch_loss như một tiểu hợp phần. Bạn có thể gọi một tính toán xây dựng với tff.tf_computation bên trong cơ thể tính toán khác, mặc dù thường này là không cần thiết - như đã nói ở trên, vì serialization thua một số thông tin gỡ lỗi, nó thường là một lợi thế cho tính toán phức tạp hơn để ghi và kiểm tra tất cả các TensorFlow mà không có sự tff.tf_computation trang trí.

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

Khi bạn gọi một hàm Python trang trí với tff.tf_computation trong cơ thể của một chức năng như vậy, logic của việc tính toán bên trong TFF được nhúng (về cơ bản, inlined) trong logic của một bên ngoài. Như đã nói ở trên, nếu bạn đang viết cả hai tính toán, có khả năng thích hợp hơn để thực hiện các chức năng bên trong ( batch_loss trong trường hợp này) một Python thường xuyên hoặc tf.function chứ không phải là một tff.tf_computation . Tuy nhiên, ở đây chúng tôi minh họa cho rằng gọi một tff.tf_computation bên khác về cơ bản hoạt động như mong đợi. Đây có thể là cần thiết nếu, ví dụ, bạn không có mã Python định batch_loss , nhưng chỉ đại diện TFF serialized của nó.

Bây giờ, hãy áp dụng chức năng này một vài lần cho mô hình ban đầu để xem liệu tổn thất có giảm hay không.

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]

Giảm dần độ dốc trên một chuỗi dữ liệu cục bộ

Bây giờ, kể từ khi batch_train dường như làm việc, chúng ta hãy viết thư tương tự như chức năng đào tạo local_train rằng tiêu thụ toàn bộ chuỗi của tất cả các lô từ một người dùng thay vì chỉ một lô duy nhất. Các tính toán mới sẽ cần đến nay tiêu thụ tff.SequenceType(BATCH_TYPE) thay vì 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]>)'

Có khá nhiều chi tiết bị chôn vùi trong đoạn mã ngắn này, chúng ta hãy xem xét từng chi tiết một.

Thứ nhất, trong khi chúng tôi có thể thực hiện logic này hoàn toàn bằng TensorFlow, dựa vào tf.data.Dataset.reduce để xử lý các chuỗi tương tự như cách chúng tôi đã thực hiện nó trước đó, chúng tôi đã chọn thời gian này để bày tỏ sự logic trong ngôn ngữ keo , như một tff.federated_computation . Chúng tôi đã sử dụng các nhà điều hành liên tff.sequence_reduce để thực hiện việc giảm.

Nhà điều hành tff.sequence_reduce được sử dụng tương tự như tf.data.Dataset.reduce . Bạn có thể nghĩ về nó như về cơ bản giống như tf.data.Dataset.reduce , nhưng để sử dụng trong tính toán liên kết, mà như bạn có thể nhớ, không thể chứa mã TensorFlow. Đó là một mẫu nhà điều hành với một tham số chính thức 3-tuple mà bao gồm một chuỗi các T -typed yếu tố, tình trạng ban đầu của việc giảm (chúng tôi sẽ đề cập đến nó một cách trừu tượng như zero) của một số loại U , và các nhà điều hành giảm(<U,T> -> U) mà làm thay đổi trạng thái của việc giảm bằng cách xử lý một yếu tố duy nhất. Kết quả là trạng thái cuối cùng của quá trình giảm, sau khi xử lý tất cả các phần tử theo thứ tự tuần tự. Trong ví dụ của chúng tôi, trạng thái giảm là mô hình được đào tạo trên một tiền tố của dữ liệu và các phần tử là các lô dữ liệu.

Thứ hai, lưu ý rằng chúng tôi đã một lần nữa sử dụng một tính toán ( batch_train ) như là một thành phần trong vòng một ( local_train ), nhưng không trực tiếp. Chúng tôi không thể sử dụng nó như một toán tử giảm vì nó cần một tham số bổ sung - tỷ lệ học tập. Để giải quyết này, chúng tôi xác định một tính toán liên nhúng batch_fn mà liên kết với các local_train 's tham số learning_rate trong cơ thể của nó. Phép tính con được định nghĩa theo cách này được phép nắm bắt một tham số chính thức của tham số chính thức của nó miễn là phép tính con không được gọi ra bên ngoài phần thân của nó. Bạn có thể nghĩ về mô hình này như là một tương đương với functools.partial bằng Python.

Hàm ý thực tế của chụp learning_rate cách này là, tất nhiên, rằng giá trị tỷ lệ học tương tự được sử dụng trên tất cả các lô.

Bây giờ, hãy thử các chức năng đào tạo địa phương mới được xác định trên toàn bộ chuỗi dữ liệu từ cùng một người dùng đã đóng góp lô mẫu (chữ số 5 ).

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

Nó đã hoạt động? Để trả lời câu hỏi này, chúng ta cần thực hiện đánh giá.

Đánh giá địa phương

Đây là một cách để thực hiện đánh giá cục bộ bằng cách cộng các tổn thất trên tất cả các lô dữ liệu (chúng tôi cũng có thể tính toán mức trung bình; chúng tôi sẽ để nó như một bài tập cho người đọc).

@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)'

Một lần nữa, có một vài yếu tố mới được minh họa bởi đoạn mã này, chúng ta hãy xem xét từng yếu tố một.

Đầu tiên, chúng tôi đã sử dụng hai nhà khai thác liên mới để xử lý chuỗi: tff.sequence_map mà phải mất một chức năng lập bản đồ T->U và một chuỗi các T , và phát ra một chuỗi các U thu được bằng cách áp dụng các chức năng lập bản đồ pointwise, và tff.sequence_sum rằng chỉ cần thêm tất cả các yếu tố. Ở đây, chúng tôi ánh xạ từng lô dữ liệu với một giá trị tổn thất, sau đó thêm các giá trị tổn thất kết quả để tính tổng tổn thất.

Lưu ý rằng chúng tôi có thể một lần nữa sử dụng tff.sequence_reduce , nhưng điều này sẽ không phải là lựa chọn tốt nhất - quá trình giảm là, theo định nghĩa, tuần tự, trong khi các bản đồ và số tiền có thể được tính song song. Khi được đưa ra một lựa chọn, tốt nhất nên gắn bó với các toán tử không hạn chế lựa chọn triển khai, để khi tính toán TFF của chúng tôi được tổng hợp trong tương lai để triển khai cho một môi trường cụ thể, người ta có thể tận dụng tất cả các cơ hội tiềm năng để nhanh hơn , khả năng mở rộng cao hơn, thực thi tiết kiệm tài nguyên hơn.

Thứ hai, lưu ý rằng cũng giống như trong local_train , hàm thành phần chúng ta cần ( batch_loss ) có các thông số nhiều hơn những gì các nhà điều hành liên ( tff.sequence_map ) hy vọng, vì vậy chúng tôi một lần nữa xác định một phần, thời gian này nội tuyến bằng cách trực tiếp gói một lambda như một tff.federated_computation . Sử dụng giấy gói nội tuyến với một chức năng như một cuộc tranh cãi là cách khuyến khích sử dụng tff.tf_computation nhúng TensorFlow logic trong TFF.

Bây giờ, chúng ta hãy xem liệu khóa đào tạo của chúng tôi có hiệu quả không.

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

Thật vậy, sự mất mát giảm đi. Nhưng điều gì sẽ xảy ra nếu chúng tôi đánh giá nó trên dữ liệu của người dùng khác?

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

Đúng như dự đoán, mọi thứ trở nên tồi tệ hơn. Mô hình này được huấn luyện để nhận 5 , và chưa bao giờ nhìn thấy một 0 . Điều này đặt ra câu hỏi - chương trình đào tạo tại địa phương đã tác động như thế nào đến chất lượng của mô hình từ quan điểm toàn cầu?

Đánh giá liên kết

Đây là điểm trong hành trình của chúng tôi, nơi cuối cùng chúng tôi quay trở lại các loại liên kết và tính toán liên hợp - chủ đề mà chúng tôi đã bắt đầu. Đây là một cặp định nghĩa về kiểu TFF cho mô hình bắt nguồn từ máy chủ và dữ liệu còn lại trên máy khách.

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

Với tất cả các định nghĩa được giới thiệu cho đến nay, việc thể hiện đánh giá liên hợp trong TFF là một công cụ duy nhất - chúng tôi phân phối mô hình cho khách hàng, để mỗi khách hàng gọi đánh giá cục bộ trên phần dữ liệu cục bộ của nó, và sau đó tính trung bình tổn thất. Đây là một cách để viết điều này.

@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]))

Chúng ta đã thấy ví dụ về tff.federated_meantff.federated_map trong các tình huống đơn giản hơn, và ở mức độ trực quan, họ làm việc như mong đợi, nhưng có nhiều trong phần này của mã hơn đáp ứng mắt, vì vậy chúng ta hãy đi qua nó một cách cẩn thận.

Đầu tiên, phá vỡ let của xuống cho mỗi khách hàng invoke đánh giá địa phương trên phần địa phương của một phần dữ liệu. Như bạn có thể nhớ lại từ các phần trước, local_eval có một loại chữ ký của form (<MODEL_TYPE, LOCAL_DATA_TYPE> -> float32) .

Các nhà điều hành liên tff.federated_map là một mẫu mà chấp nhận như một tham số 2-tuple đó bao gồm các chức năng lập bản đồ của một số loại T->U và một giá trị liên kiểu {T}@CLIENTS (ví dụ, với các thành phần thành viên của cùng loại như các tham số của hàm mapping), và trả về một kết quả của loại {U}@CLIENTS .

Kể từ khi chúng tôi ăn local_eval như một chức năng lập bản đồ để áp dụng trên một cơ sở cho mỗi khách hàng, đối số thứ hai nên có một loại liên {<MODEL_TYPE, LOCAL_DATA_TYPE>}@CLIENTS , tức là, trong danh mục của các phần trước, nó nên là một bộ liên hợp. Mỗi khách hàng nên tổ chức một cuộc tập hợp đầy đủ các đối số cho local_eval như một consituent thành viên. Thay vào đó, chúng ta ăn nó 2 yếu tố Python list . Chuyện gì đang xảy ra ở đây?

Trên thực tế, đây là một ví dụ về một loại diễn viên tiềm ẩn trong TFF, tương tự như phôi kiểu ngầm bạn có thể đã gặp ở đâu đó, ví dụ, khi bạn ăn một int để một chức năng chấp nhận một float . Tính năng đúc tiềm ẩn ít được sử dụng tại thời điểm này, nhưng chúng tôi dự định làm cho nó phổ biến hơn trong TFF như một cách để giảm thiểu quá trình đúc sẵn.

Các diễn viên tiềm ẩn đó là áp dụng trong trường hợp này là sự tương đương giữa các bộ liên có dạng {<X,Y>}@Z , và các bộ của liên giá trị <{X}@Z,{Y}@Z> . Trong khi chính thức, hai là chữ ký kiểu khác nhau, nhìn vào nó từ quan điểm của các lập trình viên, mỗi thiết bị trong Z giữ hai đơn vị dữ liệu XY . Chuyện gì xảy ra ở đây không phải là không giống như zip bằng Python, và trên thực tế, chúng tôi cung cấp một nhà điều hành tff.federated_zip cho phép bạn thực hiện chuyển đổi như vậy explicity. Khi tff.federated_map gặp một tuple như một đối số thứ hai, nó chỉ đơn giản gọi tff.federated_zip cho bạn.

Do ở trên, bây giờ bạn sẽ có thể nhận ra các biểu hiện tff.federated_broadcast(model) là đại diện cho một giá trị của TFF loại {MODEL_TYPE}@CLIENTS , và data như một giá trị của TFF loại {LOCAL_DATA_TYPE}@CLIENTS (hoặc đơn giản là CLIENT_DATA_TYPE ) , hai bị lọc với nhau thông qua một tiềm ẩn tff.federated_zip để tạo thành số thứ hai để tff.federated_map .

Nhà điều hành tff.federated_broadcast , như bạn mong muốn, bạn chỉ cần chuyển dữ liệu từ máy chủ cho khách hàng.

Bây giờ, chúng ta hãy xem việc đào tạo tại địa phương của chúng tôi đã ảnh hưởng như thế nào đến tổn thất trung bình trong hệ thống.

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

Quả thực, đúng như dự đoán, khoản lỗ đã tăng lên. Để cải thiện mô hình cho tất cả người dùng, chúng tôi sẽ cần đào tạo về dữ liệu của mọi người.

Đào tạo liên tục

Cách đơn giản nhất để thực hiện đào tạo liên đoàn là đào tạo tại địa phương, sau đó tính trung bình các mô hình. Điều này sử dụng các khối xây dựng và patters tương tự mà chúng ta đã thảo luận, như bạn có thể thấy bên dưới.

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

Lưu ý rằng trong việc thực hiện đầy đủ tính năng của trung bình Federated cung cấp bởi tff.learning , chứ không phải là trung bình các mô hình, chúng tôi muốn đồng bằng châu thổ mô hình trung bình, đối với một số lý do, ví dụ, khả năng cắt định mức cập nhật, cho nén, vv .

Chúng ta hãy xem liệu quá trình đào tạo có hiệu quả bằng cách chạy một vài vòng đào tạo và so sánh mức tổn thất trung bình trước và sau đó hay không.

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

Để hoàn thiện, bây giờ chúng ta cũng hãy chạy trên dữ liệu thử nghiệm để xác nhận rằng mô hình của chúng ta tổng quát hóa tốt.

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

Điều này kết thúc hướng dẫn của chúng tôi.

Tất nhiên, ví dụ đơn giản của chúng tôi không phản ánh một số điều bạn cần làm trong một kịch bản thực tế hơn - ví dụ: chúng tôi chưa tính toán các số liệu khác ngoài tổn thất. Chúng tôi khuyến khích bạn để nghiên cứu việc thực hiện của trung bình liên trong tff.learning như một ví dụ hoàn chỉnh hơn, và như là một cách để chứng minh một số hoạt động mã hóa chúng tôi muốn khuyến khích.