Niestandardowe algorytmy sfederowane, część 1: Wprowadzenie do rdzenia sfederowanego

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

Ten poradnik jest pierwszą częścią serii dwuczęściowym, który pokazuje jak zaimplementować niestandardowych rodzajów federacyjnych algorytmów w TensorFlow Federated (TFF) z wykorzystaniem Federalne rdzeń (FC) - zestaw interfejsów niższego poziomu, które służą jako fundament, na którym wdrożyliśmy Federalne Learning (FL) warstwę.

Ta pierwsza część jest bardziej koncepcyjna; przedstawiamy kilka kluczowych pojęć i abstrakcji programistycznych stosowanych w TFF, a także demonstrujemy ich zastosowanie na bardzo prostym przykładzie z rozproszoną tablicą czujników temperatury. W drugiej części tej serii , używamy mechanizmów Wprowadzamy tutaj zaimplementować prostą wersję algorytmów szkoleniowych i ewaluacji federacyjnych. W obserwacji, zachęcamy do zbadania wdrażania w stowarzyszonym uśredniania w tff.learning .

Pod koniec tej serii powinieneś być w stanie rozpoznać, że zastosowania Federated Core niekoniecznie ograniczają się do nauki. 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 danych rozproszonych.

Chociaż ten poradnik jest przeznaczony do samowystarczalny, zachęcamy do pierwszych tutoriali przeczytać na klasyfikację obrazu i generowania tekstu na wyższym poziomie i bardziej delikatne wprowadzenie do ram TensorFlow Federalne i Federalne Learning API ( tff.learning ), jak pomoże Ci umieścić koncepcje, które tutaj opisujemy, w kontekście.

Przeznaczenie

W skrócie, Federalne Rdzeń (FC) to środowisko programistyczne, które umożliwia zwięźle wyrazić logikę programu, który łączy TensorFlow kod z rozproszonych operatorów komunikacyjnych, takich jak te, które są stosowane w Federacji uśredniania - Komputery rozproszonych sumy, średnie i innych typów rozproszonych agregacji na zbiorze urządzeń klienckich w systemie, rozgłaszanie modeli i parametrów do tych urządzeń itp.

Możesz być świadomi tf.contrib.distribute i naturalnym pytaniem w tym momencie mogą być: w jaki sposób ma to ramy różnią? W końcu oba frameworki próbują rozproszyć obliczenia TensorFlow.

Jednym ze sposobów, aby myśleć o tym, że, podczas gdy deklarowanym celem tf.contrib.distribute jest umożliwienie użytkownikom korzystania z istniejących modeli i kod szkolenia z minimalnymi zmianami w celu umożliwienia rozproszoną szkolenia i wiele koncentruje się na tym, jak wykorzystać rozproszonej infrastruktury 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 rozproszonych algorytmów przepływu danych, a nie na konkretnym zestawie zaimplementowanych możliwości uczenia rozproszonego.

Jedną z głównych docelowych odbiorców interfejsu API FC TFF są naukowcy i praktycy, którzy mogą chcieć poeksperymentować z nowymi algorytmami sfederowanego uczenia się i ocenić konsekwencje subtelnych wyborów projektowych, które wpływają na sposób organizacji przepływu danych w systemie rozproszonym, ale bez ugrzęźnięcia w szczegółach implementacji systemu. Poziom abstrakcji, do którego dąży FC API, w przybliżeniu odpowiada pseudokodowi, którego można użyć do opisania mechaniki algorytmu sfederowanego uczenia się w publikacji naukowej – jakie dane istnieją w systemie i jak są przekształcane, ale bez schodzenia do poziomu indywidualne wymiany komunikatów w sieci punkt-punkt.

TFF jako całość jest ukierunkowana na scenariusze, w których dane są dystrybuowane i muszą takie pozostać, np. ze względu na prywatność, i gdzie zbieranie wszystkich danych w scentralizowanej lokalizacji może nie być realną opcją. Ma to wpływ na implementację algorytmów uczenia maszynowego, które wymagają zwiększonego 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 twoje środowisko jest poprawnie skonfigurowane. Jeśli to nie działa, proszę odnieść się do montażu prowadnicy do instrukcji.

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

