Odpowiedz już dziś na lokalne wydarzenie TensorFlow Everywhere!
Ta strona została przetłumaczona przez Cloud Translation API.
Switch to English

Niestandardowe algorytmy federacyjne, część 1: Wprowadzenie do rdzenia federacyjnego

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik

Ten samouczek jest pierwszą częścią dwuczęściowej serii, w której pokazano, jak zaimplementować niestandardowe typy algorytmów federacyjnych w TensorFlow Federated (TFF) przy użyciu Federated Core (FC) - zestawu interfejsów niższego poziomu, które służą jako podstawa, na której wdrożyliśmy warstwę Federated Learning (FL) .

Ta pierwsza część jest bardziej koncepcyjna; przedstawiamy niektóre kluczowe pojęcia i abstrakcje programistyczne używane w TFF oraz demonstrujemy ich zastosowanie na bardzo prostym przykładzie z rozproszonym układem czujników temperatury. W drugiej części tej serii wykorzystujemy wprowadzone przez nas mechanizmy do implementacji prostej wersji federacyjnych algorytmów szkolenia i oceny. W ramach kontynuacji zachęcamy do przestudiowania wdrażania uśredniania federacyjnego w tff.learning .

Pod koniec tej serii powinieneś być w stanie rozpoznać, że aplikacje Federated Core niekoniecznie ograniczają się do uczenia się. Oferowane przez nas abstrakcje programistyczne są dość ogólne i mogą być używane np. Do implementacji analiz i innych niestandardowych typów obliczeń na rozproszonych danych.

Chociaż ten samouczek został zaprojektowany jako samodzielny, zachęcamy do zapoznania się najpierw z samouczkami dotyczącymi klasyfikacji obrazów i generowania tekstu, aby uzyskać bardziej zaawansowane i łagodniejsze wprowadzenie do środowiska TensorFlow Federated i Federated Learning API ( tff.learning ), ponieważ pomoże ci umieścić opisane tutaj koncepcje w kontekście.

Zamierzone zastosowania

W skrócie, Federated Core (FC) to środowisko programistyczne, które umożliwia zwięzłe wyrażanie logiki programu, które łączy kod TensorFlow z operatorami komunikacji rozproszonej, takimi jak te, które są używane w uśrednianiu federacyjnym - obliczanie rozproszonych sum, średnich i innych typów rozproszonych agregacji na zbiorze urządzeń klienckich w systemie, modeli i parametrów rozgłaszania do tych urządzeń itp.

Być może jesteś świadomy istnienia tf.contrib.distribute i naturalnym pytaniem, które należy zadać w tym miejscu, może być: czym różnią się te ramy? W końcu oba frameworki próbują rozproszyć obliczenia TensorFlow.

Można o tym pomyśleć, podczas gdy deklarowanym celem tf.contrib.distribute jest umożliwienie użytkownikom korzystania z istniejących modeli i kodu szkoleniowego przy minimalnych zmianach w celu umożliwienia szkolenia rozproszonego , a duży nacisk kładzie się na wykorzystanie infrastruktury rozproszonej aby uczynić istniejący kod szkoleniowy bardziej wydajnym, celem Federated Core TFF jest zapewnienie naukowcom i praktykom wyraźnej kontroli nad określonymi wzorcami komunikacji rozproszonej, których będą używać w swoich systemach. W FC koncentruje się na zapewnieniu elastycznego i rozszerzalnego języka do wyrażania algorytmów rozproszonego przepływu danych, a nie na konkretnym zestawie zaimplementowanych rozproszonych możliwości szkoleniowych.

Jedną z głównych grup docelowych dla API FC TFF są badacze i praktycy, którzy mogą chcieć eksperymentować z nowymi federacyjnymi algorytmami uczenia się i oceniać konsekwencje subtelnych wyborów projektowych, które wpływają na sposób, w jaki przepływ danych w systemie rozproszonym jest organizowany, ale bez ugrzęźnięcia w szczegółach implementacji systemu. Poziom abstrakcji, do jakiego dąży FC API, z grubsza odpowiada pseudokodowi, którego można by użyć do opisania mechaniki federacyjnego algorytmu uczenia się w publikacji badawczej - jakie dane istnieją w systemie i jak są przekształcane, ale bez obniżania się do poziomu indywidualne wymiany komunikatów w sieci typu punkt-punkt.

TFF jako całość skupia się na scenariuszach, w których dane są dystrybuowane i muszą takimi pozostać, np. Ze względu na prywatność, i gdzie gromadzenie wszystkich danych w scentralizowanej lokalizacji może nie być realną opcją. Ma to wpływ na implementację algorytmów uczenia maszynowego, które wymagają większego stopnia jawnej kontroli w porównaniu ze scenariuszami, w których wszystkie dane mogą być gromadzone w scentralizowanej lokalizacji w centrum danych.

