想象一所学校里有位永远考满分的模范生——这正是我们训练出的 AI 模型。它在标准考卷(普通训练集)上表现完美,老师却隐隐担心:它是不是只会背题型?于是,一位刁钻的考官出现了。她专门出偏题、怪题、陷阱题:一道题换个说法学生就懵了?那就换。一道题加个条件就翻车?那就加。这些“怪题”在 AI 测试里有一个正式名字:边缘用例——那些贴近模型能力边界、专门暴露思维盲点的输入。生成边缘用例,就是考官的出题艺术。优秀的考官不会瞎出题,她会先观察学生哪里皱眉(模型不确定度),设计多变的题型(数据扰动维度),用不同策略出题(模糊测试、梯度攻击、遗传进化),再对照考纲检查覆盖是否周全(覆盖率指标),一轮轮模拟考迭代打磨(闭环优化),最后写出一份为 AI 定制的“期末评语”(安全评测报告)。这篇文章,就带你走进这位考官的工作台。

岗位关联

掌握边缘用例生成,你将拥有为任何 AI 系统设计“压力测试”的能力,成为模型上线前最后一道防线的守护者。

目录
  1. 可视化演示
  2. 1. 生成基础:为什么满分学生也会翻车
  3. 2. 数据维度:怪题的题型库
  4. 3. 关键算法:出题的三板斧
  5. 4. 不确定度:找到学生的“心虚区”
  6. 5. 覆盖率:考卷是不是覆盖了全部考点
  7. 6. 迭代流程:模拟考磨出好考卷
  8. 7. 效率与性能:批卷要快,重复题要扔
  9. 8. 安全评测:期末评语怎么写
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

演示AI安全测试中的用例生成:变异生成展示从种子输入施加删除/交换/插入/缩放扰动派生新用例;类型分类展示对抗、边界、蜕变、红队等生成策略;生成流程展示种子-变异-评估-回填的闭环。

本节配有交互动效(进入视口后自动加载,约 3KB)。

变异派生用例 种子输入→删除/交换/插入/缩放扰动 mutation 自动发现边界与对抗缺陷

1. 生成基础:为什么满分学生也会翻车

学校里的模范生小林,每次模拟考都接近满分。新来的考官却不放心,她在试卷里塞了一道应用题:“如果三辆货车同时从两个城市出发,中途一辆爆胎……”小林果然卡住了。考官微微一笑:满分只能证明你熟悉标准题型,我要用怪题看看你的思维盲点。在 AI 世界里,这位考官就是用例生成算法,它生成的“怪题”被称为边缘用例——让模型在能力边界附近“露馅”的输入。

常规测试用例就像普通考卷上的顺风题:挑一些正常数据,验证模型答得对不对。边缘用例恰恰相反,它专门挑那些容易让模型出错的地方:模糊的照片、拼写错误的长文本、超出训练分布的数值。它的目标不是“确认正确性”,而是“试探极限”。一个模型在常规测试上准确率 99%,可能一遇到雨夜场景就崩溃,这正是边缘用例要提前揭示的风险。

