このページは Cloud Translation API によって翻訳されました。
Switch to English

カスタムフェデレーションアルゴリズム、パート1:フェデレーションコアの概要

TensorFlow.orgで見る Google Colabで実行 GitHubでソースを表示する

このチュートリアルは、 Federated Core(FC)を使用してTensorFlow Federated(TFF)でカスタムタイプのフェデレーションアルゴリズムを実装する方法を示す2部構成のシリーズの最初のパートです。 Federated Learning(FL)レイヤーを実装しました。

この最初の部分はより概念的です。 TFFで使用される主要な概念とプログラミングの抽象化のいくつかを紹介し、温度センサーの分散配列を使用した非常に単純な例でその使用法を示します。 このシリーズの第2部では 、ここで紹介するメカニズムを使用して、フェデレーショントレーニングと評価アルゴリズムの単純なバージョンを実装します。フォローアップとして、 tff.learningでの統合平均化の実装を検討することをお勧めします。

このシリーズを終えると、Federated Coreのアプリケーションは必ずしも学習に限定されないことを理解できるはずです。私たちが提供するプログラミングの抽象化は非常に一般的であり、たとえば、分散データに対して分析やその他のカスタムタイプの計算を実装するために使用できます。

このチュートリアルは自己完結型であるように設計されていますが、まず画像分類テキスト生成に関するチュートリアルを読んで、TensorFlow FederatedフレームワークとFederated Learning API( tff.learning )のより高度で穏やかな概要を読むことをお勧めします。ここで説明する概念を文脈に沿って説明するのに役立ちます。

使用目的

一言で言えば、フェデレーテッド・コア(FC)は、コンパクトプログラムロジックを表現することを可能にする開発環境は、その分散通信に使用されるもののようなオペレータ、と結合TensorFlowコードフェデレーテッド・アベレージ -コンピューティング分散合計、平均、および他のタイプシステム内の一連のクライアントデバイスを介した分散集約のモデル、ブロードキャストモデルおよびそれらのデバイスへのパラメーターなど

あなたはtf.contrib.distribute知っているかもしれません、そしてこの時点で尋ねる自然な質問は:このフレームワークはどのように違うのですか?結局のところ、どちらのフレームワークもTensorFlow計算を分散させることを試みています。

それについて考える1つの方法は、 tf.contrib.distribute目標は、ユーザーが既存のモデルと最小限の変更でトレーニングコードを使用して分散トレーニングを可能にすることであり、分散インフラストラクチャを利用する方法に重点が置かれていることです。既存のトレーニングコードをより効率的にするために、TFFのFederated Coreの目標は、研究者と開業医がシステムで使用する分散通信の特定のパターンを明示的に制御できるようにすることです。 FCの焦点は、実装された分散トレーニング機能の具体的なセットではなく、分散データフローアルゴリズムを表現するための柔軟で拡張可能な言語を提供することです。

TFFのFC APIの主な対象読者の1人は、新しいフェデレーション学習アルゴリズムを試して、分散システムでのデータの流れが調整される方法に影響を与える微妙な設計選択の結果を評価する可能性のある研究者と実務家です。システム実装の詳細に悩まされることなく。 FC APIが目指している抽象化のレベルは、研究出版物で連合学習アルゴリズムの仕組みを説明するために使用できる疑似コードにほぼ対応しています-システムに存在するデータとそれがどのように変換されるかを示しますが、個別のポイントツーポイントネットワークメッセージ交換。

TFFは全体として、データが配布されるシナリオを対象としており、たとえばプライバシー上の理由から、また中央の場所ですべてのデータを収集することは実行可能なオプションではない可能性があるため、そのような状態を維持する必要があります。これは、すべてのデータをデータセンターの集中管理された場所に蓄積できるシナリオと比較して、より高度な明示的な制御を必要とする機械学習アルゴリズムの実装に影響します。

始める前に

コードに飛び込む前に、次の「Hello World」の例を実行して、環境が正しく設定されていることを確認してください。動作しない場合は、 インストールガイドを参照してください。


!pip install --quiet --upgrade tensorflow_federated_nightly
!pip install --quiet --upgrade nest_asyncio

import nest_asyncio
nest_asyncio.apply()
import collections