Zanim zaczniemy

Zanim zagłębimy się w kod, spróbuj uruchomić następujący przykład „Hello World”, aby upewnić się, że środowisko jest poprawnie skonfigurowane. Jeśli to nie zadziała, zapoznaj się z instrukcją instalacji .

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

Dane sfederowane

Jedną z wyróżniających cech TFF jest to, że umożliwia zwięzłe wyrażanie obliczeń opartych na TensorFlow na danych federacyjnych . W tym samouczku będziemy używać terminu dane federacyjne w odniesieniu do zbioru elementów danych hostowanych w grupie urządzeń w systemie rozproszonym. Na przykład aplikacje działające na urządzeniach mobilnych mogą gromadzić dane i przechowywać je lokalnie, bez przesyłania ich do scentralizowanej lokalizacji. Lub szereg rozproszonych czujników może gromadzić i przechowywać odczyty temperatury w swoich lokalizacjach.

Dane sfederowane, takie jak te w powyższych przykładach, są traktowane w TFF jako obywatele pierwszej kategorii , tj. Mogą pojawiać się jako parametry i wyniki funkcji i mają typy. Aby wzmocnić to pojęcie, będziemy nazywać stowarzyszone zbiory danych jako wartości stowarzyszone lub jako wartości typów stowarzyszonych .

Ważne jest, aby zrozumieć, że modelujemy cały zbiór elementów danych na wszystkich urządzeniach (np. Odczyty całej temperatury gromadzenia ze wszystkich czujników w tablicy rozproszonej) jako pojedynczą wartość federacyjną.

Na przykład, oto jak można zdefiniować w TFF typ sfederowanej zmiennej zmiennoprzecinkowej hostowanej przez grupę urządzeń klienckich. Zbiór odczytów temperatury, które materializują się w szeregu rozproszonych czujników, można modelować jako wartość tego typu sfederowanego.

federated_float_on_clients = tff.type_at_clients(tf.float32)

Mówiąc bardziej ogólnie, typ stowarzyszony w TFF jest definiowany przez określenie typu T jego składników członkowskich - elementów danych znajdujących się na poszczególnych urządzeniach oraz grupy G urządzeń, na których są hostowane wartości stowarzyszone tego typu (plus trzeci, opcjonalna informacja, o której wkrótce wspomnimy). Grupę G urządzeń hostujących wartość federacyjną nazywamy jej umiejscowieniem . Tak więc tff.CLIENTS jest przykładem miejsca docelowego.

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

Typ stowarzyszony ze składnikami składowymi T i umiejscowieniem G można przedstawić zwięźle jako {T}@G , jak pokazano poniżej.

str(federated_float_on_clients)
'{float32}@CLIENTS'

Nawiasy klamrowe {} w tym zwięzłym zapisie służą jako przypomnienie, że elementy składowe (elementy danych na różnych urządzeniach) mogą się różnić, jak można by się spodziewać, np. Odczytów czujników temperatury, więc klienci jako grupa wspólnie hostują wiele -Set z T -typed przedmiotów, które razem stanowią wartość stowarzyszonego.

Należy zauważyć, że elementy składowe wartości stowarzyszonej są generalnie nieprzejrzyste dla programisty, tj. Wartość stowarzyszona nie powinna być traktowana jako prosty dict kluczowany przez identyfikator urządzenia w systemie - wartości te mają na celu być zbiorczo przekształcane tylko przez operatorów federacyjnych, którzy abstrakcyjnie reprezentują różne rodzaje rozproszonych protokołów komunikacyjnych (takich jak agregacja). Jeśli brzmi to zbyt abstrakcyjnie, nie martw się - wrócimy do tego wkrótce i zilustrujemy to konkretnymi przykładami.

Typy federacyjne w TFF występują w dwóch odmianach: tych, w których składniki członkowskie o wartości federacyjnej mogą się różnić (jak widać powyżej) oraz takich, w których wiadomo, że wszystkie są równe. Jest to kontrolowane przez trzeci, opcjonalny all_equal parametrów w tff.FederatedType konstruktora (domyślnie jest to False ).

federated_float_on_clients.all_equal
False

Typ federacyjny z umiejscowieniem G w którym wszystkie składniki składowe typu T są równe, można zwięźle przedstawić jako T@G (w przeciwieństwie do {T}@G , to znaczy z opuszczonymi nawiasami klamrowymi fakt, że wielopoziomowy zbiór członków składa się z jednej pozycji).

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

