攻擊神經網路有多難?去掉一個像素就夠了

神經網路應用於各種領域早已不是什麼稀罕事了,在有些工作方面它甚至能做到的比人類更好。而隨著應用的增多,其安全性也愈加收到重視——針對特定的數據攻擊,能否讓神經網路被迷惑和欺騙,無法給出正確的結果呢?

答案是肯定的。

研究證實,深度神經網路(DNN)的輸出並不是連續的,對輸入向量的微小擾動非常敏感。在較新的論文One pixel attack for fooling deep neural networks中,九州大學的科研人員提出了一種修改少數像素達到生成對抗範例(Adversarial Examples)的方法。它需要很少的對抗信息,對更廣泛的DNN模型類型有效。

下面來通過RussellCloud復現的例子來學習一下這篇論文是如何實現「攻擊」神經網路的吧!

背景介紹

什麼是對抗攻擊?

機器學習演算法的輸入形式是一種數值型向量,所以攻擊者就會通過設計一種有針對性的數值型向量從而讓機器學習模型做出誤判,這便被稱為對抗性攻擊。這種攻擊是構造對抗性數據(對抗範例)輸入機器學習模型並得到欺騙的識別結果。

構造對抗性數據的過程中,根據攻擊者掌握機器學習模型信息的多少,可以分為兩種情況——知曉機器學習演算法及其參數的白盒攻擊和不知道其演算法與參數的黑盒攻擊。

本例子是復現了論文 One pixel attack for fooling deep neural networks 中的對抗攻擊,這裡使用了一種差分進化的演算法實現了對訓練在 CIFAR-10 數據集上的三種神經網路進行黑盒攻擊。以往對神經網路的攻擊是不限制修改像素的個數,而是限制修改的總體偏移量。這篇論文提及的方法相對於之前的對抗攻擊的主要特點是通過少量像素(一個像素)的擾動來欺騙神經網路。接下來我們來通過代碼詳細了解。

代碼解讀

比較基礎的讀入數據代碼就不說了╮(╯▽╰)╭~我們直接先來看Image Perturbation的模塊。

def perturb_image(x, img): # Copy the image to keep the original unchanged img = np.copy(img) # Split into an array of 5-tuples (perturbation pixels) # Make sure to floor the members of x as int types pixels = np.split(x.astype(int), len(x) // 5) # At each pixels x,y position, assign its rgb value for pixel in pixels: x_pos, y_pos, *rgb = pixel img[x_pos, y_pos] = rgb return img

這裡定義的是一個對圖像的擾動函數。要說一下的是我們讀入的是 32 * 32 的尺寸,RGB 3 通道的圖像。因為需要控制修改像素的個數,我們這裡定義的擾動輸入 x 是 5 個數值(如果多個像素擾動則為 5 * 擾動的像素數量),代表修改像素的坐標 x_pos 、 y_pos ,修改後的 RGB 值 rgb 。這個函數將擾動 x 和圖片 img 讀入,把擾動 x 轉換為每個修改的像素信息對圓圖像修改並返回修改後的圖像。

下面的代碼演示演示了這個函數的作用,把坐標16,16的像素修改為黃色(RGB值255,255,0),返回了一個圖像顯示出來:

擾動函數效果

接下來是load我們的模型:

lecun_net = LecunNet()resnet = ResNet()models = [lecun_net, resnet]...network_stats, correct_imgs = evaluate_models(models, x_test, y_test)correct_imgs = pd.DataFrame(correct_imgs, columns=[name, img, label, confidence, pred])network_stats = pd.DataFrame(network_stats, columns=[name, accuracy, param_count])network_stats

然後比較重要的是接下來定義的預測函數,這裡的預測函數將對擾動之後的圖像進行預測,所以接受的參數是擾動x,圖像img,目標分類target_class,使用的模型model。將會輸出擾動後對正確分類的分類置信度。

def predict_class(x, img, target_class, model, minimize=True): # Perturb the image with the given pixel(s) x and get the prediction of the model img_perturbed = perturb_image(x, img) prediction = model.predict_one(img_perturbed)[target_class] # This function should always be minimized, so return its complement if needed return prediction if minimize else 1 - prediction

然後有一個例子來看到這個函數的效果:

擾動預測函數效果

這裡特殊選出了一張圖片,修改的像素是 16 行 13 列,RGB修改為(25, 48, 156),可以看到修改之前的對正確分類鳥的置信度為 0.7066 ,當修改這個像素之後置信度就變為 0.0002 左右了,達到了欺騙神經網路的效果。