import numpy as np
import tensorflow as tf
import tensorflow_federated as tff
@tff.federated_computation
def hello_world():
  return 'Hello, World!'

hello_world()
b'Hello, World!'

連合データ

TFFの際立った特徴の1つは、 フェデレーションデータに対するTensorFlowベースの計算をコンパクトに表現できることです。このチュートリアルでは、分散データという用語を使用して、分散システムのデバイスのグループ全体でホストされるデータ項目のコレクションを指します。たとえば、モバイルデバイスで実行されているアプリケーションは、中央の場所にアップロードすることなく、データを収集してローカルに保存できます。または、分散型センサーのアレイが、その場所の温度の読み取り値を収集して保存する場合があります。

上記の例のようなフェデレーションデータはTFFでファーストクラスの市民として扱われます。つまり、それらは関数のパラメーターと結果として表示され、型を持っています。この概念を強化するために、連合データセットを連合値または連合タイプの値と呼びます

理解しておくべき重要な点は、すべてのデバイスにわたるデータ項目の収集全体(たとえば、分散アレイ内のすべてのセンサーからの収集温度測定値全体)を単一のフェデレーション値としてモデル化していることです。

たとえば、クライアントデバイスのグループがホストするフェデレーションフロートのタイプをTFFで定義する方法を次に示します。分散センサーのアレイ全体で具体化される温度測定値のコレクションは、このフェデレーションタイプの値としてモデル化できます。

federated_float_on_clients = tff.FederatedType(tf.float32, tff.CLIENTS)

より一般的には、TFFのフェデレーションタイプは、そのメンバーコンスティチュエントのタイプT個々のデバイスに存在するデータのアイテム、およびこのタイプのフェデレーション値がホストされているデバイスのグループG (さらに3つ目、後で説明するオプションの情報)。フェデレーション値をホストするデバイスのグループGを値の配置と呼びます 。したがって、 tff.CLIENTSは配置の例です。

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

以下に示すように、メンバー構成要素Tと配置Gを持つフェデレーテッドタイプは、 {T}@Gとしてコンパクトに表すことができます。

str(federated_float_on_clients)
'{float32}@CLIENTS'

この簡潔な表記の中括弧{}は、たとえば、温度センサーの読み取り値のように、メンバーの構成要素(異なるデバイス上のデータの項目)が異なる場合があることを思い出させるため、グループとしてのクライアントが共同でマルチをホストしています-連合値を構成するTタイプの項目のセット。

フェデレーション値のメンバー構成要素は一般にプログラマーには不透明であることに注意することが重要です。つまり、フェデレーション値は、システム内のデバイスの識別子によってキーイングされる単純なdictと考えるべきではありません。これらの値は、さまざまな種類の分散通信プロトコル(集約など)を抽象的に表す連合オペレーターによってのみ集合的に変換されます。これが抽象的に聞こえても心配しないでください。すぐに戻ります。具体的な例を使って説明します。

TFFのフェデレーション型には2つの種類があります。フェデレーション値のメンバーの構成要素が異なる(上記のように)場合と、すべてが等しいことがわかっている場合です。これは、 tff.FederatedTypeコンストラクターの3番目のオプションのall_equalパラメーターによって制御されます(デフォルトはFalse )。

federated_float_on_clients.all_equal
False

すべてのTタイプのメンバー構成要素が等しいことがわかっている配置Gを持つ連合タイプは、 T@Gとしてコンパクトに表すことができます( {T}@Gとは対照的に、つまり、中括弧がドロップされて反映されます)メンバー構成要素のマルチセットが単一のアイテムで構成されるという事実)。

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

実際のシナリオで発生する可能性があるそのようなタイプのフェデレーション値の1つの例は、サーバーによってフェデレーショントレーニングに参加しているデバイスのグループにブロードキャストされたハイパーパラメーター(学習率、クリッピングノルムなど)です。

別の例は、サーバーで事前にトレーニングされた機械学習モデルの一連のパラメーターであり、クライアントデバイスのグループにブロードキャストされ、ユーザーごとにパーソナライズできます。

たとえば、単純な1次元線形回帰モデルのfloat32パラメーターabペアがaとします。 TFFで使用するために、このようなモデルの(非フェデレーテッド)タイプを次のように構築できます。印刷されるタイプ文字列の山括弧<>は、名前付きまたは名前なしのタプルのコンパクトなTFF表記です。

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

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

