Benutzerdefinierte föderierte Algorithmen, Teil 1: Einführung in den föderierten Kern

Auf TensorFlow.org ansehen In Google Colab ausführen Quelle auf GitHub anzeigen Notizbuch herunterladen

Dieses Tutorial ist der erste Teil einer zweiteiligen Serie , die zeigt , wie benutzerdefinierte Typen von föderierten Algorithmen in TensorFlow Federated (TFF) mit dem implementieren Federated Core (FC) - eine Reihe von untergeordneten Schnittstellen , die als Grundlage dienen , auf dem wir haben das umgesetzt Federated Learning (FL) Schicht.

Dieser erste Teil ist eher konzeptionell; Wir stellen einige der Schlüsselkonzepte und Programmierabstraktionen vor, die in TFF verwendet werden, und demonstrieren ihre Verwendung an einem sehr einfachen Beispiel mit einem verteilten Array von Temperatursensoren. Im zweiten Teil dieser Serie , verwenden wir die Mechanismen , die wir hier vorstellen , eine einfache Version von föderierten Training und Auswertealgorithmen zu implementieren. Als Follow-up, empfehlen wir Ihnen , zu studieren , die Implementierung von föderierten Lungs in tff.learning .

Am Ende dieser Reihe sollten Sie erkennen, dass die Anwendungen von Federated Core nicht unbedingt auf das Lernen beschränkt sind. Die von uns angebotenen Programmierabstraktionen sind recht allgemein gehalten und können zB verwendet werden, um Analysen und andere benutzerdefinierte Berechnungsarten über verteilte Daten zu implementieren.

Obwohl dieses Tutorial zu sein , ist so konzipiert , in sich geschlossene, empfehlen wir Ihnen zum ersten Lese Tutorials auf Bildklassifikation und Textgenerierung für ein übergeordnetes und sanfter Einstieg in die TensorFlow Federated Rahmen und der Federated Learning APIs ( tff.learning ), wie es wird Ihnen helfen, die hier beschriebenen Konzepte in einen Kontext zu setzen.

Verwendungszweck

Auf den Punkt gebracht, ist Federated Core (FC) eine Entwicklungsumgebung , die es ermöglicht , kompakt Programmlogik , dass kombiniert TensorFlow Code mit verteilten Kommunikationsbetreiber, wie diejenigen , die verwendet werden , um auszudrücken Federated Averaging - Berechnung verteilt Summen, Mittelwerte und andere Arten von verteilten Aggregationen über eine Reihe von Client-Geräten im System, Senden von Modellen und Parametern an diese Geräte usw.

Sie können sich bewusst sein tf.contrib.distribute , und eine natürliche Frage an dieser Stelle fragen sein kann: in welcher Weise sich dieser Rahmen unterscheiden? Schließlich versuchen beide Frameworks, TensorFlow-Berechnungen zu verteilen.

Eine Möglichkeit , darüber nachzudenken ist , dass, während das erklärte Ziel von tf.contrib.distribute ist es, Benutzern zu erlauben , bestehende Modelle zu verwenden und Trainingscode mit minimalen Änderungen verteilt Ausbildung zu ermöglichen und viel Fokus liegt darauf , wie die Vorteile der verteilten Infrastruktur zu übernehmen Um den bestehenden Trainingscode effizienter zu machen, besteht das Ziel des Federated Core von TFF darin, Forschern und Praktikern explizite Kontrolle über die spezifischen Muster der verteilten Kommunikation zu geben, die sie in ihren Systemen verwenden werden. Der Schwerpunkt bei FC liegt auf der Bereitstellung einer flexiblen und erweiterbaren Sprache zum Ausdrücken von verteilten Datenflussalgorithmen und nicht auf einem konkreten Satz implementierter verteilter Trainingsfähigkeiten.

Eine der Hauptzielgruppen für die FC-API von TFF sind Forscher und Praktiker, die möglicherweise mit neuen föderierten Lernalgorithmen experimentieren und die Konsequenzen subtiler Designentscheidungen bewerten möchten, die sich auf die Art und Weise auswirken, in der der Datenfluss im verteilten System orchestriert wird ohne sich in Details der Systemimplementierung zu verzetteln. Der Abstraktionsgrad, den die FC-API anstrebt, entspricht in etwa dem Pseudocode, mit dem man in einer Forschungspublikation die Mechanik eines föderierten Lernalgorithmus beschreiben könnte - welche Daten im System vorhanden sind und wie sie transformiert werden, ohne jedoch auf die Ebene von . abzusinken einzelnen Punkt-zu-Punkt-Netzwerknachrichtenaustausch.

TFF zielt insgesamt auf Szenarien ab, in denen Daten verteilt werden und dies bleiben müssen, z. B. aus Datenschutzgründen, und in denen die Sammlung aller Daten an einem zentralen Ort möglicherweise keine praktikable Option ist. Dies hat Auswirkungen auf die Implementierung von maschinellen Lernalgorithmen, die im Vergleich zu Szenarien, in denen alle Daten an einem zentralen Ort in einem Rechenzentrum gesammelt werden können, ein erhöhtes Maß an expliziter Kontrolle erfordern.

