TFFでのランダムノイズの生成

このチュートリアルでは、TFFでランダムノイズを生成するための推奨されるベストプラクティスについて説明します。ランダムノイズの生成は、差分プライバシーなど、連合学習アルゴリズムにおける多くのプライバシー保護技術の重要なコンポーネントです。

TensorFlow.orgで表示GoogleColabで実行GitHubでソースを表示 ノートブックをダウンロード

始める前に

まず、関連するコンポーネントがコンパイルされたバックエンドにノートブックが接続されていることを確認しましょう。

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

import nest_asyncio
nest_asyncio.apply()
import numpy as np
import tensorflow as tf
import tensorflow_federated as tff

次の「HelloWorld」の例を実行して、TFF環境が正しくセットアップされていることを確認します。それが動作しない場合は、を参照してください。インストールの手順についてのガイド。

@tff.federated_computation
def hello_world():
  return 'Hello, World!'

hello_world()
b'Hello, World!'

クライアントのランダムノイズ

クライアントでのノイズの必要性は、一般に2つのケースに分類されます。同一のノイズとiidノイズです。

  • 同じノイズのために、推奨パターンがクライアントにブロードキャストサーバー上の種子を、維持し、使用することですtf.random.statelessノイズを生成する機能を。
  • iidノイズの場合、tf.random。<distribution>関数を回避するためのTFの推奨に従って、from_non_deterministic_stateを使用してクライアントで初期化されたtf.random.Generatorを使用します。

クライアントの動作はサーバーとは異なります(後で説明する落とし穴に悩まされることはありません)。これは、各クライアントが独自の計算グラフを作成し、独自のデフォルトシードを初期化するためです。

クライアントの同一のノイズ

# Set to use 10 clients.
tff.backends.native.set_local_python_execution_context(num_clients=10)

@tff.tf_computation
def noise_from_seed(seed):
  return tf.random.stateless_normal((), seed=seed)

seed_type_at_server = tff.type_at_server(tff.to_type((tf.int64, [2])))

@tff.federated_computation(seed_type_at_server)
def get_random_min_and_max_deterministic(seed):
  # Broadcast seed to all clients.
  seed_on_clients = tff.federated_broadcast(seed)

  # Clients generate noise from seed deterministicly.
  noise_on_clients = tff.federated_map(noise_from_seed, seed_on_clients)

  # Aggregate and return the min and max of the values generated on clients.
  min = tff.aggregators.federated_min(noise_on_clients)
  max = tff.aggregators.federated_max(noise_on_clients)
  return min, max

seed = tf.constant([1, 1], dtype=tf.int64)
min, max = get_random_min_and_max_deterministic(seed)
assert min == max
print(f'Seed: {seed.numpy()}. All clients sampled value {min:8.3f}.')

seed += 1
min, max = get_random_min_and_max_deterministic(seed)
assert min == max
print(f'Seed: {seed.numpy()}. All clients sampled value {min:8.3f}.')
Seed: [1 1]. All clients sampled value    1.665.
Seed: [2 2]. All clients sampled value   -0.219.

クライアントの独立したノイズ

@tff.tf_computation
def nondeterministic_noise():
  gen = tf.random.Generator.from_non_deterministic_state()
  return gen.normal(())

@tff.federated_computation(seed_type_at_server)
def get_random_min_and_max_nondeterministic(seed):
  noise_on_clients = tff.federated_eval(nondeterministic_noise, tff.CLIENTS)
  min = tff.aggregators.federated_min(noise_on_clients)
  max = tff.aggregators.federated_max(noise_on_clients)
  return min, max

min, max = get_random_min_and_max_nondeterministic(seed)
assert min != max
print(f'Values differ across clients. {min:8.3f},{max:8.3f}.')

new_min, new_max = get_random_min_and_max_nondeterministic(seed)
assert new_min != new_max
assert new_min != min and new_max != max
print(f'Values differ across rounds.  {new_min:8.3f},{new_max:8.3f}.')
Values differ across clients.   -1.810,   1.079.
Values differ across rounds.    -1.205,   0.851.