Jednym z przykładów wartości stowarzyszonej tego typu, która może wystąpić w praktycznych scenariuszach, jest hiperparametr (taki jak szybkość uczenia się, norma obcinania itp.), Który został nadany przez serwer grupie urządzeń uczestniczących w szkoleniu federacyjnym.

Innym przykładem jest zestaw parametrów modelu uczenia maszynowego, który został wstępnie wyszkolony na serwerze, a następnie został rozgłoszony do grupy urządzeń klienckich, gdzie można je spersonalizować dla każdego użytkownika.

Na przykład załóżmy, że mamy parę parametrów float32 a i b dla prostego jednowymiarowego modelu regresji liniowej. Możemy skonstruować (nie federacyjny) typ takich modeli do użytku w TFF w następujący sposób. Nawiasy kątowe <> w drukowanym łańcuchu typu są zwartą notacją TFF dla nazwanych lub nienazwanych krotek.

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

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

Zwróć uwagę, że powyżej określamy tylko dtype . Obsługiwane są również typy nieskalarne. W powyższym kodzie tf.float32 jest skrótem do bardziej ogólnego tff.TensorType(dtype=tf.float32, shape=[]) .

Gdy ten model jest rozgłaszany do klientów, typ wynikowej wartości stowarzyszonej można przedstawić w sposób pokazany poniżej.

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

W przypadku symetrii ze sfederowanym float powyżej będziemy nazywać taki typ krotką federacyjną . Mówiąc bardziej ogólnie, często będziemy używać terminu stowarzyszony XYZ w odniesieniu do wartości stowarzyszonej, w której składniki składowe są podobne do XYZ . Dlatego będziemy rozmawiać o takich rzeczach, jak krotki federacyjne, sekwencje federacyjne, modele federacyjne i tak dalej.

Wracając do float32@CLIENTS - chociaż wydaje się, że jest replikowany na wielu urządzeniach, w rzeczywistości jest to pojedynczy float32 , ponieważ wszystkie elementy są takie same. Ogólnie rzecz biorąc, możesz pomyśleć o każdym równym typie federacyjnym, tj. Jednej z postaci T@G , jako izomorficznej z typem niefederowanym T , ponieważ w obu przypadkach istnieje tylko jeden (choć potencjalnie replikowany) element typu T

Biorąc pod uwagę izomorfizm między T i T@G , możesz się zastanawiać, jakim celom, jeśli w ogóle, mogą służyć te ostatnie typy. Czytaj.

Miejsca docelowe

Przegląd projektu

W poprzedniej sekcji przedstawiliśmy koncepcję miejsc docelowych - grup uczestników systemu, którzy mogą wspólnie tff.CLIENTS wartość federacyjną, oraz zademonstrowaliśmy użycie tff.CLIENTS jako przykładowej specyfikacji miejsca docelowego.

Aby wyjaśnić, dlaczego pojęcie umiejscowienia jest tak fundamentalne, że musieliśmy włączyć je do systemu typów TFF, przypomnijmy sobie, o czym wspomnieliśmy na początku tego samouczka o niektórych zamierzonych zastosowaniach TFF.

Chociaż w tym samouczku zobaczysz, że kod TFF jest wykonywany lokalnie w symulowanym środowisku, naszym celem jest umożliwienie pisania kodu, który można wdrożyć w celu wykonania na grupach urządzeń fizycznych w systemie rozproszonym, potencjalnie obejmującym urządzenia mobilne lub wbudowane z systemem Android. Każde z tych urządzeń otrzymywałoby oddzielny zestaw instrukcji do wykonania lokalnie, w zależności od roli, jaką pełni w systemie (urządzenie użytkownika końcowego, scentralizowany koordynator, warstwa pośrednia w architekturze wielowarstwowej itp.). Ważne jest, aby umieć określić, które podzbiory urządzeń wykonują dany kod i gdzie różne części danych mogą się fizycznie zmaterializować.

Jest to szczególnie ważne w przypadku np. Danych aplikacji na urządzeniach mobilnych. Ponieważ dane są prywatne i mogą być wrażliwe, potrzebujemy możliwości statycznej weryfikacji, czy dane te nigdy nie opuszczą urządzenia (i udowodnienia faktów na temat przetwarzania danych). Specyfikacje rozmieszczenia są jednym z mechanizmów zaprojektowanych, aby to wspierać.

