Lưu ngày! Google I / O hoạt động trở lại từ ngày 18 đến 20 tháng 5 Đăng ký ngay
Trang này được dịch bởi Cloud Translation API.
Switch to English

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 loạt bài gồm hai phần trình bày cách triển khai các loại thuật toán liên hợp tùy chỉnh trong TFF bằng cách sử dụng Lõi liên kết (FC) , đóng vai trò là nền tảng cho lớp Học liên kết (FL) ( tff.learning ) .

Chúng tôi khuyến khích bạn đọc phần đầu tiên của loạt bài này , phần giới thiệu một số khái niệm chính và những điều trừu tượng về lập trình được 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 đào tạo và đánh giá liên hợp.

Chúng tôi khuyến khích bạn xem lại hướng dẫn phân loại hình ảnhtạo văn bản để có phần giới thiệu cấp cao hơn và nhẹ nhàng hơn về các API học tập liên kết của TFF, vì chúng sẽ giúp bạn đưa các khái niệm mà chúng tôi mô tả ở đây vào ngữ 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 Hướng dẫn cài đặt để đượ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

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

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

Như trong Học liên kết để phân loại hình ảnh , chúng ta sẽ sử dụng ví dụ MNIST, nhưng vì đây là hướng dẫn cấp thấp, chúng ta sẽ bỏ qua API tff.simulationtff.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()
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,))]

Dữ liệu đến dưới dạng các 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 các phép tính 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. Theo thông lệ, chúng tôi sẽ cấu trúc mỗi lô thành một cặp tenxơ có tên là xy , mỗi lô có kích thước lô hàng đầu. Trong khi ở đó, chúng tôi cũng sẽ làm phẳng mỗi hình ảnh thành một vector 784 phần tử và chia tỷ lệ các pixel trong đó thành phạm vi 0..1 để chúng tôi không phải làm 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)]

Để kiểm tra sự tỉnh táo nhanh chóng, chúng ta hãy nhìn vào tensor Y trong lô dữ liệu cuối cùng được đóng góp bởi khách hàng thứ năm (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, để gọn nhẹ, chúng tôi ngay lập tức trang trí các hàm giới thiệu logic TensorFlow 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ẫu mà chúng tôi khuyên dùng. 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 hoàn toàn và sau đó được nhập lại nhất thiết sẽ mất một số siêu dữ liệu và hạn chế 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 thực sự khuyên bạn nên viết logic TF phức tạp dưới dạng các hàm Python độc lập (nghĩa là không có trang trí tff.tf_computation ). Bằng cách này, logic TensorFlow có thể được phát triển và kiểm tra bằng cách sử dụng các công cụ và thực tiễn tốt nhất của TF (như chế độ háo hức), trước khi tuần tự hóa tính toán cho TFF (ví dụ: bằng cách gọi tff.tf_computation với một hàm Python làm đố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. Vì kích thước của các lô dữ liệu có thể khác nhau, chúng tôi đặt thứ nguyên lô thành None để chỉ ra rằng kích thước của thứ nguyên này là không xác định.

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 cuộc thảo luận trong phần 1 , nơi chúng ta đã giải thích rằng mặc dù chúng ta có thể diễn đạt logic của các phép tính TFF bằng Python, nhưng các phép tính TFF không phải là Python. Biểu tượng BATCH_TYPE được định nghĩa ở trên đại diện cho một đặc tả kiểu TFF trừu tượng. Điều quan trọng là phải phân biệt kiểu TFF trừu tượng này với các kiểu biểu diễn Python cụ thể, ví dụ: các vùng chứa như dict hoặc collections.namedtuple có thể được sử dụng để đại diện cho kiểu TFF trong phần thân của một hàm Python. Không giống như Python, TFF có một phương thức khởi tạo kiểu trừu tượng duy nhất tff.StructType cho các vùng chứa giống như tuple, với các phần tử có thể được đặt tên riêng hoặc không được đặt 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.

Bây giờ chúng ta hãy xác định kiểu TFF của các tham số mô hình, một lần nữa dưới dạng TFF có tên là bộ trọng sốđộ chệch .

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í @tff.tf_computation . @tf.function bên trong trình trang trí @tff.tf_computation . Điều này cho phép chúng tôi viết TF bằng Python giống như ngữ nghĩa mặc dù đã ở bên trong ngữ cảnh tf.Graph được tạo bởi trình trang trí 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)

Như mong đợi, tính toán batch_loss trả về tổn thất float32 cho mô hình và một lô 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 bộ 2 tham số chính thức; bạn có thể nhận ra loại 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)'