Bevor wir anfangen

Bevor wir in den Code eintauchen, versuchen Sie bitte, das folgende "Hello World"-Beispiel auszuführen, um sicherzustellen, dass Ihre Umgebung richtig eingerichtet ist. Wenn es nicht funktioniert, entnehmen Sie bitte der Installationsanleitung für Anweisungen.

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

Verbunddaten

Eines der charakteristischen Merkmale des TFF ist , dass es kompakt erlaubt TensorFlow-basierte Berechnungen auf föderierte Daten auszudrücken. Wir werden den Begriff föderierten Daten in diesem Tutorial mit zu einer Sammlung von Datenelementen über eine Gruppe von Geräten in einem verteilten System gehostet zu verweisen. Auf Mobilgeräten ausgeführte Anwendungen können beispielsweise Daten sammeln und lokal speichern, ohne sie an einen zentralen Ort hochzuladen. Oder ein Array von verteilten Sensoren kann Temperaturmesswerte an ihren Standorten sammeln und speichern.

Federated Daten wie die in den obigen Beispielen behandelt werden , in TFF als Bürger erster Klasse , dh sie können als Parameter und Ergebnisse der Funktionen erscheinen, und sie haben Typen. Um diesen Begriff zu verstärken, werden wir auf föderierte Datensätze als föderierte Werte oder als Werte von föderierten Typen beziehen.

Es ist wichtig zu verstehen, dass wir die gesamte Sammlung von Datenelementen über alle Geräte (z. B. die gesamten Sammlungstemperaturmesswerte von allen Sensoren in einem verteilten Array) als einen einzigen föderierten Wert modellieren.

Zum Beispiel, hier ist , wie man in TFF den Typen eines föderierten Schwimmer gehostet von einer Gruppe von Client - Geräten definieren würde. Eine Sammlung von Temperaturmesswerten, die über ein Array von verteilten Sensoren materialisiert werden, könnte als ein Wert dieses föderierten Typs modelliert werden.

federated_float_on_clients = tff.type_at_clients(tf.float32)

Allgemeiner gesagt , wird durch die Angabe der Art mit einer föderierten Typ in TFF definierten T seiner Mitglieds Bestandteile - die Elemente der Daten, die befinden sich auf einzelne Geräte und die Gruppe G der Geräte , auf denen Federated Werte dieses Typs sind gehostet (plus einem dritten, optionale Informationen, die wir gleich erwähnen werden). Wir verweisen auf die Gruppe G der Geräte eine föderierte Wert als der Wert der Platzierung Hosting. So tff.CLIENTS ist ein Beispiel für eine Platzierung.

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

Ein föderierte Typ mit dem Mitglied Bestandteilen T und Platzierung G kompakt dargestellt werden als {T}@G , wie unten gezeigt.

str(federated_float_on_clients)
'{float32}@CLIENTS'

Die geschweiften Klammern {} in dieser prägnanten als Erinnerung Notation dienen , dass die Mitglieds Bestandteile (Elemente von Daten auf verschiedenen Geräten) unterscheiden können, wie Sie zum Beispiel erwarten, Temperatursensorwerte, so dass die Kunden als Gruppe gemeinsam sind ein Hosting - Multi -set von T -typed Elemente , die zusammen die föderierte Wert darstellen.

Es ist wichtig zu beachten , dass das Mitglied Bestandteile eines föderierten Wertes für den Programmierer im Allgemeinen undurchsichtig sind, dh ein föderierte Wert soll nicht als einfacher gedacht wird dict durch eine Kennung eines Geräts im System eingegeben - diese Werte sollen kollektiv nur von föderierten Operatoren transformiert werden , die abstrakt verschiedene Arten von verteilten Kommunikationsprotokolle (wie beispielsweise Aggregation) darstellen. Wenn das zu abstrakt klingt, machen Sie sich keine Sorgen – wir kommen gleich darauf zurück und veranschaulichen es mit konkreten Beispielen.

Föderierte Typen in TFF gibt es in zwei Varianten: solche, bei denen sich die Mitgliedsbestandteile eines föderierten Werts unterscheiden können (wie gerade oben gesehen), und solche, bei denen bekannt ist, dass sie alle gleich sind. Dies wird durch die dritten gesteuerten, optional all_equal Parameter im tff.FederatedType Konstruktor (säumigen zu False ).

federated_float_on_clients.all_equal
False

Eine föderierte Typ mit einer Platzierung G in dem alle der T -typed Mitglied Bestandteile gleich sind , sind bekannt kompakt dargestellt werden als T@G (im Gegensatz zu {T}@G , die mit den geschweiften Klammern fallen gelassen wird, zu reflektieren , die Tatsache, dass die Mehrfachmenge von Mitgliedsbestandteilen aus einem einzigen Element besteht).

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

