在 TensorFlow.org 上查看 | 在 Google Colab 中运行 | 在 GitHub 上查看源代码 | 下载笔记本 |
本教程展示了如何使用 tff.analytics.heavy_hitters.iblt.build_iblt_computation
API 构建一个联合分析计算,以发现人群中最常见的字符串(私有热门词)。
环境设置
请运行以下命令以确保您的环境已正确设置。如果您没有看到问候语,请参阅 安装 指南以获取说明。
# tensorflow_federated_nightly also bring in tf_nightly, which
# can causes a duplicate tensorboard install, leading to errors.
pip install --quiet tensorflow-text-nightly
pip install --quiet --upgrade tensorflow-federated
import collections
import numpy as np
import tensorflow as tf
import tensorflow_federated as tff
import tensorflow_text as tf_text
np.random.seed(0)
tff.backends.test.set_sync_test_cpp_execution_context()
tff.federated_computation(lambda: 'Hello, World!')()
b'Hello, World!'
背景:联合分析中的私有热门词
考虑以下设置:每个客户端都有一组字符串,每个字符串都来自一个开放集,这意味着它可能是任意的。目标是在联合设置中私下发现最流行的字符串(**热门词**)及其计数。此 colab 演示了使用以下隐私属性解决此问题的解决方案
- 安全聚合:计算聚合的字符串计数,这样服务器不应该能够了解任何客户端的个体值。有关更多信息,请参阅
tff.federated_secure_sum
。 - 差分隐私 (DP):一种广泛使用的方法,用于限制和量化分析中敏感数据的隐私泄露。您可以将用户级中心 DP 应用于热门词结果。
安全聚合 API tff.federated_secure_sum
支持整数向量的线性求和。如果字符串来自大小为 n
的封闭集,那么将每个客户端的字符串编码为大小为 n
的向量很容易:让向量中索引 i
处的值为封闭集中第 i
个字符串的计数。然后,您可以安全地将所有客户端的向量求和,以获得整个群体中字符串的计数。但是,如果字符串来自开放集,则不清楚如何将它们正确编码以进行安全求和。在这项工作中,您可以将字符串编码为 可逆布隆查找表 (IBLT),这是一种概率数据结构,能够以高效的方式对大型(或开放)域中的项目进行编码。IBLT 草图可以线性求和,因此与安全求和兼容。
您可以使用 tff.analytics.heavy_hitters.iblt.build_iblt_computation
创建一个 TFF 计算,将每个客户端的本地字符串编码为 IBLT 结构。这些结构通过加密安全多方计算协议安全地求和到一个聚合的 IBLT 结构中,服务器可以对其进行解码。然后,服务器可以返回前几个热门词。以下部分展示了如何使用此 API 创建一个 TFF 计算并使用莎士比亚数据集运行模拟。
加载和预处理联合莎士比亚数据
莎士比亚数据集包含莎士比亚戏剧中人物的台词。在此示例中,选择了一部分人物(即客户端)。预处理器将每个人物的台词转换为字符串列表,并删除任何仅包含标点符号或符号的字符串。
# Load the simulation data.
source, _ = tff.simulation.datasets.shakespeare.load_data()
# Preprocessing function to tokenize a line into words.
def tokenize(ds):
"""Tokenizes a line into words with alphanum characters."""
def extract_strings(example):
return tf.expand_dims(example['snippets'], 0)
def tokenize_line(line):
return tf.data.Dataset.from_tensor_slices(tokenizer.tokenize(line)[0])
def mask_all_symbolic_words(word):
return tf.math.logical_not(
tf_text.wordshape(word, tf_text.WordShape.IS_PUNCT_OR_SYMBOL))
tokenizer = tf_text.WhitespaceTokenizer()
ds = ds.map(extract_strings)
ds = ds.flat_map(tokenize_line)
ds = ds.map(tf_text.case_fold_utf8)
ds = ds.filter(mask_all_symbolic_words)
return ds
batch_size = 5
def client_data(n: int) -> tf.data.Dataset:
return tokenize(source.create_tf_dataset_for_client(
source.client_ids[n])).batch(batch_size)
# Pick a subset of client devices to participate in the computation.
dataset = [client_data(n) for n in range(10)]
模拟
要运行模拟以发现莎士比亚数据集中最流行的单词(热门词),首先需要使用 tff.analytics.heavy_hitters.iblt.build_iblt_computation
API 创建一个 TFF 计算,并使用以下参数
capacity
:IBLT 草图的容量。此数字应大致等于一轮计算中可能出现的唯一字符串总数。默认为1000
。如果此数字太小,则由于哈希值的冲突,解码可能会失败。如果此数字太大,则会消耗比必要更多的内存。string_max_bytes
:IBLT 中字符串的最大长度。默认为10
。必须为正数。长度超过string_max_bytes
的字符串将被截断。max_words_per_user
: 每个客户端允许贡献的最大字符串数量。如果非None
,则必须为正整数。默认为None
,这意味着所有客户端都贡献其所有字符串。max_heavy_hitters
: 要返回的最大项目数。如果解码结果中的项目数超过此数,则将按估计计数降序排列并返回前 max_heavy_hitters 个项目。默认为None
,这意味着返回结果中的所有热门项目。secure_sum_bitwidth
: 用于安全求和的位宽。默认值为None
,这将禁用安全求和。如果非None
,则必须在[1,62]
范围内。请参阅tff.federated_secure_sum
。multi_contribution
: 每个客户端是否允许贡献多个计数,还是只允许对每个唯一词贡献一个计数。默认为True
。当需要差分隐私时,此参数可以提高效用。batch_size
: 数据集每个批次中的元素数量。默认为1
,表示输入数据集由tf.data.Dataset.batch(1)
处理。必须为正整数。
max_words_per_user = 8
iblt_computation = tff.analytics.heavy_hitters.iblt.build_iblt_computation(
capacity=100,
string_max_bytes=20,
max_words_per_user=max_words_per_user,
max_heavy_hitters=10,
secure_sum_bitwidth=32,
multi_contribution=False,
batch_size=batch_size)
现在,您可以使用 TFF 计算 iblt_computation
和预处理输入数据集来运行模拟。输出 iblt_computation
包含四个属性
- clients: 参与计算的客户端数量。
- heavy_hitters: 聚合热门项目的列表。
- heavy_hitters_counts: 聚合热门项目计数的列表。
- num_not_decoded: 未成功解码的字符串数量。
def run_simulation(one_round_computation: tff.Computation, dataset):
output = one_round_computation(dataset)
heavy_hitters = output.heavy_hitters
heavy_hitters_counts = output.heavy_hitters_counts
heavy_hitters = [word.decode('utf-8', 'ignore') for word in heavy_hitters]
results = {}
for index in range(len(heavy_hitters)):
results[heavy_hitters[index]] = heavy_hitters_counts[index]
return output.clients, dict(results)
clients, result = run_simulation(iblt_computation, dataset)
print(f'Number of clients participated: {clients}')
print('Discovered heavy hitters and counts:')
print(result)
Number of clients participated: 10 Discovered heavy hitters and counts: {'to': 8, 'the': 8, 'and': 7, 'you': 4, 'i': 4, 'a': 3, 'he': 3, 'your': 3, 'is': 3, 'of': 2}
具有差分隐私的私有热门项目
为了获得具有中心 DP 的私有热门项目,对开放集直方图应用 DP 机制。其思想是对聚合直方图中字符串的计数添加噪声,然后只保留计数超过一定阈值的字符串。噪声和阈值取决于 (epsilon, delta)-DP 预算,请参阅 此文档 以了解详细的算法和证明。噪声计数作为后处理步骤被四舍五入为整数,这不会削弱 DP 保证。请注意,当需要 DP 时,您将发现更少的热门项目。这是因为阈值步骤过滤掉了计数较低的字符串。
iblt_computation = tff.analytics.heavy_hitters.iblt.build_iblt_computation(
capacity=100,
string_max_bytes=20,
max_words_per_user=max_words_per_user,
secure_sum_bitwidth=32,
multi_contribution=False,
batch_size=batch_size)
clients, result = run_simulation(iblt_computation, dataset)
# DP parameters
eps = 20
delta = 0.01
# Calculating scale for Laplace noise
scale = max_words_per_user / eps
# Calculating the threshold
tau = 1 + (max_words_per_user / eps) * np.log(max_words_per_user / (2 * delta))
result_with_dp = {}
for word in result:
noised_count = result[word] + np.random.laplace(scale=scale)
if noised_count >= tau:
result_with_dp[word] = int(noised_count)
print(f'Discovered heavy hitters and counts with central DP:')
print(result_with_dp)
Discovered heavy hitters and counts with central DP: {'the': 8, 'you': 4, 'to': 7, 'tear': 3, 'and': 7, 'i': 3}