如何才能對神經網路攻擊呢,肯定不是我們自己去一個一個點和一個一個RGB值去試。我們先定義一個檢測對於輸入擾動、圖像來確定是否攻擊成功的函數。

def attack_success(x, img, target_class, model, targeted_attack=False, verbose=False): # Perturb the image with the given pixel(s) and get the prediction of the model attack_image = perturb_image(x, x_test[img]) confidence = model.predict_one(attack_image) predicted_class = np.argmax(confidence) # If the prediction is what we want (misclassification or # targeted classification), return True if (verbose): print(Confidence:, confidence[target_class]) if ((targeted_attack and predicted_class == target_class) or (not targeted_attack and predicted_class != target_class)): return True

這裡會提到一個概念是指向攻擊和非指向攻擊,指向攻擊是指擾動圖像將模型對圖片的預測變為某個指定的分類,非指向攻擊是讓擾動後的圖像分類變為非正確分類即可。這個函數可以通過 targeted_attack 的參數進行控制。如果 targeted_attack 為 True 則是進行指向攻擊,攻擊成功的條件是修改像素後的預測分類變為 target_class 。如果 targeted_attack 為 False 則是進行非指向攻擊,修改像素後預測分類不再是 target_class 則說明攻擊成功。

下面也也演示了一個非指向攻擊的樣例,成功對馬的圖像修改使其對馬分類的置信度從 0.5005 變為 0.0746:

判斷攻擊成功函數效果

最重要的就是接下來的Attack Function了:

我們的核心任務就是找到修改的像素來達成成功攻擊。修改的是像素的坐標、RGB值,目標函數若不能微分則不能選用基於梯度的方法。另外由於可能會對某幾個像素進行修改,使用基於梯度的演算法,可能會陷入局部最優解不能找到最合適的多個點。因此本例是用一種叫差分進化的演算法。這是一種種群進化演算法,大概的過程是在每次迭代期間,根據當前一些解(父)生成另一組候選解(子)。然後將孩子與他們相應的父親進行比較,如果他們比父親更適合,他們就可以存活下來。具體的實現可以再看一些參考資料:差分進化演算法。

看一下這個攻擊函數的代碼:

def attack(img, model, target=None, pixel_count=1, maxiter=30, popsize=30, verbose=False): # Change the target class based on whether this is a targeted attack or not targeted_attack = target is not None target_class = target if targeted_attack else y_test[img,0] # Define bounds for a flat vector of x,y,r,g,b values # For more pixels, repeat this layout bounds = [(0,32), (0,32), (0,256), (0,256), (0,256)] * pixel_count # Format the predict/callback functions for the differential evolution algorithm predict_fn = lambda x: predict_class( x, x_test[img], target_class, model, target is None) callback_fn = lambda x, convergence: attack_success( x, img, target_class, model, targeted_attack, verbose) # Call Scipys Implementation of Differential Evolution attack_result = differential_evolution( predict_fn, bounds, maxiter=maxiter, popsize=max(1, popsize // pixel_count), recombination=1, atol=-1, callback=callback_fn) # Calculate some useful statistics to return from this function attack_image = perturb_image(attack_result.x, x_test[img]) prior_probs = model.predict_one(x_test[img]) predicted_probs = model.predict_one(attack_image) predicted_class = np.argmax(predicted_probs) actual_class = y_test[img,0] success = predicted_class != actual_class cdiff = prior_probs[actual_class] - predicted_probs[actual_class] # Show the best attempt at a solution (successful or not) plot_image(attack_image, actual_class, class_names, predicted_class) return [model.name, pixel_count, img, actual_class, predicted_class, success, cdiff, prior_probs, predicted_probs, attack_image]

bounds 是約束產生擾動 x 的邊界,predict_fn 和 callback_fn 是目標的函數及後續處理。這裡 attack_result 就是實現的就是調用 Scipy 里的差分演算法來進行優化最後得出攻擊的結果。

最後就是一些調用數據集使用 Attack Function 攻擊的代碼了!來在 RussellCloud 上運行一下案例實戰吧!

RussellCloud復現

復現前準備:

  • 註冊 RussellCloud 賬號

如果你沒有邀請碼,可以到RussellCloud社區發帖,每位註冊的用戶也有5枚邀請碼。

RussellCloud社區:本站新帖 - RussellCloud - Powered by phpwind

  • 安裝 russell-cli 終端工具
  • Clone 項目文件,Git地址RussellCloud/one-pixel-attack-keras