Ein Beispiel für einen föderierten Wert dieses Typs, der in praktischen Szenarien auftreten könnte, ist ein Hyperparameter (wie eine Lernrate, eine Clipping-Norm usw.), der von einem Server an eine Gruppe von Geräten gesendet wurde, die am föderierten Training teilnehmen.

Ein weiteres Beispiel ist ein Satz von Parametern für ein auf dem Server vortrainiertes Modell für maschinelles Lernen, die dann an eine Gruppe von Clientgeräten gesendet wurden, wo sie für jeden Benutzer personalisiert werden können.

Angenommen, wir ein Paar haben float32 Parameter a und b für einen einfachen eindimensionalen linearen Regressionsmodells. Wir können den (nicht föderierten) Typ solcher Modelle zur Verwendung in TFF wie folgt konstruieren. Der Winkel Klammern <> in der gedruckten Typ String sind eine kompakte TFF Notation für benannte oder unbenannte Tupel.

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

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

Beachten Sie, dass wir nur die Angabe sind dtype s oben. Nicht-skalare Typen werden ebenfalls unterstützt. In dem obigen Code, tf.float32 ist eine Abkürzung Notation für den allgemeineren tff.TensorType(dtype=tf.float32, shape=[]) .

Wenn dieses Modell an Clients gesendet wird, kann der Typ des resultierenden Verbundwerts wie unten gezeigt dargestellt werden.

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

Per Symmetrie mit föderierten Schwimmer oben, werden wir auf eine solche Art als Verbund Tupel verweisen. Allgemeiner werden wir den Begriff föderierten XYZ verwenden häufig zu einem föderierten Wert zu beziehen , in denen das Mitglied Bestandteile XYZ -ähnlichen sind. So werden wir über Dinge wie föderierte Tupeln, föderierte Sequenzen, föderierte Modelle, und so weiter sprechen.

Nun zu kommen zurück float32@CLIENTS - während es über mehrere Geräte hinweg repliziert erscheint, es ist eigentlich ein einziges float32 , da alle Mitglieder gleich sind. Im Allgemeinen denken , Sie können jeder alles gleich föderierten Typs, dh eine der Form T@G , als isomorph zu einem Nicht-föderierten Typ T , da in beiden Fällen gibt es eigentlich nur ein einzigen ist (wenn auch möglicherweise repliziert) Artikel vom Typ T .

In Anbetracht der Isomorphismus zwischen T und T@G , können Sie sich fragen , was Zweck, wenn überhaupt, könnten die letzteren Typen dienen. Weiter lesen.

Platzierungen

Designübersicht

Im vorigen Abschnitt haben wir das Konzept der Platzierung eingeführt - Gruppen von Systemteilnehmern , die eine föderierte Wertes gemeinsam Hosting sein könnten, und wir haben die Verwendung von demonstriert tff.CLIENTS als Beispiel Vorgabe einer Platzierung.

Um zu erklären , warum der Begriff der Platzierung so grundlegend ist , dass wir es in das TFF - Typ - System zu übernehmen benötigt, daran erinnern , was wir am Anfang dieses Tutorials über einige der beabsichtigte Verwendung von TFF erwähnt.

Obwohl in diesem Tutorial nur TFF-Code lokal in einer simulierten Umgebung ausgeführt wird, ist unser Ziel, dass TFF das Schreiben von Code ermöglicht, den Sie für die Ausführung auf Gruppen physischer Geräte in einem verteilten System bereitstellen können, möglicherweise einschließlich mobiler oder eingebetteter Geräte läuft Android. Jedes dieser Geräte würde einen separaten Satz von Anweisungen zur lokalen Ausführung erhalten, abhängig von seiner Rolle im System (ein Endbenutzergerät, ein zentralisierter Koordinator, eine Zwischenschicht in einer mehrschichtigen Architektur usw.). Es ist wichtig, darüber nachdenken zu können, welche Teilmengen von Geräten welchen Code ausführen und wo unterschiedliche Teile der Daten physisch materialisiert werden könnten.

Dies ist besonders wichtig, wenn es sich zB um Anwendungsdaten auf mobilen Geräten handelt. Da die Daten privat sind und sensibel sein können, benötigen wir die Möglichkeit, statisch zu überprüfen, dass diese Daten das Gerät niemals verlassen (und Fakten über die Verarbeitung der Daten zu beweisen). Die Platzierungsvorgaben sind einer der Mechanismen, die dies unterstützen sollen.

