Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/zh-cn/federated/tutorials/random_noise_generation.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

TFF 中的随机噪声生成

本教程将讨论 TFF 中随机噪声生成的推荐最佳做法。随机噪声生成是联合学习算法中许多隐私保护技术(如差分隐私)的重要组成部分。

准备工作

首先,让我们确保笔记本连接到已编译相关组件的后端。

#@test {"skip": true} !pip install --quiet --upgrade tensorflow-federated
import numpy as np import tensorflow as tf import tensorflow_federated as tff

请运行以下“Hello World”示例以确保正确设置 TFF 环境。如果无效,请参阅安装指南查看说明。

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

客户端上的随机噪声

客户端对噪声的需求一般分为两种情况:相同噪声和独立同分布噪声。

  • 对于相同噪声,推荐模式是在服务器上维护一个种子,将其广播给客户端,并使用 tf.random.stateless 函数来生成噪声。

  • 对于独立同分布噪声,请使用在客户端上通过 from_non_deterministic_state 初始化的 tf.random.Generator 以符合 TF 的建议,从而避免使用 tf.random.<distribution> 函数。

客户端行为与服务器不同(不受后面讨论的陷阱影响),因为每个客户端都将构建自己的计算图并初始化自己的默认种子。

客户端上的相同噪声

# Set to use 10 clients. tff.backends.native.set_sync_local_cpp_execution_context(default_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 def get_random_min_and_max_nondeterministic(): 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() 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() 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.490, 1.172. Values differ across rounds. -1.358, 1.208.

客户端上的模型初始化器

def _keras_model(): inputs = tf.keras.Input(shape=(1,)) outputs = tf.keras.layers.Dense(1)(inputs) return tf.keras.Model(inputs=inputs, outputs=outputs) @tff.tf_computation def tff_return_model_init(): model = _keras_model() # return the initialized single weight value of the dense layer return tf.reshape( tff.learning.models.ModelWeights.from_model(model).trainable[0], [-1])[0] @tff.federated_computation def get_random_min_and_max_nondeterministic(): noise_on_clients = tff.federated_eval(tff_return_model_init, 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() 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() 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.022, 1.567. Values differ across rounds. -1.675, 1.550.

服务器上的随机噪声

不鼓励使用:直接使用 tf.random.normal

根据 TF 中的随机噪声生成教程,强烈建议不要在 TF2 中使用 TF1.x 之类的 API tf.random.normal 来生成随机噪声。当这些 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)
0.11990704 1.9185987

此外,可以在 TFF 中生成确定性噪声,而无需显式设置种子。以下代码段中的函数 return_two_noise 返回两个相同的噪声值。这是预期的行为,因为 TFF 将在执行之前提前构建计算图。但是,这暗示用户必须注意 tf.random.normal 在 TFF 中的用法。

小心使用:tf.random.Generator

我们可以按照 TF 教程中的建议使用 tf.random.Generator

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

但是,用户可能必须小心谨慎地使用它

  • tf.random.Generator 使用 tf.Variable 来维护 RNG 算法的状态。在 TFF 中,建议在 tff.tf_computation 中构建生成器;很难在 tff.tf_computation 函数之间传递生成器及其状态。

  • 前面的代码段还依赖于在生成器中仔细设置种子。如果我们改用 tf.random.Generator.from_non_deterministic_state(),则可能得到预期但出人意料的结果(确定性的 n1==n2)。

一般而言,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.38260335, -0.4780486 ], [-0.5187485 , -1.8471988 ]], dtype=float32), array([-0.77835274], dtype=float32)]

推荐使用:带辅助函数的 tf.random.stateless_*

TFF 中的一般建议是使用函数式 tf.random.stateless_* 函数生成随机噪声。这些函数将 seed(形状为 [2] 的张量或两个标量张量的 tuple)作为显式输入参数来生成随机噪声。我们首先定义一个辅助函数类来将种子保持为伪状态。辅助函数 RandomSeedGenerator 具有状态输入状态输出形式的函数算子。使用计数器作为 tf.random.stateless_* 的伪状态是合理的,因为这些函数在使用种子之前会对其进行加扰,从而使相关联种子产生的噪声在统计上不相关。

def timestamp_seed(): # tf.timestamp returns microseconds as decimal places, thus scaling by 1e6. return tf.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.86828816, 0.8535084 ], [ 1.0053564 , -0.42096713]], dtype=float32), array([0.18048067], dtype=float32)] n2 [array([[-1.1973879 , -0.2974589 ], [ 1.8309833 , 0.17024393]], dtype=float32), array([0.68991095], dtype=float32)]