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

カスタムの連合アルゴリズム、パート2:連合平均化の実装

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

このチュートリアルは、 Federated Core(FC レイヤー( tff.learning )の基盤として機能するFederated Core(FC)を使用してTFFでカスタムタイプのフェデレーションアルゴリズムを実装する方法を示す2部シリーズの第2部です。 。

このシリーズの最初の部分を最初に読むことをお勧めします。ここでは、ここで使用される主要な概念とプログラミングの抽象化のいくつかを紹介します。

シリーズのこのパート2では、パート1で紹介したメカニズムを使用して、フェデレーショントレーニングと評価アルゴリズムのシンプルなバージョンを実装します。

TFFのFederated Learning APIのより高レベルで穏やかな概要については、 画像分類テキスト生成のチュートリアルを確認することをお勧めします。これらは、ここで説明する概念をコンテキストに反映するのに役立ちます。

始める前に

始める前に、次の「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

# TODO(b/148678573,b/148685415): must use the ReferenceExecutor because it
# supports unbounded references and tff.sequence_* intrinsics.
tff.framework.set_default_context(tff.test.ReferenceExecutor())
 
 @tff.federated_computation
def hello_world():
  return 'Hello, World!'

hello_world()
 
'Hello, World!'

フェデレーテッドアベレージングの実装

画像分類の統合学習と同様に、MNISTの例を使用しますが、これは低レベルのチュートリアルとして意図されているため、 tff.simulation APIとtff.simulationをバイパスし、生のモデルコードを記述して、ゼロからの連合データセット。

連合データセットの準備

デモンストレーションのために、10人のユーザーからのデータがあり、各ユーザーが異なる数字を認識する方法に関する知識を提供するシナリオをシミュレートします。これは、ほぼ非iidです。

最初に、標準のMNISTデータをロードします。

 mnist_train, mnist_test = tf.keras.datasets.mnist.load_data()
 
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
11501568/11490434 [==============================] - 0s 0us/step

 [(x.dtype, x.shape) for x in mnist_train]
 
[(dtype('uint8'), (60000, 28, 28)), (dtype('uint8'), (60000,))]

データはNumpy配列として提供され、1つは画像を含み、もう1つは数字ラベルを含みます。どちらも最初の次元は個々の例に渡ります。フェデレーテッドシーケンスをTFF計算にフィードする方法と互換性のある方法でフォーマットするヘルパー関数を記述しましょう。つまり、リストのリストとして、ユーザー(数字)に及ぶ外部リスト、データのバッチに及ぶ内部リストです。各クライアントのシーケンス。通常のように、各バッチをxyという名前のテンソルのペアとして構成し、それぞれに主要なバッチディメンションを設定します。その間、各画像を784要素のベクトルにフラット化し、その中のピクセルを0..1範囲に再スケーリングするので、データ変換でモデルロジックを混乱させる必要はありません。

 NUM_EXAMPLES_PER_USER = 1000
BATCH_SIZE = 100


def get_data_for_digit(source, digit):
  output_sequence = []
  all_samples = [i for i, d in enumerate(source[1]) if d == digit]
  for i in range(0, min(len(all_samples), NUM_EXAMPLES_PER_USER), BATCH_SIZE):
    batch_samples = all_samples[i:i + BATCH_SIZE]
    output_sequence.append({
        'x':
            np.array([source[0][i].flatten() / 255.0 for i in batch_samples],
                     dtype=np.float32),
        'y':
            np.array([source[1][i] for i in batch_samples], dtype=np.int32)
    })
  return output_sequence


federated_train_data = [get_data_for_digit(mnist_train, d) for d in range(10)]

federated_test_data = [get_data_for_digit(mnist_test, d) for d in range(10)]
 

簡単な健全性チェックとして、5番目のクライアント(数字の5対応するクライアント)によって提供されたデータの最後のバッチのYテンソルを見てみましょう。

 federated_train_data[5][-1]['y']
 
array([5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5], dtype=int32)

念のため、そのバッチの最後の要素に対応する画像も見てみましょう。

 from matplotlib import pyplot as plt

plt.imshow(federated_train_data[5][-1]['x'][-1].reshape(28, 28), cmap='gray')
plt.grid(False)
plt.show()
 

png

TensorFlowとTFFの組み合わせについて

このチュートリアルでは、コンパクトにするために、TensorFlowロジックを導入する関数をすぐにtff.tf_computationでtff.tf_computationます。ただし、より複雑なロジックの場合、これは推奨されるパターンではありません。 TensorFlowのデバッグはすでに困難な場合があり、完全にシリアル化されてから再インポートされた後のTensorFlowのデバッグは、必然的に一部のメタデータを失い、対話性を制限するため、デバッグはさらに困難になります。

したがって、 複雑なTFロジックをスタンドアロンのPython関数として (つまり、 tff.tf_computation装飾なしで) 作成することを強くお勧めします。このようにして、TFFの計算をシリアル化する前に(例えば、Python関数を引数としてtff.tf_computationを呼び出すことにより)、TFのベストプラクティスとツール(eagerモードなど)を使用してTensorFlowロジックを開発およびテストできます。

損失関数の定義

これでデータが得られたので、トレーニングに使用できる損失関数を定義しましょう。最初に、入力のタイプをタプルという名前のTFFとして定義しましょう。データバッチのサイズは異なる場合があるため、バッチディメンションを[ Noneに設定して、このディメンションのサイズが不明であることを示します。

 BATCH_SPEC = collections.OrderedDict(
    x=tf.TensorSpec(shape=[None, 784], dtype=tf.float32),
    y=tf.TensorSpec(shape=[None], dtype=tf.int32))
BATCH_TYPE = tff.to_type(BATCH_SPEC)

str(BATCH_TYPE)
 
'<x=float32[?,784],y=int32[?]>'

なぜ通常のPython型を定義できないのか疑問に思われるかもしれません。 パート1の説明を思い出してください。Pythonを使用してTFF計算のロジックを表現することはできますが、内部ではTFF計算 Pythonではないことを説明しました。上記で定義されたシンボルBATCH_TYPEは、抽象的なTFFタイプ仕様を表します。この抽象 TFFタイプを具体的なPython 表現タイプ(たとえば、Python関数の本体でTFFタイプを表すために使用されるdictcollections.namedtupleなどのコンテナー)と区別することが重要です。 Pythonとは異なり、TFFにはタプルのようなコンテナー用の単一の抽象型コンストラクターtff.StructTypeがあり、要素には個別に名前をtff.StructTypeことも、名前を付けないこともできます。 TFF計算は正式には1つのパラメーターと1つの結果しか宣言できないため、このタイプは計算の正式なパラメーターのモデル化にも使用されます-これの例は間もなく表示されます。

ここで、モデルパラメーターのTFFタイプを、再度、TFFという名前の重みバイアスのタプルとして定義します。

 MODEL_SPEC = collections.OrderedDict(
    weights=tf.TensorSpec(shape=[784, 10], dtype=tf.float32),
    bias=tf.TensorSpec(shape=[10], dtype=tf.float32))
MODEL_TYPE = tff.to_type(MODEL_SPEC)

print(MODEL_TYPE)
 
<weights=float32[784,10],bias=float32[10]>

これらの定義が整ったら、単一のバッチで、特定のモデルの損失を定義できます。使い方に注意してください@tf.function内部の装飾家@tff.tf_computationデコレータを。これにより、 tff.tf_computationデコレーターによって作成されたtf.Graphコンテキスト内にあったとしても、セマンティクスのようにPythonを使用してTFを記述できます。

 # NOTE: `forward_pass` is defined separately from `batch_loss` so that it can 
# be later called from within another tf.function. Necessary because a
# @tf.function  decorated method cannot invoke a @tff.tf_computation.

@tf.function
def forward_pass(model, batch):
  predicted_y = tf.nn.softmax(
      tf.matmul(batch['x'], model['weights']) + model['bias'])
  return -tf.reduce_mean(
      tf.reduce_sum(
          tf.one_hot(batch['y'], 10) * tf.math.log(predicted_y), axis=[1]))

@tff.tf_computation(MODEL_TYPE, BATCH_TYPE)
def batch_loss(model, batch):
  return forward_pass(model, batch)
 

予想されるように、計算batch_loss戻りfloat32モデルと単一のデータ・バッチ所与損失。 MODEL_TYPEBATCH_TYPEが2タプルの仮パラメーターにまとめられていることに注意してください。 batch_lossのタイプは(<MODEL_TYPE,BATCH_TYPE> -> float32)として認識できます。

 str(batch_loss.type_signature)
 
'(<<weights=float32[784,10],bias=float32[10]>,<x=float32[?,784],y=int32[?]>> -> float32)'

健全性チェックとして、ゼロで満たされた初期モデルを作成し、上で視覚化したデータのバッチに対する損失を計算します。

 initial_model = collections.OrderedDict(
    weights=np.zeros([784, 10], dtype=np.float32),
    bias=np.zeros([10], dtype=np.float32))

sample_batch = federated_train_data[5][-1]

batch_loss(initial_model, sample_batch)
 
2.3025854

TFF計算にdictとして定義された初期モデルをフィードすることに注意してください。それを定義するPython関数の本体は、モデルパラメーターをmodel['weight']およびmodel['bias']ます。 batch_lossの呼び出しの引数は、その関数の本体に渡されるだけではありません。

batch_lossを呼び出すとbatch_lossますか? batch_lossのPython本体は、それが定義された上記のセルですでにトレースおよびシリアル化されています。 TFFは、計算定義時にbatch_lossの呼び出し元として機能し、 batch_lossが呼び出された時点での呼び出しのターゲットとしてbatch_lossます。どちらの役割でも、TFFはTFFの抽象型システムとPython表現型の間のブリッジとして機能します。呼び出し時に、TFFはほとんどの標準的なPythonコンテナタイプ( dictlisttuplecollections.namedtupleなど)を抽象的なTFFタプルの具象表現として受け入れます。また、上記のように、TFF計算は正式には単一のパラメーターのみを受け入れますが、パラメーターのタイプがタプルである場合は、位置および/またはキーワード引数でおなじみのPython呼び出し構文を使用できます-期待どおりに機能します。

単一バッチの勾配降下

次に、この損失関数を使用して勾配降下の単一ステップを実行する計算を定義しましょう。この関数を定義する際に、 batch_lossをサブコンポーネントとして使用する方法に注意してください。別の計算の本体内でtff.tf_computation構築された計算を呼び出すことができますが、通常これは必要ありません。前述のように、シリアライゼーションは一部のデバッグ情報を失うため、より複雑な計算ではすべてのTensorFlowを記述してテストする方が望ましいことがよくありますtff.tf_computationデコレーターなし。

 @tff.tf_computation(MODEL_TYPE, BATCH_TYPE, tf.float32)
def batch_train(initial_model, batch, learning_rate):
  # Define a group of model variables and set them to `initial_model`. Must
  # be defined outside the @tf.function.
  model_vars = collections.OrderedDict([
      (name, tf.Variable(name=name, initial_value=value))
      for name, value in initial_model.items()
  ])
  optimizer = tf.keras.optimizers.SGD(learning_rate)

  @tf.function
  def _train_on_batch(model_vars, batch):
    # Perform one step of gradient descent using loss from `batch_loss`.
    with tf.GradientTape() as tape:
      loss = forward_pass(model_vars, batch)
    grads = tape.gradient(loss, model_vars)
    optimizer.apply_gradients(
        zip(tf.nest.flatten(grads), tf.nest.flatten(model_vars)))
    return model_vars

  return _train_on_batch(model_vars, batch)
 
 str(batch_train.type_signature)
 
'(<<weights=float32[784,10],bias=float32[10]>,<x=float32[?,784],y=int32[?]>,float32> -> <weights=float32[784,10],bias=float32[10]>)'

別の関数の本体内でtff.tf_computationで装飾されたPython関数を呼び出すと、内側のTFF計算のロジックが外側のロジックに埋め込まれます(本質的にはインライン化されます)。上記のように、両方の計算を作成する場合は、内部関数(この場合はbatch_loss )をtf.functionではなく通常のPythonまたはtf.functionすることをおtff.tf_computationます。ただし、ここでは、あるtff.tf_computationを別の内部で呼び出すと、基本的に期待どおりに機能することを示しています。これは、たとえば、 batch_loss定義するPythonコードがなく、シリアル化されたTFF表現だけがある場合に必要になることがあります。

次に、この関数を初期モデルに数回適用して、損失が減少するかどうかを確認します。

 model = initial_model
losses = []
for _ in range(5):
  model = batch_train(model, sample_batch, 0.1)
  losses.append(batch_loss(model, sample_batch))
 
 losses
 
[0.19690022, 0.13176313, 0.10113226, 0.082738124, 0.0703014]

ローカルデータのシーケンスの勾配降下

今度は、 batch_trainが機能しているようにlocal_trainので、単一のバッチだけではなく、1人のユーザーからのすべてのバッチのシーケンス全体を消費する同様のトレーニング関数local_trainを作成してみましょう。新しい計算では、 BATCH_TYPEではなくtff.SequenceType(BATCH_TYPE)使用する必要があります。

 LOCAL_DATA_TYPE = tff.SequenceType(BATCH_TYPE)

@tff.federated_computation(MODEL_TYPE, tf.float32, LOCAL_DATA_TYPE)
def local_train(initial_model, learning_rate, all_batches):

  # Mapping function to apply to each batch.
  @tff.federated_computation(MODEL_TYPE, BATCH_TYPE)
  def batch_fn(model, batch):
    return batch_train(model, batch, learning_rate)

  return tff.sequence_reduce(all_batches, initial_model, batch_fn)
 
 str(local_train.type_signature)
 
'(<<weights=float32[784,10],bias=float32[10]>,float32,<x=float32[?,784],y=int32[?]>*> -> <weights=float32[784,10],bias=float32[10]>)'

このコードの短いセクションにはかなりの数の詳細が埋め込まれています。1つずつ詳しく見ていきましょう。

まず、TensorFlowでこのロジックを完全に実装することもできますが、以前の方法と同様にtf.data.Dataset.reduceを使用してシーケンスを処理しましたが、今回はロジックをグルー言語で表現することを選択しました、 tff.federated_computationとして。フェデレーションオペレーターtff.sequence_reduceを使用して削減を実行しました。

演算子tff.sequence_reduceは、 tff.sequence_reduceと同様に使用されtf.data.Dataset.reduce 。基本的にはtf.data.Dataset.reduceと同じと考えることができますが、 tf.data.Dataset.reduce計算の内部で使用するために、TensorFlowコードを含めることはできません。それはの配列からなる仮パラメータ3タプルとテンプレート演算子であるTの要素を-typed、還元の初期状態ある種の(我々はゼロとして抽象的にそれを参照します) U 、および縮小演算のタイプ(<U,T> -> U)は、単一の要素を処理することによって削減の状態を変更します。結果は、すべての要素を順番に処理した後の削減の最終状態です。この例では、削減の状態はデータのプレフィックスでトレーニングされたモデルであり、要素はデータバッチです。

2つ目は、ある計算( batch_train )を別の( local_train )内のコンポーネントとして再び使用したが、直接ではないことに注意してください。追加のパラメーター(学習率)が必要なため、これを簡約演算子として使用することはできません。これを解決するために、本体でlocal_trainのパラメーターlearning_rateにバインドする組み込みの統合計算batch_fnを定義します。この方法で定義された子計算は、親の本体の外部で呼び出されない限り、親の仮パラメーターを取り込むことができます。このパターンは、Pythonのfunctools.partialに相当すると考えることができます。

もちろん、 learning_rateこの方法で取得するlearning_rateの実際的な影響は、すべてのバッチで同じ学習率の値が使用されることです。

ここで、サンプルバッチを提供した同じユーザーからの一連のデータ全体(データ5 )に対して新しく定義されたローカルトレーニング関数を試してみましょう。

 locally_trained_model = local_train(initial_model, 0.1, federated_train_data[5])
 

うまくいきましたか?この質問に答えるには、評価を実装する必要があります。

現地評価

これは、すべてのデータバッチの損失を合計してローカル評価を実装する1つの方法です(平均を計算することもできます。読者のための演習として残します)。

 @tff.federated_computation(MODEL_TYPE, LOCAL_DATA_TYPE)
def local_eval(model, all_batches):
  # TODO(b/120157713): Replace with `tff.sequence_average()` once implemented.
  return tff.sequence_sum(
      tff.sequence_map(
          tff.federated_computation(lambda b: batch_loss(model, b), BATCH_TYPE),
          all_batches))
 
 str(local_eval.type_signature)
 
'(<<weights=float32[784,10],bias=float32[10]>,<x=float32[?,784],y=int32[?]>*> -> float32)'

繰り返しになりますが、このコードで示されているいくつかの新しい要素があります。それらを1つずつ見ていきましょう。

まず、シーケンスを処理するために二つの新しいフェデレーション演算子を使用した: tff.sequence_map マッピング関数かかりT->Uとの配列 T 、そして一連の発するUマッピング関数の点別を適用することによって得られ、 tff.sequence_sumそのすべての要素を追加するだけです。ここでは、各データバッチを損失値にマップし、結果の損失値を追加して、総損失を計算します。

再度tff.sequence_reduce使用することもできますが、これは最良の選択ではないことに注意してください。縮小プロセスは、定義上、順次ですが、マッピングと合計は並列に計算できます。選択肢が与えられたときは、実装の選択を制約しない演算子に固執するのが最善です。TFF計算が将来コンパイルされて特定の環境に展開されるとき、すべての潜在的な機会を最大限に活用してより高速にすることができます。よりスケーラブルでリソース効率の高い実行。

2番目に、 local_trainに、必要なコンポーネント関数( batch_loss )は、フェデレーテッドオペレーター( tff.sequence_map )が期待するものより多くのパラメーターをtff.sequence_mapため、 lambdatff.federated_computationとして直接ラップすることにより、今度はインラインでパーシャルを再度定義します。 tff.federated_computation 。関数を引数としてインラインでラッパーを使用することは、 tff.tf_computationを使用してTensorFlowロジックをTFFに埋め込むための推奨される方法です。

さて、トレーニングがうまくいったかどうか見てみましょう。

 print('initial_model loss =', local_eval(initial_model,
                                         federated_train_data[5]))
print('locally_trained_model loss =',
      local_eval(locally_trained_model, federated_train_data[5]))
 
initial_model loss = 23.025854
locally_trained_model loss = 0.4348469

実際、損失は減少しました。しかし、別のユーザーのデータで評価するとどうなるでしょうか。

 print('initial_model loss =', local_eval(initial_model,
                                         federated_train_data[0]))
print('locally_trained_model loss =',
      local_eval(locally_trained_model, federated_train_data[0]))
 
initial_model loss = 23.025854
locally_trained_model loss = 74.50075

予想通り、事態は悪化しました。モデルは5を認識するようにトレーニングされており、 0見たことはありません。これは問題をもたらします-ローカルなトレーニングはグローバルな観点からモデルの品質にどのように影響しましたか?

統合評価

これが、フェデレーション型とフェデレーション計算-始めたトピックに戻ります。これは、サーバーで発生するモデルの2つのTFFタイプ定義と、クライアントに残るデータです。

 SERVER_MODEL_TYPE = tff.FederatedType(MODEL_TYPE, tff.SERVER)
CLIENT_DATA_TYPE = tff.FederatedType(LOCAL_DATA_TYPE, tff.CLIENTS)
 

これまでに紹介したすべての定義では、TFFでフェデレーション評価を表現することはワンライナーです-モデルをクライアントに配布し、各クライアントにデータのローカル部分でローカル評価を呼び出してから、損失を平均化します。これを書く1つの方法を次に示します。

 @tff.federated_computation(SERVER_MODEL_TYPE, CLIENT_DATA_TYPE)
def federated_eval(model, data):
  return tff.federated_mean(
      tff.federated_map(local_eval, [tff.federated_broadcast(model), data]))
 

簡単なシナリオでtff.federated_meantff.federated_map例をすでに見てきましたが、直感的なレベルでは期待どおりに機能しますが、このセクションのコードにはtff.federated_mean以上のものがあるので、慎重に見ていきましょう。

まず、 各クライアントがデータ部分のローカル部分でローカル評価を呼び出すことできるようにします 。前のセクションでlocal_evalに、 local_eval(<MODEL_TYPE, LOCAL_DATA_TYPE> -> float32)という形式の型シグネチャがあります。

連合演算子tff.federated_mapは、タイプT->Uマッピング関数とタイプ{T}@CLIENTS連合値から構成される2タプルをパラメーターとして受け入れるテンプレートです(つまり、マッピング関数のパラメーターと同じタイプ)、タイプ{U}@CLIENTS結果を返します。

クライアントごとに適用するマッピング関数としてlocal_evalをフィードしているため、2番目の引数はフェデレーテッドタイプ{<MODEL_TYPE, LOCAL_DATA_TYPE>}@CLIENTSである必要があります。つまり、前のセクションの命名法では、連合タプルになる。各クライアントは、メンバー構成local_evalとしてlocal_evalの引数の完全なセットを保持する必要があります。代わりに、2要素のPython listフィードしています。ここで何が起こっているのですか?

あなたが養うとき確かに、これは、例えば、あなたが他の場所で発生した可能性があり、暗黙の型キャストに似TFF、中に暗黙の型キャストの一例であるint受け入れる関数にfloat 。現時点では、暗黙的キャストはほとんど使用されていませんが、ボイラープレートを最小限に抑える方法として、TFFでより広く使用できるようにする予定です。

この場合に適用される暗黙的なキャストは、形式が{<X,Y>}@Zの連合タプルと、連合値<{X}@Z,{Y}@Z>タプル間の等価です。正式にはこれら2つは異なる型シグネチャですが、プログラマーの観点から見ると、 Z各デバイスは2つのデータXYユニットを保持しています。ここで発生することは、Pythonのziptff.federated_zipです。実際、このような変換を明示的に実行できる演算子tff.federated_zipを提供しています。 tff.federated_mapが2番目の引数としてタプルをtff.federated_mapと、 tff.federated_zipを呼び出します。

上記を前提として、式tff.federated_broadcast(model)がTFFタイプ{MODEL_TYPE}@CLIENTS値を表し、 dataをTFFタイプ{LOCAL_DATA_TYPE}@CLIENTS (または単にCLIENT_DATA_TYPE )の値として認識することができるはずです。 、2つはtff.federated_zipへの2番目の引数を形成するために暗黙のtff.federated_zipを通じて一緒にフィルタリングされtff.federated_map

演算子tff.federated_broadcast 、ごtff.federated_broadcastどおり、サーバーからクライアントにデータを転送するだけです。

次に、ローカルトレーニングがシステムの平均損失にどのように影響したかを見てみましょう。

 print('initial_model loss =', federated_eval(initial_model,
                                             federated_train_data))
print('locally_trained_model loss =',
      federated_eval(locally_trained_model, federated_train_data))
 
initial_model loss = 23.025852
locally_trained_model loss = 54.432625

実際、予想通り、損失は増加しています。すべてのユーザーのモデルを改善するには、すべてのユーザーのデータをトレーニングする必要があります。

連携トレーニング

連合トレーニングを実装する最も簡単な方法は、ローカルでトレーニングしてから、モデルを平均化することです。これは、以下に示すように、すでに説明したのと同じビルディングブロックとパターンを使用します。

 SERVER_FLOAT_TYPE = tff.FederatedType(tf.float32, tff.SERVER)


@tff.federated_computation(SERVER_MODEL_TYPE, SERVER_FLOAT_TYPE,
                           CLIENT_DATA_TYPE)
def federated_train(model, learning_rate, data):
  return tff.federated_mean(
      tff.federated_map(local_train, [
          tff.federated_broadcast(model),
          tff.federated_broadcast(learning_rate), data
      ]))
 

モデルを平均化するのではなく、 tff.learningによって提供されるtff.learningのフル機能の実装では、たとえば、更新基準をクリップする機能、圧縮などの理由により、モデルのデルタを平均化することを好みます。 。

数回のトレーニングを実行し、前後の平均損失を比較することで、トレーニングが機能するかどうかを確認してみましょう。

 model = initial_model
learning_rate = 0.1
for round_num in range(5):
  model = federated_train(model, learning_rate, federated_train_data)
  learning_rate = learning_rate * 0.9
  loss = federated_eval(model, federated_train_data)
  print('round {}, loss={}'.format(round_num, loss))
 
round 0, loss=21.60552406311035
round 1, loss=20.365678787231445
round 2, loss=19.27480125427246
round 3, loss=18.31110954284668
round 4, loss=17.45725440979004

完全を期すために、ここでもテストデータを実行して、モデルが一般化されていることを確認します。

 print('initial_model test loss =',
      federated_eval(initial_model, federated_test_data))
print('trained_model test loss =', federated_eval(model, federated_test_data))
 
initial_model test loss = 22.795593
trained_model test loss = 17.278767

これでチュートリアルは終了です。

もちろん、この単純化された例は、より現実的なシナリオで実行する必要がある多くのことを反映していません。たとえば、損失以外のメトリックを計算していません。より完全な例として、また推奨したいコーディングプラクティスのいくつかを示す方法として、 tff.learningでの統合平均化の実装を検討することをお勧めします。