TFF wurde als datenorientierte Programmierumgebung, und als solche im Gegensatz zu einigen der bestehenden Frameworks entwickelt , dass der Fokus auf den Betrieb und wo diese Operationen ausgeführt werden könnten, TFF auf Daten konzentriert, wo diese Daten materialisiert, und wie es verwandelt. Folglich wird die Platzierung als Eigenschaft von Daten in TFF modelliert und nicht als Eigenschaft von Datenoperationen. Wie Sie im nächsten Abschnitt sehen werden, erstrecken sich einige der TFF-Operationen tatsächlich über mehrere Standorte und laufen sozusagen "im Netzwerk", anstatt von einer einzelnen Maschine oder einer Gruppe von Maschinen ausgeführt zu werden.

Stellvertretend für den Typen eines bestimmten Wertes als T@G oder {T}@G (im Gegensatz zu nur gegenüber T ) macht Datenplatzierung Entscheidungen explizit, und zusammen mit einer statischen Analyse von Programmen in TFF geschrieben, kann es als Grundlage dient für die Bereitstellung von formale Datenschutzgarantien für sensible Daten auf dem Gerät.

Eine wichtige Sache , an diesem Punkt zu beachten ist jedoch, dass , während wir TFF Benutzer ermutigen ausdrücklich über Gruppen von beteiligten Geräten zu sein , dass die Daten - Host (die Platzierungen), wird der Programmierer nie mit den Rohdaten oder Identitäten der einzelnen Teilnehmer beschäftigen .

(Hinweis: Es ist zwar weit außerhalb des Geltungsbereichs dieses Tutorial geht, sollten wir erwähnen , dass es eine bemerkenswerte Ausnahme zu dem obigen einem tff.federated_collect Operator, der als Low-Level - Primitive bestimmt ist, nur für spezielle Situationen Seiner explizite Verwendung. in Situationen, in denen dies vermieden werden kann, wird nicht empfohlen, da dies die möglichen zukünftigen Anwendungen einschränken kann.Wenn wir beispielsweise im Verlauf einer statischen Analyse feststellen, dass eine Berechnung solche Low-Level-Mechanismen verwendet, können wir deren Zugriff auf bestimmte Datentypen.)

Im Körper des TFF - Code, durch Design, gibt es keine Möglichkeit , die Geräte aufzuzählen, die die Gruppe von vertreten bilden tff.CLIENTS , oder Sonde für die Existenz eines bestimmten Geräts in der Gruppe. Es gibt nirgendwo in der Federated Core API, den zugrunde liegenden Architekturabstraktionen oder der Kernlaufzeitinfrastruktur, die wir zur Unterstützung von Simulationen bereitstellen, ein Konzept einer Geräte- oder Clientidentität. Die gesamte von Ihnen geschriebene Berechnungslogik wird als Operationen für die gesamte Clientgruppe ausgedrückt.

Recall hier , was wir bereits erwähnt über Werte von föderierten Typen im Gegensatz zu Python zu sein dict , dass man nicht einfach ihr Mitglied Bestandteile aufzuzählen. Stellen Sie sich Werte vor, die Ihre TFF-Programmlogik so manipuliert, dass sie mit Platzierungen (Gruppen) und nicht mit einzelnen Teilnehmern verbunden sind.

Platzierungen sind so konzipiert , als auch ein Bürger erster Klasse in TFF sein, und als Parameter und Ergebnisse einer erscheinen placement Art (durch dargestellt werden tff.PlacementType in der API). In Zukunft planen wir, eine Vielzahl von Operatoren zum Transformieren oder Kombinieren von Placements bereitzustellen, dies liegt jedoch außerhalb des Rahmens dieser Anleitung. Vorerst genügt es , daran zu denken , die placement als eine undurchsichtige primitive eingebauten Typ in TFF, ähnlich wie int und bool sind undurchsichtig eingebauten Typen in Python, mit tff.CLIENTS eine Konstante wörtliche dieser Art ist, nicht unähnlich 1 eine konstante Literal - Typ ist int .

Platzierungen angeben

TFF bietet zwei grundlegende Platzierung Literale, tff.CLIENTS und tff.SERVER , es zu machen , einfach , die reiche Vielfalt von praktischen Szenarien zum Ausdruck bringen , die als Client-Server - Architekturen natürlich modelliert werden, mit mehreren Client - Geräten (Mobiltelefone, eingebettete Geräte, verteilte Datenbanken , Sensoren, etc.) von einem einzigen zentralen Server Koordinator orchestriert. TFF wurde entwickelt, um auch benutzerdefinierte Platzierungen, mehrere Clientgruppen, mehrstufige und andere, allgemeinere verteilte Architekturen zu unterstützen, aber deren Erörterung würde den Rahmen dieses Tutorials sprengen.

TFF schreibt nicht vor , was entweder die tff.CLIENTS oder die tff.SERVER tatsächlich darstellen.