上記のdtypeのみを指定していることに注意してください。非スカラー型もサポートされています。上記のコードで、 tf.float32は、より一般的なtff.TensorType(dtype=tf.float32, shape=[])ショートカット表記です。

このモデルがクライアントにブロードキャストされると、結果のフェデレーション値のタイプは、次のように表すことができます。

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

上記のフェデレーテッドフロートとの対称性により、このようなタイプをフェデレーテッドタプルと呼びます。より一般的には、 フェデレーテッドXYZという用語を使用して、メンバーの構成要素がXYZに似ているフェデレーション値を指すことがよくあります。したがって、 連合タプル連合シーケンス連合モデルなどについて説明します。

ここで、 float32@CLIENTS戻ります。複数のデバイス間で複製されているように見えますが、すべてのメンバーが同じであるため、実際には単一のfloat32です。一般に、 すべての等しいフェデレーションタイプ、つまりT@G形式の1つは、非フェデレーションタイプTと同型であると考えることができます。どちらの場合も、実際には単一の(複製される可能性があります)アイテムしかないためです。タイプT

TT@G間の同型を考えると、後者のタイプが役立つかもしれない場合、どのような目的があるのか​​疑問に思うかもしれません。読む。

配置

設計の概要

前のセクションでは、 配置の概念を紹介しました -連合値を共同でホストしているシステム参加者のグループであり、配置の仕様の例としてtff.CLIENTSの使用をtff.CLIENTSました。

配置の概念が非常に基本的であり、TFF型システムに組み込む必要がある理由を説明するために、TFFのいくつかの使用目的について、このチュートリアルの冒頭で述べたことを思い出してください。

このチュートリアルでは、TFFコードはシミュレートされた環境でローカルに実行されるだけですが、TFFが目標とするのは、モバイルシステムや組み込みデバイスを含む可能性のある分散システムの物理デバイスのグループで実行するためにデプロイできるコードを作成できるようにすることですAndroidを実行しています。これらの各デバイスは、システムで実行する役割(エンドユーザーデバイス、集中型コーディネーター、多層アーキテクチャーの中間層など)に応じて、ローカルで実行する個別の命令セットを受け取ります。デバイスのどのサブセットがどのコードを実行し、データのさまざまな部分が物理的に実体化する可能性があるかについて推論できることが重要です。

これは、たとえばモバイルデバイス上のアプリケーションデータを処理する場合に特に重要です。データは非公開で機密性が高い可能性があるため、このデータがデバイスから出ないことを静的に検証する(およびデータの処理方法に関する事実を証明する)機能が必要です。配置仕様は、これをサポートするために設計されたメカニズムの1つです。

TFFはデータ中心のプログラミング環境として設計されているため、 操作とその操作が実行される場所に焦点を当てている既存のフレームワークの一部とは異なり、TFFはデータ 、そのデータが具体化される場所 、およびその変換方法に焦点を当てています 。したがって、配置は、データの操作のプロパティとしてではなく、TFFのデータのプロパティとしてモデル化されます。実際、次のセクションで説明するように、一部のTFF操作は複数の場所にまたがり、いわば「ネットワーク内」で実行され、単一のマシンまたはマシンのグループによって実行されるのではありません。

特定の値のタイプをT@Gまたは{T}@G (単にTとは対照的)として{T}@G 、データ配置の決定が明示的になり、TFFで記述されたプログラムの静的分析と合わせて、デバイス上の機密データの正式なプライバシー保証。

ただし、この時点で注意すべき重要な点は、TFFユーザーがデータ(プレースメント)をホストする参加デバイスのグループについて明示することを推奨する一方で、プログラマーが生のデータや個々の参加者のIDを扱うことは決してないということです。 。

(注:このチュートリアルの範囲をはるかに超えていますが、上記の1つの例外、 tff.federated_collect演算子は、特殊な状況でのみ低レベルのプリミティブとして意図されていることにtff.federated_collect 。その明示的な使用可能性のある将来のアプリケーションを制限する可能性があるため、回避できない状況ではお勧めできません。たとえば、静的分析の過程で、計算がそのような低レベルのメカニズムを使用していると判断した場合、特定のデータのタイプ。)

