Эта страница была переведа с помощью Cloud Translation API.
Switch to English

Федеративное обучение для классификации изображений

Посмотреть на TensorFlow.org Запускаем в Google Colab Посмотреть исходный код на GitHub

В этом руководстве мы используем классический пример обучения MNIST, чтобы представить уровень API федеративного обучения (FL) для TFF, tff.learning - набор интерфейсов более высокого уровня, которые можно использовать для выполнения общих типов задач федеративного обучения, таких как федеративное обучение на основе пользовательских моделей, реализованных в TensorFlow.

Это руководство и API федеративного обучения предназначены в первую очередь для пользователей, которые хотят подключить свои собственные модели TensorFlow к TFF, рассматривая последние в основном как черный ящик. Для более глубокого понимания TFF и того, как реализовать собственные алгоритмы федеративного обучения, см. Учебные материалы по FC Core API - настраиваемые федеративные алгоритмы, часть 1 и часть 2 .

Чтобы tff.learning больше о tff.learning , перейдите к руководству по федеративному обучению для генерации текста , которое помимо tff.learning повторяющихся моделей также демонстрирует загрузку предварительно обученной сериализованной модели Keras для уточнения с федеративным обучением в сочетании с оценкой с помощью Keras.

Прежде чем мы начнем

Прежде чем мы начнем, выполните следующие действия, чтобы убедиться, что ваша среда настроена правильно. Если вы не видите приветствия, обратитесь к руководству по установке за инструкциями.


!pip install --quiet --upgrade tensorflow_federated_nightly
!pip install --quiet --upgrade nest_asyncio

import nest_asyncio
nest_asyncio.apply()

%load_ext tensorboard
import collections

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

np.random.seed(0)

tff.federated_computation(lambda: 'Hello, World!')()
b'Hello, World!'

Подготовка входных данных

Начнем с данных. Для федеративного обучения требуется объединенный набор данных, т. Е. Сбор данных от нескольких пользователей. Объединенные данные, как правило, не являются iid , что создает уникальный набор проблем.

Чтобы облегчить эксперименты, мы заполнили репозиторий TFF несколькими наборами данных, включая объединенную версию MNIST, которая содержит версию исходного набора данных NIST , который был повторно обработан с помощью Leaf, так что данные вводятся исходным автором цифры. Поскольку каждый писатель имеет уникальный стиль, этот набор данных демонстрирует поведение без идентификаторов, ожидаемое от федеративных наборов данных.

Вот как мы можем его загрузить.

emnist_train, emnist_test = tff.simulation.datasets.emnist.load_data()

Наборы данных, возвращаемые load_data() являются экземплярами tff.simulation.ClientData , интерфейса, который позволяет вам перечислять набор пользователей, создавать tf.data.Dataset , представляющий данные конкретного пользователя, и запрашивать состав отдельных элементов. Вот как вы можете использовать этот интерфейс для изучения содержимого набора данных. Имейте в виду, что, хотя этот интерфейс позволяет вам перебирать идентификаторы клиентов, это только функция данных моделирования. Как вы вскоре увидите, идентификационные данные клиентов не используются фреймворком федеративного обучения - их единственная цель - позволить вам выбрать подмножества данных для моделирования.

len(emnist_train.client_ids)
3383
emnist_train.element_type_structure
OrderedDict([('pixels', TensorSpec(shape=(28, 28), dtype=tf.float32, name=None)), ('label', TensorSpec(shape=(), dtype=tf.int32, name=None))])
example_dataset = emnist_train.create_tf_dataset_for_client(
    emnist_train.client_ids[0])

example_element = next(iter(example_dataset))

example_element['label'].numpy()
1
from matplotlib import pyplot as plt
plt.imshow(example_element['pixels'].numpy(), cmap='gray', aspect='equal')
plt.grid(False)
_ = plt.show()

PNG

Изучение неоднородности объединенных данных

Объединенные данные обычно не являются идентификаторами , пользователи обычно имеют разное распределение данных в зависимости от моделей использования. У некоторых клиентов может быть меньше обучающих примеров на устройстве из-за нехватки данных локально, в то время как у некоторых клиентов будет более чем достаточно обучающих примеров. Давайте рассмотрим эту концепцию неоднородности данных, типичную для федеративной системы, с данными EMNIST, которые у нас есть. Важно отметить, что этот глубокий анализ данных клиента доступен только нам, потому что это среда моделирования, в которой все данные доступны нам локально. В реальной производственной интегрированной среде вы не сможете проверять данные одного клиента.