Jedną z cech wyróżniających TFF jest to, że pozwala na zwięźle wyrazić obliczeń TensorFlow-na podstawie danych federacyjnych. Będziemy używać terminu danych federacyjnych w tym poradniku w odniesieniu do zbioru elementów danych przechowywanych w całej 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 centralnej lokalizacji. Albo szereg rozproszonych czujników może gromadzić i przechowywać odczyty temperatury w swoich lokalizacjach.

Stowarzyszonych dane, takie jak te w powyższych przykładach są traktowane w TFF jako obywateli pierwszej klasy , to znaczy, mogą pojawić się jako parametry i wyniki funkcji i mają typy. Aby wzmocnić ten pogląd, będziemy odnosić się do federacyjnych zbiorów danych jako wartości federacyjnych, lub jako wartości stowarzyszonych typów.

Ważnym punktem do zrozumienia jest to, że modelujemy cały zbiór elementów danych we wszystkich urządzeniach (np. odczyty temperatury całego zbioru ze wszystkich czujników w rozproszonej tablicy) jako pojedynczą wartość sfederowaną.

Na przykład, oto jak można by określić w TFF rodzaj federacji pływaka hostowane 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)

Bardziej ogólnie, stowarzyszony typu w TFF określa się przez określenie typu T z jego składników użytkowników - elementy danych, które znajdują się w poszczególnych urządzeniach, a grupa G z urządzeń, na którym stowarzyszone wartości tego typu są gospodarzem (plus trzeci opcjonalna informacja, o której wkrótce wspomnimy). Odwołujemy się do grupy G urządzeń przyjmujących wartości stowarzyszonego jako umieszczenia wartość za. Zatem tff.CLIENTS jest przykładem miejsca.

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

Stowarzyszony typu składnikami użytkownika T i umieszczania G może być przedstawiony jako zwarty {T}@G , jak pokazano poniżej.

str(federated_float_on_clients)
'{float32}@CLIENTS'

Nawiasy klamrowe {} w tym zwięzły zapis służyć jako przypomnienie, że składniki elementu (elementy danych na różnych urządzeniach) mogą się różnić, jak można się spodziewać na przykład odczytów czujników temperatury, więc klienci jako grupa wspólnie gospodarzem multi -Set z T -typed przedmiotów, które razem stanowią wartość stowarzyszonego.

Ważne jest, aby pamiętać, że składniki członek wartości stowarzyszonego są zwykle nieprzezroczyste dla programisty, czyli wartość stowarzyszonego nie powinny być traktowane jako prosty dict zamocowanej przez identyfikator urządzenia w systemie - wartości te są przeznaczone do zostać przekształcone tylko zbiorowo przez operatorów zrzeszonych że abstrakcyjnie reprezentują różne rodzaje rozproszonych protokołów komunikacyjnych (takich jak agregacja). Jeśli brzmi to zbyt abstrakcyjnie, nie martw się – niedługo do tego wrócimy i zilustrujemy to konkretnymi przykładami.

Typy sfederowane w TFF występują w dwóch odmianach: te, w których składowe wartości sfederowanej mogą się różnić (jak pokazano powyżej) oraz te, o 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

Stowarzyszony typu z umieszczenia G , w którym wszystkie T -typed składniki użytkownika znane są równe może być zwięźle przedstawiona jako T@G (w przeciwieństwie do {T}@G , to znaczy z klamrami spadła do odzwierciedlenia fakt, że zbiór elementów składowych pręta składa się z jednego elementu).

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

Jednym z przykładów takiej wartości sfederowanej, która może pojawić się w praktycznych scenariuszach, jest hiperparametr (taki jak wskaźnik uczenia się, norma obcinania itp.), który został rozesłany przez serwer do grupy urządzeń uczestniczących w szkoleniu sfederowanym.

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

Na przykład załóżmy, że posiada parę float32 parametrów a i b za pomocą prostego jednowymiarowym liniowym modelu regresji. Możemy skonstruować (niesfederowany) typ takich modeli do użytku w TFF w następujący sposób. Wsporniki kątowe <> w typu string drukowanej są zwarte notacja TFF 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>'

Należy pamiętać, że jesteśmy określając tylko dtype s powyżej. Obsługiwane są również typy nieskalarne. W powyższym kodu tf.float32 jest oznaczenie skrót dla bardziej ogólnego tff.TensorType(dtype=tf.float32, shape=[]) .

Gdy ten model jest emitowany do klientów, typ wynikowej wartości federacyjnej można przedstawić w sposób przedstawiony poniżej.

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