TFFコードの本体内では、仕様により、 tff.CLIENTSで表されるグループを構成するデバイスを列挙したり、グループ内の特定のデバイスの存在をプローブしたりする方法はありません。 Federated Core API、基盤となるアーキテクチャの抽象化セット、またはシミュレーションをサポートするために提供するコアランタイムインフラストラクチャには、デバイスまたはクライアントIDの概念はありません。記述したすべての計算ロジックは、クライアントグループ全体に対する操作として表現されます。

フェデレーション型の値がPython dictとは異なることについて前述したことを思い出してください。メンバーの構成要素を単純に列挙することはできません。 TFFプログラムロジックが操作する値を、個々の参加者ではなく配置(グループ)に関連付けられていると考えてください。

プレースメント 、TFFでもファーストクラスの市民になるように設計されており、 placementタイプのパラメーターおよび結果として表示できます(APIではtff.PlacementTypeで表されます)。将来的には、プレースメントを変換または組み合わせるためのさまざまな演算子を提供する予定ですが、これはこのチュートリアルの範囲外です。今のところ、それは考えるすればよいplacementビルトインタイプの方法に似TFFに不透明なプリミティブとしてintbool付き、内蔵のPythonでタイプ不透明であるtff.CLIENTSこのタイプの定数リテラルであること、ではないとは異なり1 int型の定数リテラルです。

配置の指定

TFFは、 tff.CLIENTStff.SERVER 2つの基本的な配置リテラルを提供し、複数のクライアントデバイス(携帯電話、組み込みデバイス、分散データベース)を使用して、クライアント/サーバーアーキテクチャとして自然にモデル化される多様な実用シナリオを簡単に表現できるようにします、センサーなど)。単一の集中サーバーコーディネーターによって調整されます。 TFFは、カスタムプレースメント、複数のクライアントグループ、多層およびその他のより一般的な分散アーキテクチャもサポートするように設計されていますが、それらについて説明することは、このチュートリアルの範囲外です。

TFFは、 tff.CLIENTSまたはtff.SERVER実際に何を表すかを規定していません。

特に、 tff.SERVERは単一の物理デバイス(シングルトングループのメンバー)である場合がありますが、ステートマシンレプリケーションを実行するフォールトトレラントクラスター内のレプリカのグループである場合もあります。特別なアーキテクチャは作成しません。仮定。むしろ、前のセクションで述べたall_equalビットを使用して、通常はサーバーで単一のデータ項目のみを処理しているという事実を表現します。

同様に、一部のアプリケーションのtff.CLIENTSは、システムのすべてのクライアントを表す場合があります-フェデレーテッドラーニングのコンテキストでは集団と呼ばれることもありますが、たとえば、 Federated Averagingの実稼働実装では、 コホート -のサブセットを表す場合があります特定のトレーニングで参加するために選択されたクライアント。抽象的に定義された配置は、それらが表示される計算が実行のためにデプロイされるとき(または、このチュートリアルで示されるように、シミュレートされた環境でPython関数のように単に呼び出されるとき)に具体的な意味が与えられます。ローカルシミュレーションでは、クライアントのグループは、入力として提供されるフェデレーションデータによって決定されます。

連合計算

連合計算の宣言

TFFは、モジュール式開発をサポートする、強く型付けされた関数型プログラミング環境として設計されています。

TFFの構成の基本単位は、 連合計算です -連合値を入力として受け入れ、連合値を出力として返すロジックのセクション。前の例のセンサーアレイによって報告された温度の平均を計算する計算を定義する方法を次に示します。

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

上記のコードを見て、この時点であなたは尋ねるかもしれませんtf.functionなどの構成可能なユニットを定義するためのデコレータ構成tf.functionはまだありませんか?

短い答えはで生成されたコードであることをtff.federated_computationそれは内部プラットフォームに依存しない接着剤の言語での分散システムの仕様だ- どちら TensorFlowあり、 またそれは、Pythonでラッパー。この時点で、これは間違いなく不可解に聞こえるでしょうが、連合計算のこの直感的な解釈は、分散システムの抽象的な仕様として覚えておいてください。すぐに説明します。

まず、少し定義を試してみましょう。 TFFの計算は、通常、関数としてモデル化されます。パラメーターの有無にかかわらず、明確に定義された型シグネチャが使用されます。以下に示すように、 type_signatureプロパティをクエリすることで、計算の型シグネチャを出力できます。

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

タイプシグネチャは、計算がクライアントデバイス上のさまざまなセンサー読み取り値のコレクションを受け入れ、サーバー上で単一の平均を返すことを示しています。

先に進む前に、これについて少し考えてみましょう。この計算の入力と出力は異なる場所にありますCLIENTS SERVER )。前のセクションで配置についてTFF演算が複数の場所にまたがってネットワーク実行される方法について述べたこと、および分散システムの抽象的な仕様を表すものとして連合計算について述べたことを思い出してください。そのような計算を1つだけ定義しました。データがクライアントデバイスで消費され、集計結果がサーバーで現れる単純な分散システムです。

多くの実際的なシナリオでは、トップレベルのタスクを表す計算は、サーバーで入力を受け入れて出力を報告する傾向があります。これは、サーバーで開始および終了するクエリによって計算がトリガーされる可能性があるという考えを反映しています。

ただし、FC APIはこの仮定を課さないため、内部で使用するビルディングブロックの多く(APIにある多数のtff.federated_...演算子を含む)には、配置が異なる入力と出力があるため、通常、連合計算をサーバー上実行されるもの、またはサーバーによって実行されるものと考えないでください。サーバーは、連合計算の参加者の1つのタイプにすぎません。このような計算のメカニズムについて考えるときは、単一​​の集中型コーディネーターの観点ではなく、常にグローバルなネットワーク全体の観点にデフォルト設定するのが最善です。

一般に、関数型シグネチャは、入力と出力のそれぞれ型TUに対して(T -> U)としてコンパクトに表されます。仮パラメーターのタイプ(この場合はsensor_readingsなど)は、デコレーターへの引数として指定されます。結果のタイプを指定する必要はありません-それは自動的に決定されます。

TFFは限られた形式のポリモーフィズムを提供しますが、プログラマは、コードのプロパティの理解、デバッグ、および正式な検証を容易にするため、操作するデータのタイプを明示することを強くお勧めします。場合によっては、型を明示的に指定する必要があります(たとえば、現在、ポリモーフィック計算は直接実行できません)。

連合計算の実行

開発とデバッグをサポートするために、TFFでは、以下に示すように、このようにPython関数として定義された計算を直接呼び出すことができます。計算でall_equalビットがFalseに設定されたフェデレーション型の値を想定している場合、Pythonでプレーンlistとしてフィードできますall_equalビットがTrueに設定されたフェデレーション型の場合、直接(単一)フィードできます。メンバーの構成要素。これは、結果が報告される方法でもあります。

get_average_temperature([68.5, 70.3, 69.8])
69.53334

このような計算をシミュレーションモードで実行するときは、システム全体のビューを持つ外部オブザーバーとして機能します。このオブザーバーは、ネットワーク内の任意の場所で入力を供給し、出力を消費することができます。入力時に、サーバーの結果を消費しました。

ここで、 グルー言語でコードをtff.federated_computationするtff.federated_computationデコレーターについて前に作成したメモに戻りましょう。 TFF計算のロジックはPythonでは通常の関数として表すことができますが(上で行ったようにtff.federated_computationtff.federated_computationする必要があります)、これを他のPython関数と同じようにPython引数で直接呼び出すことができますノートブック、裏で述べたように、TFFの計算は実際に Pythonではありません

これは、Pythonインタープリターがtff.federated_computationで装飾された関数に遭遇すると、この関数の本体のステートメントを(定義時に)一度トレースし、その後の使用のために、計算のロジックのシリアル化された表現を構築することを意味します。実行用、またはサブコンポーネントとして別の計算に組み込むため。

これを確認するには、次のようにprintステートメントを追加します。

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

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

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

非熱心なコンテキストでTensorFlowグラフを構築するPythonコードと同様に、連合計算を定義するPythonコードを考えることができます(TensorFlowの非熱心な使用法に慣れていない場合は、後で実行される操作のグラフを定義するPythonコード。ただし、実際に実行中に実行されるわけではありません)。 TensorFlowの非意欲的なグラフ構築コードはPythonですが、このコードによって構築されたTensorFlowグラフはプラットフォームに依存せず、シリアル化可能です。