在 AI 安全测试中,边缘用例的形式化定义常写作:在原始样本 \(x\) 附近的小邻域内,找到一个让模型预测结果发生翻转的输入 \(x'\)。这里的“附近”由距离函数 \(d\) 约束(如像素差、编辑距离),“翻转”则意味着模型对边界条件的判断不稳定。这类用例广泛用于鲁棒性评测、对抗攻击防御和公平性分析。

正如考官设计应用题的初衷不是为难小林,而是暴露他“不会知识迁移”的短板,边缘用例的价值也不在于把模型“考倒”,而在于把模型的薄弱环节摆在桌面上。看清短板,才有下一步补强。

E(x)={x′  |  f(x′)≠f(x),  d(x′,x)≤ε}\displaystyle E(x) = \left\{ x' \;\middle|\; f(x') \neq f(x),\; d(x', x) \le \varepsilon \right\}
这个集合告诉工程师:在“改动不大、但能改变模型判断”的邻域里,藏着哪些危险输入。
  1. 原始样本 \(x\)一个被模型正确识别的合法输入,比如一张晴天路况的照片,它是生成用例的出发点。
    x∈Xx \in \mathcal{X}
  2. 预测函数 \(f\)模型对输入给出的判断结果,比如“图片里的物体是行人”。
    f:X→Yf: \mathcal{X} \rightarrow \mathcal{Y}
  3. 距离约束 \(d(x', x) \le \varepsilon\)限定 \(x'\) 不能离 \(x\) 太远,保证它“看起来仍然正常”。
    d(x′,x)≤εd(x', x) \le \varepsilon
  4. 翻转条件 \(f(x') \neq f(x)\)新输入让模型改变判断,这正是我们想要的“怪题”。
    f(x′)≠f(x)f(x') \neq f(x)
  5. 集合 \(E(x)\)所有满足上述条件的候选构成一个危险区域,即边缘用例集合。
    E(x)={x′∣f(x′)≠f(x),  d(x′,x)≤ε}E(x) = \{ x' \mid f(x') \neq f(x),\; d(x', x) \le \varepsilon \}

2. 数据维度:怪题的题型库

有了出题理念,考官开始打磨题型库:选择题测记忆,应用题测迁移,陷阱题测细致。边缘用例生成也一样,不同模态的数据就像不同的题型,每种都有专属的“变形手法”。

这里的“变形手法”在技术上叫扰动。文本可以同义词替换(把“开心”换成“愉悦”)、插入错别字(把“欢迎”改成“欢迎你”)、调整语序(主动句变被动句);图像可以调整亮度、加噪声、旋转角度、遮挡局部;数值数据可以取极端值、填 NaN、压边界。每种扰动都让输入在特征空间中移动一小步。

扰动并不是乱动,它受约束。所有“和原始样本距离不超过 \(\varepsilon\)”的变体,共同构成一个扰动球。这个球定义了出题的边界:太远就面目全非,太近又考不出水平。不同类型的距离度量(L2 欧氏距离、L∞ 最大分量差、编辑距离)对应不同风格的“题型限制”。

题型库丰富,才能测出学生各种隐藏短板。图像敢变光,文本敢换词,数值敢压边界,组合条件下还能交叉出新的陷阱——这是一名合格考官的底气。

Bp(x,ε)={x′  |  ∥x′−x∥p≤ε}\displaystyle \mathcal{B}_p(x, \varepsilon) = \left\{ x' \;\middle|\; \lVert x' - x \rVert_p \le \varepsilon \right\}
扰动球就像考卷上的“作答规范”:你可以变换表达方式,但不能跳出题目给定的框架。
  1. 原始输入 \(x\)作为出题基准的正常样本,比如一张亮度适中的猫照片。
    x∈Rdx \in \mathbb{R}^d
  2. Lp 范数 \(\lVert \cdot \rVert_p\)衡量两个输入差距的尺子,\(p=2\) 是直线距离,\(p=\infty\) 看最大分量差。
    ∥x′−x∥p\lVert x' - x \rVert_p
  3. 扰动预算 \(\varepsilon\)允许的最大改动幅度,控制“怪题”的偏离程度。
    ε>0\varepsilon > 0
  4. 集合 \(\mathcal{B}_p(x, \varepsilon)\)所有满足约束的候选输入,即出题的合法范围。
    Bp(x,ε)\mathcal{B}_p(x, \varepsilon)

3. 关键算法:出题的三板斧

同一个考点,考官有三种出题风格:第一种是“随缘式”,闭眼翻开题库随机挑题——对应模糊测试;第二种是“顺藤摸瓜式”,先观察学生哪道题皱眉,再朝那个方向连续追问——对应梯度方向的对抗生成;第三种是“杂交进化式”,把两道难题拼在一起形成更难的题——对应遗传算法。

模糊测试实现简单但效率偏低,正如随机选题经常选到学生会的题。遗传算法模拟自然选择,让“难倒模型”的用例存活下来并杂交变异,一代比一代刁钻。而 FGSM(快速梯度符号法)是梯度派最经典的招式:它计算模型损失对输入的梯度,找到让模型“最难受”的方向,然后朝这个方向迈出一步。

FGSM 的核心洞察是:模型不是均匀脆弱的,它对某些方向的微小变化极度敏感。只要顺着损失函数上升最快的方向加上一个很小的扰动,就能让模型从“确信”变成“误判”。公式里的每个符号都服务于这个目的:梯度指明方向,符号函数统一步幅,\(\varepsilon\) 控制力度。

三种风格各有长短:随缘式覆盖面广,梯度式精准打击,进化式长线优化。聪明的考官不会只用一个套路——组合出击,才能把学生的软肋全都挖出来。

x′=x+ε⋅sign(∇xJ(θ,x,y))\displaystyle x' = x + \varepsilon \cdot \mathrm{sign}\left( \nabla_x J(\theta, x, y) \right)
一句话记忆:沿着“让损失变大”的方向轻轻推一把,模型就掉进了陷阱。
  1. 原始输入 \(x\)被正确分类的样本,例如一只被识别为“猫”的图片。
    x∈Rdx \in \mathbb{R}^d
  2. 损失梯度 \(\nabla_x J\)损失函数对输入求偏导,告诉算法“朝哪个方向推,模型错得最厉害”。
    ∇xJ(θ,x,y)\nabla_x J(\theta, x, y)
  3. 符号函数 \(\mathrm{sign}\)只保留方向、统一步长,把梯度各分量压成 \(+1\) 或 \(-1\)。
    sign(⋅)∈{−1,+1}\mathrm{sign}(\cdot) \in \{-1, +1\}
  4. 扰动幅度 \(\varepsilon\)限制每个像素/维度被改动的最大值,保证篡改不易察觉。
    ε>0\varepsilon > 0
  5. 生成用例 \(x'\)原始样本与扰动相加,得到一个让模型预测翻转的边缘用例。
    x′=x+ε⋅sign(∇xJ(θ,x,y))x' = x + \varepsilon \cdot \mathrm{sign}\left( \nabla_x J(\theta, x, y) \right)

4. 不确定度:找到学生的“心虚区”

有经验的考官会发现,小林每次遇到行程问题都会先皱眉、再咬笔杆。这种细微的犹豫,正是知识漏洞的信号。模型也有类似的“皱眉”信号:面对某些输入时,它的输出概率不再是“九成把握”,而是 0.51 对 0.49 的摇摆。

这种摇摆程度在技术上有精确的度量,统称不确定度。最常用的是预测熵:当模型对各个类别的概率分布越均匀,熵越大,意味着它越“拿不准”。此外还有置信度评分(最高概率有多高)、分歧采样(多个模型或多次前向的预测不一致程度)、边界距离(样本离决策边界有多近)。

利用不确定度来生成用例,能让出题事半功倍:与其在整个输入空间盲目搜索,不如先让模型“自我评估”哪里最心虚,然后集中火力攻击这些区域。高熵样本往往是天然的高价值边缘用例,它们恰好站在决策边界附近,只差最后一击。

专攻心虚之处,等于把力气花在刀刃上。考官不需要把整本书重新考一遍,只要盯着学生眼珠乱转的位置连续追问,就能逼出真相。

H(p)=−∑i=1Cpilog⁡pi\displaystyle H(p) = -\sum_{i=1}^{C} p_i \log p_i
熵就像考官的“雷达”:哪里信号乱,哪里就是学生的薄弱区。
  1. 类别概率 \(p_i\)模型对第 \(i\) 个类别的置信度,所有类别的概率和为 1。
    ∑i=1Cpi=1,pi≥0\sum_{i=1}^{C} p_i = 1,\quad p_i \ge 0
  2. 对数项 \(\log p_i\)概率越接近 1,该项越接近 0;概率越小,惩罚越大。
    log⁡pi≤0\log p_i \le 0
  3. 加权求和把每个类别的“惊讶程度”按概率加权汇总,得到整体的不确定度。
    H(p)=−∑i=1Cpilog⁡piH(p) = -\sum_{i=1}^{C} p_i \log p_i
  4. 最大熵情形当所有类别概率相等(如二分类各 0.5),熵达到最大值,模型彻底“没主意”。
    Hmax⁡=log⁡CH_{\max} = \log C

5. 覆盖率:考卷是不是覆盖了全部考点

出完一套考卷,资深考官会对照考纲逐项打勾:函数覆盖了、几何覆盖了、概率遗漏了——得补题。AI 测试同理:生成了一批边缘用例,如何知道它们是否逼着模型展现了全部能力?这就需要覆盖率指标。

最常见的结构指标是神经元覆盖率:统计有多少比例的神经元在测试中被“激活”。直觉上,如果一个用例能让更多神经元参与计算,它就在更大范围上“触及”了模型的内部逻辑。此外还有行为覆盖率(输出类别是否覆盖各种预测)、边界覆盖率(决策边界各区域是否都被探索)、多样性度量(用例之间是否足够不同)。

覆盖率指标的价值在于发现“出题盲区”。如果一万个用例只激活了模型 20% 的神经元,说明剩下的 80% 从未被考验过——那里可能藏着巨大的漏洞。但要注意,覆盖率是代理指标而非最终目标,它衡量“触及了多少结构”,并不直接代表“发现了多少真实风险”。

一套只考一个章节的考卷没有说服力,一批只激活一小部分神经元的用例同样如此。覆盖率是考官的“考纲对照表”,提醒我们补上遗漏的角落。

NC=∣{n∈N:ϕ(x,n)>τ}∣∣N∣\displaystyle \mathrm{NC} = \frac{ \left| \left\{ n \in N : \phi(x, n) > \tau \right\} \right| }{ \left| N \right| }
神经元覆盖率 = 被“点亮”的神经元数量 ÷ 神经元总数,是衡量测试充分性的体温计。
  1. 神经元集合 \(N\)模型内部所有可统计的神经元(或卷积核、注意力头)。
    N={n1,n2,…,nm}N = \{ n_1, n_2, \dots, n_m \}
  2. 激活函数 \(\phi(x, n)\)输入 \(x\) 在神经元 \(n\) 上的输出值,代表该神经元的“兴奋程度”。
    ϕ(x,n)∈R\phi(x, n) \in \mathbb{R}
  3. 阈值 \(\tau\)判断神经元是否被激活的临界值,超过它才会计入有效覆盖。
    ϕ(x,n)>τ\phi(x, n) > \tau
  4. 覆盖比例激活神经元数量与总数量之比,越接近 1 代表结构探索越充分。
    NC=激活数总数\mathrm{NC} = \frac{\text{激活数}}{\text{总数}}

6. 迭代流程:模拟考磨出好考卷

好考卷不是一次写成的。考官先出初稿,找几个学生试考,分析哪些题所有人都答对(太简单)、哪些题无人能答(太偏),然后修改、再考、再改。边缘用例生成遵循同样的闭环:种子池、变异筛选、反馈回注、收敛判断。

种子池是一批“正常但接近边界”的初始用例,相当于初稿。变异与筛选负责让种子在扰动空间里跑动,淘汰无效变异,保留让模型出错的个例,就像保留那些能难倒学生的题。反馈回注把新发现的边界点重新加入种子池,使下一轮搜索从更接近极限的位置出发。

这套机制在遗传算法语义下尤为清晰:每个用例是个体,能否“难倒模型”是适应度;适应度高者被选为父母,通过交叉和变异产生下一代。选择压力让种群整体向决策边界推进,直到连续多轮没有显著更新的用例时收敛——此时考卷已足够刁钻,继续磨题的收益变得微乎其微。

一次次模拟考推动试卷不断逼近学生的能力极限,直到所有的薄弱点都被点亮。用例生成不是一锤子买卖,而是一场持续逼近极限的“军备竞赛”。

Pi=fitnessi∑j=1Mfitnessj\displaystyle P_i = \frac{\mathrm{fitness}_i}{\sum_{j=1}^{M} \mathrm{fitness}_j}
选择概率 = 自己的适应度 ÷ 全体适应度之和,适应度越高的用例越容易被选中“繁殖”。
  1. 适应度 \(\mathrm{fitness}_i\)衡量第 \(i\) 个用例的质量,比如是否让模型出错、是否位于低置信区。
    fitnessi≥0\mathrm{fitness}_i \ge 0
  2. 适应度总和种群中所有个体适应度之和,作为归一化的分母。
    ∑j=1Mfitnessj\sum_{j=1}^{M} \mathrm{fitness}_j
  3. 选择概率 \(P_i\)个体被选中参与下一代繁殖的概率,适应度越高概率越大。
    Pi=fitnessi∑j=1MfitnessjP_i = \frac{\mathrm{fitness}_i}{\sum_{j=1}^{M} \mathrm{fitness}_j}
  4. 迭代与收敛反复进行“选择 → 交叉 → 变异 → 评估”,直到新增有效用例趋近于零。
    Δfitness→0\Delta \mathrm{fitness} \to 0

7. 效率与性能:批卷要快,重复题要扔

考试规模一大,批卷就成灾难。如果一万道题里有八千道重复,考官不仅白费力气,还会被重复的正确率误导。大规模生成边缘用例时,同样的麻烦找上门:模型推理开销高、候选空间海量、相似用例大量冗余。

去重是第一要务。衡量两个用例是否“重复”,常用 Jaccard 相似度:两个集合的交集大小除以并集大小。对文本用例可以看字词集合的重合度,对图像用例可以在特征空间计算距离。与去重并行的还有并行加速——多卡推理、多进程变异互不干扰;以及降维策略——在低维扰动子空间中搜索,而不是全维乱跑。

这些优化手段共同决定了一次评测的规模上限。能做到每秒生成上千个候选,才有底气说“百万级压力测试”;能做到实时去重,才不会被海量近似样本淹没信号。

高效率让大规模出题成为可能,去重让每一道题都值得被批改。考官既要有出难题的创意,也要有快速批卷的流水线。

J(A,B)=∣A∩B∣∣A∪B∣\displaystyle J(A, B) = \frac{\left| A \cap B \right|}{\left| A \cup B \right|}
Jaccard 相似度 = 两集交集 ÷ 两集并集,数值越大说明两个用例越像,可以直接丢掉一个。
  1. 集合 \(A, B\)两个用例各自携带的特征集合,比如两条文本消息里的关键词集合。
    A={a1,a2,… },B={b1,b2,… }A = \{a_1, a_2, \dots\},\quad B = \{b_1, b_2, \dots\}
  2. 交集 \(\lvert A \cap B \rvert\)两个集合共有的元素个数,反映共同信息量。
    ∣A∩B∣|A \cap B|
  3. 并集 \(\lvert A \cup B \rvert\)两个集合合并后的总元素个数,用于归一化尺度差异。
    ∣A∪B∣|A \cup B|
  4. 相似度判定比值接近 1 表示高度重复,接近 0 表示完全不同,据此决定是否去重。
    J(A,B)∈[0,1]J(A, B) \in [0, 1]

8. 安全评测:期末评语怎么写

期末考试结束,考官的真正工作才刚刚开始——写评语。不是简单打分,而是逐条列出:小林在行程问题上失分严重,几何证明掌握扎实,注意力分配有偏科。AI 安全评测同样如此,边缘用例是检查手段,最终产出是一份覆盖多维度风险的报告。

评测报告的核心量化指标之一是鲁棒准确率:在扰动条件下,模型仍能保持正确预测的比例。它直观回答“模型被怪题考倒的比率有多高”。更进一步,工程师会把所有令模型失分的用例聚类,找出共因:是光照问题、是语序问题、还是特定人群的表达方式问题?这些聚类结果直接指导训练数据的补充方向。

安全评测还关注对抗鲁棒性与公平性偏差:前者考察模型能否抵御恶意攻击,后者考察不同群体(按性别、年龄、地区等划分)的预测质量是否一致。边缘用例生成在其中扮演的角色是“精准钓鱼”——把偏见和漏洞从模型的黑暗角落中钓出来。

一份漂亮的期末评语,不会只说“这孩子不错”,而是把优势、短板、改进建议写得明明白白。边缘用例生成最终交付的,正是这样一份 AI 模型的安全体检报告。

Racc=1n∑i=1nI[f(xi′)=f(xi)=yi]\displaystyle R_{\mathrm{acc}} = \frac{1}{n} \sum_{i=1}^{n} \mathbb{I}\left[ f(x_i') = f(x_i) = y_i \right]
鲁棒准确率 = 在扰动下依然判断正确的样本比例,是贴在模型额头的“抗压成绩单”。
  1. 测试样本集共 \(n\) 个原始样本,每个都有真实标签 \(y_i\)。
    {(xi,yi)}i=1n\{(x_i, y_i)\}_{i=1}^{n}
  2. 扰动样本 \(x_i'\)对每个原始样本生成对应的边缘用例(或常规扰动变体)。
    xi′∈Bp(xi,ε)x_i' \in \mathcal{B}_p(x_i, \varepsilon)
  3. 正确条件要求模型同时满足“扰动前预测正确”和“扰动后预测不变”两件事,才算通过。
    f(xi′)=f(xi)=yif(x_i') = f(x_i) = y_i
  4. 平均成绩用指示函数计数后除以总数,得到 0 到 1 之间的鲁棒准确率。
    Racc∈[0,1]R_{\mathrm{acc}} \in [0, 1]

AI 应用场景

自动驾驶感知模型的多模态边缘场景测试

阶段:模型上线前的安全评测与缺陷发现

解决的问题:实车路测成本极高,且很难遇到极端天气、逆光、障碍物遮挡等长尾场景,常规测试集又都是“风和日丽”的顺风题,模型隐患被掩盖。

具体案例:某自动驾驶团队以白天正常路况图片为种子池,用亮度调节 + 雨纹叠加 + 高斯模糊组合生成“夜间逆光 + 雨天”边缘用例,再叠加 FGSM 梯度扰动向感知模型发起百万级压力测试。结果显示,低照度下行人检测漏检率从 2% 暴涨至 19%,团队据此在训练集中补充针对性增强数据,重新评测后漏检率压回 4% 以内。

大语言模型的越狱防护测试

阶段:内容安全策略的持续迭代

解决的问题:人工编写提示词测试覆盖不全,难以发现新出现的越狱话术,安全策略的迭代缺少数千级的“磨刀石”。

具体案例:安全团队以开源 LLM 为靶子,从正常提问的种子池出发,用同义词替换、句式变换、角色扮演模板拼接三种扰动策略组合变异,生成数万条候选提示词;再按困惑度过滤(低困惑度更容易让模型放松警惕),保留高威胁用例做红队测试。每月批量运行一轮,持续为安全策略迭代输送弹药。

金融风控模型的公平性评测

阶段:模型公平性与偏见审计

解决的问题:只看整体 AUC 等指标会掩盖特定人群的偏差,一家“总体表现优秀”的模型可能在某个年龄段的拒绝率异常偏高,而普通测试用例难以触达这些边界人群。

具体案例:风控团队用“特征组合边界搜索”代替随机抽样,构造高收入 + 低龄、低收入 + 高负债、高学历 + 无征信记录等组合条件用例,批量送入信贷审批模型评测。结果显示“低龄 + 高收入”群体的拒绝率是平均水平的 1.8 倍,定位到特征交叉偏差后调整模型权重,复测后该群体拒绝率回落正常区间。

Python 演示

python82 行
import numpy as np class SimpleCLF:    def __init__(self, w, b):        self.w = np.array(w, dtype=float)        self.b = float(b)     def logit(self, X):        return X @ self.w + self.b     def predict(self, X):        return (self.logit(X) > 0).astype(int)     def proba(self, X):        z = self.logit(X)        return 1.0 / (1.0 + np.exp(-z)) def generate_seeds(rng, n=40):    cls0 = rng.normal(loc=[-1.5, 1.0], scale=0.6, size=(n // 2, 2))    cls1 = rng.normal(loc=[1.5, -1.0], scale=0.6, size=(n // 2, 2))    X = np.vstack([cls0, cls1])    y = np.array([0] * (n // 2) + [1] * (n // 2))    return X, y def random_fuzz(model, x, eps=0.8, k=5, seed=0):    rng = np.random.default_rng(seed)    out = []    for _ in range(k):        noise = rng.uniform(-eps, eps, size=x.shape)        out.append(x + noise)    return np.array(out) def gradient_fuzz(model, x, y, eps=0.6, k=3):    out = []    for _ in range(k):        grad = model.w * (model.predict(x) - y)        if np.linalg.norm(grad) == 0:            grad = model.w        grad = grad / np.linalg.norm(grad)        out.append(x + eps * grad)    return np.array(out) def boundary_hunt(model, x, direction, lo=0.0, hi=4.0, iters=20):    left = x.copy()    right = x + hi * direction    for _ in range(iters):        mid = (left + right) / 2        if model.predict(mid) == model.predict(x):            left = mid        else:            right = mid    return right def evaluate(model, X, y, rng):    found = []    for xi, yi in zip(X, y):        cands = random_fuzz(model, xi, k=4)        cands = np.vstack([cands, gradient_fuzz(model, xi, yi, k=2)])        for _ in range(2):            dir_vec = rng.normal(size=xi.shape)            dir_vec = dir_vec / (np.linalg.norm(dir_vec) + 1e-8)            cands = np.vstack([cands, boundary_hunt(model, xi, dir_vec)])        for c in cands:            if model.predict(c) != model.predict(xi):                found.append(c)                break    return found def main():    model = SimpleCLF([1.2, -1.5], 0.2)    rng = np.random.default_rng(42)    X, y = generate_seeds(rng)    found = evaluate(model, X, y, rng)    print("seed count:", len(X))    print("edge cases found:", len(found))    print("edge rate: %.2f%%" % (100.0 * len(found) / len(X)))    if found:        conf = model.proba(np.array(found))        print("avg confidence on edge cases: %.3f" % conf.mean()) if __name__ == "__main__":    main()
目的:用三种策略(随机模糊、梯度扰动、边界二分搜索)为二维逻辑回归模型批量生成边缘用例,统计发现率与平均置信度。
  1. class SimpleCLF:定义一个“被测试的 AI 模型”,用逻辑回归模拟一个可解释的决策边界。
  2. def random_fuzz(model, x, eps=0.8, k=5, seed=0):对应“随缘式”出题:朝随机方向撒噪声,生成 k 个候选。
  3. noise = rng.uniform(-eps, eps, size=x.shape)每个特征维都在 ±eps 内随机跳动,模拟蒙题式变异。
  4. grad = model.w * (model.predict(x) - y)计算梯度方向:当模型预测错误时反推模型最敏感的轴,即“顺藤摸瓜”。
  5. grad = grad / np.linalg.norm(grad)归一化梯度向量,保证每次迈出的步长一致,只保留方向。
  6. def boundary_hunt(model, x, direction, ...)二分搜索沿指定方向缩小包围圈,一步步逼近决策边界。
  7. if model.predict(c) != model.predict(xi):判断候选是否翻转了预测,这是“成为边缘用例”的硬标准。
  8. found.append(c)把攻破模型判断的样本收进边缘用例集合。
  9. edge_rate边缘用例发现率,直观反映这批种子池和策略组合的“出题效率”。

常见误区

误区初学者常以为边缘用例就是对抗样本,凡是能让模型预测错误的输入都叫对抗样本,两者是一回事。

正解对抗样本是边缘用例的一个子集,但两者目的不同。边缘用例关注“贴近决策边界、暴露真实场景隐患”,比如雨夜遮挡的车牌、语序别扭的客服对话;对抗样本则强调“刻意击败防御”,比如在图片上叠加人眼不可见的噪声。前者是体检,后者是攻击。

为何常见:对抗样本在媒体上曝光度高,很多教程用它引出边缘用例的概念,导致读者把“最锋利的矛”当成了“全部的矛”。

误区认为生成的用例越离谱越好,最好让模型完全答错,纯噪声图片、胡言乱语的长文本都是好用例。

正解边缘用例的价值在于“揭示边界的薄弱”,而非“击穿模型”。一张纯噪声图被识别成狗,模型错了,但这个错误在真实场景中几乎不会发生,对改进模型没有指导意义。真正有效的边缘用例通常“看似正常、实则临界”,比如有遮挡的交通标志、带方言口音的语音指令。

为何常见:自媒体常渲染“AI 被一张图骗过”的戏剧效果,让初学者误以为偏离度越大越有价值。

误区只要神经元覆盖率达到 100%,就说明模型已经被测试充分了,可以安心上线。

正解覆盖率是代理指标,只回答“触及了多少结构”,不回答“暴露了多少真实错误”。类比:考卷把所有章节都出了一道题,覆盖率 100%,但每道题都是课本原题,照样测不出学生的高阶思维。需要将神经元覆盖、行为覆盖、边界覆盖结合使用,并以模型实际出错行为为最终验收标准。

为何常见:覆盖率是精确可计算的数字,而真实风险难以量化,人们很容易用简单的数字安慰自己。

误区既然边缘用例就在边界附近,那用随机噪声大批量扰动输入,总能把边界区域填满。

正解高维空间里随机扰动几乎总是垂直于任何给定的决策边界方向。想象朝教室里撒一把豆子,能精准打中角落里的目标吗?梯度引导、覆盖引导和不确定度采样相当于“瞄准镜”,让扰动沿模型最敏感、最薄弱的方向推进,效率高出几个数量级。

为何常见:二维平面上的直觉(随手乱画也能碰到分界线)直接套用到高维空间,导致对随机策略的期望严重高估。

误区认为生成一批用例、测完一轮、把报告交上去,整个工作就结束了。

正解模型在迭代,数据分布会漂移,今天发现的边界可能明天就不再是边界,也可能出现新的边界。好的用例生成体系是持续运行的闭环:种子池 → 变异 → 测试 → 反馈 → 扩大种子池 → 再变异。就像好考官会翻看历次模拟考成绩不断微调考卷,而不是一劳永逸。

为何常见:很多团队把安全测试当作版本发布的“过场仪式”,拿到报告就束之高阁,忽略了评测的持续性和动态性。

面试题

Q1. 边缘用例的核心目标是什么?

单选题 · easy

  • A.提升模型的训练速度
  • B.暴露边界条件下的错误← 正确答案
  • C.减少模型的参数量
  • D.增强数据的标注质量

答案:B。边缘用例专门选择贴近模型能力边界的输入,目的是暴露正常测试发现不了的错误。它不关心训练速度、参数量或标注质量,而是像考官用怪题照出学生的思维盲点。

Q2. 模糊测试生成用例的主要特点是什么?

单选题 · easy

  • A.完全依赖梯度信息
  • B.随机变异输入数据← 正确答案
  • C.穷举所有可能输入
  • D.只针对图像数据

答案:B。模糊测试的本质是随机变异:对种子输入加噪声、做变换,看模型是否翻车。它不依赖梯度、不做穷举,也绝不只限图像;好比考官闭眼翻题库随机抽题,简单但效率有限。

Q3. FGSM 公式中 sign 函数的核心作用是什么?

单选题 · mid

  • A.将梯度归一化为固定长度
  • B.计算梯度的二阶导数
  • C.保留方向并统一扰动幅度← 正确答案
  • D.消除梯度的符号信息

答案:C。sign 把梯度每一维压成 +1 或 -1,让每个维度的扰动幅度完全相同,只保留“往哪个方向推最让模型难受”。它不是归一化长度,也不计算二阶导,更不会消除符号信息。

Q4. 当模型预测概率接近均匀分布时,熵值 H(p) 的变化趋势是?

单选题 · mid

  • A.恒等于零
  • B.恒等于一
  • C.达到最大值← 正确答案
  • D.趋向负无穷

答案:C。熵度量不确定性。概率越均匀,模型越“没主意”,熵越大;概率越集中,熵越小。均匀分布时熵取最大值,而非固定为 0 或 1,更不可能为负无穷。

Q5. 神经元覆盖率 NC 的含义是什么?

单选题 · mid

  • A.激活神经元占全部神经元比例← 正确答案
  • B.参数数量与数据量的比值
  • C.训练轮数与准确率的比值
  • D.损失函数下降的相对速度

答案:A。NC 统计测试用例中激活值超过阈值的神经元数量除以总神经元数,反映结构层面的探索充分度。它与参数总量、训练轮数、损失下降速度均无关。

Q6. 遗传算法中,选择概率 P_i 直接由什么决定?

单选题 · hard

  • A.单个样本的适应度高低← 正确答案
  • B.随机种子的初始数值
  • C.模型参数规模的大小
  • D.批次数据的排列顺序

答案:A。P_i = fitness_i / Σ fitness_j,分子分母都由适应度决定。适应度越高的个体被选中的概率越大,实现“优胜劣汰”。随机种子、参数规模、批次顺序都不进入选择概率公式。

Q7. Jaccard 相似度的值越大,说明两个用例之间?

单选题 · hard

  • A.特征差异越大
  • B.重复程度越高← 正确答案
  • C.模型置信度越低
  • D.数据不平衡越严重

答案:B。Jaccard 相似度 = 交集 ÷ 并集,值越接近 1 说明两个集合重叠越多,即两个用例的重复度越高,应当去重。它与置信度、数据不平衡无关。

Q8. 边缘用例生成常用的数据维度包括哪些?

多选题 · easy

  • A.文本同义词替换← 正确答案
  • B.图像亮度变换← 正确答案
  • C.数值边界值填充← 正确答案
  • D.模型权重随机初始化

答案:ABC。文本、图像、数值是边缘用例最常见的三类扰动维度,分别对应词级替换、像素级变换和取值范围试探。模型权重初始化属于训练环节,与用例生成无关。

Q9. 下列哪些属于覆盖率指标?

多选题 · mid

  • A.神经元覆盖率← 正确答案
  • B.行为覆盖率← 正确答案
  • C.决策边界覆盖率← 正确答案
  • D.磁盘空间占用率

答案:ABC。神经元覆盖、行为覆盖、边界覆盖分别从结构、行为和几何三个角度评估测试充分性。磁盘占用属于工程资源指标,与覆盖率无关。

Q10. 提升边缘用例生成效率的常见手段有哪些?

多选题 · hard

  • A.基于相似度进行去重← 正确答案
  • B.并行化候选用例生成← 正确答案
  • C.覆盖引导减少冗余搜索← 正确答案
  • D.增大模型训练数据规模

答案:ABC。去重、并行、覆盖引导分别从输出端、计算端和搜索端提升效率。增大训练数据规模属于模型训练环节,不会加快用例生成,反而可能增加额外成本。