线性模型
在我们深入到深度网络上的对抗攻击和防御的讨论之前,值得讨论一下当假设类是线性的时候出现的情况。即,对于多分类设定 ,我们考虑这个形式的分类器
其中 。在回到多分类情况之前,我们还将简要地考虑一种稍微不同形式的二分类器,因为许多思想在这种设定下更容易描述。
把这个假设代回我们的鲁棒优化框架中,同时也关注扰动集 是范数球 的情况,其中我们并没有实际指明范数的种类,所以可能是 等等,我们得到最小最大问题
本节我们强调的关键点是,在这种形式化表述下,我们可以精确地解决二分类优化问题的内部最大化,并为多分类提供相对紧的上界。此外,由于产生的最小化问题在 上仍然是凸的(我们很快会看到即使在 上最大化后也仍然是凸的),因此产生的鲁棒训练过程也可以被最优地解决,从而我们可以实现全局最优的鲁棒分类器(至少对于二分类的情况)。这与深度网络的情况形成了鲜明对比,在深度网络中,内部最大化问题和外部最小化问题都不能被全局解出(在外部最小化问题中,即使我们假设内部问题有精确的解法,也由于网络本身的非凸性而无法解出)。
然而,理解线性情况为对抗鲁棒性的理论和实践提供了重要的见解,也与机器学习中更常研究的方法(如支持向量机)提供了联系。
二分类
首先考虑二分类的情况,即在我们上面所述的多分类设定下 。在这种情况下,我们不会使用多分类交叉熵损失,而是会采用更常见的方式,使用二元交叉熵,或者说 logistic 损失。在这个设定下,我们有假设函数
对于 ,类别标签 ,以及损失函数
其中为方便下面我们定义函数 ,我们下面在讨论如何解涉及这个损失的优化问题时会使用这个函数。这个设定的语义是对于一个数据点 ,分类器以下面的概率预测为类别
另:再一次,对于那些可能不熟悉这个设定是如何与前面看到的多分类情况相联系的人们,注意如果我们使用两个类的传统的多分类交叉熵损失,我们会有类别 1 的概率为
以及类似地类别 2 的概率
因此我们可以定义一个标量值假设
其有与之相关的概率
其中 被定义为 如上述。对这个值取负对数得到
就恰好是我们我们上面定义的 logistic 损失。
解内部最大化问题
现在让我们回到鲁棒优化问题,讨论内部最大化问题,其在这种情况下有如下形式
我们在这里要强调的关键点在于,在这个设定下,实际上可以精确地解出这个内部最大化问题。为了证明这一点,首先注意到 如我们之前描述的是一个标量函数,它单调递减,如下图所示:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-4, 4)
plt.plot(x, np.log(1 + np.exp(-x)))
由于这个函数是单调递减的,如果我们想要最大化这个作用于一个标量的函数,就等价于最小化这个标量值。即
其中我们把线性项分出去得到了第二行。
所以我们只需要考虑如何解这个问题
为了直观地理解,仅考虑 的情况,考虑 范数限制 。由于 范数表明 中的每个元素都必须有小于等于 的绝对值,我们对 设置 并对 设置 就显然能最小化这个值。对于 ,我们就反转这些值。即,上面最优化问题对于 范数的最优解由下式给出
此外,我们也可以确定由这个解得到的函数值,
我们事实上可以解析地计算出内部最大化问题的解,有如下形式
因此,不用以真正的最小-最大问题来解鲁棒最小-最大问题,我们可以把它转换为一个纯最小化问题,即
这个问题在 中仍然是凸的,所以可以被精确解出,或例如 SGD 也可以接近全局最优解。更一般一点,一般来说优化问题
其中 表示 上原始范数的对偶范数( 和 是对偶范数对于 )。所以不管我们范数限制,我们事实上能够通过一个最小化问题解出这个鲁棒优化问题(并找到最坏情况对抗攻击的解析解),不需要显式地解最小-最大问题。
注意最终的鲁棒优化问题(现在采用一般形式),
看起来非常像我们在机器学习中通常考虑的典型标准正则化目标 除了正则化项在损失函数内部。直觉上,这意味着在鲁棒优化的情况下,如果一个点远离决策边界,我们不会惩罚参数的范数,但我们会对接近决策边界的点惩罚参数的范数(由损失函数转换)。这些公式与支持向量机间的联系已被充分研究。
二分类设定的示例
让我们看看对于一个实际的线性分类器是什么样子的。在这样做的过程中,我们还可以了解到传统线性模型在防止对抗性例子方面的效果如何(剧透:不太好,除非你确实正则化了)。为此,我们将考虑 MNIST 数据集,它实际上将作为本教程其余大部分内容的运行示例。MNIST 实际上是一个相当糟糕的问题的选择,原因有很多:除了对于现代 ML 来说非常小之外,它还具有容易被“二值化”的性质,即因为像素值本质上只是黑色和白色,我们可以通过四舍五入到 0 或 1 并对结果图像进行分类来去除更多 噪声。但是假设我们不使用这样的策略,对于初始实验来说,它仍然是一个合理的选择,并且足够小,以至于我们在后面的部分中讨论的一些更复杂的方法仍然可以在合理的时间内运行。
由于我们现在处于二分类设定中,让我们关注更简单的问题,即 MNIST 数据中的 0 和 1 之间的分类(我们将很快回到线性模型的多分类设定)。让我们首先使用 PyTorch 库加载数据,并使用梯度下降构建一个简单的线性分类器。请注意,我们将更显式地重复上面的逻辑(即,使用 +1/-1 的标签,使用 函数的直接计算等),而不是从典型的 PyTorch 函数中逆向工程它。
让我们首先加载简化为 0/1 样本的 MNIST 数据。
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
mnist_train = datasets.MNIST("./data", train-True, download=True, transform=transforms.ToTensor())
mnist_test = datasets.MNIST("./data", train=False, download=True, transform=transforms.ToTensor())
train_idx = mnist_train.train_labels <= 1
mnist_train.train_data = mnist_train.train_data[train_idx]
mnist_train.train_labels = mnist_train.train_labels[train_idx]
test_idx = mnist_test.test_labels <= 1
mnist_test.test_data = mnist_test.test_data[test_idx]
mnist_test.test_labels = mnist_test.test_labels[test_idx]
train_loader = DataLoader(mnist_train, batch_size=100, shuffle=True)
test_loader = DataLoader(mnist_test, batch_size=100, shuffle=False)