Insbesondere tff.SERVER sein kann , ein einziges physisches Gerät (ein Mitglied einer Singleton - Gruppe), aber es könnte genauso gut eine Gruppe von Repliken in einer fehlertoleranten Cluster ausgeführt Zustandsmaschine Replikation sein - wir keine besonderen architektonischen machen Annahmen. Vielmehr nutzen wir das all_equal Bit im vorhergehenden Abschnitt erwähnte die Tatsache zum Ausdruck bringen , dass wir im Allgemeinen mit nur einem einzigen Elemente von Daten auf dem Server zu tun.

Ebenso tff.CLIENTS in einigen Anwendungen könnte alle Clients im System darstellen - was im Zusammenhang mit dem föderierten Lernen wir manchmal verweisen , da die Bevölkerung, sondern zB in Produktionsimplementierungen von Federated Averaging , kann es eine Kohorte darstellen - eine Teilmenge von die Klienten, die für die Teilnahme an einer bestimmten Trainingsrunde ausgewählt wurden. Die abstrakt definierten Platzierungen erhalten eine konkrete Bedeutung, wenn eine Berechnung, in der sie vorkommen, zur Ausführung bereitgestellt wird (oder einfach wie eine Python-Funktion in einer simulierten Umgebung aufgerufen wird, wie in diesem Tutorial gezeigt). In unseren lokalen Simulationen wird die Gruppe der Clients durch die als Eingabe gelieferten föderierten Daten bestimmt.

Föderierte Berechnungen

Föderierte Berechnungen deklarieren

TFF ist als stark typisierte funktionale Programmierumgebung konzipiert, die die modulare Entwicklung unterstützt.

Die Grundeinheit der Zusammensetzung in TFF ist eine föderierte Berechnung - ein Abschnitt der Logik , die föderierte Werte als Eingabe annehmen und föderierte Werte als Ausgabe zurück. So können Sie eine Berechnung definieren, die den Durchschnitt der vom Sensor-Array gemeldeten Temperaturen aus unserem vorherigen Beispiel berechnet.

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

Mit Blick auf die oben genannten Code, Sie an dieser Stelle fragen könnte - nicht bereits Dekorateur Konstrukte zusammensetzbaren Einheiten wie definieren tf.function in TensorFlow, und wenn ja, warum vorstellen noch ein anderes, und wie ist es anders?

Die kurze Antwort ist , dass der Code durch die erzeugte tff.federated_computation Wrapper weder TensorFlow ist, noch ist es Python - es ist eine Spezifikation eines verteilten Systems in einer internen plattformunabhängigen Leim Sprache. Dies mag an dieser Stelle sicherlich kryptisch klingen, aber bedenken Sie bitte diese intuitive Interpretation einer föderierten Berechnung als abstrakte Spezifikation eines verteilten Systems. Wir erklären es gleich.

Spielen wir zunächst ein wenig mit der Definition. TFF-Berechnungen werden im Allgemeinen als Funktionen modelliert – mit oder ohne Parameter, aber mit wohldefinierten Typsignaturen. Sie können die Art Signatur einer Berechnung drucken , indem seine Abfrage type_signature Eigenschaft, wie unten gezeigt.

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

Die Typsignatur sagt uns, dass die Berechnung eine Sammlung verschiedener Sensormesswerte auf Clientgeräten akzeptiert und einen einzelnen Durchschnitt auf dem Server zurückgibt.

Bevor wir weiter gehen, lassen Sie für eine Minute darüber nachdenken ist - der Eingang und der Ausgang dieser Berechnung sind in verschiedenen Orten (auf CLIENTS vs. am SERVER ). Recall , was wir im vorhergehenden Abschnitt gesagt auf Platzierungen darüber , wie TFF Operationen über mehrere Standorte erstrecken können, und führen Sie in dem Netzwerk, und das, was wir gerade gesagt über föderierte Berechnungen als Darstellung abstrakte Spezifikationen von verteilten Systemen. Wir haben nur eine solche Berechnung definiert - ein einfaches verteiltes System, in dem Daten auf Client-Geräten verbraucht werden und die aggregierten Ergebnisse auf dem Server erscheinen.

In vielen praktischen Szenarien, die die Berechnungen der obersten Ebene Aufgaben darstellen , werden ihre Eingaben akzeptieren und ihre Ausgänge auf dem Server zu berichten - dies spiegelt die Idee , dass Berechnungen könnten durch Abfragen ausgelöst werden , die auf dem Server beginnen und enden.

Allerdings ist FC API nicht diese Annahme verhängen, und viele der Bausteine verwenden wir intern (darunter zahlreiche tff.federated_... Operatoren , die Sie in der API finden können) haben Ein- und Ausgänge mit unterschiedlichen Platzierungen, so dass im Allgemeinen, sollten Sie nicht denkt über eine föderierte Berechnung als etwas , das auf dem Server ausgeführt wird oder von einem Server ausgeführt wird. Der Server ist nur eine Art von Teilnehmer an einer föderierten Berechnung. Wenn Sie über die Mechanik solcher Berechnungen nachdenken, ist es am besten, immer die globale netzwerkweite Perspektive anstelle der Perspektive eines einzelnen zentralisierten Koordinators zu verwenden.

