Tạo nhiễu ngẫu nhiên trong TFF

Hướng dẫn này sẽ thảo luận về các phương pháp hay nhất được đề xuất để tạo nhiễu ngẫu nhiên trong TFF. Tạo nhiễu ngẫu nhiên là một thành phần quan trọng của nhiều kỹ thuật bảo vệ quyền riêng tư trong các thuật toán học tập liên hợp, ví dụ: quyền riêng tư khác biệt.

Xem trên TensorFlow.org Chạy trong Google Colab Xem nguồn trên GitHub Tải xuống sổ ghi chép

Trước khi chúng tôi bắt đầu

Trước tiên, hãy đảm bảo sổ ghi chép được kết nối với một chương trình phụ trợ có các thành phần liên quan được biên dịch.

!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

Chạy ví dụ "Hello World" sau đây để đảm bảo môi trường TFF được thiết lập chính xác. Nếu nó không hoạt động, vui lòng tham khảo các cài đặt hướng dẫn để được hướng dẫn.

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

hello_world()
b'Hello, World!'

Nhiễu ngẫu nhiên trên máy khách

Nhu cầu về nhiễu trên máy khách thường rơi vào hai trường hợp: nhiễu giống hệt nhau và nhiễu iid.

  • Đối với tiếng ồn giống hệt nhau, mô hình đề nghị là để duy trì một hạt giống trên máy chủ, phát sóng cho các khách hàng, và sử dụng tf.random.stateless chức năng để tạo ra tiếng ồn.
  • Đối với nhiễu iid, hãy sử dụng tf.random.Generator được khởi tạo trên máy khách với from_non_deterministic_state, phù hợp với khuyến nghị của TF để tránh các hàm tf.random. <distribution>.

Hành vi của máy khách khác với máy chủ (không mắc phải những cạm bẫy được thảo luận ở phần sau) vì mỗi máy khách sẽ xây dựng đồ thị tính toán của riêng họ và khởi tạo hạt giống mặc định của riêng họ.

Tiếng ồn giống hệt nhau trên khách hàng

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

Tiếng ồn độc lập trên khách hàng

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

Nhiễu ngẫu nhiên trên máy chủ

Sử dụng nản: trực tiếp sử dụng tf.random.normal

TF1.x như API tf.random.normal cho thế hệ tiếng ồn ngẫu nhiên không được khuyến khích mạnh mẽ trong TF2 theo hướng dẫn thế hệ tiếng ồn ngẫu nhiên trong TF . Hành vi bất ngờ có thể xảy ra khi các API được sử dụng cùng với tf.functiontf.random.set_seed . Ví dụ, đoạn mã sau sẽ tạo ra cùng một giá trị với mỗi cuộc gọi. Hành vi đáng kinh ngạc này được dự kiến cho TF, và lời giải thích có thể được tìm thấy trong các tài liệu hướng dẫn của 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

Trong TFF, mọi thứ hơi khác một chút. Nếu chúng ta quấn thế hệ tiếng ồn như tff.tf_computation thay vì tf.function , không xác định tiếng ồn ngẫu nhiên sẽ được tạo ra. Tuy nhiên, nếu chúng tôi chạy đoạn mã này nhiều lần, bộ khác nhau của (n1, n2) sẽ được tạo ra mỗi lần. Không có cách nào dễ dàng để thiết lập một hạt giống ngẫu nhiên toàn cầu cho 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

Hơn nữa, nhiễu xác định có thể được tạo ra trong TFF mà không cần thiết lập hạt giống một cách rõ ràng. Chức năng return_two_noise trong đoạn mã sau đoạn mã trở lại hai giá trị tiếng ồn giống hệt nhau. Đây là hành vi được mong đợi bởi vì TFF sẽ xây dựng biểu đồ tính toán trước trước khi thực thi. Tuy nhiên, điều này cho thấy người sử dụng phải chú ý về việc sử dụng tf.random.normal trong 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

Cách sử dụng một cách cẩn thận: tf.random.Generator

Chúng ta có thể sử dụng tf.random.Generator như đề xuất trong hướng dẫn 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

Tuy nhiên, người dùng có thể phải cẩn thận khi sử dụng

  • tf.random.Generator sử dụng tf.Variable để duy trì trạng thái cho các thuật toán RNG. Trong TFF, nó được khuyến khích để contruct máy phát điện bên trong một tff.tf_computation ; và rất khó để vượt qua các máy phát điện và trạng thái của nó giữa tff.tf_computation chức năng.
  • đoạn mã trước đó cũng dựa vào việc thiết lập cẩn thận các hạt giống trong trình tạo. Chúng tôi sẽ nhận được kỳ vọng nhưng kết quả đáng ngạc nhiên (xác định n1==n2 ) nếu chúng tôi sử dụng tf.random.Generator.from_non_deterministic_state() để thay thế.

Nói chung, TFF thích hoạt động chức năng và chúng tôi sẽ giới thiệu việc sử dụng tf.random.stateless_* chức năng trong các phần sau.

Trong TFF cho việc học liên kết, chúng tôi thường làm việc với các cấu trúc lồng nhau thay vì các đại lượng vô hướng và đoạn mã trước đó có thể được mở rộng một cách tự nhiên cho các cấu trúc lồng nhau.

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

Một khuyến nghị chung trong TFF là sử dụng chức năng tf.random.stateless_* chức năng cho thế hệ tiếng ồn ngẫu nhiên. Những chức năng lấy seed (một tensor với hình dạng [2] hoặc một tuple của hai tensors vô hướng) như một tham số đầu vào rõ ràng để tạo ra tiếng ồn ngẫu nhiên. Đầu tiên chúng ta định nghĩa một lớp trợ giúp để duy trì hạt giống ở trạng thái giả. Các helper RandomSeedGenerator có nhà khai thác chức năng trong một thời trang do nhà nước trong nhà nước-out. Có lý do để sử dụng một bộ đếm như trạng thái giả cho tf.random.stateless_* như các chức năng này tranh giành hạt giống trước khi sử dụng nó để làm cho tiếng ồn tạo ra bởi hạt tương quan không tương quan về mặt thống kê.

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)

Bây giờ chúng ta sử dụng lớp helper và tf.random.stateless_normal để tạo ra (cấu trúc lồng nhau của) tiếng ồn ngẫu nhiên trong TFF. Đoạn mã sau trông rất giống một quá trình lặp đi lặp lại TFF, xem simple_fedavg như một ví dụ về hiện thuật toán học liên như quá trình lặp TFF. Tình trạng hạt giả vào đây để thế hệ tiếng ồn ngẫu nhiên là tf.Tensor có thể dễ dàng vận chuyển trong các chức năng TFF và 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)]