# clone代碼$ git clone https://github.com/RussellCloud/one-pixel-attack-keras

使用命令行登錄:

# 使用russell login命令$ russell login

輸入y,網頁登錄後在網頁端拷貝賬戶的Token,粘貼進終端,回車。如果你使用Windows的命令行,可能會出現粘貼不進的情況,請右鍵窗口粘貼。

成功登錄輸出:

Login Successful as XXX

新建項目:

來到RussellCloud主頁,進入控制台,新建一個項目。項目名隨便起一個,默認容器環境一定要選擇:keras 。

初始化項目:

項目創建完成後記得在項目主頁複製概覽ID,用於項目初始化。

# 綁定遠程項目,此處<project_id>是在網頁上複製的項目概覽 ID$ russell init --id <project_id>

初始化成功輸出:

Project "XXX" initialized in current directory

運行項目:

我們的項目是一個IPython Notebook工程,我們使用Jupyter模式啟動並且加上GPU參數。

# 以jupyter模式啟動我們的項目,掛載訓練數據集、測試數據集、預訓練模型數據集$ russell run --mode jupyter --data 9727e7f8109f46a49823ccc35b2d9959:cifar-10

成功運行會輸出任務成功提交的提示,接著會自動打開網頁Notebook的操作。

打開Jupyter

打開Notebook項目,一步步運行到 Attack Function 的位置:

Attack Function說明

來看看 Attack Function 攻擊的效果。為了以示區別,分別演示一張圖的目標攻擊和非目標攻擊。首先來看一下對於 102 的圖像的非目標攻擊,這裡設定的是修改兩個像素進行圖像的擾動,可以看到第一次迭代對於正確分類 Frog 的置信度是高達 0.9903 ,經過 10 次迭代後,對於 frog 的分類置信度變為 0.3879(低於 0.5 ),非目標攻擊成功。

圖片102 Frog 非目標攻擊

再進行目標攻擊,目標將分類為青蛙(Frog)的圖片經過神經網路被分類為汽車(Automobile)。可以看到經過多次迭代之後,對於汽車(Automobile)的置信度從第一次迭代的 0.1109 ,然後慢慢變為 0.6095 。也就是說修改了圖上這三個像素,會使我們的青蛙圖像被神經網路識別為汽車,成功實現了目標攻擊。

圖片102 Frog 目標攻擊為 Automobile

然後我們可以抽取數據集中所有的圖像,對神經網路發起攻擊,下面是對兩張 model 非目標攻擊的結果顯示:

查看非目標攻擊的結果

最後是對攻擊結果進行一定的分析,獲取其攻擊成功的概率。你可以在下圖看到對兩不同神經網路攻擊的成功率對比,對於 lecun_net 網路非目標攻擊成功率為 72.67% ,對於 resnet 網路的非目標攻擊成功率為 53.33% 。而目標攻擊則成功率分別為 34.44% ,和 23.33% 。其實我們的代碼中也可以加入其他的模型,如果你想要運行這一部分,我們建議你使用 GPU 的支持進行更快的計算以及獲得更多的內存。

對不同網路攻擊成功率

論文中對不同的神經網路以及不同的數據集都做了較多的實驗:

結果表明,在 CIFAR-10 測試數據集中 68.36% 的自然圖像和在 ImageNet(ILSVRC 2012)數據集中 41.22% 的驗證圖像在被擾動後可以被神經網路判斷為另一個目標類。僅僅修改一個像素就分別有 73.22% 和 5.52% 的平均置信度。相對於以往不同的對抗攻擊,本篇論文實現了一種在極端有限條件下的對抗攻擊,這實在表明當前的深度神經網路也會容易受到這種低維攻擊的影響。

(譯自論文摘要)

參考資料

1、論文:One pixel attack for fooling deep neural networks

2、RC項目地址:RussellCloud/one-pixel-attack-keras

3、相關推薦閱讀資料:

  • 對抗攻擊最新研究:僅修改「一個像素」即可騙過神經網路! - CSDN博客
  • 差分進化演算法 - CSDN

推薦閱讀:

燒餅修改器綜合、深度、模糊、聯合搜索使用技巧是什麼?
密碼學家百年來無法辨認,500年前古怪手稿的加密希伯來語被AI演算法破譯
《好奇心漫遊》S01E02:走進微軟,EQ類機器人只是人類無用的自嗨?
探索「意識」的起源:從RNA世界假說到人類大腦糾纏網路
人工智慧進化史:和機器人結婚你願意嗎?

TAG:神經網路 | 人工智慧 | 計算機視覺 |