原文课程: Lecture 8 — Private Multiplicative Weights (Gautam Kamath, CS 860, Fall 2020)
前面我们学习了拉普拉斯机制、高斯机制和指数机制。它们能回答单个或少量查询,但如果要回答海量查询呢?
比如一个数据分析师想问你一万个关于人口普查数据的问题——每个问题都精确到 1% 以内。用拉普拉斯机制,你需要至少 O(|Q|) 个样本——大约一万人才能回答一个问题(误差 1%),而回答一万个问题就需要一亿人!
这合理吗?显然不合理——数据集中包含的信息是有限的,它不可能支持任意多查询的精确回答。但问题在于:能不能比拉普拉斯机制做得更好?
答案是能。而且方法出乎意料地优雅——它来自一个叫"乘法权重"的古老算法。
1. 从一条数据到"合成数据"
线性查询的定义
先来定义我们要回答的问题。**线性查询(Linear Query)是一类非常基础的问题:给定一个查询函数 q: X → [0,1],它在某个数据集 D 上的答案是所有样本取值的平均值**:
q(D) = (1/n) · Σ_{x∈D} q(x)
举个具体例子:
| 查询 | q(x) 的含义 | 在数据集上的平均值 |
|---|---|---|
| "有多少人是女性?" | q(x)=1 如果是女性,否则 0 | 女性比例 |
| "平均年龄是多少?" | 将年龄归一化到 [0,1] | 归一化平均年龄 |
| "有多少人年收入超过 5 万?" | q(x)=1 如果收入>5万 | 高收入比例 |
| "有多少人是大学以上学历?" | q(x)=1 如果学历≥本科 | 高学历比例 |
线性查询看起来简单,但几乎所有基础统计都可以表示为线性查询的组合。
拉普拉斯机制需要多少数据?
如果要回答 |Q| 个查询,每个误差 ≤ α,拉普拉斯机制需要多少个样本?根据组合定理:
n = O(|Q| / (αε))
这是线性增长的——查询越多,所需数据越多。如果 |Q| = 10,000,α = 0.01,ε = 1,你需要 n = 1,000,000 条数据。
graph LR
A["|Q| 个查询"] --> B{"所需样本量"}
B --> C["拉普拉斯: O(|Q|/αε) 📈"]
B --> D["乘法权重: O(√|Q|/αε) 📉"]
乘法权重算法能把这个需求降到 O(√|Q|)——从线性变成平方根,质的飞跃!
2. 核心思想:你需要一个"草稿"
直觉
想象你在考试前复习。你手上没有标准答案(原始数据集),但你有一个草稿本(一个对数据的初步估计)。一开始,你的草稿很粗糙——你可能随便猜了猜数据的分布。
然后你开始做题(回答查询)。每次做完一道题,你发现自己的答案和标准答案有差距,你就回到草稿本上修正一下。
经过多次迭代,你的草稿本越来越接近真实数据。
graph TB
A["初始化: 均匀分布 D₁"] --> B["找出差异最大的查询 q"]
B --> C["用指数机制选查询"]
C --> D["比较 D₁ 与真实数据在 q 上的答案"]
D --> E["用乘法权重更新 → D₂"]
E --> B
E --> F["重复 T 轮后输出最终分布"]
style A fill:#e3f2fd,stroke:#1565c0
style E fill:#e8f5e9,stroke:#2e7d32
style F fill:#fff3e0,stroke:#e65100
算法总览
Private Multiplicative Weights (PMW) 算法包含以下组件:
| 组件 | 作用 | 类比 |
|---|---|---|
| 真实数据集 D | 我们想保护的原始数据 | 标准答案 |
| 合成分布 | 对真实数据的逐步修正的估计 | 草稿本 |
| 查询集合 Q | 所有待回答的问题 | 考题 |
| 指数机制 | 选择"当前误差最大"的查询 | 标记最需要纠正的题 |
| 乘法权重更新 | 根据误差调整合成分布 | 修正草稿 |
3. 算法拆解:一步一步来
第一步:初始化
假设数据集来自某个空间 X(例如,X 是"性别、年龄、收入"等所有可能属性的组合)。一开始,我们对真实数据一无所知,所以合成分布 D₁ 设为均匀分布:
D₁(x) = 1/|X| 对所有 x ∈ X
如果 X 有 100 种可能的属性组合,那每种组合的初始概率都是 1%。
第二步:找到"最不准确"的查询
现在,对每个查询 q ∈ Q,我们计算它的误差:
Error(q) = |q(合成分布) - q(真实数据)|
误差衡量了当前合成分布在这个查询上的表现有多糟糕。
graph TD
A["查询 q₁: 女性比例真实: 52% 合成: 50%误差: 2%"]
B["查询 q₂: 平均年龄真实: 0.42 合成: 0.50误差: 8%"]
C["查询 q₃: 高收入比例真实: 30% 合成: 28%误差: 2%"]
D["查询 q₄: 高学历比例真实: 35% 合成: 50%误差: 15% ← 最大!"]
D --> E["指数机制选中 q₄ 的概率最高"]
但我们不能直接选误差最大的查询——那会暴露真实数据信息。我们用指数机制来选:
Pr[选择 q] ∝ exp(ε' · Error(q) / 2)
其中 ε' 是每轮分配的隐私预算。这样选中最差查询的概率最高,但也不排除其他查询——这保护了隐私。
第三步:用乘法权重更新分布
找到误差最大的查询 q 后,我们就知道了真实数据在这个查询上的答案 v = q(真实数据),以及当前合成分布的答案 u = q(合成分布)。
如果 u < v(低估了),意味着真实数据中满足 q 条件的人比我们猜的要多。我们就要提高那些满足 q 条件的个体的权重。
具体更新规则是:
对每个 x ∈ X:
如果 q(x) 接近 1(满足条件):
D_new(x) = D_old(x) · exp(η · (v - u))
如果 q(x) 接近 0(不满足条件):
D_new(x) = D_old(x) · exp(-η · (v - u))
其中 η 是学习率(类似于机器学习中的步长)。然后归一化,使所有概率之和为 1。
graph LR
subgraph "更新前"
A1["满足条件的个体权重: 较低"]
A2["不满足条件的个体权重: 较高"]
end
subgraph "乘法权重更新"
B["乘以 exp(η·(v-u)) ↑"]
C["乘以 exp(-η·(v-u)) ↓"]
end
subgraph "更新后"
D1["满足条件的个体权重: 提高 ✅"]
D2["不满足条件的个体权重: 降低"]
end
A1 --> B --> D1
A2 --> C --> D2
直观理解:这就像你发现自己的草稿低估了女性比例,于是你调高了"女性"这一格的权重,同时调低了"男性"的权重。多次迭代后,分布会越来越准确。
第四步:重复 T 轮
重复第 2-3 步 T 次。每轮分配 ε' = ε/T 的隐私预算(用高级组合定理可以做得更好)。
最后把最后一轮的合成分布作为输出——所有查询在这个分布上的答案就是最终答案。
4. 为什么它能节省样本量?
信息论视角
拉普拉斯机制浪费了大量信息——它对每个查询独立地添加噪声,完全没有利用查询之间的相关性。
而 PMW 算法通过维护一个合成分布,把所有查询的答案"拧成一股绳"——合成分布只需描述数据本身(|X| 维),而不是 |Q| 个独立答案。
graph TD
subgraph "拉普拉斯机制"
L1["查询 q₁: 真实答案 + 噪声₁"]
L2["查询 q₂: 真实答案 + 噪声₂"]
L3["查询 q₃: 真实答案 + 噪声₃"]
L4["......"]
end
subgraph "PMW 算法"
P1["合成分布 D(一个统一的模型)"]
P2["在 D 上同时回答所有查询"]
P3["答案自动满足一致性"]
end
L1 --> L4
P1 --> P2 --> P3
拉普拉斯:每个答案独立加噪 → O(|Q|) 条数据/查询 PMW:用合成分布统一建模 → O(√|Q|) 条数据/查询
理论保证
PMW 的核心定理是:
对于 |Q| 个线性查询,如果样本量满足:
n ≥ O( √(|Q| · log|X|) / (αε) )则 PMW 算法能以高概率回答所有查询,误差 ≤ α。
注意是 √|Q| 而不是 |Q|——当查询数量很大时,这是巨大的改进!
| 查询数 | 拉普拉斯所需 n | PMW 所需 n | 节省 |
|---|---|---|---|
| 100 | 10,000 | 1,000 | 10x |
| 1,000 | 100,000 | 3,162 | 32x |
| 10,000 | 1,000,000 | 10,000 | 100x |
| 1,000,000 | 100,000,000 | 100,000 | 1,000x |
查询越多,优势越明显!
5. 一个具体例子
假设 X = {0,1}³(3 个二进制属性,共 8 种组合)。我们有 n=100 条数据,想回答 |Q|=100 个线性查询。
初始状态
合成分布 D₁ = [1/8, 1/8, ..., 1/8](均匀分布)。
真实数据分布 D_real = [0.10, 0.05, 0.15, 0.10, 0.20, 0.08, 0.12, 0.20]。
第 1 轮
指数机制选中查询 q = "第一个属性 = 1"。
- 真实答案: 0.10 + 0.05 + 0.15 + 0.10 = 0.40
- 合成答案: 0.50(均匀分布下,一半的个体第一个属性=1)
- 误差: 0.10
乘法权重更新:提高前 4 种组合的权重,降低后 4 种组合的权重。
第 2 轮
指数机制选中查询 q = "第二个属性 = 0 且第三个属性 = 1"。
- 真实答案: 0.15 + 0.12 = 0.27
- 合成答案: 约 0.25(经过了第一轮修正)
- 误差: 0.02
乘法权重更新:进一步微调。
第 20 轮
经过 20 轮迭代,合成分布已经非常接近真实分布:
| 组合 | 真实 | 第 1 轮后 | 第 5 轮后 | 第 20 轮后 |
|---|---|---|---|---|
| 000 | 0.10 | 0.08 | 0.09 | 0.10 |
| 001 | 0.05 | 0.06 | 0.05 | 0.05 |
| 010 | 0.15 | 0.17 | 0.16 | 0.15 |
| 011 | 0.10 | 0.11 | 0.11 | 0.10 |
| 100 | 0.20 | 0.17 | 0.19 | 0.20 |
| 101 | 0.08 | 0.09 | 0.08 | 0.08 |
| 110 | 0.12 | 0.14 | 0.13 | 0.12 |
| 111 | 0.20 | 0.18 | 0.19 | 0.20 |
所有 100 个查询的误差都 ≤ 0.01。
Python 代码实现
下面我们用 Python 实现一个简化的 PMW 算法,复现上面这个具体例子。代码会模拟 100 条数据记录(8 种属性组合)、随机生成 100 个线性查询,然后运行 20 轮 PMW 迭代。
import numpy as np
# ========== 设置参数 ==========
# 数据域 X = {0,1}³,共 8 种可能的属性组合
domain_size = 8
num_records = 100
num_queries = 100
num_rounds = 20
epsilon = 1.0 # 总隐私预算
epsilon_per_round = epsilon / num_rounds # 每轮隐私预算
learning_rate = 0.5 # 乘法权重更新步长 η
# ========== 1. 模拟真实数据集 ==========
# 真实分布:8 种组合的概率
true_distribution = np.array([0.10, 0.05, 0.15, 0.10, 0.20, 0.08, 0.12, 0.20])
# 生成 100 条数据记录
np.random.seed(42)
dataset = np.random.choice(domain_size, size=num_records, p=true_distribution)
# 真实数据直方图(归一化)
true_histogram = np.bincount(dataset, minlength=domain_size) / num_records
print("真实数据直方图:", np.round(true_histogram, 3))
# ========== 2. 生成随机查询集合 ==========
# 每个查询是一个长度为 domain_size 的向量,元素在 [0,1] 之间
# q(D) = (1/n) * Σ q(x_i)
queries = np.random.rand(num_queries, domain_size)
# 计算所有查询在真实数据上的答案
true_answers = queries @ true_histogram
# ========== 3. PMW 核心循环 ==========
# 初始化:均匀分布
synth_dist = np.ones(domain_size) / domain_size
print("\n开始 PMW 迭代...\n")
for t in range(1, num_rounds + 1):
# 计算所有查询在当前合成分布上的回答
synth_answers = queries @ synth_dist
# 计算每个查询的误差
errors = np.abs(synth_answers - true_answers)
# 指数机制:选择误差最大的查询(添加隐私保护噪声)
# 每轮隐私预算为 epsilon_per_round
scores = errors * epsilon_per_round / 2
scores = scores - np.max(scores) # 防止数值溢出
probs = np.exp(scores)
probs = probs / np.sum(probs)
# 按概率选择查询
chosen_idx = np.random.choice(num_queries, p=probs)
chosen_query = queries[chosen_idx]
# 获取真实答案和合成答案
v = true_answers[chosen_idx] # 真实答案
u = synth_answers[chosen_idx] # 合成答案
# 乘法权重更新
# 对每个 x ∈ X,如果 q(x) 大(满足条件)且 v > u(低估),提高权重
# 如果 q(x) 大(满足条件)且 v < u(高估),降低权重
update_factor = np.exp(learning_rate * (v - u) * (2 * chosen_query - 1))
synth_dist = synth_dist * update_factor
synth_dist = synth_dist / np.sum(synth_dist) # 归一化
if t % 5 == 0 or t == 1:
max_error = np.max(errors)
avg_error = np.mean(errors)
print(f"第 {t:2d} 轮 | 最大误差: {max_error:.4f} | 平均误差: {avg_error:.4f}")
# ========== 4. 最终结果对比 ==========
print("\n" + "=" * 50)
print("PMW 估计 vs 真实分布")
print("=" * 50)
final_errors = np.abs((queries @ synth_dist) - true_answers)
print(f"\n所有 {num_queries} 个查询的最大误差: {np.max(final_errors):.4f}")
print(f"所有 {num_queries} 个查询的平均误差: {np.mean(final_errors):.4f}")
print(f"\n{'组合':>6} | {'真实':>8} | {'PMW估计':>8} | {'误差':>8}")
print("-" * 35)
for i in range(domain_size):
err = abs(true_histogram[i] - synth_dist[i])
print(f" {i:03b} | {true_histogram[i]:.4f} | {synth_dist[i]:.4f} | {err:.4f}")
代码说明:这个实现使用指数机制代替直接选择最大误差查询(差分隐私的关键),然后通过乘法权重调整合成分布。每轮迭代中,合成分布都会向真实数据靠近一步。你可以调整 num_rounds、learning_rate 和 epsilon 来观察不同参数对收敛速度和精度的影响。
6. 与在线学习的巧妙联系
PMW 算法的设计思路和**在线学习(Online Learning)**有着惊人的相似:
graph TB
subgraph "在线学习"
L1["初始化模型参数"]
L2["接收一个样本/问题"]
L3["做出预测"]
L4["看到真实标签"]
L5["计算损失并更新模型"]
L1 --> L2 --> L3 --> L4 --> L5 --> L2
end
subgraph "PMW 算法"
P1["初始化合成分布"]
P2["用指数机制选一个查询"]
P3["用合成分布回答该查询"]
P4["看到真实答案"]
P5["计算误差并用乘法权重更新"]
P1 --> P2 --> P3 --> P4 --> P5 --> P2
end
| 在线学习概念 | PMW 中的对应 |
|---|---|
| 模型参数 | 合成分布 D |
| 训练样本 | 查询 q |
| 预测值 | q(合成分布) |
| 真实标签 | q(真实数据) |
| 损失函数 | |
| 梯度下降 | 乘法权重更新 |
PMW 本质上是在"在线地"学习真实数据的分布——每一轮选取一个"最有信息量"的查询,用它的答案来修正当前分布。这正是为什么它比一次性加噪的拉普拉斯机制高效得多:它迭代地从查询中提取信息,而不是一次性处理所有查询。
7. PMW 的局限与变体
不是万能的
PMW 虽然强大,但也有其局限:
| 局限 | 原因 | 应对 |
|---|---|---|
| 仅适用于线性查询 | 更新规则依赖线性性质 | 对非线性查询需特殊处理 |
| 空间复杂度高 | 需要维护 | X |
| **需要知道 | X | ** |
| 轮数 T 需预先设定 | 隐私预算需按轮分配 | 可用自适应停止改进 |
重要变体
- Multiplicative Weights with Exponential Mechanism (MWEM):Hardt、Ligett 和 McSherry 在 2012 年提出的变体,是 PMW 家族中最著名的版本。它显式地使用指数机制选择查询,每轮仅消耗少量的隐私预算。
- Iterative Database Construction:Roth 和 Roughgarden 的版本,更强调博弈论的解释。
- 在线 PMW:对动态数据流的版本,不需要预先知道所有查询。
graph TD
A["Private Multiplicative Weights 家族"]
A --> B["原始 PMWHardt & Rothberg (2009)"]
A --> C["MWEMHardt, Ligett & McSherry (2012)"]
A --> D["Iterative DatabaseRoth & Roughgarden"]
A --> E["在线 PMW动态查询流"]
C --> F["最实用的版本指数机制 + 乘法权重"]
8. 直观理解:为什么它能成立?
"压缩"的力量
想象你有两种方式描述一个数据集:
方式一(拉普拉斯):针对每个问题,单独记录答案,每个答案都加噪声。如果问题数量 |Q| = 10,000,你需要记录 10,000 个数字。
方式二(PMW):先描述数据的分布(比如 100 个可能的属性组合,每种的概率),然后用这个分布自动回答所有问题。你只需要维护 100 个数字。
PMW 本质上在进行信息压缩:用 O(|X|) 的参数来描述数据,而不是用 O(|Q|) 的参数来描述所有可能的查询。由于 |X| 通常远小于 |Q|(特别是当数据维度不高时),这就产生了巨大的效率提升。
graph LR
A["原始数据集 Dn 条记录"] --> B["合成分布|X| 个参数"]
B --> C["无限查询在合成分布上计算"]
B --> D["但只选 T 个最有价值的查询来更新"]
D -.->|"修正"| B
"主动学习"的视角
PMW 不是被动地回答所有查询,而是主动选择那些最有信息量的查询。这就像学生复习时不是看所有课本内容,而是只刷"不会的题"——显然高效得多。
每次迭代,PMW 做两件事:
- 诊断:找出当前分布与真实数据差异最大(即最不了解)的查询
- 修正:根据这个查询的答案,调整分布
经过 T 轮,那些"最需要修正"的查询都被处理了,分布也就接近真实数据了。
小结
| 要点 | 说明 |
|---|---|
| 核心思想 | 用合成分布逐步逼近真实数据 |
| 关键机制 | 指数机制选择最差查询 + 乘法权重更新分布 |
| 样本复杂度 | O(√\ |
| 适用范围 | 线性查询集合,有限数据域 |
| 与在线学习的关系 | 本质上是迭代学习过程的差分隐私版本 |
PMW 算法展示了差分隐私中的一个重要设计范式:与其直接保护每个查询,不如构建一个受保护的数据模型,然后在这个模型上自由回答问题。这种"先建模、再回答"的思路,在后续的很多差分隐私算法中都有体现,包括我们后面会讲的稀疏向量技术。
下一讲,我们将学习稀疏向量技术(Sparse Vector Technique)——另一种高效处理大量查询的方法,它通过只回答"足够大"的查询来节省隐私预算。
下一篇: 稀疏向量技术:只回答"大"查询

1423

被折叠的 条评论
为什么被折叠?