サーバー上のランダムノイズ

落胆使用方法:直接使用tf.random.normal

TF1.xのAPIなどtf.random.normalランダムノイズ生成を強くに従ってTF2に推奨されTFにおけるランダムノイズ生成チュートリアル。これらのAPIを一緒に使用する場合には驚くべき行動が起こる可能性がありtf.functiontf.random.set_seed 。たとえば、次のコードは、呼び出しごとに同じ値を生成します。この驚くべき行動は、TFのために期待されている、と説明がで見つけることができるのドキュメントtf.random.set_seed

tf.random.set_seed(1)

@tf.function
def return_one_noise(_):
  return tf.random.normal([])

n1=return_one_noise(1)
n2=return_one_noise(2) 
assert n1 == n2
print(n1.numpy(), n2.numpy())
0.3052047 0.3052047

TFFでは、状況が少し異なります。私たちのようにノイズの発生をラップする場合tff.tf_computation代わりのtf.function 、非決定論的ランダムノイズが生成されます。私たちは、このコードスニペットを複数回実行する場合は、別の一組(n1, n2)毎回生成されます。 TFFのグローバルランダムシードを設定する簡単な方法はありません。

tf.random.set_seed(1)

@tff.tf_computation
def return_one_noise(_):
  return tf.random.normal([])

n1=return_one_noise(1)
n2=return_one_noise(2) 
assert n1 != n2
print(n1, n2)
1.3283143 0.45740178

さらに、シードを明示的に設定しなくても、TFFで決定論的ノイズを生成できます。機能return_two_noise次のコードスニペット復帰中の2つの同一のノイズ値。 TFFは実行前に事前に計算グラフを作成するため、これは予想される動作です。しかし、これは、ユーザーがの使用に注意を払わなければならない示唆tf.random.normal TFFインチ

@tff.tf_computation
def tff_return_one_noise():
  return tf.random.normal([])

@tff.federated_computation
def return_two_noise():
  return (tff_return_one_noise(), tff_return_one_noise())

n1, n2=return_two_noise() 
assert n1 == n2
print(n1, n2)
-0.15665223 -0.15665223

注意して使用法: tf.random.Generator

我々は使用することができますtf.random.Generatorで提案されているようにTFのチュートリアル

@tff.tf_computation
def tff_return_one_noise(i):
  g=tf.random.Generator.from_seed(i)
  @tf.function
  def tf_return_one_noise():
    return g.normal([])
  return tf_return_one_noise()

@tff.federated_computation
def return_two_noise():
  return (tff_return_one_noise(1), tff_return_one_noise(2))

n1, n2 = return_two_noise() 
assert n1 != n2
print(n1, n2)
0.3052047 -0.38260338

ただし、ユーザーはその使用法に注意する必要があるかもしれません

  • tf.random.Generator使用していますtf.Variable RNGアルゴリズムのための状態を維持します。 TFFでは、内部の発電機をcontructすることをお勧めしますtff.tf_computation 。発電機との間でその状態通過することが困難であるtff.tf_computation機能を。
  • 前のコードスニペットも、ジェネレーターでシードを注意深く設定することに依存しています。私たちは、期待されるが、驚くべき結果(確定的しまいますn1==n2 、我々が使用している場合) tf.random.Generator.from_non_deterministic_state()代わりに。

一般的に、TFFは、機能的な操作を好む、我々はの使い方紹介するtf.random.stateless_*以下のセクションで機能します。

連合学習のTFFでは、スカラーの代わりにネストされた構造を使用することが多く、前​​のコードスニペットはネストされた構造に自然に拡張できます。

@tff.tf_computation
def tff_return_one_noise(i):
  g=tf.random.Generator.from_seed(i)
  weights = [
         tf.ones([2, 2], dtype=tf.float32),
         tf.constant([2], dtype=tf.float32)
     ]
  @tf.function
  def tf_return_one_noise():
    return tf.nest.map_structure(lambda x: g.normal(tf.shape(x)), weights)
  return tf_return_one_noise()