TFF został zaprojektowany jako środowisko programistyczne skoncentrowane na danych i jako takie, w przeciwieństwie do niektórych istniejących frameworków, które koncentrują się na operacjach i gdzie te operacje mogą działać , TFF koncentruje się na danych , gdzie te dane się materializują i jak są przekształcane . W konsekwencji plasowanie jest modelowane jako właściwość danych w TFF, a nie jako właściwość operacji na danych. Rzeczywiście, jak zobaczysz w następnej sekcji, niektóre operacje TFF rozciągają się na różne lokalizacje i działają „w sieci”, że tak powiem, a nie są wykonywane przez pojedynczą maszynę lub grupę maszyn.

Przedstawienie typu określonej wartości jako T@G lub {T}@G (w przeciwieństwie do samego T ) sprawia, że ​​decyzje dotyczące umieszczania danych są wyraźne i wraz ze statyczną analizą programów napisanych w TFF może służyć jako podstawa do zapewnienia formalne gwarancje prywatności wrażliwych danych na urządzeniu.

Ważną rzeczą, na którą należy zwrócić uwagę w tym miejscu, jest jednak to, że chociaż zachęcamy użytkowników TFF do wyraźnego określania grup uczestniczących urządzeń, które przechowują dane (miejsca docelowe), programista nigdy nie zajmie się surowymi danymi lub tożsamościami poszczególnych uczestników .

(Uwaga: chociaż wykracza to daleko poza zakres tego samouczka, powinniśmy wspomnieć, że istnieje jeden znaczący wyjątek od powyższego, operator tff.federated_collect , który jest przeznaczony jako prymityw niskiego poziomu, tylko do specjalnych sytuacji. Jego jawne użycie w sytuacjach, w których można tego uniknąć, nie jest zalecane, ponieważ może to ograniczyć możliwe przyszłe zastosowania. Na przykład, jeśli w toku analizy statycznej stwierdzimy, że obliczenia wykorzystują tak niskopoziomowe mechanizmy, możemy uniemożliwić mu dostęp do pewnych rodzaje danych.)

W treści kodu TFF z założenia nie ma możliwości wyliczenia urządzeń, które stanowią grupę reprezentowaną przez tff.CLIENTS , ani tff.CLIENTS istnienia konkretnego urządzenia w grupie. Nigdzie w Federated Core API, podstawowym zestawie abstrakcji architektonicznych ani podstawowej infrastrukturze środowiska wykonawczego, które zapewniamy do obsługi symulacji, nie ma koncepcji tożsamości urządzenia lub klienta. Cała logika obliczeniowa, którą napiszesz, zostanie wyrażona jako operacje na całej grupie klientów.

Przypomnij sobie to, o czym wspomnieliśmy wcześniej, że wartości typów federacyjnych są odmienne od dict Pythona, ponieważ nie można po prostu wyliczyć ich składników członkowskich. Pomyśl o wartościach, którymi manipuluje logika twojego programu TFF, jako o powiązaniu z miejscami docelowymi (grupami), a nie z indywidualnymi uczestnikami.

Miejsca docelowe są również zaprojektowane jako obywatele pierwszej klasy w TFF i mogą pojawiać się jako parametry i wyniki typu placement (które mają być reprezentowane przez tff.PlacementType w API). W przyszłości planujemy udostępnić różne operatory do przekształcania lub łączenia miejsc docelowych, ale wykracza to poza zakres tego samouczka. Na razie wystarczy pomyśleć o placement jako nieprzezroczystym prymitywnym typie wbudowanym w TFF, podobnie jak w Pythonie int i bool są nieprzezroczystymi typami wbudowanymi, z tff.CLIENTS jest stałym literałem tego typu, podobnie jak 1 będąc stałym literałem typu int .

Określanie miejsc docelowych

TFF zapewnia dwa podstawowe literały umieszczania, tff.CLIENTS i tff.SERVER , aby ułatwić wyrażenie bogatej różnorodności praktycznych scenariuszy, które są naturalnie modelowane jako architektury klient-serwer, z wieloma urządzeniami klienckimi (telefony komórkowe, urządzenia wbudowane, rozproszone bazy danych , czujniki itp.) koordynowane przez jednego scentralizowanego koordynatora serwera . TFF jest przeznaczony do obsługi niestandardowych miejsc docelowych, wielu grup klientów, wielopoziomowych i innych, bardziej ogólnych architektur rozproszonych, ale omawianie ich wykracza poza zakres tego samouczka.

TFF nie określa, co faktycznie reprezentuje tff.CLIENTS lub tff.SERVER .

W szczególności tff.SERVER może być pojedynczym urządzeniem fizycznym (członkiem grupy singleton), ale równie dobrze może to być grupa replik w klastrze odpornym na uszkodzenia z replikacją maszyny stanu - nie tworzymy żadnej specjalnej architektury założenia. Zamiast tego all_equal bitu all_equal wspomnianego w poprzedniej sekcji, aby wyrazić fakt, że generalnie mamy do czynienia tylko z pojedynczą pozycją danych na serwerze.