Za symetrii stowarzyszonym pływaka powyżej, będziemy odnosić się do takiego rodzaju jak stowarzyszonym krotki. Ogólniej, będziemy często używać terminu stowarzyszonego XYZ odnoszą się do wartości stowarzyszonym, w którym składniki członka Pan XYZ -jak. W ten sposób będziemy rozmawiać o takich rzeczach federacyjnych krotek, Federalne sekwencji, modeli federacyjnych, i tak dalej.

Teraz, wracając do float32@CLIENTS - gdy pojawia się ono powielane na wielu urządzeniach, to jest rzeczywiście jeden float32 , ponieważ wszystkie państwa są takie same. Ogólnie rzecz biorąc, można myśleć o jakiejkolwiek wszystko równego stowarzyszonym typu, czyli jedną z form T@G , jako izomorficzna non-stowarzyszonym typu T , ponieważ w obu przypadkach, istnieje faktycznie tylko jeden (choć potencjalnie replikowane) pozycja typu T .

Ze względu na izomorfizm między T i T@G , można się zastanawiać, w jakim celu, jeśli w ogóle, te ostatnie rodzaje mogą służyć. Czytaj.

Miejsca docelowe

Przegląd projektu

W poprzednim rozdziale, wprowadziliśmy pojęcie praktyki - grupy uczestników systemu, które mogą być wspólnie gospodarzem wartości stowarzyszonego, a my wykazały stosowanie tff.CLIENTS jako specyfikacji przykładem miejsca.

Aby wyjaśnić, dlaczego pojęcie stażu jest tak fundamentalne, że trzeba włączyć go do systemu typu TFF, przypomnieć, co pisaliśmy na początku tego tutoriala na temat niektórych planowanych zastosowań TFF.

Chociaż w tym samouczku zobaczysz tylko kod TFF wykonywany lokalnie w symulowanym środowisku, naszym celem jest umożliwienie TFF 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 osobny 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 móc wnioskować o tym, 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 (oraz udowodnienia faktów dotyczących sposobu przetwarzania danych). Specyfikacje rozmieszczenia są jednym z mechanizmów zaprojektowanych do wspierania tego.

TFF został zaprojektowany jako środowisko programistyczne danych-centric, i jako takie, w przeciwieństwie do niektórych istniejących ram, które koncentrują się na działalności i gdzie może uruchomić te operacje, TFF koncentruje się na danych, a jeżeli materializuje danych i jak to się przekształca. W związku z tym rozmieszczenie jest modelowane jako właściwość danych w TFF, a nie jako właściwość operacji na danych. Rzeczywiście, jak zaraz 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.

Reprezentujący typ pewnej wartości jako T@G lub {T}@G (a nie tylko T ) podejmuje decyzje pośrednictwa dane jawne, a wraz z analizy statycznej programów napisanych w TFF, może służyć jako podstawa do zapewnienia formalne gwarancje prywatności dla poufnych danych na urządzeniu.

Ważną rzeczą, aby pamiętać w tym momencie jest jednak to, że podczas gdy my zachęcić użytkowników TFF być jawne temat grup urządzeń uczestniczących że gospodarze dane (miejsca docelowe), programista będzie nigdy do czynienia z surowych danych lub tożsamości poszczególnych uczestników .

W organizmie kodu TFF, z założenia nie jest w żaden sposób do wyliczania urządzeń, które stanowią grupę o tff.CLIENTS lub sondę do istnienia urządzenia określonego w grupie. W interfejsie API Federated Core, podstawowym zestawie abstrakcji architektonicznych ani podstawowej infrastrukturze środowiska uruchomieniowego, 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.

Przypomnijmy tu, co pisaliśmy wcześniej o wartości stowarzyszonych typów będących w przeciwieństwie do Pythona dict , że jeden nie może po prostu wyliczyć ich składników członków. Pomyśl o wartościach, którymi manipuluje logika programu TFF, jako powiązanych z miejscami docelowymi (grupami), a nie z poszczególnymi uczestnikami.