同様に、TFFの計算はPythonで定義されますが、本文で示した例のtff.federated_meanなどの本体のPythonステートメントは、 tff.federated_meanでポータブルでプラットフォームに依存しないシリアル化可能な表現にコンパイルされます。

開発者は、この表現の詳細を気にする必要はありません。直接操作する必要はないためですが、TFFの計算は根本的に非熱心であるという事実に注意してください。また、任意のPython状態をキャプチャすることはできません。 TFF計算の本体に含まれるPythonコードは、 tff.federated_computationtff.federated_computationされたPython関数の本体がシリアル化される前にトレースされるときに、定義時に実行されます。呼び出し時に再度トレースされることはありません(関数が多態性の場合を除きます。詳細については、ドキュメントのページを参照してください)。

なぜPython以外の専用の内部表現を導入することにしたのか疑問に思われるかもしれません。 1つの理由は、最終的に、TFF計算は実際の物理環境に展開可能であり、Pythonが利用できないモバイルまたは組み込みデバイスでホストされることを目的としていることです。

もう1つの理由は、個々の参加者のローカル動作を表すPythonプログラムとは対照的に、TFF計算は分散システムのグローバル動作を表すことです。上記の簡単な例では、クライアントデバイスのデータを受け入れるがサーバーに結果を送信する特殊な演算子tff.federated_meanていることがtff.federated_meanます。

オペレーターtff.federated_meanはローカルで実行されないため、Pythonの通常のオペレーターとして簡単にモデル化することはできません。 tff.federated_mean 、これは複数のシステム参加者の動作を調整する分散システムを表します。このような演算子を連合演算子と呼び、Pythonの通常の(ローカル)演算子と区別します。

TFF型システム、およびTFFの言語でサポートされる基本的な操作のセットは、Pythonの操作から大幅に逸脱しているため、専用の表現を使用する必要があります。

連合計算の構成

上記のように、連合計算とその構成要素は分散システムのモデルとして最もよく理解されており、連合計算を構成することは、単純なものからより複雑な分散システムを構成することと考えることができます。 tff.federated_mean演算子は、型シグネチャ({T}@CLIENTS -> T@SERVER)備えた組み込みのテンプレートフェデレーション計算の一種と考えることができます(実際、作成した計算と同様に、この演算子も複雑です。構造-内部では、それをより単純な演算子に分解します)。

連合計算の構成についても同様です。計算get_average_temperatureは、 tff.federated_computation装飾された別のPython関数の本体で呼び出すことができます。これにより、親の本体に埋め込まれます。これは、 tff.federated_meanが以前の本体に埋め込まれていたのと同じです。

注意すべき重要な制限は、 tff.federated_computation装飾されたPython関数の本体は連合演算子のみで構成する必要があることです。つまり、TensorFlow演算を直接含めることはできません。たとえば、 tf.nestインターフェースを直接使用して、フェデレーション値のペアを追加することはできません。 TensorFlowコードは、次のセクションで説明するtff.tf_computationされたコードのブロックに限定する必要があります。このように包まれた場合にのみ、ラップTensorFlowコードは本体に呼び出すことができますtff.federated_computation

この分離の理由は、技術的(非テンソルで動作するようにtf.addなどの演算子をだますことは難しい)およびアーキテクチャ上の理由です。連合計算の言語(つまり、 tff.federated_computation装飾されたPython関数の直列化された本体から構築されたロジック)は、プラットフォームに依存しないグルー言語として機能するように設計されています。このグルー言語は現在、TensorFlowコードの埋め込みセクション( tff.tf_computationブロックに限定)から分散システムを構築するために使用されてtff.tf_computationます。時間の中で、すべて同じグルー言語( tff.federated_computationブロック)を使用して接続された、入力パイプラインを表す可能性があるリレーショナルデータベースクエリなど、他の非TensorFlowロジックのセクションを埋め込む必要があると予想しています。

TensorFlowロジック

TensorFlow計算の宣言

TFFはTensorFlowで使用するように設計されています。そのため、TFFで記述するコードの大部分は、通常の(つまり、ローカルで実行される)TensorFlowコードである可能性があります。上記のようにTFFでこのようなコードを使用するには、 tff.tf_computationtff.tf_computationする必要があります。

たとえば、数値を受け取り、それに0.5を加える関数を実装する方法を次に示します。

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

