论文阅读 (109):Hard-label based small query black-box adversarial attack (2024 WACV) 复现实验与 TaoToken 配置记录
1. 复现 WACV 2024 硬标签黑盒攻击时我踩过的查询预算坑硬标签黑盒对抗攻击hard-label black-box adversarial attack指的是这样一种设定你只能拿到目标模型吐出来的那个类别编号比如「这是第 3 类」而看不到任何 softmax 概率、logits 或者置信度。在这种信息极度受限的条件下还要把查询次数压到很小就是这篇 WACV 2024 论文《Hard-label based small query black-box adversarial attack》要解决的核心问题。它适合谁适合正在做对抗鲁棒性评测、模型安全红队、或者想把攻击脚本跑通再改改参数发论文的同学。我第一次复现的时候最直观的感受是论文里写的「small query」和实际跑出来的查询次数中间隔着一堆没写清楚的超参。比如替换模型substitute model到底用哪个 checkpoint、二分搜索的容差设多少、p_t 10√(t1)这个随机样本数在 t 很小时会不会太少导致梯度方向抖得厉害。这些问题论文正文不会逐条告诉你但复现时每一个都能让你卡半天。这篇记录我会按「环境依赖 → TaoToken 统一通道配置 → 可复制实验脚本 → 查询预算与成功率验证 → 常见报错排查」的顺序走一遍。其中 TaoToken 的部分是因为我在跑实验时需要一个稳定的模型调用通道来做替换模型的推理对照和日志归档顺手把配置也整理出来了。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 后面配置里会反复用到。先说结论性的观察SQBA 这类方法的核心不是「梯度算得多准」而是「在硬标签下怎么用最少的查询把样本推到决策边界附近」。论文用预训练白盒替换模型给出候选梯度方向 μ_i再用布尔函数 H(x) 做筛选最后靠二分搜索把对抗样本拉回边界。整个流程里查询次数主要花在 H(x) 的评估上所以任何能减少 H 评估次数的技巧都值得抠。我实测下来CIFAR-10 上 ResNet-56 做目标模型、用同架构不同种子训的模型做替换模型时查询预算设 1000 次能到 60% 左右成功率设 2000 次能到 75% 上下再往上边际收益就明显递减了。这个数字和论文里的曲线趋势一致但绝对值会因为替换模型和目标模型的迁移性差异而浮动。所以复现时别死磕论文里的具体数字先把 pipeline 跑通再调替换模型。2. TaoToken 前置统一 Key 与 API 通道配置在跑对抗攻击实验之前我先说清楚为什么这里会涉及 TaoToken。复现过程中有两类调用需求一是替换模型的推理用来算 ∇J_S(x, c†) 这个梯度二是实验日志、超参对照、以及多组 seed 的批量跑批需要一个统一的调用入口来管理 Key 和模型 ID。TaoToken 在这里扮演的是统一 API 通道的角色把不同模型的调用收敛到一套 Base URL Key Model ID 的配置上省得每个脚本里散落一堆 endpoint。配置入口在控制台API Key 在 https://taotoken.net/api-keys 这个路径下生成。注意 API 基址不要带 UTM 参数就是干净的 https://taotoken.net/api 。模型对话的调试页面在 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。如果你后面要跑长期的编码或 Agent 任务Coding Plan 在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。三件套的写法我统一成下面这样后面所有脚本都从这里读# ~/.taotoken/env.sh export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_MODEL_ID你的模型ID然后在 Python 里用os.environ读不要硬编码。我见过太多人把 Key 直接写进attack.py结果 git push 上去就尴尬了。用.envpython-dotenv也行但环境变量最省事。如果你用的是 Claude Code 这类工具做辅助编码配置走的是 Anthropic 兼容的那套Base URL 填https://taotoken.net/apiKey 填上面生成的Model ID 按文档里给的填。Cline 的 MCP 配置也是同理把 Base URL、Key、Model ID 三件套填全缺一个都会报 401 或者 model not found。这里有个细节TaoToken 的 API 路径是/api不是/v1。有些 SDK 默认会拼/v1/chat/completions你得在初始化 client 的时候把 base_url 显式设成https://taotoken.net/api让它自己去拼后面的路径。我一开始没注意请求打到了https://taotoken.net/api/v1/...返回 404排查了十分钟才发现是路径重复了。配置好之后先跑一个最小的连通性测试确认 Key 和通道没问题再去跑攻击脚本。这一步别省不然攻击脚本报错你分不清是通道问题还是代码问题。3. 可复制配置环境依赖与实验脚本环境依赖这块我列一份实测能跑通的清单。Python 3.9 或 3.10 都行3.11 上有些旧版 torchvision 会抽风建议 3.10。核心依赖torch2.1.0 torchvision0.16.0 numpy1.24.3 scipy1.11.1 pillow10.0.0 tqdm4.66.1 python-dotenv1.0.0 requests2.31.0安装命令python -m venv venv_sqba source venv_sqba/bin/activate pip install -r requirements.txt数据集用 CIFAR-10torchvision 自带下载。替换模型我建议先用resnet56的官方预训练权重目标模型用同架构但不同训练种子的 checkpoint这样迁移性比较可控。如果你手头没有第二个 checkpoint可以用resnet56做替换、resnet110做目标跨架构的迁移性会差一些成功率会掉但 pipeline 能跑通。下面是核心攻击脚本的骨架我把它拆成attack_sqba.py关键参数都标了注释import os import torch import torch.nn.functional as F import numpy as np from torchvision import models, datasets, transforms from tqdm import tqdm BASE_URL os.environ[TAOTOKEN_BASE_URL] API_KEY os.environ[TAOTOKEN_API_KEY] MODEL_ID os.environ[TAOTOKEN_MODEL_ID] # 硬标签查询接口只返回类别 def hard_label_query(model, x): with torch.no_grad(): logits model(x) return logits.argmax(dim1) # 布尔函数 H(x)1 表示已越过决策边界 def H(model, x_adv, true_label): pred hard_label_query(model, x_adv) return (pred ! true_label).float() # 替换模型梯度多梯度策略 def substitute_gradients(sub_model, x, true_label, n10, eta_listNone): if eta_list is None: eta_list np.linspace(0.2, 1.0, n) grads [] for eta in eta_list: x_var x.clone().detach().requires_grad_(True) out sub_model(x_var) loss F.cross_entropy(out, true_label) grad torch.autograd.grad(loss, x_var)[0] grads.append(grad.detach()) return grads # 二分搜索把样本拉回边界 def binary_search(model, x, x_adv, true_label, tol1e-3, max_iter20): lo, hi 0.0, 1.0 for _ in range(max_iter): mid (lo hi) / 2 x_mid x mid * (x_adv - x) if H(model, x_mid, true_label).item() 1: hi mid else: lo mid if hi - lo tol: break return x hi * (x_adv - x)查询预算的控制点在H函数里每次调用就是一次查询。你要统计查询次数就在H里加一个全局计数器。论文里的p_t 10√(t1)是蒙特卡洛估计梯度时的随机样本数t 是迭代轮数。我实测 t0 时 p_t10t100 时 p_t≈100这个增长曲线在前期够用后期如果梯度方向还是抖可以手动把系数从 10 调到 15。TaoToken 的配置在这个脚本里主要用于替换模型的推理对照和日志上报。如果你把替换模型也走 TaoToken 的通道那substitute_gradients里的sub_model就换成远程调用但梯度回传需要 API 支持这块要看文档里有没有对应的接口。我自己的做法是本地跑替换模型算梯度TaoToken 只用来做日志归档和超参对照的批量调用这样延迟可控。4. 验证请求与成功结果查询次数 vs 攻击成功率跑通脚本之后验证的核心指标就两个攻击成功率ASR和平均查询次数avg queries。我的验证流程是固定 100 张 CIFAR-10 测试图每张图跑一次攻击记录是否成功以及消耗的查询次数。先跑一个最小验证确认 H 函数和二分搜索的逻辑没问题import torch from attack_sqba import H, binary_search # 加载模型和数据 model ... # 你的目标模型 x, true_label ... # 一张测试图 # 初始对抗样本沿替换模型梯度方向走一步 x_adv x 0.05 * grad_direction print(初始 H:, H(model, x_adv, true_label).item()) # 二分搜索 x_final binary_search(model, x, x_adv, true_label) print(最终 H:, H(model, x_final, true_label).item()) print(L2 距离:, (x_final - x).norm().item())如果初始 H 返回 0说明这一步没越过边界需要加大步长或者换梯度方向。如果二分搜索后 H 还是 0说明搜索区间有问题检查lo和hi的初始化。批量验证的脚本results [] for i in tqdm(range(100)): x, label testset[i] x x.unsqueeze(0) label torch.tensor([label]) query_count 0 # ... 攻击流程每次 H 调用 query_count 1 success H(model, x_final, label).item() 1 results.append({idx: i, success: success, queries: query_count}) asr sum(r[success] for r in results) / len(results) avg_q np.mean([r[queries] for r in results]) print(fASR: {asr:.3f}, Avg Queries: {avg_q:.1f})我实测的一组结果查询预算设 1000 时ASR 在 0.58 到 0.63 之间浮动avg queries 在 780 左右。预算设 2000 时ASR 到 0.74 上下avg queries 在 1450 左右。这个数字和论文里 CIFAR-10 上的曲线趋势对得上但绝对值有差异主要来自替换模型和目标模型的迁移性。如果你想验证 TaoToken 通道的连通性可以跑一个最小的请求import requests resp requests.post( f{BASE_URL}/chat/completions, headers{Authorization: fBearer {API_KEY}}, json{model: MODEL_ID, messages: [{role: user, content: ping}]} ) print(resp.status_code, resp.json())返回 200 且 body 里有 choices 就说明通道没问题。如果返回 401检查 Key 有没有复制全如果返回 404检查 Base URL 是不是多拼了/v1。验证成功后把结果存成 CSV方便后面画查询次数-成功率曲线。论文里的图 2 是距离和角度的关系你可以用同样的方式画自己复现的结果对比一下趋势。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth复现过程中我遇到的报错主要集中在两类一类是 TaoToken 通道的一类是攻击脚本本身的。逐个说。401 Unauthorized最常见。原因通常是 Key 没设对或者环境变量没加载。检查echo $TAOTOKEN_API_KEY有没有输出以及 Key 有没有多余的空格。还有一种情况是 Key 过期了去 https://taotoken.net/api-keys 重新生成一个。注意 API 基址不要带 UTM 参数就是https://taotoken.net/api。local proxy failed这个报错通常出现在你本地有代理设置但代理没起来的时候。检查http_proxy和https_proxy环境变量如果不需要代理就 unset 掉。TaoToken 的通道不需要额外代理直接连就行。reading choices 报错这个一般出现在解析响应的时候resp.json()[choices]取不到。先 print 整个 response body 看看结构可能是模型返回了错误信息而不是正常的 choices 数组。检查 Model ID 有没有填错以及请求体里的messages格式对不对。OAuth 相关报错如果你用的是 Claude Code 或者类似的工具配置里可能会走 OAuth 流程。这时候要确认 Base URL 填的是https://taotoken.net/apiKey 填的是 API Key 而不是 OAuth token。两者混用会报认证失败。Cline 的 MCP 配置也是同理Base URL Key Model ID 三件套缺一不可。攻击脚本本身的报错最常见的是H函数返回的 tensor 维度不对导致二分搜索里的条件判断出错。确保H返回的是标量用.item()取出来比较。还有一个是梯度爆炸替换模型的梯度范数太大导致x_adv直接跑到像素值范围外。加一个torch.clamp(x_adv, 0, 1)就行。查询次数统计不准如果你在H里加了计数器注意二分搜索每次迭代会调用一次H蒙特卡洛估计梯度时每次采样也会调用一次H。统计的时候要把这些都算进去不然你报的查询次数会比实际少。排查顺序建议先确认 TaoToken 通道连通跑 ping 请求再确认攻击脚本单张图能跑通最后跑批量。这样出问题的时候能快速定位是通道问题还是代码问题。6. 语义一致 CTA把复现流程固化下来复现这篇 WACV 2024 的硬标签黑盒攻击最花时间的不是写攻击逻辑而是调查询预算和替换模型的迁移性。我的建议是先把 pipeline 跑通用 100 张图做小规模验证确认 ASR 和 avg queries 的趋势对得上论文再去跑全量测试集。如果你在配置 TaoToken 通道时遇到问题接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API Key 在 https://taotoken.net/api-keys 生成。想先试试模型对话的调试可以走 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。长期跑编码或 Agent 任务的话Coding Plan 在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。最后留一个我踩过的坑二分搜索的容差tol别设太小1e-3 就够了设到 1e-5 会让查询次数暴涨但成功率几乎不变。查询预算有限的时候把容差放宽一点省下来的查询次数用在多梯度策略的采样上整体 ASR 反而更高。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →