Пользовательские федеративные алгоритмы, часть 1: Введение в федеративное ядро

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

Этот учебник является первой частью серии из двух частей , которая демонстрирует , как реализовать пользовательские типы федеративных алгоритмов в TensorFlow федеративного (ФАТ) с помощью федеративного ядра (FC) - набор интерфейсов нижнего уровня , которые служат в качестве основы , на которой мы реализовали Федеративные Learning (FL) слой.

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

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

Хотя этот учебник предназначен быть самодостаточным, мы рекомендуем сначала прочитать учебники по классификации изображений и генерации текста для более высокого уровня и более мягкого введения в рамках TensorFlow Федеративного и Федеративные обучения (API tff.learning ), а это поможет вам поместить понятия, которые мы здесь описываем, в контекст.

Предполагаемое использование

В двух словах, Федеративное ядро (FC) представляет собой среда разработки , которая позволяет компактно выразить логику программы , которая объединяет в себе TensorFlow код с распределенными операторами связи, такими , как те, которые используются в Федеративном Усреднении - вычислительные распределенных суммами, средние и другими типами распределенных агрегаций по набору клиентских устройств в системе, широковещательных моделей и параметров на эти устройства и т. д.

Вы можете быть в курсе tf.contrib.distribute и естественный вопрос : в этот момент может быть: каким образом делает эти рамки отличаются? В конце концов, обе платформы пытаются сделать вычисления TensorFlow распределенными.

Один из способов думать об этом является то , что, в то время как заявленной цель tf.contrib.distribute , чтобы позволить пользователям использовать существующие модели и подготовку коду с минимальными изменениями для включения распределенного обучения, и много внимания уделяется тому, как воспользоваться преимуществами распределенной инфраструктуры Чтобы сделать существующий обучающий код более эффективным, цель Federated Core TFF — предоставить исследователям и практикам явный контроль над конкретными шаблонами распределенной связи, которые они будут использовать в своих системах. Основное внимание в FC уделяется предоставлению гибкого и расширяемого языка для выражения алгоритмов распределенного потока данных, а не конкретного набора реализованных возможностей распределенного обучения.

Одной из основных целевых аудиторий TFF FC API являются исследователи и практики, которые, возможно, захотят поэкспериментировать с новыми федеративными алгоритмами обучения и оценить последствия тонких решений, влияющих на то, как организован поток данных в распределенной системе. не увязая в деталях реализации системы. Уровень абстракции, к которому стремится FC API, примерно соответствует псевдокоду, который можно использовать для описания механики федеративного алгоритма обучения в исследовательской публикации — какие данные существуют в системе и как они преобразуются, но не опускаясь до уровня обмен отдельными сетевыми сообщениями «точка-точка».

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
@tff.federated_computation
def hello_world():
  return 'Hello, World!'

hello_world()
b'Hello, World!'

Федеративные данные

Одной из отличительных особенностей TFF является то , что она позволяет компактно выразить вычисления TensorFlow основанных на федеративных данных. Мы будем использовать термин федеративных данные в этом руководстве для обозначения набора элементов данных , размещенных по всей группе устройств в распределенной системе. Например, приложения, работающие на мобильных устройствах, могут собирать данные и хранить их локально, не загружая их в централизованное хранилище. Или массив распределенных датчиков может собирать и хранить показания температуры в своих местах.

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

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

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

federated_float_on_clients = tff.type_at_clients(tf.float32)

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

str(federated_float_on_clients.member)
'float32'
str(federated_float_on_clients.placement)
'CLIENTS'

Федеративный тип с трехсторонними члены T и размещения G можно представить компактной форме как {T}@G , как показано ниже.

str(federated_float_on_clients)
'{float32}@CLIENTS'

Фигурные скобки {} в этом Concise обозначения служат напоминанием о том , что составные члены (элементы данных на разных устройствах) может отличаться, как и следовало ожидать , например, показаний датчиков температуры, так что клиенты как группа совместно хостинг мульти -множество из T -typed элементов , которые вместе составляют федеративное значение.

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