Podobnie tff.CLIENTS w niektórych aplikacjach może reprezentować wszystkich klientów w systemie - co w kontekście uczenia federacyjnego czasami nazywamy populacją , ale np. We wdrożeniach produkcyjnych uśredniania federacyjnego może reprezentować kohortę - podzbiór klienci zakwalifikowani do udziału w danej rundzie szkoleń. Abstrakcyjnie zdefiniowane miejsca docelowe mają konkretne znaczenie, gdy obliczenia, w których się pojawiają, są wdrażane do wykonania (lub po prostu wywoływane jak funkcja Pythona w symulowanym środowisku, jak pokazano w tym samouczku). W naszych symulacjach lokalnych grupa klientów jest określana na podstawie danych federacyjnych dostarczanych jako dane wejściowe.

Obliczenia federacyjne

Deklarowanie obliczeń stowarzyszonych

TFF jest zaprojektowany jako funkcjonalne środowisko programowania o silnym typie, które obsługuje rozwój modułowy.

Podstawową jednostką kompozycji w TFF jest obliczenie federacyjne - sekcja logiki, która może akceptować wartości stowarzyszone jako dane wejściowe i zwracać wartości stowarzyszone jako dane wyjściowe. Oto, jak można zdefiniować obliczenia, które obliczają średnią temperatur raportowanych przez tablicę czujników z naszego poprzedniego przykładu.

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

Patrząc na powyższy kod, w tym miejscu możesz zapytać - czy nie istnieją już konstrukcje dekoratora do definiowania jednostek komponowalnych, takich jak tf.function w TensorFlow, a jeśli tak, po co wprowadzać kolejny i czym się różni?

Krótka odpowiedź jest taka, że ​​kod generowany przez opakowanie tff.federated_computation nie jest ani TensorFlow, ani Pythonem - jest to specyfikacja systemu rozproszonego w wewnętrznym, niezależnym od platformy języku kleju . W tym momencie będzie to niewątpliwie zabrzmiało zagadkowo, ale proszę, miej na uwadze tę intuicyjną interpretację obliczeń federacyjnych jako abstrakcyjną specyfikację systemu rozproszonego. Wyjaśnimy to za minutę.

Najpierw pobawmy się trochę definicją. Obliczenia TFF są zwykle modelowane jako funkcje - z parametrami lub bez, ale z dobrze zdefiniowanymi sygnaturami typów. Możesz wydrukować podpis typu obliczenia, type_signature zapytanie do jego właściwości type_signature , jak pokazano poniżej.

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

Podpis typu informuje nas, że obliczenia akceptują zbiór różnych odczytów czujników na urządzeniach klienckich i zwracają jedną średnią na serwerze.

Zanim przejdziemy dalej, zastanówmy się nad tym przez minutę - dane wejściowe i wyjściowe tego obliczenia znajdują się w różnych miejscach (na CLIENTS vs. na SERVER ). Przypomnij sobie, co powiedzieliśmy w poprzedniej sekcji o rozmieszczeniach o tym, jak operacje TFF mogą obejmować różne lokalizacje i działać w sieci , a także to, co właśnie powiedzieliśmy o obliczeniach federacyjnych jako reprezentujących abstrakcyjne specyfikacje systemów rozproszonych. Właśnie zdefiniowaliśmy jedno takie obliczenie - prosty system rozproszony, w którym dane są pobierane na urządzeniach klienckich, a zagregowane wyniki pojawiają się na serwerze.

W wielu praktycznych scenariuszach obliczenia reprezentujące zadania najwyższego poziomu będą miały tendencję do akceptowania swoich danych wejściowych i zgłaszania wyników na serwerze - odzwierciedla to ideę, że obliczenia mogą być wyzwalane przez zapytania, które powstają i kończą się na serwerze.

Jednak FC API nie narzuca tego założenia, a wiele z bloków konstrukcyjnych, których używamy wewnętrznie (w tym liczne operatory tff.federated_... , które można znaleźć w API) ma wejścia i wyjścia z różnymi miejscami docelowymi, więc ogólnie powinieneś nie myśl o obliczeniach stowarzyszonych jako o czymś, co działa na serwerze lub jest wykonywane przez serwer . Serwer jest tylko jednym typem uczestnika w obliczeniach federacyjnych. Myśląc o mechanice takich obliczeń, najlepiej jest zawsze domyślnie przyjmować perspektywę globalnej sieci zamiast perspektywy pojedynczego scentralizowanego koordynatora.