Im allgemeinen wird funktionaler Typ Signaturen kompakt wie dargestellt (T -> U) für die Typen T und U der Ein- und Ausgänge auf. Der Typ des formalen Parameters (wie sensor_readings in diesem Fall) , wird als das Argument zu der Überzugseinrichtung spezifiziert. Sie müssen den Typ des Ergebnisses nicht angeben - er wird automatisch bestimmt.

Obwohl TFF begrenzte Formen von Polymorphismus bietet, wird Programmierern dringend empfohlen, die Datentypen, mit denen sie arbeiten, explizit anzugeben, da dies das Verständnis, das Debuggen und die formale Überprüfung der Eigenschaften Ihres Codes erleichtert. In einigen Fällen ist die explizite Angabe von Typen erforderlich (zB sind polymorphe Berechnungen derzeit nicht direkt ausführbar).

Ausführen föderierter Berechnungen

Um Entwicklung und Debugging zu unterstützen, können Sie mit TFF Berechnungen, die auf diese Weise als Python-Funktionen definiert wurden, direkt aufrufen, wie unten gezeigt. Wenn die Berechnung einen Wert eines föderierten Typ mit dem erwartet all_equal Bit auf False , können Sie es als eine einfache Feed - list in Python, und für föderierte Typen mit dem all_equal Bit auf True , können Sie einfach direkt füttern (single) Mitglied konstituiert. Auf diese Weise werden Ihnen auch die Ergebnisse zurückgemeldet.

get_average_temperature([68.5, 70.3, 69.8])
69.53334

Wenn Sie solche Berechnungen im Simulationsmodus ausführen, agieren Sie als externer Beobachter mit einer systemweiten Sicht, der die Möglichkeit hat, an beliebigen Stellen im Netzwerk Eingänge zu liefern und Ausgänge zu verbrauchen, wie es hier tatsächlich der Fall ist - Sie haben Client-Werte geliefert bei der Eingabe und verbrauchte das Serverergebnis.

Nun wollen wir die Rückkehr zu einer Notiz haben wir früher über die tff.federated_computation Dekorateur in einem Klebstoff Sprache emittierenden Code. Obwohl die Logik der TFF Berechnungen können als gewöhnliche Funktionen in Python ausgedrückt werden ( die Sie gerade brauchen , um sie mit dekorieren tff.federated_computation wie wir oben getan haben), und Sie können direkt invoke sie mit Python Argumente wie jede andere Python - Funktionen in dieser Notebook, hinter den Kulissen, wie wir bereits erwähnt, TFF Berechnungen sind eigentlich nicht Python.

Was wir damit meinen , ist , dass , wenn der Python - Interpreter eine Funktion mit verzierter begegnet tff.federated_computation es die Aussagen in dieser Funktion Körper Spuren einmal (Definitionszeit), und dann baut eine serialisierte Darstellung der Logik der Berechnung für die zukünftige Verwendung - ob zur Ausführung oder als Unterkomponente in eine andere Berechnung einzubauen.

Sie können dies überprüfen, indem Sie wie folgt eine print-Anweisung hinzufügen:

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

Sie können sich Python-Code vorstellen, der eine föderierte Berechnung definiert, ähnlich wie Python-Code, der einen TensorFlow-Graphen in einem nicht eifrigen Kontext erstellt (wenn Sie mit der nicht eifrigen Verwendung von TensorFlow nicht vertraut sind, denken Sie an Ihre Python-Code, der einen Graphen von Operationen definiert, die später ausgeführt werden sollen, diese aber nicht im laufenden Betrieb ausführen). Der nicht eifrige Code zum Erstellen von Diagrammen in TensorFlow ist Python, aber der von diesem Code erstellte TensorFlow-Diagramm ist plattformunabhängig und serialisierbar.

Ebenso werden TFF Berechnungen in Python definiert, aber die Python - Anweisungen in ihren Körpern, wie tff.federated_mean im Beispiel weve gerade gezeigt, sind in eine tragbare und plattformunabhängige serializable Darstellung unter der Haube zusammengestellt.

Als Entwickler müssen Sie sich nicht mit den Details dieser Darstellung beschäftigen, da Sie nie direkt damit arbeiten müssen, aber Sie sollten sich ihrer Existenz bewusst sein, die Tatsache, dass TFF-Berechnungen grundsätzlich nicht eifrig sind, und kann keinen beliebigen Python-Zustand erfassen. Python - Code enthielt im Körper einer TFF Berechnung wird zur Definitionszeit ausgeführt wird , wenn der Körper der Funktion Python mit verzierter tff.federated_computation verfolgt wird , bevor serialisiert zu werden . Sie wird zum Zeitpunkt des Aufrufs nicht erneut zurückverfolgt (außer wenn die Funktion polymorph ist; Einzelheiten finden Sie auf den Dokumentationsseiten).