Федеративные типы в TFF бывают двух видов: те, в которых компоненты-члены федеративного значения могут различаться (как показано выше), и те, в которых все они известны как равные. Это контролируется с помощью третьего, по желанию all_equal параметра в tff.FederatedType конструктора (недобросовестное значение False ).

federated_float_on_clients.all_equal
False

Федеративные типа с размещением G , в которой все T -typed составляющих членов , как известны, может быть равно компактно представлена как T@G (в отличие от {T}@G , то есть с фигурными скобками упали , чтобы отразить тот факт, что множественное множество составных элементов состоит из одного элемента).

str(tff.type_at_clients(tf.float32, all_equal=True))
'float32@CLIENTS'

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

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

Например, предположим , что мы имеем пару float32 параметров a и b для простой одномерной модели линейной регрессии. Мы можем построить (нефедеративный) тип таких моделей для использования в TFF следующим образом. Угловые скобки <> в строке напечатанного типа представляют собой компактный ПТФ обозначение имени или неназванные кортежи.

simple_regression_model_type = (
    tff.StructType([('a', tf.float32), ('b', tf.float32)]))

str(simple_regression_model_type)
'<a=float32,b=float32>'

Обратите внимание , что мы только с указанием dtype s выше. Также поддерживаются нескалярные типы. В приведенном выше коде, tf.float32 ярлык обозначения для более общего tff.TensorType(dtype=tf.float32, shape=[]) .

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

str(tff.type_at_clients(
    simple_regression_model_type, all_equal=True))
'<a=float32,b=float32>@CLIENTS'

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

Теперь, возвращаясь к float32@CLIENTS - в то время как он появляется реплицируются между несколькими устройствами, это на самом деле один float32 , так как все члены одинаковы. В общем, вы можете думать о каком - либо всех равном федеративном типе, то есть, один из вида T@G , изоморфные к не федеративному типу T , так как в обеих случаях, есть на самом деле только один (хотя и потенциально реплицируется) пункт типа T .

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

Места размещения

Обзор дизайна

В предыдущем разделе мы ввели понятие размещения - групп участников системы , которые могут быть совместно принимающими федеративное значение, и мы продемонстрировали использование tff.CLIENTS в качестве примера уточнения размещения.

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

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

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

ПТФ был разработан в качестве среды программирования данных , ориентированных, и как таковые, в отличие от некоторых из существующих структур , которые сосредоточатся на операциях и где эти операции могут работать, ПТФ фокусирует на данных, где эта материализуется данные, и как это трансформируется. Следовательно, размещение моделируется как свойство данных в TFF, а не как свойство операций над данными. Действительно, как вы увидите в следующем разделе, некоторые операции TFF охватывают несколько мест и выполняются, так сказать, «в сети», а не на одной машине или группе машин.

Представляя тип определенного значения , как T@G или {T}@G (в отличие от всего T ) принимает решение размещения данных явным, и вместе с статическим анализом программ , написанных на TFF, он может служить в качестве основы для обеспечения формальные гарантии конфиденциальности для конфиденциальных данных на устройстве.

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

В теле TFF кода, в соответствии с проектом, нет никакого способа перечислить устройства , которые представляют собой группу , представленную tff.CLIENTS , или зонд для существования конкретного устройства в группе. Ни в Federated Core API, ни в базовом наборе архитектурных абстракций, ни в базовой инфраструктуре среды выполнения, которую мы предоставляем для поддержки моделирования, нет концепции устройства или идентификатора клиента. Вся логика вычислений, которую вы пишете, будет выражена в виде операций над всей клиентской группой.

Напомним здесь , что мы уже упоминали ранее о значениях федеративных типов , находящихся в отличие от Python dict , в том , что не может просто перечислить их составляющие членов. Думайте о значениях, которыми манипулирует логика вашей программы TFF, как о связанных с местами размещения (группами), а не с отдельными участниками.