Во-первых, давайте возьмем выборку данных одного клиента, чтобы получить представление о примерах на одном моделируемом устройстве. Поскольку набор данных, который мы используем, был запрограммирован уникальным писателем, данные одного клиента представляют собой почерк одного человека для выборки цифр от 0 до 9, имитируя уникальный «шаблон использования» одного пользователя.

## Example MNIST digits for one client
figure = plt.figure(figsize=(20, 4))
j = 0

for example in example_dataset.take(40):
  plt.subplot(4, 10, j+1)
  plt.imshow(example['pixels'].numpy(), cmap='gray', aspect='equal')
  plt.axis('off')
  j += 1

PNG

Теперь давайте визуализируем количество примеров на каждом клиенте для каждой цифровой метки MNIST. В федеративной среде количество примеров на каждом клиенте может незначительно варьироваться в зависимости от поведения пользователя.

# Number of examples per layer for a sample of clients
f = plt.figure(figsize=(12, 7))
f.suptitle('Label Counts for a Sample of Clients')
for i in range(6):
  client_dataset = emnist_train.create_tf_dataset_for_client(
      emnist_train.client_ids[i])
  plot_data = collections.defaultdict(list)
  for example in client_dataset:
    # Append counts individually per label to make plots
    # more colorful instead of one color per plot.
    label = example['label'].numpy()
    plot_data[label].append(label)
  plt.subplot(2, 3, i+1)
  plt.title('Client {}'.format(i))
  for j in range(10):
    plt.hist(
        plot_data[j],
        density=False,
        bins=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

PNG

Теперь давайте визуализируем среднее изображение для каждого клиента для каждой метки MNIST. Этот код будет производить среднее значение каждого пикселя для всех пользовательских примеров для одной метки. Мы увидим, что среднее изображение для одной цифры одного клиента будет отличаться от среднего изображения другого клиента для той же цифры из-за уникального стиля почерка каждого человека. Мы можем размышлять о том, как каждый локальный раунд обучения будет подталкивать модель в разном направлении для каждого клиента, поскольку мы учимся на собственных уникальных данных этого пользователя в этом локальном раунде. Позже в этом руководстве мы увидим, как мы можем принимать каждое обновление модели от всех клиентов и объединять их вместе в нашу новую глобальную модель, которая извлекается из собственных уникальных данных каждого нашего клиента.

# Each client has different mean images, meaning each client will be nudging
# the model in their own directions locally.

for i in range(5):
  client_dataset = emnist_train.create_tf_dataset_for_client(
      emnist_train.client_ids[i])
  plot_data = collections.defaultdict(list)
  for example in client_dataset:
    plot_data[example['label'].numpy()].append(example['pixels'].numpy())
  f = plt.figure(i, figsize=(12, 5))
  f.suptitle("Client #{}'s Mean Image Per Label".format(i))
  for j in range(10):
    mean_img = np.mean(plot_data[j], 0)
    plt.subplot(2, 5, j+1)
    plt.imshow(mean_img.reshape((28, 28)))
    plt.axis('off')

PNG

PNG

PNG

PNG

PNG

Пользовательские данные могут быть зашумленными и ненадежными. Например, глядя на данные клиента №2 выше, мы видим, что для метки 2 возможно, что были некоторые примеры с неправильной маркировкой, создающие более шумное среднее изображение.

Предварительная обработка входных данных

Поскольку данные уже являются tf.data.Dataset , предварительную обработку можно выполнить с помощью преобразований набора данных. Здесь мы 28x28 изображения 28x28 в массивы из 784 элементов, перемешиваем отдельные примеры, организуем их в пакеты и переименовываем функции из pixels и label в x и y для использования с Keras. Мы также добавляем repeat набора данных для запуска нескольких эпох.

NUM_CLIENTS = 10
NUM_EPOCHS = 5
BATCH_SIZE = 20
SHUFFLE_BUFFER = 100
PREFETCH_BUFFER= 10

def preprocess(dataset):

  def batch_format_fn(element):
    """Flatten a batch `pixels` and return the features as an `OrderedDict`."""
    return collections.OrderedDict(
        x=tf.reshape(element['pixels'], [-1, 784]),
        y=tf.reshape(element['label'], [-1, 1]))

  return dataset.repeat(NUM_EPOCHS).shuffle(SHUFFLE_BUFFER).batch(
      BATCH_SIZE).map(batch_format_fn).prefetch(PREFETCH_BUFFER)

Давайте проверим, что это сработало.

preprocessed_example_dataset = preprocess(example_dataset)

sample_batch = tf.nest.map_structure(lambda x: x.numpy(),
                                     next(iter(preprocessed_example_dataset)))

sample_batch
OrderedDict([('x', array([[1., 1., 1., ..., 1., 1., 1.],
       [1., 1., 1., ..., 1., 1., 1.],
       [1., 1., 1., ..., 1., 1., 1.],
       ...,
       [1., 1., 1., ..., 1., 1., 1.],
       [1., 1., 1., ..., 1., 1., 1.],
       [1., 1., 1., ..., 1., 1., 1.]], dtype=float32)), ('y', array([[2],
       [1],
       [2],
       [3],
       [6],
       [0],
       [1],
       [4],
       [1],
       [0],
       [6],
       [9],
       [9],
       [3],
       [6],
       [1],
       [4],
       [8],
       [0],
       [2]], dtype=int32))])

У нас есть почти все строительные блоки для построения объединенных наборов данных.

Один из способов передать объединенные данные в TFF при моделировании - это просто в виде списка Python, в котором каждый элемент списка содержит данные отдельного пользователя, будь то в виде списка или в виде tf.data.Dataset . Поскольку у нас уже есть интерфейс, который предоставляет последнее, давайте воспользуемся им.

Вот простая вспомогательная функция, которая создаст список наборов данных из заданного набора пользователей в качестве входных данных для цикла обучения или оценки.

def make_federated_data(client_data, client_ids):
  return [
      preprocess(client_data.create_tf_dataset_for_client(x))
      for x in client_ids
  ]

Теперь, как нам выбирать клиентов?

В типичном сценарии федеративного обучения мы имеем дело с потенциально очень большой группой пользовательских устройств, только часть из которых может быть доступна для обучения в данный момент времени. Это имеет место, например, когда клиентские устройства являются мобильными телефонами, которые участвуют в обучении только тогда, когда они подключены к источнику питания, отключены от сети с измерением и в противном случае находятся в режиме ожидания.

Конечно, мы находимся в среде моделирования, и все данные доступны локально. Обычно при запуске моделирования мы просто выбираем случайное подмножество клиентов, которые будут участвовать в каждом раунде обучения, обычно разные в каждом раунде.

Тем не менее, как вы можете узнать, изучив документ об алгоритме федеративного усреднения , достижение сходимости в системе со случайно выбранными подмножествами клиентов в каждом раунде может занять некоторое время, и было бы непрактично проводить сотни раундов в это интерактивное руководство.

Вместо этого мы сделаем выборку набора клиентов один раз и повторно используем тот же набор в раундах, чтобы ускорить сходимость (намеренно подгоняя под данные этих нескольких пользователей). Мы оставляем читателю в качестве упражнения изменить это руководство для имитации случайной выборки - это довольно легко сделать (как только вы это сделаете, имейте в виду, что приведение модели к схождению может занять некоторое время).

sample_clients = emnist_train.client_ids[0:NUM_CLIENTS]

federated_train_data = make_federated_data(emnist_train, sample_clients)

print('Number of client datasets: {l}'.format(l=len(federated_train_data)))
print('First dataset: {d}'.format(d=federated_train_data[0]))
Number of client datasets: 10
First dataset: <DatasetV1Adapter shapes: OrderedDict([(x, (None, 784)), (y, (None, 1))]), types: OrderedDict([(x, tf.float32), (y, tf.int32)])>

Создание модели с Керасом

Если вы используете Keras, у вас, вероятно, уже есть код, создающий модель Keras. Вот пример простой модели, которая подойдет для наших нужд.

def create_keras_model():
  return tf.keras.models.Sequential([
      tf.keras.layers.Input(shape=(784,)),
      tf.keras.layers.Dense(10, kernel_initializer='zeros'),
      tf.keras.layers.Softmax(),
  ])

Чтобы использовать любую модель с TFF, ее необходимо обернуть в экземпляр интерфейса tff.learning.Model , который предоставляет методы для штамповки прямого прохода модели, свойств метаданных и т. Д., Аналогично Keras, но также вводит дополнительные элементы, такие как способы управления процессом вычисления объединенных метрик. Давайте пока не будем об этом беспокоиться; если у вас есть модель Keras, подобная той, которую мы только что определили выше, вы можете настроить ее в TFF, вызвав tff.learning.from_keras_model , передав модель и образец пакета данных в качестве аргументов, как показано ниже.

def model_fn():
  # We _must_ create a new model here, and _not_ capture it from an external
  # scope. TFF will call this within different graph contexts.
  keras_model = create_keras_model()
  return tff.learning.from_keras_model(
      keras_model,
      input_spec=preprocessed_example_dataset.element_spec,
      loss=tf.keras.losses.SparseCategoricalCrossentropy(),
      metrics=[tf.keras.metrics.SparseCategoricalAccuracy()])

Обучение модели на объединенных данных

Теперь, когда у нас есть модель, обернутая как tff.learning.Model для использования с TFF, мы можем позволить TFF построить алгоритм федеративного усреднения, вызвав вспомогательную функцию tff.learning.build_federated_averaging_process , как tff.learning.build_federated_averaging_process ниже.

Имейте в виду, что аргумент должен быть конструктором (например, model_fn выше), а не уже model_fn экземпляром, чтобы создание вашей модели могло происходить в контексте, контролируемом TFF ​​(если вам интересно узнать о причинах мы рекомендуем вам прочитать последующее руководство по пользовательским алгоритмам ).

Одно важное замечание по поводу приведенного ниже алгоритма федеративного усреднения: есть 2 оптимизатора: оптимизатор _client и оптимизатор _server. Оптимизатор _client используется только для вычисления обновлений локальной модели на каждом клиенте. Оптимизатор _server применяет усредненное обновление к глобальной модели на сервере. В частности, это означает, что выбор оптимизатора и используемой скорости обучения может отличаться от тех, которые вы использовали для обучения модели на стандартном наборе данных iid. Мы рекомендуем начинать с обычного SGD, возможно, с меньшей скоростью обучения, чем обычно. Скорость обучения, которую мы используем, не была тщательно настроена, не стесняйтесь экспериментировать.

iterative_process = tff.learning.build_federated_averaging_process(
    model_fn,
    client_optimizer_fn=lambda: tf.keras.optimizers.SGD(learning_rate=0.02),
    server_optimizer_fn=lambda: tf.keras.optimizers.SGD(learning_rate=1.0))

Что только что произошло? TFF построил пару объединенных вычислений и упаковал их в tff.templates.IterativeProcess в котором эти вычисления доступны как пара свойств initialize и next .

Вкратце, федеративные вычисления - это программы на внутреннем языке TFF, которые могут выражать различные федеративные алгоритмы (вы можете найти больше об этом в руководстве по пользовательским алгоритмам ). В этом случае два вычисления, сгенерированные и упакованные в iterative_process реализуют объединенное усреднение .

Целью TFF является определение вычислений таким образом, чтобы они могли выполняться в реальных настройках федеративного обучения, но в настоящее время реализована только среда выполнения моделирования локального выполнения. Чтобы выполнить вычисление в симуляторе, вы просто вызываете его как функцию Python. Эта интерпретируемая среда по умолчанию не предназначена для обеспечения высокой производительности, но ее будет достаточно для этого руководства; мы ожидаем предоставить более производительные среды выполнения моделирования, чтобы облегчить более масштабные исследования в будущих выпусках.

Начнем с вычисления initialize . Как и все федеративные вычисления, вы можете рассматривать это как функцию. Вычисление не принимает аргументов и возвращает один результат - представление состояния процесса федеративного усреднения на сервере. Хотя мы не хотим углубляться в детали TFF, может быть полезно посмотреть, как это состояние выглядит. Вы можете представить это следующим образом.

str(iterative_process.initialize.type_signature)
'( -> <model=<trainable=<float32[784,10],float32[10]>,non_trainable=<>>,optimizer_state=<int64>,delta_aggregate_state=<>,model_broadcast_state=<>>@SERVER)'

Хотя приведенная выше сигнатура типа может сначала показаться немного загадочной, вы можете распознать, что состояние сервера состоит из model (исходные параметры модели для MNIST, которые будут распространяться на все устройства) и optimizer_state (дополнительная информация, поддерживаемая сервером, например, количество раундов для использования в расписаниях гиперпараметров и т. д.).

Давайте вызовем вычисление initialize чтобы построить состояние сервера.

state = iterative_process.initialize()

Второе из пары объединенных вычислений, next , представляет собой один раунд федеративного усреднения, который состоит из передачи состояния сервера (включая параметры модели) клиентам, обучения их локальным данным на устройстве, сбора и усреднения обновлений модели. , и производство новой обновленной модели на сервере.

Концептуально вы можете думать о next как о сигнатуре функционального типа, которая выглядит следующим образом.

SERVER_STATE, FEDERATED_DATA -> SERVER_STATE, TRAINING_METRICS

В частности, следует думать о next() не как о функции, которая выполняется на сервере, а как о декларативном функциональном представлении всего децентрализованного вычисления - некоторые входные данные предоставляются сервером ( SERVER_STATE ), но каждый из участвующих устройство предоставляет свой собственный локальный набор данных.

Давайте проведем один раунд обучения и визуализируем результаты. Мы можем использовать объединенные данные, которые мы уже создали выше, для выборки пользователей.

state, metrics = iterative_process.next(state, federated_train_data)
print('round  1, metrics={}'.format(metrics))
round  1, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.12037037312984467,loss=3.0108425617218018>>

Давайте проведем еще несколько раундов. Как отмечалось ранее, обычно на этом этапе вы выбираете подмножество данных моделирования из новой случайно выбранной выборки пользователей для каждого раунда, чтобы смоделировать реалистичное развертывание, в котором пользователи постоянно приходят и уходят, но в этой интерактивной записной книжке для ради демонстрации мы просто повторно используем одних и тех же пользователей, чтобы система быстро сходилась.

NUM_ROUNDS = 11
for round_num in range(2, NUM_ROUNDS):
  state, metrics = iterative_process.next(state, federated_train_data)
  print('round {:2d}, metrics={}'.format(round_num, metrics))
round  2, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.14814814925193787,loss=2.8865506649017334>>
round  3, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.148765429854393,loss=2.9079062938690186>>
round  4, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.17633745074272156,loss=2.724686622619629>>
round  5, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.20226337015628815,loss=2.6334855556488037>>
round  6, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.22427983582019806,loss=2.5482592582702637>>
round  7, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.24094650149345398,loss=2.4472343921661377>>
round  8, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.259876549243927,loss=2.3809611797332764>>
round  9, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.29814815521240234,loss=2.156442403793335>>
round 10, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.31687241792678833,loss=2.122845411300659>>

Потери в обучении уменьшаются после каждого раунда федеративного обучения, что указывает на сближение модели. В отношении этих показателей обучения есть несколько важных оговорок, однако см. Раздел « Оценка» далее в этом руководстве.

Отображение метрик модели в TensorBoard

Затем давайте визуализируем метрики этих объединенных вычислений с помощью Tensorboard.

Начнем с создания каталога и соответствующего средства записи сводки для записи показателей.


logdir = "/tmp/logs/scalars/training/"
summary_writer = tf.summary.create_file_writer(logdir)
state = iterative_process.initialize()

Постройте соответствующие скалярные метрики с тем же автором сводки.


with summary_writer.as_default():
  for round_num in range(1, NUM_ROUNDS):
    state, metrics = iterative_process.next(state, federated_train_data)
    for name, value in metrics.train._asdict().items():
      tf.summary.scalar(name, value, step=round_num)

Запустите TensorBoard с указанным выше корневым каталогом журналов. Загрузка данных может занять несколько секунд.


%tensorboard --logdir /tmp/logs/scalars/ --port=0

# Run this this cell to clean your directory of old output for future graphs from this directory.
rm -R /tmp/logs/scalars/*

Чтобы таким же образом просматривать метрики оценки, вы можете создать отдельную папку eval, например «logs / scalars / eval», для записи в TensorBoard.

Настройка реализации модели

Keras - это рекомендуемый API высокоуровневых моделей для TensorFlow , и мы рекомендуем по возможности использовать модели tff.learning.from_keras_model (через tff.learning.from_keras_model ) в TFF.

Однако tff.learning предоставляет интерфейс модели нижнего уровня, tff.learning.Model , который предоставляет минимальную функциональность, необходимую для использования модели для федеративного обучения. Непосредственная реализация этого интерфейса (возможно, все еще с использованием строительных блоков, таких как tf.keras.layers ) обеспечивает максимальную настройку без изменения внутренних компонентов алгоритмов федеративного обучения.

Так что давайте сделаем все заново с нуля.

Определение переменных модели, прямого прохода и показателей

Первый шаг - определить переменные TensorFlow, с которыми мы собираемся работать. Чтобы сделать следующий код более разборчивым, давайте определим структуру данных для представления всего набора. Это будет включать в себя такие переменные, как weights и bias , что мы будем обучать, а также переменные , которые будут содержать различную сводную статистику и счетчик мы будем обновлять в процессе обучения, такие как loss_sum , accuracy_sum и num_examples .

MnistVariables = collections.namedtuple(
    'MnistVariables', 'weights bias num_examples loss_sum accuracy_sum')

Вот метод, который создает переменные. Для простоты мы представляем всю статистику как tf.float32 , так как это устранит необходимость преобразования типов на более позднем этапе. Обертывание инициализаторов переменных как лямбда-выражений является требованием, налагаемым переменными ресурсов .

def create_mnist_variables():
  return MnistVariables(
      weights=tf.Variable(
          lambda: tf.zeros(dtype=tf.float32, shape=(784, 10)),
          name='weights',
          trainable=True),
      bias=tf.Variable(
          lambda: tf.zeros(dtype=tf.float32, shape=(10)),
          name='bias',
          trainable=True),
      num_examples=tf.Variable(0.0, name='num_examples', trainable=False),
      loss_sum=tf.Variable(0.0, name='loss_sum', trainable=False),
      accuracy_sum=tf.Variable(0.0, name='accuracy_sum', trainable=False))

Имея переменные для параметров модели и совокупную статистику, теперь мы можем определить метод прямого прохода, который вычисляет потери, генерирует прогнозы и обновляет совокупную статистику для одного пакета входных данных, как показано ниже.

def mnist_forward_pass(variables, batch):
  y = tf.nn.softmax(tf.matmul(batch['x'], variables.weights) + variables.bias)
  predictions = tf.cast(tf.argmax(y, 1), tf.int32)

  flat_labels = tf.reshape(batch['y'], [-1])
  loss = -tf.reduce_mean(
      tf.reduce_sum(tf.one_hot(flat_labels, 10) * tf.math.log(y), axis=[1]))
  accuracy = tf.reduce_mean(
      tf.cast(tf.equal(predictions, flat_labels), tf.float32))

  num_examples = tf.cast(tf.size(batch['y']), tf.float32)

  variables.num_examples.assign_add(num_examples)
  variables.loss_sum.assign_add(loss * num_examples)
  variables.accuracy_sum.assign_add(accuracy * num_examples)

  return loss, predictions

Затем мы определяем функцию, которая возвращает набор локальных метрик, снова используя TensorFlow. Это значения (в дополнение к обновлениям модели, которые обрабатываются автоматически), которые могут быть агрегированы на сервере в процессе федеративного обучения или оценки.

Здесь мы просто возвращаем среднюю loss и accuracy , а также num_examples , которые нам понадобятся для правильного взвешивания вкладов от разных пользователей при вычислении объединенных агрегатов.

def get_local_mnist_metrics(variables):
  return collections.OrderedDict(
      num_examples=variables.num_examples,
      loss=variables.loss_sum / variables.num_examples,
      accuracy=variables.accuracy_sum / variables.num_examples)

Наконец, нам нужно определить, как агрегировать локальные метрики, испускаемые каждым устройством через get_local_mnist_metrics . Это единственная часть кода, которая не написана в TensorFlow - это федеративное вычисление, выраженное в TFF. Если вы хотите копнуть глубже, просмотрите руководство по пользовательским алгоритмам , но в большинстве приложений в этом нет необходимости; вариантов рисунка, показанного ниже, должно хватить. Вот как это выглядит:

@tff.federated_computation
def aggregate_mnist_metrics_across_clients(metrics):
  return collections.OrderedDict(
      num_examples=tff.federated_sum(metrics.num_examples),
      loss=tff.federated_mean(metrics.loss, metrics.num_examples),
      accuracy=tff.federated_mean(metrics.accuracy, metrics.num_examples))
  

Аргумент входных metrics соответствует OrderedDict возвращаемому get_local_mnist_metrics выше, но, что критически важно, значения больше не являются tf.Tensors - они «упакованы» как tff.Value s, чтобы было ясно, что вы больше не можете манипулировать ими с помощью TensorFlow, а только используя федеративные операторы TFF, такие как tff.federated_mean и tff.federated_sum . Возвращенный словарь глобальных агрегатов определяет набор показателей, которые будут доступны на сервере.

Создание экземпляра tff.learning.Model

Имея все вышеперечисленное, мы готовы создать представление модели для использования с TFF, подобное тому, которое создается для вас, когда вы позволяете TFF принимать модель Keras.

class MnistModel(tff.learning.Model):

  def __init__(self):
    self._variables = create_mnist_variables()

  @property
  def trainable_variables(self):
    return [self._variables.weights, self._variables.bias]

  @property
  def non_trainable_variables(self):
    return []

  @property
  def local_variables(self):
    return [
        self._variables.num_examples, self._variables.loss_sum,
        self._variables.accuracy_sum
    ]

  @property
  def input_spec(self):
    return collections.OrderedDict(
        x=tf.TensorSpec([None, 784], tf.float32),
        y=tf.TensorSpec([None, 1], tf.int32))

  @tf.function
  def forward_pass(self, batch, training=True):
    del training
    loss, predictions = mnist_forward_pass(self._variables, batch)
    num_exmaples = tf.shape(batch['x'])[0]
    return tff.learning.BatchOutput(
        loss=loss, predictions=predictions, num_examples=num_exmaples)

  @tf.function
  def report_local_outputs(self):
    return get_local_mnist_metrics(self._variables)

  @property
  def federated_output_computation(self):
    return aggregate_mnist_metrics_across_clients

Как видите, абстрактные методы и свойства, определенные tff.learning.Model соответствуют фрагментам кода в предыдущем разделе, в котором были представлены переменные и определены потери и статистика.

Вот несколько моментов, которые стоит выделить:

  • Все состояния, которые будет использовать ваша модель, должны быть записаны как переменные TensorFlow, поскольку TFF не использует Python во время выполнения (помните, что ваш код должен быть написан таким образом, чтобы его можно было развернуть на мобильных устройствах; см. Руководство по пользовательским алгоритмам для более подробного комментарий к причинам).
  • Ваша модель должна описывать, какую форму данных она принимает ( input_spec ), так как в целом TFF ​​является строго типизированной средой и хочет определить сигнатуры типов для всех компонентов. Объявление формата ввода вашей модели - важная часть этого.
  • Хотя технически это не требуется, мы рекомендуем обернуть всю логику TensorFlow (прямой проход, метрические вычисления и т. Д.) Как tf.function s, поскольку это помогает обеспечить сериализацию tf.function и устраняет необходимость в явных зависимостях tf.function управления.

Вышеупомянутого достаточно для оценки и таких алгоритмов, как Federated SGD. Однако для федеративного усреднения нам нужно указать, как модель должна обучаться локально для каждого пакета. Мы укажем локальный оптимизатор при построении алгоритма федеративного усреднения.

Имитация федеративного обучения с новой моделью

Когда все вышеперечисленное выполнено, оставшаяся часть процесса выглядит так, как мы уже видели - просто замените конструктор модели на конструктор нашего нового класса модели и используйте два объединенных вычисления в итеративном процессе, который вы создали для циклического прохождения тренировочные раунды.

iterative_process = tff.learning.build_federated_averaging_process(
    MnistModel,
    client_optimizer_fn=lambda: tf.keras.optimizers.SGD(learning_rate=0.02))
state = iterative_process.initialize()
state, metrics = iterative_process.next(state, federated_train_data)
print('round  1, metrics={}'.format(metrics))
round  1, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.9713594913482666,accuracy=0.13518518209457397>>

for round_num in range(2, 11):
  state, metrics = iterative_process.next(state, federated_train_data)
  print('round {:2d}, metrics={}'.format(round_num, metrics))
round  2, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.975412607192993,accuracy=0.14032921195030212>>
round  3, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.9395227432250977,accuracy=0.1594650149345398>>
round  4, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.710164785385132,accuracy=0.17139917612075806>>
round  5, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.5891618728637695,accuracy=0.20267489552497864>>
round  6, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.5148487091064453,accuracy=0.21666666865348816>>
round  7, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.2816808223724365,accuracy=0.2580246925354004>>
round  8, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.3656885623931885,accuracy=0.25884774327278137>>
round  9, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.23549222946167,accuracy=0.28477364778518677>>
round 10, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=1.974222183227539,accuracy=0.35329216718673706>>

Чтобы увидеть эти метрики в TensorBoard, см. Шаги, перечисленные выше в разделе «Отображение метрик модели в TensorBoard».

Оценка

Все наши эксперименты до сих пор представляли только федеративные показатели обучения - средние показатели по всем пакетам данных, обученным для всех клиентов в раунде. Это вызывает обычные опасения по поводу переобучения, тем более что для простоты мы использовали один и тот же набор клиентов в каждом раунде, но есть дополнительное понятие переобучения в показателях обучения, специфичных для алгоритма федеративного усреднения. Это легче всего увидеть, если представить, что у каждого клиента есть один пакет данных, и мы тренируемся на этом пакете для многих итераций (эпох). В этом случае локальная модель будет быстро точно соответствовать этому одному пакету, и поэтому показатель локальной точности, который мы усредняем, приблизится к 1,0. Таким образом, эти показатели обучения можно рассматривать как признак того, что обучение прогрессирует, но не более того.

Чтобы выполнить оценку объединенных данных, вы можете построить другое объединенное вычисление, предназначенное именно для этой цели, используя функцию tff.learning.build_federated_evaluation и передав свой конструктор модели в качестве аргумента. Обратите внимание, что в отличие от федеративного усреднения, где мы использовали MnistTrainableModel , достаточно передать MnistModel . Оценка не выполняет градиентный спуск, и нет необходимости создавать оптимизаторы.

Для экспериментов и исследований, когда доступен централизованный набор тестовых данных, Федеративное обучение для создания текста демонстрирует другой вариант оценки: взятие обученных весов из федеративного обучения, их применение к стандартной модели tf.keras.models.Model.evaluate() , а затем простой вызов tf.keras.models.Model.evaluate() в централизованном наборе данных.

evaluation = tff.learning.build_federated_evaluation(MnistModel)

Вы можете проверить сигнатуру абстрактного типа функции оценки следующим образом.

str(evaluation.type_signature)
'(<<trainable=<float32[784,10],float32[10]>,non_trainable=<>>@SERVER,{<x=float32[?,784],y=int32[?,1]>*}@CLIENTS> -> <num_examples=float32@SERVER,loss=float32@SERVER,accuracy=float32@SERVER>)'

На данном этапе не нужно беспокоиться о деталях, просто имейте в виду, что он имеет следующую общую форму, похожую на tff.templates.IterativeProcess.next но с двумя важными отличиями. Во-первых, мы не возвращаем состояние сервера, поскольку оценка не изменяет модель или какой-либо другой аспект состояния - вы можете думать об этом как об отсутствии состояния. Во-вторых, для оценки нужна только модель и не требуется какой-либо другой части состояния сервера, которая может быть связана с обучением, например переменных оптимизатора.

SERVER_MODEL, FEDERATED_DATA -> TRAINING_METRICS

Давайте вызовем оценку последнего состояния, к которому мы пришли во время обучения. Чтобы извлечь последнюю обученную модель из состояния сервера, вы просто .model члену .model , как .model ниже.

train_metrics = evaluation(state.model, federated_train_data)

Вот что мы получаем. Обратите внимание, что цифры выглядят немного лучше, чем то, что было сообщено в предыдущем раунде обучения выше. По соглашению, показатели обучения, сообщаемые итеративным процессом обучения, обычно отражают производительность модели в начале цикла обучения, поэтому показатели оценки всегда будут на шаг впереди.

str(train_metrics)
'<num_examples=4860.0,loss=1.7142657041549683,accuracy=0.38683128356933594>'

Теперь давайте скомпилируем тестовую выборку объединенных данных и повторно запустим оценку на тестовых данных. Данные будут поступать из той же выборки реальных пользователей, но из отдельного набора данных.

federated_test_data = make_federated_data(emnist_test, sample_clients)

len(federated_test_data), federated_test_data[0]
(10,
 <DatasetV1Adapter shapes: OrderedDict([(x, (None, 784)), (y, (None, 1))]), types: OrderedDict([(x, tf.float32), (y, tf.int32)])>)
test_metrics = evaluation(state.model, federated_test_data)
str(test_metrics)
'<num_examples=580.0,loss=1.861915111541748,accuracy=0.3362068831920624>'

На этом урок завершен. Мы рекомендуем вам поиграть с параметрами (например, размеры пакетов, количество пользователей, эпохи, скорость обучения и т. Д.), Изменить приведенный выше код, чтобы имитировать обучение на случайных выборках пользователей в каждом раунде, а также изучить другие руководства. мы разработали.