李毅宏的机器学习作业6


作业所需数据 akti
作业参考答案

作业要求

本此作业是一个“adversarial attack”的简单实践。要求我们使用两种方法攻击神经网络,一种是“FGSM”,另一种自选

对抗攻击

adversarial attack”被翻译为“对抗攻击”,它的手段是对输入样本故意添加一些人无法察觉的细微的干扰,导致模型以高置信度给出一个错误的输出。直观的理解是:对训练数据添加细微的扰动使其不被发现,同时使得神经网络误判,影响其性能。

对抗攻击分类

对抗攻击大体分为四类:
白盒攻击,称为White-box attack,也称为open-box 对模型和训练集完全了解,这种情况比较简单,但是和实际情况不符合。

黑盒攻击,称为Black-box attack,对模型不了解,对训练集不了解或了解很少。这种攻击和实际情况比较符合,主要也是主要研究方向。

定向攻击,称为targeted attack,对于一个多分类网络,把输入分类误判到一个指定的类上

非定向攻击,称为non-target attack,只需要生成对抗样本来欺骗神经网络,可以看作是上面的一种特例。

FGSM

FGSM,全称Fast Gradient Sign Method(快速梯度下降法),FGSM会生成一个噪声,在原图像上加上这个噪声,会使得图像分类错误。
在这里插入图片描述
上图是论文中的示例,原本是熊猫的图加上噪声,被模型识别成了长臂猿。

噪声是如何得到的呢?我们原本训练网络,图像作为输入,优化的是模型的参数,而FGSM则把模型参数当作输入,改变的图像的输入,并且改变的方向与其损失函数的方向相反。使得损失函数越来越大。

表达式:

X ′ = X + ε ∗ s i g n ( ∇ x J ( X , y ) ) X^{'}=X+\varepsilon *sign(\nabla_{x} J(X,y)) X=X+εsign(xJ(X,y))

x x x是原图像, x ′ x^{'} x表示被攻击后的图像, ε \varepsilon ε 是一个调节系数, s i g n ( ) sign() sign() 是一个符号函数,代表的意思也很简单,就是取一个值的符号,当值大于 0 时取 1,当值等于 0 时取 0,当值小于 0 时取 -1,▽ 表示求 x 的梯度,可以理解为偏导,J 是训练模型的损失函数。

从函数可以看出,FGSM攻击且只采用方向,不直接采用梯度值, ε \varepsilon ε会控制扰动的大小。

FGSM算法简单有效,在图像攻击领域扮演着非常重要的角色,后续还有类似迭代版FGSM等改进算法。

任务1示例代码

下面给出的参考答案中的代码:

class Attacker:
    def __init__(self, img_dir, label):
        # 讀入預訓練模型 vgg16(也可选择其它模型)
        self.model = models.vgg16(pretrained = True)
        self.model.cuda()
        self.model.eval()
        self.mean = [0.485, 0.456, 0.406]
        self.std = [0.229, 0.224, 0.225]
        # 把圖片 normalize 到 0~1 之間 mean 0 variance 1
        self.normalize = transforms.Normalize(self.mean, self.std, inplace=False)
        transform = transforms.Compose([                
                        transforms.Resize((224, 224), interpolation=3),
                        transforms.ToTensor(),
                        self.normalize
                    ])
        # 利用 Adverdataset 這個 class 讀取資料
        self.dataset = Adverdataset('./data/images', label, transform)
        
        self.loader = torch.utils.data.DataLoader(
                self.dataset,
                batch_size = 1,
                shuffle = False)
                
    ####################FGSM 核心代码
    # FGSM 攻擊
    def fgsm_attack(self, image, epsilon, data_grad):
        # 找出 gradient 的方向
        sign_data_grad = data_grad.sign()
        # 將圖片加上 gradient 方向乘上 epsilon 的 noise(对应公式)
        perturbed_image = image + epsilon * sign_data_grad
        # 將圖片超過 1 或是小於 0 的部分 clip 掉
        # perturbed_image = torch.clamp(perturbed_image, 0, 1)
        return perturbed_image
    
    def attack(self, epsilon):
        # 存下一些成功攻擊後的圖片 以便之後顯示
        adv_examples = []
        wrong, fail, success = 0, 0, 0
        for (data, target) in self.loader:
            data, target = data.to(device), target.to(device)
            data_raw = data;
            data.requires_grad = True
            # 將圖片丟入 model 進行測試 得出相對應的 class
            output = self.model(data)
            init_pred = output.max(1, keepdim=True)[1]

            # 如果 class 錯誤 就不進行攻擊
            if init_pred.item() != target.item():
                wrong += 1
                continue
            
            # 如果 class 正確 就開始計算 gradient 進行 FGSM 攻擊
            loss = F.nll_loss(output, target)
            self.model.zero_grad()
            loss.backward()
            data_grad = data.grad.data
            perturbed_data = self.fgsm_attack(data, epsilon, data_grad)

            # 再將加入 noise 的圖片丟入 model 進行測試 得出相對應的 class        
            output = self.model(perturbed_data)
            final_pred = output.max(1, keepdim=True)[1]
          
            if final_pred.item() == target.item():
                # 辨識結果還是正確 攻擊失敗
                fail += 1
            else:
                # 辨識結果失敗 攻擊成功
                success += 1
                # 將攻擊成功的圖片存入
                if len(adv_examples) < 5:
                  adv_ex = perturbed_data * torch.tensor(self.std, device = device).view(3, 1, 1) + \
                  torch.tensor(self.mean, device= device).view(3, 1, 1)
                  adv_ex = adv_ex.squeeze().detach().cpu().numpy() 
                  data_raw = data_raw * torch.tensor(self.std, device = device).view(3, 1, 1) + \
                  torch.tensor(self.mean, device = device).view(3, 1, 1)
                  data_raw = data_raw.squeeze().detach().cpu().numpy()
                  adv_examples.append( (init_pred.item(), final_pred.item(), data_raw , adv_ex) )        
        final_acc = (fail / (wrong + success + fail))
        
        print("Epsilon: {}\tTest Accuracy = {} / {} = {}\n".format(epsilon, fail, len(self.loader), final_acc))
        return adv_examples, final_acc

最后攻击生成的图像
在这里插入图片描述

在这里插入图片描述

任务2尝试

除了使用FGSM,作业还要求使用另一种算法,这里我使用I-FGSM,是基于FGSM的一个改进算法。

FGSM每次更新只更新一次,而有时候单次更新不足以成功,因此在原来的基础上提出迭代式的FGSM, 也就是I-FGSM(iterative FGSM)
I-FGSM的公式如下:

X 0 ′ = X , X N + 1 ′ = C l i p x , ε { X N ′ + α s i g n ( ∇ x J ( X N ′ , y ) ) } X_{0}^{'}=X, X_{N+1}^{'}=Clip_{x}, _{\varepsilon}\{X_{N}^{'}+\alpha sign(\nabla_{x} J(X_{N}^{'},y)) \} X0=X,XN+1=Clipx,ε{XN+αsign(xJ(XN,y))}