Места размещения предназначены для первого класса гражданина в TFF , а также, и могут появляться в качестве параметров и результатов placement типа (быть представленными tff.PlacementType в API). В будущем мы планируем предоставить различные операторы для преобразования или объединения мест размещения, но это выходит за рамки этого руководства. На данном момент, достаточно подумать о placement в непрозрачном примитивном встроенном типе в TFF, подобно тому , как int и bool являются непрозрачным встроенными типами в Python, с tff.CLIENTS будучи постоянным буквальное этим типа, а не в отличие от 1 будучи постоянным литерал типа int .

Указание мест размещения

ПТФ обеспечивает два основных размещения литералов, tff.CLIENTS и tff.SERVER , чтобы сделать его легко выразить богатое разнообразие практических сценариев, которые естественным образом моделируется как клиент-серверных архитектур, с несколькими клиентскими устройствами (мобильные телефоны, встраиваемых устройств, распределенных баз данных , датчики и т.д.) , организованные с помощью одного координатора централизованного сервера. TFF также предназначен для поддержки настраиваемых мест размещения, нескольких клиентских групп, многоуровневых и других, более общих распределенных архитектур, но их обсуждение выходит за рамки данного руководства.

ПТФ не предписывает , что либо tff.CLIENTS или tff.SERVER на самом деле представляет.

В частности, tff.SERVER может быть одно физическое устройство (член одноплодной группы), но это может быть просто , как хорошо быть группой реплик в отказоустойчивый кластер под управлением состояния репликации машины - мы не делаем какие - либо специальные архитектурные предположения. Скорее всего , мы используем all_equal бит , упомянутых в предыдущем разделе , чтобы выразить то , что мы обычно дело только с одного элемента данных на сервере.

Точно так же, tff.CLIENTS в некоторых приложениях могут представлять все клиент в системе - что в контексте федеративного обучения мы иногда называем население, а , например, в производственных реализациях Федеративного усреднения , он может представлять собой когорту - подмножество клиенты, выбранные для участия в конкретном раунде обучения. Абстрактно определенные места размещения получают конкретное значение, когда вычисление, в котором они появляются, развертывается для выполнения (или просто вызывается, как функция Python в моделируемой среде, как показано в этом руководстве). В наших локальных симуляциях группа клиентов определяется объединенными данными, предоставленными в качестве входных данных.

Федеративные вычисления

Объявление объединенных вычислений

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

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

@tff.federated_computation(tff.type_at_clients(tf.float32))
def get_average_temperature(sensor_readings):
  return tff.federated_mean(sensor_readings)

Глядя на приведенный выше код, в этот момент вы можете спросить - не существует уже декоратор конструкций для определения компонуемые единиц , таких как tf.function в TensorFlow, и если да, то зачем вводить еще один один, и чем она отличается?

Короткий ответ , что код , сгенерированный tff.federated_computation обертка не является ни TensorFlow, и не является Python - это спецификация распределенной системы во внутреннем независимой от платформы языка клея. На данный момент это, несомненно, прозвучит загадочно, но, пожалуйста, имейте в виду эту интуитивную интерпретацию федеративных вычислений как абстрактной спецификации распределенной системы. Мы объясним это через минуту.

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

str(get_average_temperature.type_signature)
'({float32}@CLIENTS -> float32@SERVER)'

Сигнатура типа говорит нам, что вычисление принимает набор различных показаний датчиков на клиентских устройствах и возвращает одно среднее значение на сервере.

Прежде чем идти дальше, давайте задумаемся об этом на минуту - на входе и выходе из этого расчета находятся в разных местах (на CLIENTS против на SERVER ). Напомним , что мы говорили в предыдущем разделе , посвященном размещения о том , как TFF операции могут охватывать через местах и работать в сети, и то , что мы только что сказали о федеративных вычислениях , как представляющие абстрактные спецификации распределенных систем. У нас есть только одно определенное такое вычисление — простая распределенная система, в которой данные потребляются на клиентских устройствах, а совокупные результаты появляются на сервере.

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