Staże przeznaczone do pierwszej klasy obywatel w TFF, jak również, a może pojawić się jako parametrach i wynikach placement typu (być reprezentowane przez tff.PlacementType w API). W przyszłości planujemy udostępnić różnych operatorów do przekształcania lub łączenia miejsc docelowych, ale jest to poza zakresem tego samouczka. Na razie wystarczy pomyśleć o placement jako nieprzejrzysty prymitywne typu wbudowanego w TFF, podobnie jak int i bool są nieprzezroczyste typów wbudowanych w Pythonie, z tff.CLIENTS bycie stałym dosłownym tego typu, podobnie 1 będąc stałym dosłownym typu int .

Określanie miejsc docelowych

TFF przewiduje dwa podstawowe literały pośrednictwa, tff.CLIENTS i tff.SERVER , aby go łatwo wyrazić bogatą gamę praktycznych scenariuszy, które są naturalnie modelowanych jako architekturze klient-serwer, z wieloma urządzeniami klienckimi (telefonów komórkowych, urządzeń wbudowanych, rozproszonych baz danych czujniki itp) zaaranżowane przez jednego koordynatora centralnym serwerze. TFF jest również przeznaczony do obsługi niestandardowych miejsc docelowych, wielu grup klientów, wielowarstwowych i innych bardziej ogólnych architektur rozproszonych, ale ich omawianie wykracza poza zakres tego samouczka.

TFF nie określa, co albo gdy tff.CLIENTS lub tff.SERVER faktycznie reprezentować.

W szczególności tff.SERVER mogą być pojedyncze urządzenie fizyczne (członek grupy jednoelementowy), ale może równie dobrze być grupą replik w klastrze odporny na uszkodzenia systemem państwowego maszynowego replikacji - nie robimy jakieś specjalne architektoniczny założenia. Raczej używamy all_equal trochę wspomniano w poprzednim rozdziale, aby wyrazić fakt, że mamy do czynienia z reguły tylko w jednym punkcie danych na serwerze.

Podobnie tff.CLIENTS w niektórych zastosowaniach może reprezentować wszystkich klientów w systemie - co w kontekście uczenia stowarzyszonym czasami nazywamy ludności, ale na przykład w implementacjach produkcyjnych Federalne uśredniania może reprezentować kohortę - podzbiór klienci wybrani do udziału w danej rundzie szkolenia. 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 lokalnych symulacjach grupa klientów jest określana na podstawie danych sfederowanych dostarczanych jako dane wejściowe.

Obliczenia federacyjne

Deklarowanie obliczeń sfederowanych

TFF zaprojektowano jako silnie zdefiniowane funkcjonalne środowisko programistyczne, które obsługuje programowanie modułowe.

Podstawową jednostką kompozycji TFF jest stowarzyszony obliczenia - przekrój sterującego, który może przyjmować wartości stowarzyszone na wejściu i zwraca wartość stowarzyszone jako wyjście. 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ższym kodzie, w tym momencie można się pytać - nie są tam już dekorator konstruktów do zdefiniowania jednostki dające się składać takie jak tf.function w TensorFlow, a jeśli tak, to dlaczego wprowadzić jeszcze jeden, i jak to jest inaczej?

Krótka odpowiedź jest taka, że kod wygenerowany przez tff.federated_computation owinięcie jest ani TensorFlow, ani nie jest Python - to specyfikacja systemu rozproszonego w wewnętrznym języku klej niezależny od platformy. W tym momencie będzie to bez wątpienia brzmiało tajemniczo, ale proszę miej na uwadze tę intuicyjną interpretację obliczeń sfederowanych jako abstrakcyjnej specyfikacji systemu rozproszonego. Wyjaśnimy to za minutę.

Najpierw pobawmy się trochę definicją. Obliczenia TFF są generalnie modelowane jako funkcje — z parametrami lub bez, ale z dobrze zdefiniowanymi sygnaturami typów. Można wydrukować podpis typu dla obliczeń przez odpytywanie jego type_signature nieruchomości, jak pokazano poniżej.

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

Sygnatura typu mówi nam, że obliczenia akceptują zbiór różnych odczytów czujników na urządzeniach klienckich i zwracają pojedynczą średnią na serwerze.