这个公式中,α表示权重,论文中取1,迭代次数用N表示,论文中N取min(e+4, 1.25e),这部分其实就是将总的噪声幅值分配到每一次迭代中,因此在给定噪声幅值e的前提下,还可以直接用α=e/N来设置α和N参数。另外式子中的Clip表示将溢出的数值用边界值(0或1)代替,以解决部分像素值可能会溢出(比如超出0到1的范围)的问题,保证生成有效的图像。其具体表达式如下:

c l i p x , ε { X ′ } = { X + ε , 如 果 X ′ > X + ε X − ε , 如 果 X ′ < X − ε X ′ , 如 果 X ′ ∈ ( X − ε , X + ε ) 1 , 数 值 超 过 像 素 数 值 上 限 0 , 数 值 超 过 像 素 数 值 下 限 clip_{x,\varepsilon}\{X^{'} \}=\left\{ \begin{aligned} X+\varepsilon,如果X^{'}>X+\varepsilon \\ X-\varepsilon,如果X^{'}<X-\varepsilon \\ X^{'},如果X^{'}\in(X-\varepsilon,X+\varepsilon)\\ 1,数值超过像素数值上限\\ 0,数值超过像素数值下限 \end{aligned} \right. clipx,ε{X}=X+εX>X+εXεX<XεX,XXεX+ε)1,0,

代码尝试

 def where(self,cond, x, y):
        cond = cond.float()
        return (cond*x) + ((1-cond)*y)
def i_fgsm_attack(self,image,epsilon, data_grad,alpha,iteration):
        perturbed_image=image
        for i in range(iteration):
            sign_data_grad = data_grad.sign()
            perturbed_image = perturbed_image + alpha * sign_data_grad
            # for j in range(len(perturbed_image)):
            #   perturbed_image[j]=self.clip(image[j],epsilon,perturbed_image[j])
            perturbed_image=self.where(perturbed_image > (image + epsilon),image + epsilon,perturbed_image)
            perturbed_image=self.where(perturbed_image < (image - epsilon),image - epsilon,perturbed_image)

            perturbed_image = torch.clamp(perturbed_image, 0, 1)
        return perturbed_image

最终结果,alpha 选择的是1像素.

在这里插入图片描述
在这里插入图片描述

参考

https://zhuanlan.zhihu.com/p/104532285?utm_source=qq

https://www.cnblogs.com/shona/p/11274170.html

https://blog.csdn.net/u014380165/article/details/90723948

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值