Однако FC API не навязывает это предположение, и многие из строительных блоков , которые мы используем внутри ( в том числе многочисленных tff.federated_... операторов вы можете найти в API) имеют входы и выходы с различными местами размещения, так и в целом, вы должны не думать о федеративных вычислениях как - то , что работает на сервере или выполняется сервером. Сервер — это всего лишь один из участников федеративных вычислений. Размышляя о механике таких вычислений, лучше всегда по умолчанию использовать глобальную сетевую перспективу, а не перспективу одного централизованного координатора.

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

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

Выполнение объединенных вычислений

Для поддержки разработки и отладки TFF позволяет напрямую вызывать вычисления, определенные таким образом, как функции Python, как показано ниже. Если вычисление ожидает значение федеративного типа с all_equal бит установлен в значение False , вы можете кормить его как простой list в Python, и для федеративных типов с all_equal бит установлен в значение True , вы можете просто напрямую кормить (одного) членский состав. Таким же образом вам сообщают результаты.

get_average_temperature([68.5, 70.3, 69.8])
69.53334

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

Теперь возвращение давайте к ноте мы сделали ранее о tff.federated_computation декоратора излучающей код на языке клея. Хотя логика вычислений ПТФ могут быть выражены как обычные функции в Python (вам просто нужно , чтобы украсить их tff.federated_computation , как мы делали выше), и вы можете непосредственно вызывать их с аргументами Python так же , как любые другие функции Python в этом ноутбук, за кулисами, как мы уже отмечали ранее, TFF вычисления на самом деле не Python.

То , что мы подразумеваем под этим, что , когда интерпретатор Python встречает функцию украшенной tff.federated_computation , он отслеживает заявления в теле этой функции один раз (во время определения), а затем строит упорядоченное представление логики вычисление для использования в будущем - будь то для выполнения или для включения в качестве подкомпонента в другое вычисление.

Вы можете проверить это, добавив оператор печати следующим образом:

@tff.federated_computation(tff.type_at_clients(tf.float32))
def get_average_temperature(sensor_readings):

  print ('Getting traced, the argument is "{}".'.format(
      type(sensor_readings).__name__))

  return tff.federated_mean(sensor_readings)
Getting traced, the argument is "ValueImpl".

Вы можете думать о коде Python, определяющем федеративные вычисления, подобно тому, как вы думаете о коде Python, который строит граф TensorFlow в неактивном контексте (если вы не знакомы с неактивным использованием TensorFlow, подумайте о своем Код Python, определяющий граф операций, которые будут выполняться позже, но фактически не выполняющий их на лету). Код для построения графиков в TensorFlow, не требующий больших усилий, — это Python, но граф TensorFlow, построенный с помощью этого кода, не зависит от платформы и сериализуем.

Аналогично, TFF вычисление определены в Python, но операторы Python в их телах, такие как tff.federated_mean в примере WeVe просто показаны, компилируются в портативное и независимой от платформы сериализуемого представления под капотом.

Как разработчику, вам не нужно беспокоиться о деталях этого представления, так как вам никогда не придется работать с ним напрямую, но вы должны знать о его существовании, о том факте, что вычисления TFF в принципе не спешат, и не может захватывать произвольное состояние Python. Python код , содержащийся в теле ПТФА вычисления по выполняются во время определения, когда тело функции Python , украшенной tff.federated_computation прослеживается перед началом сериализации. Он не отслеживается снова во время вызова (за исключением случаев, когда функция является полиморфной; подробности см. на страницах документации).

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

Другая причина заключается в том, что вычисления TFF выражают глобальное поведение распределенных систем, в отличие от программ Python, которые выражают локальное поведение отдельных участников. Вы можете видеть , что в простом примере , приведенном выше, с помощью специального оператора tff.federated_mean , который принимает данные на клиентских устройствах, но складывает результат на сервере.

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

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

Составление объединенных вычислений