Để 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.3025854

Lưu ý rằng chúng tôi cung cấp cho phép tính TFF với mô hình ban đầu được định nghĩa là một dict , mặc dù phần thân của hàm Python xác định nó sử dụng các tham số model['weight']model['weight']model['bias'] . Các đối số của lệnh gọi batch_loss không chỉ đơn giản được chuyển đến phần nội dung của hàm đó.

Điều gì xảy ra khi chúng ta gọi batch_loss ? Phần thân Python của batch_loss đã được theo dõi và tuần tự hóa trong ô ở trên nơi nó được xác định. TFF đóng vai trò là người gọi tới batch_loss tại thời điểm định nghĩa tính toán và là mục tiêu của lệnh gọi tại thời điểm 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. Tại thời điểm gọi, TFF sẽ chấp nhận hầu hết các loại vùng chứa Python tiêu chuẩn ( dict , list , tuple , collections.namedtuple , v.v.) dưới dạng đạ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ần độ dốc. Lưu ý cách xác định hàm này, chúng tôi sử dụng batch_loss làm thành phần con. Bạn có thể gọi một tính toán được xây dựng bằng tff.tf_computation bên trong phần thân của một tính toán khác, mặc dù thông thường điều này là không cần thiết - như đã lưu ý ở trên, bởi vì tuần tự hóa làm mất một số thông tin gỡ lỗi, nên các tính toán phức tạp hơn thường thích viết và kiểm tra tất cả TensorFlow mà không có trình trang trí 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]>)'

Khi bạn gọi một hàm Python được trang trí bằng tff.tf_computation bên trong phần thân của một hàm khác như vậy, logic của phép tính TFF bên trong sẽ được nhúng (về cơ bản, nội tuyến) trong logic của hàm bên ngoài. Như đã lưu ý ở trên, nếu bạn đang viết cả hai phép tính, bạn nên đặt hàm bên trong (trong trường hợp này là batch_loss ) là một hàm Python hoặc tf.function thông thường hơn là một tff.tf_computation . Tuy nhiên, ở đây chúng tôi minh họa rằng việc gọi một tff.tf_computation bên trong một tff.tf_computation khác về cơ bản hoạt động như mong đợi. Điều này có thể cần thiết nếu, ví dụ, bạn không có mã Python xác định batch_loss , mà chỉ có biểu diễn TFF được tuần tự hóa 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.19690022, 0.13176313, 0.10113226, 0.082738124, 0.0703014]

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

Bây giờ, vì batch_train dường như hoạt động, hãy viết một hàm đào tạo tương tự local_train 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. Tính toán mới bây giờ sẽ cần sử dụng 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):

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

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.

Đầu tiên, mặc dù chúng tôi có thể triển khai logic này hoàn toàn trong TensorFlow, dựa vào tf.data.Dataset.reduce để xử lý trình tự tương tự như cách chúng tôi đã thực hiện trước đó, nhưng lần này chúng tôi đã chọn diễn đạt logic bằng ngôn ngữ keo. , dưới dạng tff.federated_computation . Chúng tôi đã sử dụng toán tử liên kết tff.sequence_reduce để thực hiện việc giảm bớt.