Ogólnie rzecz biorąc, sygnatury typów funkcjonalnych są zwięźle reprezentowane jako (T -> U) dla typów T i U wejść i wyjść. Typ parametru formalnego (w tym przypadku sensor_readings ) jest określony jako argument dekoratora. Nie musisz określać typu wyniku - jest on określany automatycznie.

Chociaż TFF oferuje ograniczone formy polimorfizmu, zdecydowanie zachęca się programistów do wyraźnego informowania o typach danych, z którymi pracują, ponieważ ułatwia to zrozumienie, debugowanie i formalną weryfikację właściwości kodu. W niektórych przypadkach jawne określenie typów jest wymagane (np. Obliczenia polimorficzne nie są obecnie bezpośrednio wykonywalne).

Wykonywanie obliczeń stowarzyszonych

Aby wspierać programowanie i debugowanie, TFF umożliwia bezpośrednie wywoływanie obliczeń zdefiniowanych w ten sposób jako funkcje Pythona, jak pokazano poniżej. Tam, gdzie obliczenia oczekują wartości typu stowarzyszonego z bitem all_equal ustawionym na wartość False , możesz podać go jako zwykłą list w Pythonie, a dla typów stowarzyszonych z bitem all_equal ustawionym na True , możesz po prostu bezpośrednio podać (pojedynczy) składnik członkowski. W ten sposób wyniki są również przekazywane użytkownikowi.

get_average_temperature([68.5, 70.3, 69.8])
69.53334

Podczas wykonywania takich obliczeń w trybie symulacji działasz jako zewnętrzny obserwator z ogólnosystemowym widokiem, który ma możliwość dostarczania danych wejściowych i konsumowania wyjść w dowolnym miejscu w sieci, tak jak ma to miejsce w tym przypadku - podałeś wartości klienta na wejściu i zużywał wynik serwera.

Wróćmy teraz do notatki, którą zrobiliśmy wcześniej, dotyczącej kodu emitującego dekorator tff.federated_computation w języku kleju . Chociaż logika obliczeń TFF może być wyrażona jako zwykłe funkcje w Pythonie (wystarczy ozdobić je tff.federated_computation jak zrobiliśmy powyżej), i możesz bezpośrednio wywołać je za pomocą argumentów Pythona, tak jak wszystkie inne funkcje Pythona w tym notebook, za kulisami, jak zauważyliśmy wcześniej, obliczenia TFF nie są w rzeczywistości Pythonem.

tff.federated_computation przez to, że kiedy interpreter Pythona napotyka funkcję ozdobioną tff.federated_computation , śledzi instrukcje w treści tej funkcji raz (w czasie definiowania), a następnie konstruuje serializowaną reprezentację logiki obliczeń do wykorzystania w przyszłości - niezależnie od tego, czy do wykonania lub do włączenia jako składnik podrzędny do innych obliczeń.

Możesz to sprawdzić, dodając instrukcję print w następujący sposób:

@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".

Możesz pomyśleć o kodzie w Pythonie, który definiuje obliczenie federacyjne, podobnie do tego, jak myślisz o kodzie w Pythonie, który tworzy wykres TensorFlow w niesprzyjającym kontekście (jeśli nie jesteś zaznajomiony z niechętnymi zastosowaniami TensorFlow, pomyśl o swoim Kod Pythona definiujący wykres operacji do wykonania później, ale nie uruchamiający ich w locie). Niezbyt chętny do tworzenia wykresów kod w TensorFlow to Python, ale wykres TensorFlow skonstruowany przez ten kod jest niezależny od platformy i można go serializować.

Podobnie obliczenia TFF są zdefiniowane w Pythonie, ale instrukcje Pythona w ich tff.federated_mean , takie jak tff.federated_mean w pokazanym właśnie przykładzie, są kompilowane do przenośnej i niezależnej od platformy, tff.federated_mean do serializacji reprezentacji.

Jako programista nie musisz zajmować się szczegółami tej reprezentacji, ponieważ nigdy nie będziesz musiał bezpośrednio z nią pracować, ale powinieneś być świadomy jej istnienia, faktu, że obliczenia TFF są zasadniczo niechętne, i nie może przechwytywać dowolnego stanu Pythona. Kod Pythona zawarty w treści obliczeń TFF jest wykonywany w czasie definiowania, kiedy ciało funkcji Pythona ozdobione tff.federated_computation jest śledzone przed uzyskaniem serializacji. Nie jest odtwarzana ponownie w czasie wywołania (z wyjątkiem sytuacji, gdy funkcja jest polimorficzna; szczegółowe informacje można znaleźć na stronach dokumentacji).