Как отмечалось выше, федеративные вычисления и их составляющие лучше всего понимать как модели распределенных систем, и вы можете думать о федеративных вычислениях как о составлении более сложных распределенных систем из более простых. Вы можете думать о tff.federated_mean оператора как своего родом встроенных шаблонов федеративных вычислений с сигнатурой типа ({T}@CLIENTS -> T@SERVER) ( на самом деле, так же , как вы пишете вычисления, этот оператор также имеет сложный структура — под капотом разбиваем на более простые операторы).

То же самое верно и для составления федеративных вычислений. Вычисление get_average_temperature может быть использовано в теле другой функции Python , украшенной tff.federated_computation - делать это заставит его быть встроен в теле родителя, во многом таким же образом tff.federated_mean был вложенным в его собственном теле раньше.

Важное ограничение , чтобы быть в курсе, что органы функций Python , украшенных tff.federated_computation должны состоять только из федеративных операторов, то есть, они не могут непосредственно содержать операции TensorFlow. Например, вы не можете напрямую использовать tf.nest интерфейсов , чтобы добавить пару федеративных значений. TensorFlow код должен быть ограничен блоками коды , украшенных с tff.tf_computation обсуждается в следующем разделе. Только тогда , когда обернутый таким образом , может обернутый код TensorFlow быть вызван в теле tff.federated_computation .

Причины такого разделения являются техническими (это трудно обмануть оператор , такие как tf.add для работы с не-тензорами), а также архитектурно. Язык федеративных вычислений (то есть, логика построена из сериализовать тел функций Python украшенных tff.federated_computation ) предназначен для использования в качестве независимого от платформы языка клея. Этот язык клея в настоящее время используется для создания распределенных систем от вложенных разделов TensorFlow коды (ограничивается tff.tf_computation блоков). В полноте времени, мы ожидаем , что необходимость в встраивают разделы других, не TensorFlow логики, такие как реляционные запросы к базе данных , которые могут представлять входные трубопроводы, все соединены вместе , используя один и тот же язык , клея (в tff.federated_computation блоки).

Логика TensorFlow

Объявление вычислений TensorFlow

TFF предназначен для использования с TensorFlow. Таким образом, большая часть кода, который вы будете писать в TFF, скорее всего, будет обычным (то есть локально выполняемым) кодом TensorFlow. Для того , чтобы использовать такой код с TFF, как было отмечено выше, он просто должен быть украшен tff.tf_computation .

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

@tff.tf_computation(tf.float32)
def add_half(x):
  return tf.add(x, 0.5)

Опять же, глядя на это, вы можете быть удивлены , почему мы должны определить другую декоратора tff.tf_computation вместо того , чтобы просто используя существующий механизм, как tf.function . В отличие от предыдущего раздела, здесь мы имеем дело с обычным блоком кода TensorFlow.

Этому есть несколько причин, полное рассмотрение которых выходит за рамки данного туториала, но стоит назвать основную:

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

В целом, мы рекомендуем использовать собственные механизмы TensorFlow для композиции, такие как tf.function , везде, где это возможно, так как точный способ , в котором можно ожидать , декоратор взаимодействует ПТФ с нетерпеливыми функциями эволюционировать.

Теперь, возвращаясь к примеру кода выше, вычисление add_half мы только что определили , можно лечить TFF так же , как и любой другой вычисления TFF. В частности, он имеет сигнатуру типа TFF.

str(add_half.type_signature)
'(float32 -> float32)'

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

Теперь Вы можете также использовать add_half в качестве строительного блока в других вычислениях. Например, вот как вы можете использовать tff.federated_map оператор применять add_half точечно для всех составляющих членов объединения поплавка на клиентских устройствах.

@tff.federated_computation(tff.type_at_clients(tf.float32))
def add_half_on_clients(x):
  return tff.federated_map(add_half, x)
str(add_half_on_clients.type_signature)
'({float32}@CLIENTS -> {float32}@CLIENTS)'

Выполнение вычислений TensorFlow