Zanim przejdziemy dalej, niech zastanowić się nad tym przez chwilę - wejście i wyjście z tego wyliczenia znajdują się w różnych miejscach (na CLIENTS kontra w SERVER ). Przypomnijmy, co mówiliśmy w poprzednim rozdziale dotyczącym praktyk dotyczących sposobu działania TFF może rozciągać się w poprzek miejsc, i działać w sieci, a co właśnie powiedział o federacyjnych obliczeń jako reprezentujące streszczenie specyfikacji systemów rozproszonych. Właśnie zdefiniowaliśmy jedno takie obliczenie - prosty system rozproszony, w którym dane są konsumowane na urządzeniach klienckich, a zagregowane wyniki pojawiają się na serwerze.

W wielu praktycznych scenariuszy, obliczenia, które stanowią zadania najwyższego poziomu będzie miał tendencję do zaakceptowania ich wejść i wyjść zgłosić ich na serwerze - to odzwierciedla ideę, że obliczenia mogą być wywołane przez zapytań i kończyć się na serwerze.

Jednak FC API nie nakłada to założenie, a wiele bloków używamy wewnętrznie (w tym licznych tff.federated_... operatorów można znaleźć w API) posiada wejścia i wyjścia z różnych miejsc docelowych, tak w ogóle, należy nie myśleć o stowarzyszonym obliczeń jako coś, co działa na serwerze lub jest wykonywana przez serwer. Serwer jest tylko jednym typem uczestnika obliczeń federacyjnych. Myśląc o mechanice takich obliczeń, najlepiej jest zawsze domyślnie używać perspektywy globalnej sieci, a nie perspektywy pojedynczego scentralizowanego koordynatora.

Ogólnie rzecz biorąc, typ sygnatury funkcjonalne zwarty reprezentowane (T -> U) o typy T i U z wejściami i wyjściami, odpowiednio. Typ parametru formalnego (takie sensor_readings w tym przypadku) jest określony jako argument do dekoratora. Nie musisz określać typu wyniku - jest on określany automatycznie.

Chociaż TFF oferuje ograniczone formy polimorfizmu, programiści są silnie zachęcani do jednoznacznego określania typów danych, z którymi pracują, ponieważ ułatwia to zrozumienie, debugowanie i formalną weryfikację właściwości kodu. W niektórych przypadkach wymagane jest wyraźne określenie typów (np. obliczeń polimorficznych nie można obecnie wykonywać bezpośrednio).

Wykonywanie obliczeń sfederowanych

W celu wsparcia rozwoju i debugowania, TFF pozwala bezpośrednio wywoływać obliczenia zdefiniowane w ten sposób jako funkcje Pythona, jak pokazano poniżej. Gdzie obliczenia spodziewa się wartość stowarzyszonym typu z all_equal bitu False , można karmić go jako zwykłego list w Pythonie i stowarzyszonych typów z all_equal bitu True , można po prostu bezpośrednio karmić (single) członek składowy. W ten sposób wyniki są przesyłane do Ciebie.

get_average_temperature([68.5, 70.3, 69.8])
69.53334

Wykonując takie obliczenia w trybie symulacji, działasz jako zewnętrzny obserwator z widokiem na cały system, który ma możliwość dostarczania danych wejściowych i konsumowania danych wyjściowych w dowolnym miejscu w sieci, tak jak to ma miejsce w tym przypadku - dostarczyłeś wartości klienta na wejściu i zużył wynik serwera.

Teraz powrót Chodźmy do notatki zrobiliśmy wcześniej o tff.federated_computation dekoratora emitującej kod w języku kleju. Chociaż logika obliczeń TFF może być wyrażona jako zwykłych funkcji w Pythonie (wystarczy udekorować je tff.federated_computation jak zrobiliśmy powyżej) i można bezpośrednio wywołuj je z argumentów Pythona, tak jak innych funkcji Pythona w tym notebook, za kulisami, jak już wspomniano wcześniej, obliczenia TFF nie są faktycznie Python.

Co rozumiemy przez to, że kiedy interpreter Pythona napotyka funkcję ozdobione tff.federated_computation , że śledzi wypowiedzi w organizmie Ta funkcja jest raz (w czasie definicji), a następnie konstruuje zserializowaną reprezentacji logiki obliczeń dla wykorzystania w przyszłości - czy do wykonania lub do włączenia jako podkomponent do innego obliczenia.

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 myśleć o kodzie w Pythonie, który definiuje obliczenia sfederowane, podobnie jak o kodzie Pythona, który buduje wykres TensorFlow w niegorliwym kontekście (jeśli nie jesteś zaznajomiony z niegorliwymi zastosowaniami TensorFlow, pomyśl o swoim Kod Pythona definiujący wykres operacji do wykonania później, ale nie uruchamiający ich w locie). Niechętnym kodem do budowania wykresów w TensorFlow jest Python, ale wykres TensorFlow skonstruowany przez ten kod jest niezależny od platformy i możliwy do serializacji.

