大型语言模型,尤其是以ChatGPT为代表的模型,在展现卓越能力的同时,也面临着显著的隐私泄露风险。这种风险不仅源于训练过程中可能接触到的海量用户敏感数据,也因为在推理时模型可能“回忆”起训练集中的具体内容。例如,GPT-2曾被证实能记住训练数据中的个人信息。这引发了业界对模型记忆效应的广泛担忧,甚至促使部分公司禁止员工使用相关AI服务。
本文聚焦于一项关键技术:如何从大型语言模型中高效提取其训练数据。核心内容源自ICML 2023的论文《Bag of Tricks for Training Data Extraction from Language Models》,我们将结合代码实践,深入剖析攻击原理与优化技巧。
背景:针对性数据提取挑战
为了推动该领域的研究,谷歌曾举办过一个公开挑战赛,其项目仓库 lm-extraction-benchmark 在GitHub上公开。该仓库包含 baseline、datasets、example_submission 等核心目录,以及描述“Training Data Extraction Challenge”的README文件。
这项挑战旨在测试“针对性数据提取”能力。与无差别地搜索任意训练数据不同,攻击者的目标是在给定一个前缀(prompt)的情况下,准确预测出其在训练数据中对应的后缀(suffix)。这种攻击更具实际价值,因为它可能用于恢复与特定主题或个体相关的敏感信息,且评估方式更为直接。
比赛使用基于The Pile数据集训练的GPT-Neo 1.3B参数模型。基准测试集包含20,000个样本,每个样本被分割为长度各50个token的前缀和后缀。任务设定为恢复“1-eidetic”记忆的数据,即在整个训练集中仅出现一次的完整序列。
相关研究脉络
- 训练数据提取:由Carlini等人明确定义,旨在从预训练模型中恢复训练示例。
- 成员推理攻击(MIA):一种密切相关的任务,旨在判断给定数据是否属于模型的训练集,可分为基于分类器和基于度量(如困惑度)两类方法。
- 其他基于记忆的攻击:包括模型提取攻击(复制模型功能)和属性推断攻击(提取数据集的统计属性)。
威胁模型与攻击流程
攻击场景设定为:给定一个来自训练集的前缀 p,攻击者利用预训练的自回归语言模型 f_θ 来生成可能的后缀 s。语言模型通过链式规则生成序列,其概率可以分解为条件概率的乘积:
f_θ(x₀, x₁, …, x_N) = ∏(n=0到N) f_θ(x_n | x_[0,n-1])
针对性提取的目标是找到模型认为最可能的后缀,形式化定义为:
s = argmax_{s'} f_θ(s' | p)
我们的攻击流程分为两个核心阶段:
- 后缀生成:利用模型
f_θ,通过采样策略(如top-k采样)为每个前缀生成多个候选后缀。
- 后缀排名:使用成员推理攻击技术,依据每个生成后缀的“成员分数”对候选进行排序和筛选。一个基础的分数是序列的困惑度(Perplexity, P):
P = exp ( -1/N ∑_{n=0}^{N} log f_θ(x_n | x_[0:n-1]) )
其中N为序列长度。困惑度越低,表示模型认为该序列越“熟悉”,它属于训练集的可能性通常越高。
核心改进策略与技巧分析
研究发现,仅依靠基线方法(如beam search)效果有限。通过对真实训练数据后缀和模型生成后缀的logits分布进行分析,发现两者存在显著差异。生成数据的logits分布在较高值区域(>10^-1)更为密集,而真实数据的分布则不同。这表明需要调整生成和排名策略来逼近真实分布。
1. 采样策略优化
在生成阶段,直接寻找全局最优解不现实,常用的束搜索缺乏多样性。因此采用随机采样,并优化其参数:
- Top-k采样:实验表明,k值在10左右时提取精度达到较优水平,之后提升平缓。
- Nucleus采样(Top-p):从累积概率达到阈值η的候选词中采样。在故事生成中,较低的η(如0.6)利于多样性;但在数据提取任务中,较高的η(约0.8)能获得更好的精度,较基线提升31%。η值过高或过低均会导致性能下降。
- 典型采样(Typical Sampling):选择概率接近熵典型集的token。参数φ需要精细调整,实验显示其在某个中间值能达到最佳精度。
2. 概率分布直接调整
- 温度控制:调节softmax的温度参数T,T>1平滑分布增加多样性,T<1锐化分布提高确定性。动态调整温度可能有益。
- 重复惩罚:通过降低已出现token的logit来抑制重复。然而,在数据提取任务中,简单的重复惩罚往往有负面影响。实验数据如下:
| Repetition penalty |
MP(%) (↑) |
MR(%) (↑) |
MH (↓) |
| 0.9 |
19.8 |
66.4 |
27.927 |
| 1 |
37.0 |
76.5 |
19.614 |
| 1.1 |
37.3 |
76.5 |
20.181 |
| 1.2 |
37.1 |
76.5 |
20.323 |
| 1.3 |
36.7 |
76.4 |
20.332 |
| 1.5 |
34.7 |
75.7 |
21.154 |
3. 动态上下文窗口
在训练时,句子常被截断或打包成固定长度序列,因此训练时模型看到的“前缀”长度可能不同。为此,提出动态调整生成时使用的上下文窗口大小。定义函数:
f_θ(x_n; W) = h_W(f_θ(x_n | x_[n-w1,n-1]), ..., f_θ(x_n | x_[n-wm,n-1]))
其中 W 是一组不同的窗口大小 {w1, ..., wm},h_W 是集成方法(如加权平均或投票)。实践中可采用 m=4, w_i ∈ {n, n-1, n-2, n-3}。
4. 动态位置偏移
像GPT-Neo这类模型使用位置嵌入。训练时,同一句子在不同批次中可能拥有不同的位置偏移。攻击时,可以通过评估不同偏移位置 c_i 下前缀的困惑度,并选择困惑度最低的位置来模拟训练时的状态,从而提升提取效果。选择过程可形式化为:
c = argmin P(p, c^i); ŝ(x_i) = ψ(c_n) + φ(x_n)
c^i ∈ C
其中 ψ(·) 是位置编码层,φ(·) 是特征映射,P 计算困惑度。
5. 前瞻(Look-Ahead)解码
为了解决早期单个错误token导致后续生成全部偏离的问题,可以使用前瞻ν步的技术,利用未来token的概率来修正当前token的生成决策。其核心是计算后验概率:
f_θ (x_n | x_{n+1}, x_{<n}) = f_θ (x_{n+1} | x_n, x_{<n}) f_θ (x_n | x_{<n}) / Σ_{x'_n} f_θ (x_{n+1} | x'_n, x_{<n}) f_θ (x'_n | x_{<n})
定义轨迹概率 Track 为一系列条件概率的乘积,则ν步后验可写为:
f_θ(x_n|x_{n+v}, x_{<n}) = Track(x_n, x_{n+v}|x_{<n}) / Σ_{x'_n} Track(x'_n, x_{n+v}|x_{<n})
6. 后缀排名改进
研究发现,真实训练句子的困惑度并非总是最低。统计显示,在100个生成样本中,真实句子的困惑度排名分布广泛。因此,需要更精细的排名标准。
- 词级别奖励高置信度:记忆效应常伴随高置信度的token。若生成后缀中包含概率高于阈值(如0.9)的token,则对该后缀的最终分数(原始为困惑度)进行奖励(如减去一个固定值)。
- 鼓励惊讶模式:人类文本常包含个别高困惑度(惊讶)token。为了在排名时不惩罚这种模式,可以只基于“多数”token计算困惑度。具体方法是,设
μ 和 σ 为一批次内 log p(x_n | x_[0:n-1]) 的均值和标准差,然后构造子集:
X^ = {x_j|μ - 3σ < log p(x_n|x_[0:n-1]) < μ + 3σ}
最终用于排名的损失 L_s 仅基于子集 X^ 计算:
L_s = 1/|X^| Σ log p(x_n|x_[0:n-1])
实战代码分析与效果
我们将深入关键代码,看上述策略如何实现。首先是基础的生成与损失计算函数 generate_for_prompts。
def generate_for_prompts(prompts: np.ndarray, batch_size: int=32) -> Tuple[np.ndarray, np.ndarray]:
generations = []
losses = []
generation_len = _SUFFIX_LEN + _PREFIX_LEN
for i, off in enumerate(range(0, len(prompts), batch_size)):
prompt_batch = prompts[off:off+batch_size]
prompt_batch = np.stack(prompt_batch, axis=0)
input_ids = torch.tensor(prompt_batch, dtype=torch.int64)
with torch.no_grad():
# 1. Generate outputs from the model
generated_tokens = _MODEL.generate(
input_ids=input_ids.cuda(),
max_length=generation_len, #100
do_sample=True,
top_k=10,
pad_token_id=50256 # Silences warning,
).cpu().detach()
# 2. Compute each sequence's probability, excluding EOS and SOS.
outputs = _MODEL(
generated_tokens.cuda(),
labels=generated_tokens.cuda(),
)
logits = outputs.logits.cpu().detach() #50(batch)*100*50257
logits = logits[:, :-1].reshape(-1, logits.shape[-1]).float() #4950(50*99)*50257
loss_per_token = torch.nn.functional.cross_entropy(
logits, generated_tokens[:, 1:].flatten(), reduction='none') #4950
loss_per_token = loss_per_token.reshape((-1, generation_len - 1))[:,_SUFFIX_LEN:] #50(batchsize)*...
generations.extend(generated_tokens.numpy())
losses.extend(likelihood.numpy())
该函数以批处理方式工作:1)使用模型生成候选后缀(采用top-k采样);2)将生成序列送回模型,计算每个token的交叉熵损失;3)提取后缀部分的平均损失作为该生成序列的“分数”。
后续的评估与比较需要一系列辅助函数,例如计算汉明距离、比较损失排名等:
def write_array(file_path: str, array: np.ndarray, unique_id: Union[int, str]):
file_ = file_path.format(unique_id)
np.save(file_, array)
def hamming(gt, generate):
if len(generate.shape) == 2:
hamming_dist = (gt != generate).sum(1)
else:
hamming_dist = (gt != generate[0]).sum(1)
return hamming_dist.mean(), hamming_dist.shape
def compare_loss(gt_loss, gene_loss):
loss_all = np.concatenate((gt_loss, gene_loss), axis=1)
loss_ranked = np.sort(loss_all, axis=1)
argrank = np.argsort(loss_all, axis=1)
top1 = argrank[:, 0] #, flaten(top1, top5)
return loss_ranked, argrank, top1, top5
compare_loss 函数将真实后缀的损失与所有生成后缀的损失拼接、排序,从而确定真实后缀在生成候选中的排名,这是评估攻击精度的关键。
效果验证:Zlib熵方法
论文中提到结合Zlib压缩熵与困惑度作为排名标准,但指出其改进边际。我们可通过修改损失计算函数来验证。在计算完 loss_per_token 后,加入Zlib压缩调整:
# ... 计算 loss_per_token 之后 ...
generations_np = generated_tokens.numpy()
for seq_idx in range(generations_np.shape[0]):
seq = generations_np[seq_idx]
# 将token id序列转换为字节并进行zlib压缩
compressed_len = len(zlib.compress(seq.tobytes()))
# 根据压缩长度调整损失,例如使损失与压缩长度成反比
adjusted_loss = loss_per_token[seq_idx] / compressed_len
likelihood = adjusted_loss.mean()
losses.append(likelihood)
实践表明,此方法带来的精度提升有限,与论文结论一致。
效果验证:动态上下文窗口与投票策略
动态上下文窗口的实现涉及从不同长度的历史中获取logits并进行集成。以下是加权平均和投票两种集成策略的核心函数:
def winlen_logits_output(input_batch, win_len, input_len, answer_batch):
with torch.no_grad():
output = _MODEL(input_batch[:, win_len:input_len].cuda())
last_logits = output.logits.cpu().detach()
val = []
# ... 处理 logits ...
return last_logits, val
def vote_for_the_one(last_logits, k, answers, input_len):
vote_count = np.zeros((last_logits[0].shape[0], last_logits[0].shape[2]))
for logit in last_logits:
topk_indices = np.argsort(logit, axis=2)[:, :, -k:] # 取top-k
weights = np.arange(k, 0, -1) # 线性权重
for i in range(topk_indices.shape[0]):
for pos in range(topk_indices.shape[1]):
for rank, idx in enumerate(topk_indices[i, pos]):
vote_count[pos, idx] += weights[rank]
final_prediction = np.argmax(vote_count, axis=1)
return final_prediction
def logits_add(last_logits, weight_win):
weighted_logits = sum(w * l for w, l in zip(weight_win, last_logits))
final_prediction = np.argmax(weighted_logits, axis=2)
return final_prediction
使用动态上下文窗口(如结合n, n-1, n-2, n-3四个窗口)进行生成后,评估指标如精确度(Precision)得到显著提升,汉明距离(Hamming Dist)显著下降,证明了该策略的有效性。
效果验证:词级别高置信度奖励
将“奖励高置信度token”的策略融入损失计算过程:
def generate_for_prompts_with_highconf(prompts: np.ndarray, batch_size: int=32):
# ... 前期的生成和logits计算与基础函数相同 ...
# 计算 loss_per_token 后,进行后处理
loss_per_token_reshaped = loss_per_token.reshape((-1, generation_len - 1))
for seq_idx in range(loss_per_token_reshaped.shape[0]):
seq_loss = loss_per_token_reshaped[seq_idx]
seq_logits = logits[seq_idx] # 假设logits已适当重塑
# 找出高置信度token (概率 > 0.9)
probs = torch.softmax(seq_logits, dim=-1)
high_conf_mask = (torch.max(probs, dim=-1)[0] > 0.9)
# 如果存在高置信度token,则对整句损失进行奖励(减去一个值)
if high_conf_mask.any():
reward = 0.1 * high_conf_mask.sum().item() / len(seq_loss)
seq_mean_loss = seq_loss.mean() - reward
else:
seq_mean_loss = seq_loss.mean()
losses.append(seq_mean_loss)
# ... 返回结果 ...
应用此策略后,攻击的精确度等指标相比基线方法也有明显提升。
总结与思考
通过对采样策略、概率调整、上下文建模、排名算法等多方面的“技巧”进行系统性优化,可以从像GPT-Neo这样的大语言模型中更有效地提取出训练数据。这些方法揭示了人工智能模型记忆效应的具体机制,并量化了隐私泄露的风险。
这项研究对于安全领域具有重要意义。它迫使开发者必须在模型效用与隐私保护之间寻找平衡。未来的防御方向可能包括更严格的数据去重、差分隐私训练、以及对模型输出进行检测和过滤。同时,这也对智能 & 数据 & 云服务提供商提出了更高的安全合规要求,在释放大模型强大能力的同时,必须筑牢数据安全的防线。
参考资料
[1] 大模型隐私泄露攻击技巧分析与复现, 微信公众号:mp.weixin.qq.com/s/_vTWPvk0_kb8-NSFTTiXcg
版权声明:本文由 云栈社区 整理发布,版权归原作者所有。