Выполнение расчетов , определенных с tff.tf_computation следует тем же правилам, что мы описали для tff.federated_computation . Их можно вызывать как обычные вызываемые объекты в Python следующим образом.

add_half_on_clients([1.0, 3.0, 2.0])
[<tf.Tensor: shape=(), dtype=float32, numpy=1.5>,
 <tf.Tensor: shape=(), dtype=float32, numpy=3.5>,
 <tf.Tensor: shape=(), dtype=float32, numpy=2.5>]

Опять же, стоит отметить , что применение вычисления add_half_on_clients таким образом имитирует распределенный процесс. Данные потребляются на клиентах и ​​возвращаются на клиентах. Действительно, в этом вычислении каждый клиент выполняет локальное действие. Там нет tff.SERVER явно упоминается в этой системе (даже если на практике, организуя такую обработку может включать в себя один). Подумайте о вычислении определенного таким образом , как концептуально аналогично Map стадии MapReduce .

Кроме того , имейте в виду, что мы говорили в предыдущем разделе о TFF вычисления получения сериализовать в то время определение остается верным для tff.tf_computation кода , а также - тело Python из add_half_on_clients получает проследили один раз во время определения. При последующих вызовах TFF использует свое сериализованное представление.

Единственное различие между методами Python украшенных tff.federated_computation и награжденным tff.tf_computation , что последний сериализуются в виде графиков TensorFlow ( в то время как первые не могут содержать TensorFlow код непосредственно внедренный в них).

Под капотом, каждый метод украшено tff.tf_computation временно отключает нетерпеливое исполнение , с тем чтобы структура вычислению для захватываться. Хотя активное выполнение локально отключено, вы можете использовать готовые конструкции TensorFlow, AutoGraph, TensorFlow 2.0 и т. д., если вы пишете логику своих вычислений таким образом, чтобы ее можно было правильно сериализовать.

Например, следующий код не сработает:

try:

  # Eager mode
  constant_10 = tf.constant(10.)

  @tff.tf_computation(tf.float32)
  def add_ten(x):
    return x + constant_10

except Exception as err:
  print (err)
Attempting to capture an EagerTensor without building a function.

Выше не потому , что constant_10 уже построен за пределами графика, tff.tf_computation конструирует внутренне в организме add_ten в процессе сериализации.

С другой стороны, применение питона функций , которые можно изменить текущий график при вызове внутри tff.tf_computation нормально:

def get_constant_10():
  return tf.constant(10.)

@tff.tf_computation(tf.float32)
def add_ten(x):
  return x + get_constant_10()

add_ten(5.0)
15.0

Обратите внимание, что механизмы сериализации в TensorFlow развиваются, и мы ожидаем, что детали того, как TFF сериализует вычисления, также будут развиваться.

Работа с tf.data.Dataset s

Как уже отмечалось выше, является уникальной особенностью tff.tf_computation s является то , что они позволяют вам работать с tf.data.Dataset с определенной отвлеченно , как формальных параметров с помощью кода. Параметры должны быть представлены в TensorFlow , как должны быть объявлены с использованием наборов данных tff.SequenceType конструктора.

Например, спецификация типа tff.SequenceType(tf.float32) определяет абстрактную последовательность поплавковых элементов в TFF. Последовательности могут содержать либо тензоры, либо сложные вложенные структуры (позже мы увидим их примеры). Краткое представление последовательности T -typed элементов является T* .

float32_sequence = tff.SequenceType(tf.float32)

str(float32_sequence)
'float32*'

Suppose that in our temperature sensor example, each sensor holds not just one temperature reading, but multiple. Here's how you can define a TFF computation in TensorFlow that calculates the average of temperatures in a single local data set using the tf.data.Dataset.reduce operator.

@tff.tf_computation(tff.SequenceType(tf.float32))
def get_local_temperature_average(local_temperatures):
  sum_and_count = (
      local_temperatures.reduce((0.0, 0), lambda x, y: (x[0] + y, x[1] + 1)))
  return sum_and_count[0] / tf.cast(sum_and_count[1], tf.float32)