もう一度、これを見て、単にtf.functionなどの既存のメカニズムを使用するのではなく、なぜ別のデコレーターtff.tf_computationを定義する必要があるのか​​疑問に思うかもしれません。前のセクションとは異なり、ここではTensorFlowコードの通常のブロックを扱っています。

これにはいくつかの理由がありますが、その完全な扱いはこのチュートリアルの範囲を超えていますが、主なものに名前を付ける価値があります。

  • TensorFlowコードを使用して実装された再利用可能なビルディングブロックを連合計算の本体に埋め込むには、定義時にトレースやシリアル化を行う、タイプシグネチャを持つなど、特定のプロパティを満たす必要があります。これには通常、何らかの形のデコレータが必要です。

一般的に、我々のような、組成物TensorFlowのネイティブメカニズムを使用することをお勧めしますtf.function可能な限り熱心な機能を有するTFFのデコレータの相互作用が発展すると予想することができる正確な方法として、。

ここで、上記のコードスニペットの例に戻ると、先ほど定義した計算add_halfは、他のTFF計算と同じようにTFFで処理できます。特に、TFFタイプシグネチャがあります。

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

この型シグネチャには配置がないことに注意してください。 TensorFlowの計算では、フェデレーションタイプを使用または返すことはできません。

add_halfを他の計算のビルディングブロックとして使用することもできます。たとえば、 tff.federated_map演算子を使用して、クライアントデバイス上のフェデレーテッドフロートのすべてのメンバーadd_halfを適用する方法を次に示します。

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

TensorFlow計算の実行

tff.tf_computation定義された計算の実行は、 tff.tf_computationについて説明したのと同じルールにtff.federated_computationます。次のように、Pythonの通常の呼び出し可能オブジェクトとして呼び出すことができます。

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

繰り返しになりますが、この方法でadd_half_on_clientsの計算を呼び出すと、分散プロセスがシミュレートされることに注意してください。データはクライアントで消費され、クライアントで返されます。実際、この計算では、各クライアントがローカルアクションを実行します。このシステムでは、明示的に言及されているtff.SERVERはありません(実際にそのような処理を調整する場合でも、そのような処理が含まれる場合があります)。この方法で定義された計算は、概念的にはMapReduce Mapステージに類似していると考えてください。

また、私たちがTFFの計算が定義時に連載取得について前節で述べたことについて、真のままであることを心に留めておくtff.tf_computationのPythonのボディ-だけでなく、コードadd_half_on_clients定義時に一度トレースします。以降の呼び出しでは、TFFはシリアル化された表現を使用します。

Pythonの飾ら方法の唯一の違いtff.federated_computationと飾られるものtff.tf_computation (前者が直接に埋め込まTensorFlowコードを含むことが許されないのに対し)、後者はTensorFlowグラフとしてシリアル化されることです。

tff.tf_computationは、 tff.tf_computation装飾された各メソッドは、計算の構造をキャプチャできるようにするため、一時的な実行を一時的に無効にします。熱心な実行はローカルで無効になっていますが、正しくシリアル化できるように計算のロジックを記述している限り、熱心なTensorFlow、AutoGraph、TensorFlow 2.0コンストラクトなどを使用できます。

たとえば、次のコードは失敗します。

try:

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

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

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

ため、上記の失敗constant_10既にそのグラフの外側に構築されたtff.tf_computation本体の内部構成add_tenシリアライゼーションプロセスの間。

一方、 tff.tf_computation内で呼び出されたときに現在のグラフを変更するpython関数を呼び出すことは問題ありtff.tf_computation

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

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

add_ten(5.0)
15.0

TensorFlowのシリアル化メカニズムは進化しており、TFFが計算をシリアル化する方法の詳細も進化することに注意してください。

Working with tf.data.Dataset s

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

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

float32_sequence = tff.SequenceType(tf.float32)

str(float32_sequence)
'float32*'

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

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

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

You can easily verify this as follows.

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

foo([1, 2, 3])
6

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

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

get_local_temperature_average([68.5, 70.3, 69.8])
69.53333

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

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

str(foo.type_signature)
'(<A=int32,B=int32>* -> int32)'
foo([{'A': 2, 'B': 3}, {'A': 4, 'B': 5}])
26

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

Putting it all together

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

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

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

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

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

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

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

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