Sie fragen sich vielleicht, warum wir uns entschieden haben, eine dedizierte interne Nicht-Python-Darstellung einzuführen. Ein Grund dafür ist, dass TFF-Berechnungen letztendlich in realen physischen Umgebungen bereitgestellt und auf mobilen oder eingebetteten Geräten gehostet werden sollen, auf denen Python möglicherweise nicht verfügbar ist.

Ein weiterer Grund ist, dass TFF-Berechnungen das globale Verhalten verteilter Systeme ausdrücken, im Gegensatz zu Python-Programmen, die das lokale Verhalten einzelner Teilnehmer ausdrücken. Sie können , dass in dem einfachen Beispiel oben, mit dem speziellen Operator sehen tff.federated_mean , die Daten auf Client - Geräten, sondern legt die Ergebnisse auf dem Server akzeptiert.

Der Betreiber tff.federated_mean kann nicht einfach wie ein gewöhnlicher Operator in Python modelliert werden, da sie nicht lokal ausgeführt werden - wie bereits erwähnt, stellt es ein verteiltes System , dass Koordinaten das Verhalten von mehreren Systemteilnehmern. Wir werden als föderierte Betreiber solchen Betreiber verweisen, um sie von gewöhnlichen (lokal) Operatoren in Python zu unterscheiden.

Das TFF-Typsystem und der grundlegende Satz von Operationen, die in der TFF-Sprache unterstützt werden, weichen daher erheblich von denen in Python ab, was die Verwendung einer dedizierten Darstellung erforderlich macht.

Zusammenstellen föderierter Berechnungen

Wie oben erwähnt, lassen sich föderierte Berechnungen und ihre Bestandteile am besten als Modelle verteilter Systeme verstehen, und Sie können sich das Zusammensetzen föderierter Berechnungen so vorstellen, dass komplexere verteilte Systeme aus einfacheren zusammengesetzt werden. Sie denken , kann der tff.federated_mean Betreiber als eine Art eingebaute Template föderierten Berechnung mit einer Art Signatur ({T}@CLIENTS -> T@SERVER) (in der Tat, wie Berechnungen Sie schreiben, dieser Operator hat auch einen Komplex Struktur - unter der Haube zerlegen wir sie in einfachere Operatoren).

Das gleiche gilt für die Erstellung föderierter Berechnungen. Die Berechnung get_average_temperature kann mit geschmückter in einem Körper einer anderen Python - Funktion aufgerufen werden tff.federated_computation - so führt dazu , dass es im Körper des Mutter eingebettet werden, viel in der gleichen Art und Weise tff.federated_mean in seinem eigenen Körper früher eingebettet war.

Eine wichtige Einschränkung bewusst sein, dass Körper von Python - Funktionen verziert mit tff.federated_computation nur von föderierten Operatoren bestehen muss, das heißt, sie können nicht direkt TensorFlow Operationen enthalten. Zum Beispiel können Sie nicht direkt verwenden tf.nest Schnittstellen ein Paar von föderierten Werten hinzuzufügen. TensorFlow Code muss mit einer zu dekorierenden Codeblöcken beschränkt wird tff.tf_computation im folgenden Abschnitt diskutiert. Nur wenn auf diese Weise kann der umhüllte TensorFlow Code gewickelt werden in den Körper eines aufgerufen tff.federated_computation .

Die Gründe für diese Trennung sind technische (es ist schwer Operatoren wie Trick tf.add zur Arbeit mit nicht-Tensoren) sowie Architektur. Die Sprache der föderierte Berechnungen (dh die Logik von serialisiert Körpern von Python Funktionen mit dekorierten konstruiert tff.federated_computation ) als eine plattformunabhängige Sprache Klebstoff dienen. Dieser Kleber Sprache wird zur Zeit aufzubauen verteilte Systeme von eingebetteten Abschnitten TensorFlow Code (beschränkt verwendet tff.tf_computation Blöcke). In der Fülle der Zeit erwarten wir die Notwendigkeit einzubetten Abschnitte von anderen, nicht TensorFlow Logik, wie relationale Datenbankabfragen , die Eingangsleitungen darstellen könnten, die alle miteinander verbunden sind, die gleiche Klebstoff - Sprache (die tff.federated_computation Blöcke).

TensorFlow-Logik

Deklarieren von TensorFlow-Berechnungen

TFF wurde für die Verwendung mit TensorFlow entwickelt. Daher ist der Großteil des Codes, den Sie in TFF schreiben, wahrscheinlich gewöhnlicher (dh lokal ausgeführter) TensorFlow-Code. Um einen solchen Code mit TFF zu verwenden, wie oben erwähnt, es muss nur mit dekoriert werden tff.tf_computation .

Zum Beispiel, hier ist , wie wir eine Funktion implementieren könnte , die eine Zahl nimmt und fügt 0.5 zu.

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

