在CV领域,我们需要通过对模型的对抗攻击和防御来增强模型的稳健型,比如在自动驾驶系统中,要防止模型因为一些随机噪声就将红灯识别为绿灯。在NLP领域,类似的对抗训练也是存在的,不过NLP中的对抗训练更多是作为一种正则化手段来提高模型的泛化能力!
对抗样本
简单来说,它是指对于人类来说“看起来”几乎一样、但对于模型来说预测结果却完全不一样的样本。
理解对抗样本之后,也就不难理解各种相关概念了,比如“对抗攻击”,其实就是想办法造出更多的对抗样本,而“对抗防御”,就是想办法让模型能正确识别更多的对抗样本。所谓对抗训练,则是属于对抗防御的一种,它构造了一些对抗样本加入到原数据集中,希望增强模型对对抗样本的鲁棒性;同时,如本文开篇所提到的,在NLP中它通常还能提高模型的表现。
训练流程
1、往属于x里边注入扰动Δx,Δx的目标是让L(x+Δx,y;θ)越大越好,也就是说尽可能让现有模型的预测出错;
2、当然Δx也不是无约束的,它不能太大,否则达不到“看起来几乎一样”的效果,所以Δx要满足一定的约束,常规的约束是||Δx||≤ϵ,其中ϵ是一个常数;
3、每个样本都构造出对抗样本x+Δx之后,用(x+Δx,y)作为数据对去最小化loss来更新参数θ(梯度下降);
4、反复交替执行1、2、3步。
如何计算Δx
现在的问题是如何计算Δx,它的目标是增大L(x+Δ,y;θ),而我们知道让loss减少的方法是梯度下降,那反过来,让loss增大的方法自然就是梯度上升,因此可以简单地取
Δx=ϵ ∇x L(x,y;θ)
当然,为了防止Δx过大,通常要对∇xL(x,y;θ)做些标准化,比较常见的方式是
这种训练方法就是Fast Gradient Method(FGM)
多迭代几次
此外,对抗训练还有一种方法,叫做Projected Gradient Descent(PGD),其实就是通过多迭代几步来达到让L(x+Δx,y;θ)更大的Δx(如果迭代过程中模长超过了ϵ,就缩放回去,细节请参考
NLP怎么处理
对于CV领域的任务,上述对抗训练的流程可以顺利执行下来,因为图像可以视为普通的连续实数向量,Δx也是一个实数向量,因此x+Δx依然可以是有意义的图像。但NLP不一样,NLP的输入是文本,它本质上是one hot向量(如果还没认识到这一点,欢迎阅读《词向量与Embedding究竟是怎么回事?》),而两个不同的one hot向量,其欧氏距离恒为√2,因此对于理论上不存在什么“小扰动”。
对于NLP任务来说,原则上也要对Embedding层的输出进行同样的操作,Embedding层的输出shape为(b,n,d)
,所以也要在Embedding层的输出加上一个shape为(b,n,d)的Variable,然后进行上述步骤。但这样一来,我们需要拆解、重构模型,对使用者不够友好。
不过,我们可以退而求其次。Embedding层的输出是直接取自于Embedding参数矩阵的,因此我们可以直接对Embedding参数矩阵进行扰动。这样得到的对抗样本的多样性会少一些(因为不同样本的同一个token共用了相同的扰动),但仍然能起到正则化的作用,而且这样实现起来容易得多。
实现方式
1 | import torch |
在上面的代码中,fgsm_attack()函数接受一个模型、一个损失函数、一个输入张量、一个标签和一个扰动大小作为输入,并返回一个对抗样本。该函数首先计算输入张量的损失,并计算损失相对于输入张量的梯度。然后,它将梯度的符号与扰动大小相乘,并将结果添加到输入张量中。最后,它将结果截断到0和1之间,并返回对抗样本。在训练循环中,我们使用fgsm_attack()函数生成对抗样本,并使用它来计算 模型的损失。然后,我们反向传播并更新模型的参数。
需要注意的是,FGM只是对抗训练的一种方法,还有其他方法,如PGD(Projected Gradient Descent)和FreeLB(Free Adversarial Training with Label Bootstrapping)。此外,FGM只是对抗训练的一部分,还需要使用其他技术来提高模型的鲁棒性,如数据增强和模型集成。