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

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

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

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

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

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

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

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

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

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

Один из способов подумать об этом заключается в том, что, в то время как заявленная цель tf.contrib.distribute заключается в том, чтобы позволить пользователям использовать существующие модели и обучающий код с минимальными изменениями для обеспечения распределенного обучения , и большое внимание уделяется тому, как использовать преимущества распределенной инфраструктуры Чтобы сделать существующий обучающий код более эффективным, цель Федеративного ядра 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.FederatedType(tf.float32, tff.CLIENTS)

В более общем смысле, объединенный тип в 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'

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

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

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

federated_float_on_clients.all_equal
False

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

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

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

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

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

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

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

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

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

str(tff.FederatedType(
    simple_regression_model_type, tff.CLIENTS, 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 позволял писать код, который можно было бы развернуть для выполнения на группах физических устройств в распределенной системе, потенциально включая мобильные или встроенные устройства. под управлением Android. Каждое из этих устройств будет получать отдельный набор инструкций для локального выполнения в зависимости от роли, которую оно играет в системе (устройство конечного пользователя, централизованный координатор, промежуточный уровень в многоуровневой архитектуре и т. Д.). Важно иметь возможность рассуждать о том, какие подмножества устройств какой код выполняют и где могут физически материализоваться различные части данных.

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

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

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

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

(Примечание: хотя это выходит далеко за рамки данного руководства, мы должны упомянуть, что есть одно заметное исключение из вышеизложенного, оператор tff.federated_collect который предназначен как низкоуровневый примитив, только для tff.federated_collect ситуаций. Его явное использование в ситуациях, когда этого можно избежать, не рекомендуется, поскольку это может ограничить возможные будущие приложения. Например, если в ходе статического анализа мы определяем, что вычисление использует такие низкоуровневые механизмы, мы можем запретить его доступ к определенным типы данных.)

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

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

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

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

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

TFF не предписывает, что на tff.SERVER деле представляют tff.CLIENTS или tff.SERVER .

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

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

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

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

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

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

@tff.federated_computation(tff.FederatedType(tf.float32, tff.CLIENTS))
def get_average_temperature(sensor_readings):
  return tff.federated_mean(sensor_readings)

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

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

Во-первых, давайте немного поиграем с определением. Вычисления TFF обычно моделируются как функции - с параметрами или без них, но с четко определенными сигнатурами типов. Вы можете распечатать подпись типа вычисления, type_signature его свойство 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 , вы можете all_equal его как простой list в Python, а для федеративных типов с битом all_equal установленным на True , вы можете просто напрямую all_equal (одиночный) составной член. Таким же образом вам сообщают о результатах.

get_average_temperature([68.5, 70.3, 69.8])
69.53334

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

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

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

Вы можете проверить это, добавив оператор печати, как показано ниже:

@tff.federated_computation(tff.FederatedType(tf.float32, tff.CLIENTS))
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 в только что показанном примере, компилируются в переносимое и независимое от платформы сериализуемое представление под капотом.

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

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

Другая причина заключается в том, что вычисления TFF выражают глобальное поведение распределенных систем в отличие от программ Python, которые выражают локальное поведение отдельных участников. Вы можете увидеть это в приведенном выше простом примере со специальным оператором tff.federated_mean который принимает данные на клиентских устройствах, но 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 обсуждаемыми в следующем разделе. Только когда обернутый таким образом, обернутый код tff.federated_computation может быть вызван в теле tff.federated_computation .

Причины такого разделения являются техническими (сложно обмануть операторов, таких как tf.add для работы с tf.add ), а также архитектурными. Язык объединенных вычислений (то есть логика, построенная из сериализованных тел функций Python, украшенных tff.federated_computation ) предназначен для использования в качестве платформенно-независимого связующего языка. Этот связующий язык в настоящее время используется для построения распределенных систем из встроенных разделов кода tff.tf_computation (ограниченных блоками tff.tf_computation ). Мы ожидаем, что со временем возникнет необходимость встраивать разделы другой, не относящейся к TensorFlow логики, такой как запросы к реляционной базе данных, которые могут представлять входные конвейеры, все связанные вместе с использованием одного tff.federated_computation языка (блоки 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, в тела объединенных вычислений, они должны удовлетворять определенным свойствам, таким как отслеживание и сериализация во время определения, наличие сигнатур типов и т. Д. Обычно для этого требуется какой-то декоратор.

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

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

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

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

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

@tff.federated_computation(tff.FederatedType(tf.float32, tff.CLIENTS))
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 вычисления 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 в процессе сериализации.

С другой стороны, вызов функций python, которые изменяют текущий график при вызове внутри 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 сериализует вычисления, также будут развиваться.

Working with tf.data.Dataset s

As noted earlier, a unique feature of tff.tf_computation s is that they allows you to work with tf.data.Dataset s defined abstractly as formal parameters by your code. Parameters to be represented in TensorFlow as data sets need to be declared using the tff.SequenceType constructor.

For example, the type specification tff.SequenceType(tf.float32) defines an abstract sequence of float elements in TFF. Sequences can contain either tensors, or complex nested structures (we'll see examples of those later). The concise representation of a sequence of T -typed items is 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.FederatedType(tff.SequenceType(tf.float32), tff.CLIENTS))
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 .