Podobnie obliczenia TFF są zdefiniowane w Pythonie, ale wypowiedzi Python w ich ciałach, takich jak tff.federated_mean na przykład weve właśnie pokazano, są kompilowane w przenośny i niezależny od platformy serializable reprezentacji pod maską.

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 nieciekawe, i nie może przechwycić dowolnego stanu Pythona. Kod Pythona zawarty w ciele obliczeń TFF jest wykonywane w momencie definicji, gdy ciało funkcji Pythona ozdobione tff.federated_computation jest drogi, zanim się w odcinkach. Nie jest on odtwarzany 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 nie być dostępny.

Innym powodem jest to, że obliczenia TFF wyrażają globalne zachowanie systemów rozproszonych, w przeciwieństwie do programów Pythona, które wyrażają lokalne zachowanie poszczególnych uczestników. Widać, że w prostym przykładzie powyżej, ze szczególnym operatora tff.federated_mean który akceptuje dane na urządzeniach klienckich, ale depozyty wyniki na serwerze.

Operator tff.federated_mean nie można łatwo modelować jako zwykłego operatora w Pythonie, ponieważ nie wykonuje lokalnie - jak wspomniano wcześniej, stanowi rozproszony system, który koordynuje zachowanie wielu uczestników systemu. Będziemy odwoływać się do takich operatorów jak operatorzy federacyjnych, 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.

Komponowanie obliczeń sfederowanych

Jak wspomniano powyżej, obliczenia stowarzyszone i ich elementy składowe są najlepiej rozumiane jako modele systemów rozproszonych, a tworzenie obliczeń stowarzyszonych można traktować jako składanie bardziej złożonych systemów rozproszonych z prostszych. Można myśleć o tff.federated_mean operatora jako rodzaj wbudowanych szablonów stowarzyszonym obliczeń z podpisem typu ({T}@CLIENTS -> T@SERVER) (rzeczywiście, tak jak obliczeń piszesz, to operator posiada również kompleks struktura - pod maską dzielimy ją na prostsze operatory).

To samo dotyczy tworzenia obliczeń sfederowanych. Obliczenia get_average_temperature mogą być powoływane w ciele innej funkcji Pythona ozdobione tff.federated_computation - spowoduje to, że jest osadzony w ciele rodzica, w taki sam sposób tff.federated_mean został osadzony w jego własne ciało wcześniej.

Ważnym ograniczeniem aby mieć świadomość, że organy funkcji Pythona ozdobione tff.federated_computation muszą składać się wyłącznie z podmiotów stowarzyszonych, to znaczy, że nie mogą bezpośrednio zawierać operacje TensorFlow. Na przykład, nie można korzystać bezpośrednio tf.nest interfejsy dodać parę wartości federacyjnych. Kod TensorFlow musi ograniczać się do bloków kodu ozdobione tff.tf_computation omówione w następnym rozdziale. Dopiero gdy owinięty w ten sposób może zawinięty kod TensorFlow być powoływane w ciele tff.federated_computation .

Przyczyny tego rozdziału są techniczne (trudno nakłonić operatorów takich jak tf.add do pracy z nie-tensorów), jak również architektonicznych. 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. Język ten klej jest obecnie wykorzystywane do budowania systemów wbudowanych rozprowadzany z sekcji kodu TensorFlow (ograniczonej do tff.tf_computation bloków). W pełni czasu, możemy przewidywać konieczność osadzić sekcji drugiej, logiki nie TensorFlow, takie jak relacyjnych baz danych, które mogłyby zapytań reprezentują rurociągi wejściowe, wszystkie połączone ze sobą za pomocą tego samego języka kleju (na tff.federated_computation bloków).

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. W celu korzystania z takiego kodu z TFF, jak wspomniano powyżej, to po prostu musi być ozdobione tff.tf_computation .

Na przykład, oto jak moglibyśmy wdrożyć funkcję, która pobiera liczbę i dodaje 0.5 do niego.

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