Toán tử tff.sequence_reduce được sử dụng tương tự như tf.data.Dataset.reduce . Bạn có thể nghĩ về nó về cơ bản giống như tf.data.Dataset.reduce , nhưng để sử dụng bên trong các tính toán liên hợp, như bạn có thể nhớ, không thể chứa mã TensorFlow. Nó là một toán tử khuôn mẫu với một bộ 3 tham số chính thức bao gồm một chuỗi các phần tử kiểu T , trạng thái ban đầu của phép giảm (chúng ta sẽ gọi nó một cách trừu tượng là không ) của một số kiểu Utoán tử giảm của gõ (<U,T> -> U) thay đổi trạng thái giảm bằng cách xử lý một phần 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 đã sử dụng một lần nữa một phép tính ( batch_train ) như một thành phần bên trong một tính toán khác ( local_train ), nhưng không sử dụ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 vấn đề này, chúng tôi xác định một batch_fn tính toán được liên kết nhúng liên kết với tham số learning_rate của local_train trong phần thân 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 cha 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 cha của nó. Bạn có thể coi mẫu này tương đương với functools.partial trong Python.

Tất nhiên, ngụ ý thực tế của việc nắm bắt tốc độ learning_rate theo cách này là cùng một giá trị tốc độ học tập được sử dụng trên tất cả các lô.

Bây giờ, chúng ta hãy thử chức năng đào tạo cục bộ 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):
  # 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)'

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 toán tử liên kết mới để xử lý trình tự: tff.sequence_map nhận hàm ánh xạ T->U và một chuỗi T , và tạo ra một chuỗi U thu được bằng cách áp dụng hàm ánh xạ theo tff.sequence_sumtff.sequence_sum 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 ta có thể đã sử dụng lại tff.sequence_reduce , nhưng đây không phải là lựa chọn tốt nhất - theo định nghĩa, quá trình tff.sequence_reduce là tuần tự, trong khi ánh xạ và tổng có thể được tính song song. Khi được 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 biên soạn 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 , 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 ) nhận nhiều tham số hơn những gì mà toán tử liên kết ( tff.sequence_map ) mong đợi, vì vậy chúng ta lại xác định một phần, lần này là nội tuyến bằng cách trực tiếp bao bọc một lambda dưới dạng tff.federated_computation . Sử dụng trình bao bọc nội dòng với một hàm làm đối số là cách được khuyến nghị sử dụng tff.tf_computation để nhúng logic TensorFlow vào 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.4348469

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. Người mẫu được huấn luyện để nhận ra số 5 và chưa bao giờ nhìn thấy số 0 . Điều này đặt ra câu hỏi - việc đà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ó, 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 các ví dụ về tff.federated_meantff.federated_map trong các tình huống đơn giản hơn và ở cấp độ trực quan, chúng hoạt động như mong đợi, nhưng có nhiều thứ trong phần mã này hơn là bắt mắt, vì vậy hãy xem xét nó một cách cẩn thận.

Đầu tiên, hãy chia nhỏ việc để 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ó . 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 biểu mẫu (<MODEL_TYPE, LOCAL_DATA_TYPE> -> float32) .

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

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

Thật vậy, đây là một ví dụ về kiểu ép kiểu ngầm định trong TFF, tương tự như kiểu ép kiểu ngầm định mà bạn có thể đã gặp ở những nơi khác, ví dụ: khi bạn cấp một int cho một hàm 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 việc đúc sẵn.

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

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

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

Bây giờ, 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ùng các khối xây dựng và người bảo trợ 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 triển khai Trung bình liên kết đầy đủ tính năng được cung cấp bởi tff.learning , thay vì tính trung bình các mô hình, chúng tôi thích tính trung bình các tff.learning của mô hình, vì một số lý do, ví dụ: khả năng cắt các định mức cập nhật, để nén, v.v. .

Chúng ta hãy xem liệu việc đà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.60552406311035
round 1, loss=20.365678787231445
round 2, loss=19.27480125427246
round 3, loss=18.31110954284668
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 tình huống 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 triển khai tính trung bình được liên kết trong tff.learning như một ví dụ đầy đủ hơn và như một cách để chứng minh một số phương pháp mã hóa mà chúng tôi muốn khuyến khích.