Możesz się zastanawiać, dlaczego zdecydowaliśmy się wprowadzić dedykowaną wewnętrzną reprezentację inną niż Python. Jednym z powodów jest to, że ostatecznie obliczenia TFF mają być wdrażane w rzeczywistych środowiskach fizycznych i hostowane na urządzeniach mobilnych lub wbudowanych, gdzie Python może być niedostępny.

Innym powodem jest to, że obliczenia TFF wyrażają globalne zachowanie systemów rozproszonych, w przeciwieństwie do programów w języku Python, które wyrażają lokalne zachowanie poszczególnych uczestników. Możesz to zobaczyć w prostym przykładzie powyżej, ze specjalnym operatorem tff.federated_mean który akceptuje dane na urządzeniach klienckich, ale umieszcza wyniki na serwerze.

Operator tff.federated_mean nie może być łatwo modelowany jako zwykły operator w Pythonie, ponieważ nie jest wykonywany lokalnie - jak wspomniano wcześniej, reprezentuje system rozproszony, który koordynuje zachowanie wielu uczestników systemu. Takie operatory będziemy nazywać operatorami federacyjnymi , aby odróżnić je od zwykłych (lokalnych) operatorów w Pythonie.

System typów TFF i podstawowy zestaw operacji obsługiwanych w języku TFF znacznie odbiega od tych w Pythonie, co wymaga użycia dedykowanej reprezentacji.

Tworzenie obliczeń stowarzyszonych

Jak wspomniano powyżej, obliczenia federacyjne i ich składniki najlepiej rozumieć jako modele systemów rozproszonych i można myśleć o tworzeniu obliczeń federacyjnych jako o składaniu bardziej złożonych systemów rozproszonych z prostszych. Możesz myśleć o operatorze tff.federated_mean jako o rodzaju wbudowanych obliczeń stowarzyszonych z szablonem z podpisem typu ({T}@CLIENTS -> T@SERVER) (tak jak w przypadku obliczeń, które piszesz, ten operator również ma złożony konstrukcja - pod maską rozkładamy ją na prostszych operatorów).

To samo dotyczy tworzenia obliczeń federacyjnych. Obliczenie get_average_temperature może zostać wywołane w treści innej funkcji Pythona ozdobionej tff.federated_computation - spowoduje to osadzenie go w ciele rodzica, podobnie jak tff.federated_mean osadzono tff.federated_mean we własnym ciele.

Ważnym ograniczeniem, o którym należy pamiętać, jest to, że ciała funkcji Pythona ozdobione tff.federated_computation muszą składać się tylko z operatorów federacyjnych, tj. Nie mogą bezpośrednio zawierać operacji TensorFlow. Na przykład nie można bezpośrednio użyć interfejsów tf.nest celu dodania pary wartości stowarzyszonych. Kod TensorFlow musi być ograniczony do bloków kodu udekorowanych za pomocą tff.tf_computation omówionych w następnej sekcji. Tylko w ten sposób opakowany kod TensorFlow może zostać wywołany w treści tff.federated_computation .

Powody tego oddzielenia są techniczne (trudno oszukać operatorów takich jak tf.add do pracy z elementami nie-tensorami), a także architektoniczne. Językiem federacyjnych obliczeń (czyli logika zbudowane z serializacji ciał funkcji Pythona ozdobione tff.federated_computation ) ma służyć jako języka klej niezależny od platformy. Ten język kleju jest obecnie używany do tworzenia systemów rozproszonych z osadzonych sekcji kodu TensorFlow (ograniczony do bloków tff.tf_computation ). W miarę upływu czasu spodziewamy się potrzeby osadzania sekcji innej logiki spoza TensorFlow, takiej jak zapytania do relacyjnej bazy danych, które mogą reprezentować potoki wejściowe, wszystkie połączone ze sobą za pomocą tego samego języka kleju (bloki tff.federated_computation ).

Logika TensorFlow

Deklarowanie obliczeń TensorFlow

TFF jest przeznaczony do użytku z TensorFlow. W związku z tym większość kodu, który napiszesz w TFF, będzie prawdopodobnie zwykłym (tj. Wykonywanym lokalnie) kodem TensorFlow. Aby używać takiego kodu z TFF, jak wspomniano powyżej, wystarczy ozdobić go tff.tf_computation .

Na przykład, oto jak możemy zaimplementować funkcję, która pobiera liczbę i dodaje do niej 0.5 .

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

Po raz kolejny, patrząc na to, możesz się zastanawiać, dlaczego powinniśmy zdefiniować inny dekorator tff.tf_computation zamiast po prostu używać istniejącego mechanizmu, takiego jak tf.function . W przeciwieństwie do poprzedniej sekcji, tutaj mamy do czynienia ze zwykłym blokiem kodu TensorFlow.