Po raz kolejny, patrząc na to, można się zastanawiać, dlaczego powinniśmy zdefiniować inny dekorator tff.tf_computation zamiast po prostu przy użyciu 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 sfederowanych obliczeń, muszą one spełniać pewne właściwości — takie jak śledzenie i serializacja w czasie definiowania, posiadanie sygnatur typów itp. Zwykle wymaga to jakiejś formy dekoratora.

Na ogół zaleca się używanie natywnych mechanizmów TensorFlow dla kompozycji, takich jak tf.function , w miarę możliwości, jak dokładny sposób, w jaki można oczekiwać współdziała dekorator TFF jest z funkcjami chcących rozwijać.

Teraz, wracając do kodu przykładowy fragment powyższego obliczenia add_half po prostu zdefiniowane mogą być traktowane przez TFF jak każdy inny obliczeń TFF. W szczególności posiada sygnaturę typu TFF.

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

Pamiętaj, że ten typ podpisu nie ma miejsc docelowych. Obliczenia TensorFlow nie mogą wykorzystywać ani zwracać typów stowarzyszonych.

Teraz można również użyć add_half jako blok konstrukcyjny w innych obliczeń. Na przykład, oto jak można użyć tff.federated_map operatorowi zastosować add_half punktowo do wszystkich składników członkiem stowarzyszonym pływaka 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

Wykonanie obliczeń zdefiniowanych z tff.tf_computation zastosowanie te same zasady, jak te opisaliśmy dla tff.federated_computation . Można je wywoływać 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>]

Po raz kolejny, to warto zauważyć, że powołując się na obliczeniach add_half_on_clients w ten sposób symuluje rozproszonego procesu. Dane są zużywane na klientach i zwracane na klientach. Rzeczywiście, to obliczenie powoduje, że każdy klient wykonuje akcję lokalną. Nie ma tff.SERVER wyraźnie wymienione w tym systemie (nawet jeśli w praktyce wzniecanie takie przetwarzanie może obejmować jeden). Pomyśl o obliczenia określonej w ten sposób jako koncepcyjnie analogiczne do Map etapie MapReduce .

Ponadto, należy pamiętać, że to, co mówiliśmy w poprzednim rozdziale o obliczenia TFF coraz odcinkach w momencie definicji pozostaje prawdziwe dla tff.tf_computation kodu, jak również - ciało Python od add_half_on_clients dostaje prześledzić raz, przy rozdzielczości. W kolejnych wywołaniach TFF używa swojej serializowanej reprezentacji.

Jedyna różnica pomiędzy sposobami Pythona ozdobione tff.federated_computation i tych ozdobione tff.tf_computation jest to, że są one w odcinkach w postaci wykresów TensorFlow (podczas gdy pierwsze nie mogą zawierać kod TensorFlow bezpośrednio osadzony w nich).

Pod maską, każda metoda ozdobione tff.tf_computation czasowo wyłącza chętny egzekucji w celu umożliwienia struktura obliczenia do niewoli. Podczas gdy przyspieszone wykonywanie jest lokalnie wyłączone, możesz używać gorliwych konstrukcji TensorFlow, AutoGraph, TensorFlow 2.0 itp., o ile piszesz logikę obliczeń w taki sposób, aby można je było poprawnie zserializować.

Na przykład następujący kod nie powiedzie się:

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 nie ponieważ constant_10 już wykonana na zewnątrz wykresu że tff.tf_computation konstruuje wewnętrznie w korpusie add_ten podczas serializacji.

Z drugiej strony, powołując funkcje Pythona, które modyfikują aktualny wykres po nazwie 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 spodziewamy się, że ewoluują również szczegóły dotyczące serializacji obliczeń TFF.

Praca z tf.data.Dataset s

Jak wspomniano wcześniej, to unikalna cecha tff.tf_computation s jest to, że pozwala im na pracę z tf.data.Dataset s zdefiniowane abstrakcyjnie jako parametrów formalnych przez kod. Parametry mają być reprezentowane w TensorFlow jako zbiory danych muszą być deklarowane za pomocą tff.SequenceType konstruktora.

Na przykład, określenie typu tff.SequenceType(tf.float32) określa abstrakcyjny sekwencję elementów pływak w TFF. Sekwencje mogą zawierać tensory lub złożone struktury zagnieżdżone (przykłady tych przedstawimy później). Zwięzłe przedstawienie z sekwencji T -typed elementów jest 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 .