找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2735

积分

0

好友

412

主题
发表于 昨天 22:44 | 查看: 3| 回复: 0

大型语言模型,尤其是以ChatGPT为代表的模型,在展现卓越能力的同时,也面临着显著的隐私泄露风险。这种风险不仅源于训练过程中可能接触到的海量用户敏感数据,也因为在推理时模型可能“回忆”起训练集中的具体内容。例如,GPT-2曾被证实能记住训练数据中的个人信息。这引发了业界对模型记忆效应的广泛担忧,甚至促使部分公司禁止员工使用相关AI服务。

本文聚焦于一项关键技术:如何从大型语言模型中高效提取其训练数据。核心内容源自ICML 2023的论文《Bag of Tricks for Training Data Extraction from Language Models》,我们将结合代码实践,深入剖析攻击原理与优化技巧。

背景:针对性数据提取挑战

为了推动该领域的研究,谷歌曾举办过一个公开挑战赛,其项目仓库 lm-extraction-benchmark 在GitHub上公开。该仓库包含 baselinedatasetsexample_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)

我们的攻击流程分为两个核心阶段:

  1. 后缀生成:利用模型 f_θ,通过采样策略(如top-k采样)为每个前缀生成多个候选后缀。
  2. 后缀排名:使用成员推理攻击技术,依据每个生成后缀的“成员分数”对候选进行排序和筛选。一个基础的分数是序列的困惑度(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

版权声明:本文由 云栈社区 整理发布,版权归原作者所有。




上一篇:计算机组成原理考研笔记:从CPU架构到指令系统的底层技术解析
下一篇:揭秘Kimi系统提示词泄露:安全风险分析与技术对抗
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-28 02:29 , Processed in 0.553423 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表