str(get_local_temperature_average.type_signature)
'(float32* -> float32)'

In the body of a method decorated with tff.tf_computation , formal parameters of a TFF sequence type are represented simply as objects that behave like tf.data.Dataset , ie, support the same properties and methods (they are currently not implemented as subclasses of that type - this may change as the support for data sets in TensorFlow evolves).

You can easily verify this as follows.

@tff.tf_computation(tff.SequenceType(tf.int32))
def foo(x):
  return x.reduce(np.int32(0), lambda x, y: x + y)

foo([1, 2, 3])
6

Keep in mind that unlike ordinary tf.data.Dataset s, these dataset-like objects are placeholders. They don't contain any elements, since they represent abstract sequence-typed parameters, to be bound to concrete data when used in a concrete context. Support for abstractly-defined placeholder data sets is still somewhat limited at this point, and in the early days of TFF, you may encounter certain restrictions, but we won't need to worry about them in this tutorial (please refer to the documentation pages for details).

When locally executing a computation that accepts a sequence in a simulation mode, such as in this tutorial, you can feed the sequence as Python list, as below (as well as in other ways, eg, as a tf.data.Dataset in eager mode, but for now, we'll keep it simple).

get_local_temperature_average([68.5, 70.3, 69.8])
69.53333

Like all other TFF types, sequences like those defined above can use the tff.StructType constructor to define nested structures. For example, here's how one could declare a computation that accepts a sequence of pairs A , B , and returns the sum of their products. We include the tracing statements in the body of the computation so that you can see how the TFF type signature translates into the dataset's output_types and output_shapes .

@tff.tf_computation(tff.SequenceType(collections.OrderedDict([('A', tf.int32), ('B', tf.int32)])))
def foo(ds):
  print('element_structure = {}'.format(ds.element_spec))
  return ds.reduce(np.int32(0), lambda total, x: total + x['A'] * x['B'])
element_structure = OrderedDict([('A', TensorSpec(shape=(), dtype=tf.int32, name=None)), ('B', TensorSpec(shape=(), dtype=tf.int32, name=None))])
str(foo.type_signature)
'(<A=int32,B=int32>* -> int32)'
foo([{'A': 2, 'B': 3}, {'A': 4, 'B': 5}])
26

The support for using tf.data.Datasets as formal parameters is still somewhat limited and evolving, although functional in simple scenarios such as those used in this tutorial.

Putting it all together

Now, let's try again to use our TensorFlow computation in a federated setting. Suppose we have a group of sensors that each have a local sequence of temperature readings. We can compute the global temperature average by averaging the sensors' local averages as follows.

@tff.federated_computation(
    tff.type_at_clients(tff.SequenceType(tf.float32)))
def get_global_temperature_average(sensor_readings):
  return tff.federated_mean(
      tff.federated_map(get_local_temperature_average, sensor_readings))

Note that this isn't a simple average across all local temperature readings from all clients, as that would require weighing contributions from different clients by the number of readings they locally maintain. We leave it as an exercise for the reader to update the above code; the tff.federated_mean operator accepts the weight as an optional second argument (expected to be a federated float).

Also note that the input to get_global_temperature_average now becomes a federated float sequence . Federated sequences is how we will typically represent on-device data in federated learning, with sequence elements typically representing data batches (you will see examples of this shortly).

str(get_global_temperature_average.type_signature)
'({float32*}@CLIENTS -> float32@SERVER)'

Here's how we can locally execute the computation on a sample of data in Python. Notice that the way we supply the input is now as a list of list s. The outer list iterates over the devices in the group represented by tff.CLIENTS , and the inner ones iterate over elements in each device's local sequence.

get_global_temperature_average([[68.0, 70.0], [71.0], [68.0, 72.0, 70.0]])
70.0

This concludes the first part of the tutorial... we encourage you to continue on to the second part .