Powodów jest kilka, których pełne omówienie wykracza poza zakres tego samouczka, ale warto wymienić główny:

  • Aby osadzić bloki konstrukcyjne wielokrotnego użytku zaimplementowane przy użyciu kodu TensorFlow w ciałach obliczeń federacyjnych, muszą one spełniać określone właściwości - takie jak śledzenie i serializacja w czasie definiowania, podpisy typu itp. Zwykle wymaga to jakiejś formy dekoratora.

Ogólnie rzecz biorąc, zalecamy stosowanie natywnych mechanizmów kompozycji tf.function , takich jak tf.function , jeśli to możliwe, ponieważ można oczekiwać, że dokładny sposób, w jaki dekorator TFF współdziała z pożądanymi funkcjami, będzie ewoluował.

Wracając do powyższego przykładowego fragmentu kodu, obliczenie add_half które właśnie zdefiniowaliśmy, może być traktowane przez TFF tak samo, jak każde inne obliczenia TFF. W szczególności ma podpis typu TFF.

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

Zwróć uwagę, że ten typ podpisu nie zawiera miejsc docelowych. Obliczenia TensorFlow nie mogą używać ani zwracać typów stowarzyszonych.

Możesz teraz również używać add_half jako bloku konstrukcyjnego w innych obliczeniach. Na przykład, oto jak można użyć operatora tff.federated_map , aby zastosować add_half punktowo do wszystkich składników członkowskich stowarzyszonej liczby zmiennoprzecinkowej na urządzeniach klienckich.

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

Wykonywanie obliczeń TensorFlow

Wykonywanie obliczeń zdefiniowanych za pomocą tff.tf_computation przebiega według tych samych zasad, które opisaliśmy dla tff.federated_computation . Mogą być wywoływane jako zwykłe wywołania w Pythonie w następujący sposób.

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

Jeszcze raz warto zauważyć, że wywołanie obliczenia add_half_on_clients w ten sposób symuluje proces rozproszony. Dane są pobierane na klientach i zwracane na klientach. Rzeczywiście, to obliczenie każe każdemu klientowi wykonać akcję lokalną. Nie ma tff.SERVER wyraźnie wymieniony w tym systemie (nawet jeśli w praktyce orkiestracja takiego przetwarzania może obejmować jeden). Pomyśl o obliczeniu zdefiniowanym w ten sposób jako koncepcyjnie analogiczne do etapu Map w MapReduce .

Należy również pamiętać, że to, co powiedzieliśmy w poprzedniej sekcji o obliczeniach TFF, które są serializowane w czasie definicji, pozostaje prawdziwe również dla kodu tff.tf_computation - add_half_on_clients w add_half_on_clients jest śledzona raz w czasie definicji. Przy kolejnych wywołaniach TFF używa swojej serializowanej reprezentacji.

Jedyną różnicą między metodami Pythona ozdobionymi tff.federated_computation a tymi ozdobionymi tff.tf_computation jest to, że te ostatnie są serializowane jako wykresy TensorFlow (podczas gdy te pierwsze nie mogą zawierać bezpośrednio osadzonego w nich kodu TensorFlow).

Pod maską każda metoda ozdobiona tff.tf_computation tymczasowo wyłącza tff.tf_computation wykonywanie, aby umożliwić uchwycenie struktury obliczenia. Chociaż gorliwe wykonywanie jest lokalnie wyłączone, możesz używać gorliwych konstrukcji TensorFlow, AutoGraph, TensorFlow 2.0 itp., O ile napiszesz logikę obliczeń w taki sposób, aby można je było poprawnie serializować.

Na przykład poniższy kod zakończy się niepowodzeniem:

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.

Powyższe zawodzi, ponieważ constant_10 został już skonstruowany poza grafem, który tff.tf_computation konstruuje wewnętrznie w treści add_ten podczas procesu serializacji.

Z drugiej strony, wywoływanie funkcji Pythona, które modyfikują bieżący wykres, gdy są wywoływane wewnątrz tff.tf_computation jest w porządku:

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

Zwróć uwagę, że mechanizmy serializacji w TensorFlow ewoluują i oczekujemy, że ewoluują również szczegóły dotyczące sposobu serializacji obliczeń TFF.

Praca ztf.data.Dataset s

Jak wspomniano wcześniej, unikalną cechą tff.tf_computation s jest to, że umożliwiają one pracę ztf.data.Dataset zdefiniowanymi abstrakcyjnie jako parametry formalne w kodzie. 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 liketf.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 ordinarytf.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 atf.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 .