Wieder einmal in diesem suchen, können Sie sich fragen, warum wir einen weiteren Dekorateur definieren sollte tff.tf_computation anstatt einfach mit einem vorhandenen Mechanismus wie tf.function . Anders als im vorherigen Abschnitt haben wir es hier mit einem gewöhnlichen Block von TensorFlow-Code zu tun.

Dafür gibt es einige Gründe, deren vollständige Behandlung den Rahmen dieses Tutorials sprengen würde, aber es lohnt sich, den wichtigsten zu nennen:

  • Um wiederverwendbare Bausteine, die mit TensorFlow-Code implementiert wurden, in die Körper von föderierten Berechnungen einzubetten, müssen sie bestimmte Eigenschaften erfüllen – z.

Im Allgemeinen empfehlen wir TensorFlow native Mechanismen für die Zusammensetzung, wie die Verwendung von tf.function , wo immer möglich, da die genaue Art und Weise , in der TFF Dekoratorklasse wirkt mit eifrigen Funktionen erwartet werden kann , entwickeln.

Nun, zum Beispiel Code des Rückwegs Snippet oben, die Berechnung add_half wir können nur definiert durch TFF wie jede andere TFF Berechnung behandelt werden. Insbesondere weist es eine Signatur vom Typ TFF auf.

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

Beachten Sie, dass dieser Signaturtyp keine Platzierungen hat. TensorFlow-Berechnungen können keine föderierten Typen verarbeiten oder zurückgeben.

Sie können jetzt auch verwenden add_half als Baustein in anderen Berechnungen. Zum Beispiel, hier ist , wie Sie die verwenden können tff.federated_map Operator anwenden add_half punktuellen an alle Mitglieds Bestandteile eines föderierten Schwimmer auf den Client - Geräten.

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

Ausführen von TensorFlow-Berechnungen

Die Ausführung von Berechnungen mit definierten tff.tf_computation folgt den gleichen Regeln wie die , die wir für beschrieben tff.federated_computation . Sie können in Python wie folgt als normale Callables aufgerufen werden.

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

Wieder einmal ist es erwähnenswert, dass die Berechnung Aufruf add_half_on_clients auf diese Weise ein verteiltes Prozess simuliert. Daten werden auf Clients verbraucht und auf Clients zurückgegeben. Tatsächlich führt diese Berechnung dazu, dass jeder Client eine lokale Aktion ausführt. Es gibt keine tff.SERVER explizit in diesem System erwähnt (auch wenn in der Praxis eine solche Verarbeitung Orchestrierung könnte man einbeziehen). Denken Sie an eine Berechnung auf diese Weise definiert als konzeptionell analog der Map Stufe in MapReduce .

Auch im Auge behalten , dass das, was gesagt , dass wir im vorhergehenden Abschnitt über TFF Berechnungen bei der Definition Zeit bleibt für echten serialisiert immer tff.tf_computation als auch Code - der Python Körper add_half_on_clients einmal zur Definitionszeit zurückverfolgt wird. Bei nachfolgenden Aufrufen verwendet TFF seine serialisierte Darstellung.

Der einzige Unterschied zwischen Python Methoden mit dekorierten tff.federated_computation und solche mit dekoriert tff.tf_computation ist , dass letztere als TensorFlow Graphen serialisiert werden (wobei die erstere nicht nutzen TensorFlow Code direkt in sie eingebettet enthalten).

Unter der Haube jede Methode mit dekorierten tff.tf_computation sperrt vorübergehend gespannt , um die Ausführung erfasst der Berechnung der Struktur zu ermöglichen. Während die Ausführung von Eager lokal deaktiviert ist, können Sie die Konstrukte Eager TensorFlow, AutoGraph, TensorFlow 2.0 usw. gerne verwenden, solange Sie die Logik Ihrer Berechnung so schreiben, dass sie korrekt serialisiert werden kann.

Der folgende Code schlägt beispielsweise fehl:

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.

Die oben versagt , weil constant_10 bereits außerhalb des Graphen konstruiert worden , dass tff.tf_computation konstruiert intern im Körper des add_ten während der Serialisierung.

Auf der anderen Seite, Python - Funktionen aufrufen, die das aktuelle Diagramm ändern , wenn sie innerhalb eines gerufenen tff.tf_computation ist in Ordnung:

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

Beachten Sie, dass sich die Serialisierungsmechanismen in TensorFlow weiterentwickeln und wir erwarten, dass sich auch die Details zur Serialisierung von Berechnungen durch TFF weiterentwickeln.

Arbeiten mit tf.data.Dataset s

Wie bereits erwähnt, ein einzigartiges Merkmal von tff.tf_computation ist s , dass sie Ihnen die Arbeit mit ermöglicht tf.data.Dataset s abstrakt als formalen Parametern von Ihrem Code definiert. Parameter werden in TensorFlow dargestellt als Datensätze , die deklariert werden müssen mit tff.SequenceType Konstruktor.

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