@tff.federated_computation
def return_two_noise():
  return (tff_return_one_noise(1), tff_return_one_noise(2))

n1, n2 = return_two_noise() 
assert n1[1] != n2[1]
print('n1', n1)
print('n2', n2)
n1 [array([[0.3052047 , 0.5671378 ],
       [0.41852272, 0.2326421 ]], dtype=float32), array([1.1675092], dtype=float32)]
n2 [array([[-0.38260338, -0.47804865],
       [-0.5187485 , -1.8471988 ]], dtype=float32), array([-0.77835274], dtype=float32)]

TFFにおける一般的な勧告は、機能を使用することであるtf.random.stateless_*ランダムノイズ生成する機能を。これらの機能は、取るseed (形状テンソル[2]またはtupleランダムノイズを生成するために、明示的な入力引数として2つのスカラーテンソルのを)。まず、シードを疑似状態として維持するためのヘルパークラスを定義します。ヘルパーRandomSeedGenerator状態・イン・ステート・アウト方式で機能的な演算子を持っています。のための擬似状態としてカウンタを使用するのが妥当であるtf.random.stateless_*これらの関数としてスクランブル統計的に無相関の相関種子によって生成された音を作るためにそれを使用する前に、種子。

def timestamp_seed():
  # tf.timestamp returns microseconds as decimal places, thus scaling by 1e6.
  return tf.math.cast(tf.timestamp() * 1e6, tf.int64)

class RandomSeedGenerator():

  def initialize(self, seed=None):
    if seed is None:
      return tf.stack([timestamp_seed(), 0])
    else:
      return tf.constant(self.seed, dtype=tf.int64, shape=(2,))

  def next(self, state):
    return state + tf.constant([0, 1], tf.int64)

  def structure_next(self, state, nest_structure):
    "Returns seed in nested structure and the next state seed."
    flat_structure = tf.nest.flatten(nest_structure)
    flat_seeds = [state + tf.constant([0, i], tf.int64) for
                  i in range(len(flat_structure))]
    nest_seeds = tf.nest.pack_sequence_as(nest_structure, flat_seeds)
    return nest_seeds, flat_seeds[-1] + tf.constant([0, 1], tf.int64)

今、私たちは、ヘルパークラスと使用させtf.random.stateless_normal (の入れ子構造)TFFでランダムノイズを生成します。次のコードスニペットは、TFF反復プロセスのようにたくさん見える、参照simple_fedavgをTFF反復プロセスとして連合学習アルゴリズムを表現する例として。ランダムノイズ生成ここで疑似種状態であるtf.Tensor容易TFF及びTF機能に搬送することができます。

@tff.tf_computation
def tff_return_one_noise(seed_state):
  g=RandomSeedGenerator()
  weights = [
         tf.ones([2, 2], dtype=tf.float32),
         tf.constant([2], dtype=tf.float32)
     ]
  @tf.function
  def tf_return_one_noise():
    nest_seeds, updated_state = g.structure_next(seed_state, weights)
    nest_noise = tf.nest.map_structure(lambda x,s: tf.random.stateless_normal(
        shape=tf.shape(x), seed=s), weights, nest_seeds)
    return nest_noise, updated_state
  return tf_return_one_noise()

@tff.tf_computation
def tff_init_state():
  g=RandomSeedGenerator()
  return g.initialize()

@tff.federated_computation
def return_two_noise():
  seed_state = tff_init_state()
  n1, seed_state = tff_return_one_noise(seed_state)
  n2, seed_state = tff_return_one_noise(seed_state)
  return (n1, n2)

n1, n2 = return_two_noise() 
assert n1[1] != n2[1]
print('n1', n1)
print('n2', n2)
n1 [array([[-0.21598858, -0.30700883],
       [ 0.7562299 , -0.21218438]], dtype=float32), array([-1.0359321], dtype=float32)]
n2 [array([[ 1.0722181 ,  0.81287116],
       [-0.7140338 ,  0.5896157 ]], dtype=float32), array([0.44190162], dtype=float32)]