對抗圖像和攻擊在Keras和TensorFlow上的實現

作者:Adrian Rosebrock 翻譯:吳振東 校對:張達敏

本文約8000字,建議閱讀10+分鐘

本文將會告訴你如何用基於圖像的對抗攻擊來破壞深度學習模型,利用Keras和TensorFlow深度學習庫來實現你自己的對抗攻擊。

[ 摘要 ]在這篇教程中,你將會學到如何用基於圖像的對抗攻擊來破壞深度學習模型。我們將會利用Keras和TensorFlow深度學習庫來實現自己的對抗攻擊。

              

試想下從現在起的二十年後。現在路上所有的車輛都是利用人工智能、深度學習和計算機視覺來驅動的無人駕駛交通工具。每一次轉彎、車道轉換、加速和剎車都是由深度神經網絡來提供技術支持。現在,想象自己在高速路上,你正坐在“駕駛室(當車自主行駛時,駕駛室還能被稱爲‘駕駛室’嗎?)”裏,你的妻子坐在副駕上,你的孩子們坐在後座。

 

朝前看,你看到一張巨大的貼紙正在車輛所行駛的車道上,這看上去對車的前進毫無影響。看上去這像是一張受歡迎的大幅塗鴉藝術版畫,可能是幾個高中生開玩笑把它放在這裏,或者他們只是爲了完成某個大冒險遊戲。

圖一:執行一次對抗攻擊需要一張輸入圖像(左),故意用一個噪聲向量來擾亂它(中),迫使神經網絡對輸入圖像進行錯誤分類,最後得到一個錯誤的分類結果,很可能會出現一次嚴重的後果(右)。

一瞬間,你的汽車突然急剎車,然後立即變道,因爲放在路上的那張貼畫可能印的是行人、動物或者另一輛汽車。你在車裏被猛推了一下,感到頸部似乎受了傷。你的妻子尖叫着,孩子們的零食在後座上蹦了起來,彈在擋風玻璃上後灑在中控臺上。你和你的家人都很安全,但所經歷的這一切看上去不能再糟糕了。

發生了什麼?爲什麼你的自動駕駛車輛會做出這樣的反應?是不是車內的某段代碼或者某個軟件存在奇怪的“bug”?

 

答案就是,爲車輛視覺組件提供技術支持的深度神經網絡看到了一張對抗圖像。

 

對抗圖像是指:含有蓄意或故意干擾像素來混淆或欺騙模型的圖像。

 

但與此同時,這張圖像對於人類來說看上去是無害且無惡意的。

 

這些圖像導致深度神經網絡故意做出錯誤的預測。對抗圖像在某種方式上去幹擾模型,導致它們無法做出正確的分類。

 

事實上,人類通過視覺可能無法區分正常圖片和對抗攻擊的圖片——本質上,這兩張圖片對於肉眼來說是相同的。

 

這或許不是一個準確(或者說正確)的類比,但我喜歡在圖像密碼學的背景下來解釋對抗進攻。利用密碼學算法,我們可以把數據(例如純文本信息)嵌入到圖像中,且不改變圖像本身的表象。這幅圖像可以純粹地傳送到接收者那裏,然後接收者再去提取圖像中所隱藏的信息。

 

同樣的,對抗攻擊在輸入圖像中嵌入一條信息,但是這條信息並非是人們可以理解的純文本信息,因爲對抗攻擊所嵌入到輸入圖像的是一個噪聲向量。這個噪聲向量是用於戲弄或混淆深度學習模型而故意構建的。

 

對抗攻擊是如何起作用的?我們應該如何去進行防禦呢?

 

在這篇教程中,以及這個系列的其他推文中,我們將會準確地去回答這些問題。

 

想要了解如何用Keras/TensorFlow在對抗攻擊和圖像中來破壞深度學習模型,請繼續往下讀。

 

利用Keras和TensorFlow來實現對抗圖像和攻擊

在這篇教程中的第一部分,會討論下對抗攻擊是什麼以及它們是如何影響深度學習模型的。

在那之後,我們會實現三段獨立的Python腳本:

  1. 第一段Python腳本是加載ImageNet數據集並解析類別標籤的使用助手。

  2. 第二段Python腳本是利用在ImageNet數據集上預訓練好的ResNet模型來實現基本的圖像分類(由此來演示“標準”的圖像分類)。

  3. 最後一段Python腳本用於執行一次對抗攻擊,並且組成一張故意混淆我們的ResNet模型的對抗圖像,而這兩張圖像對於肉眼來說看上去是一樣的。

讓我們開始吧!

 

什麼是對抗對象和對抗攻擊呢?它們是如何影響深度學習模型的呢?

圖二:當執行對抗攻擊時,給予神經網絡一張圖像(左),然後利用梯度下降來構建一個噪聲向量(中)。這個噪聲向量被加入到輸入圖像中,生成一個誤分類(右)。

 

在2014年,古德費洛等人發表了一篇名爲《Explaining and HarnessingAdversarial Examples(解釋並馴服對抗樣本)》的論文,展示了深度神經網絡吸引人的屬性——有可能故意擾亂一張輸入圖像,導致神經網絡對其進行錯誤的分類。這種擾亂就被稱爲對抗攻擊。

 

經典的對抗攻擊示例就像上面圖二所展示的一樣。在左邊,我們的輸入圖像在神經網絡中被分在“熊貓”類別,置信度爲57.7%。

 

在中間有一個噪音向量,在人類眼中看上去是隨機的,但事實上絕非如此。

 

恰恰相反,這一噪聲向量中的像素“等於輸入圖像的損失函數的梯度元素符號”(Goodfellow et al.)。

 

把這個噪聲向量嵌入到輸入圖像中,產出了圖二中的輸出(右)。對於我們來說,這副新圖像看起來與輸入圖像完全一樣,但我們的神經網絡卻會將這站圖像分到“長臂猿”類別,置信度高達99.7%。很奇怪吧?

  

一段對抗攻擊和對抗圖像的簡短歷史

圖三:一個關於對抗機器學習和深度神經網絡安全性出版物的時間軸(圖片來源:CanMachine Learning Be Secure?圖8)

 

對抗機器學習並不是一個新興領域,這些攻擊也不是針對深度神經網絡。在2006年,巴雷諾等人發表了一篇名爲《CanMachine Learning Be Secure?》的論文。論文討論了對抗攻擊,包括提出了一些對抗攻擊的防禦方式。

 

回到2006年,最先進的機器學習模型包括支持向量機(SVMs)和隨機森林(RFs),這兩種類型的模型均易受對抗攻擊的影響。

 

隨着2012年深度神經網絡的普及度開始升高,曾寄希望於這些非線性的模型不會輕易受到這種攻擊的影響,然而古德費洛等人打破了這一幻想。

 

他們發現深度神經網絡和“前輩們”一樣,都很容易受到對抗攻擊的影響。

 

想要了解更多關於對抗攻擊的歷史,我建議你們去看下比格奧(Biggio)和羅利(Roli)在2017年發表的論文《Wild Patterns: Ten Years Afterthe Rise of Adversarial Machine Learning.》

 

爲什麼對抗攻擊和對抗圖像會成爲一個麻煩呢?

圖四:爲什麼對抗攻擊會是一種麻煩?爲什麼我們應該關注它?(圖片來源imagesource)

 

本篇教程頂部所談到的示例中概述了爲什麼對抗攻擊會給我們的健康、生活和財產帶來嚴重損失。

 

另外一些後果沒有那麼嚴重的示例,例如一羣黑客識別了一個谷歌用於給Gmail過濾垃圾郵件的模型,或者識別了一個Facebook自動檢測色情內容的NSFW(不適合上班時間瀏覽)過濾器模型。

 

如果這些黑客想要繞過Gmail垃圾郵件過濾器來讓用戶受到資源過載攻擊,或者繞過Facebook的NSFW過濾器來上傳大量的色情內容,理論上講他們是可以做到的。

 

這些都是對抗攻擊中造成輕微後果的示例。

 

關於到對抗攻擊帶有嚴重後果的劇本可以包括黑客恐怖分子識別了全世界自動駕駛汽車所使用的深度神經網絡(試想一下如果特斯拉壟斷了市場,成爲世界唯一自動駕駛汽車生產商)。

 

對抗圖片有可能被有戰略性地擺放在車道或公路上,造成連環車禍、財產損失或車內乘客的受傷甚至死亡。

 

只有你的想象力,對已知模型的瞭解程度以及對模型的使用程度是對抗攻擊的天花板。

 

我們能夠抵禦對抗攻擊嗎?

好消息是我們可以降低對抗攻擊所造成的影響(但無法將它們完全消除)。

 

這方面的話題將不會在今天的教程中展開,可能會在未來的教程中進行討論。

 

配置你的開發環境

在這篇教程中配置你的系統,我建議你跟隨這兩篇教程:

  • 如何在Ubuntu系統下配置TensorFlow2.0 ?(How to installTensorFlow 2.0 on Ubuntu)

  • 如何在macOS系統下配置TensorFlow2.0 ?How to install TensorFlow 2.0 on macOS

這兩篇教程都會協助你在便捷的Python虛擬環境中利用必要的軟件來配置系統。

項目結構:

$ tree --dirsfirst
.
├── pyimagesearch
│ ├── __init__.py
│ ├── imagenet_class_index.json
│ └── utils.py
├── adversarial.png
├── generate_basic_adversary.py
├── pig.jpg
└── predict_normal.py
1 directory, 7 files

 

在pyimagesearch模塊中,有兩個文件:

  1. imagenet_class_index.json: 一個JSON文件,將ImageNet類別標籤標記爲可讀的字符串。我們將會利用這個JSON文件來決定這樣一組特殊標籤的整數值索引,在構建對抗圖像攻擊時,這個索引將會給予我們幫助。

  2. utils.py: 包含簡單的Python輔助函數,用於載入和解析imagenet_class_index.json

 

這裏面還有兩個我們今天需要檢驗的Python腳本:

  1. predict_normal.py: 接收一張輸入圖像(pig.jpg),載入ResNet50模型,對輸入圖像進行分類。這個腳本的輸出會是預測類別標籤在ImageNet的類別標籤索引。

  2. generate_basic_adversary.py:利用predict_normal.py腳本中的輸出,我們將構建一次對抗攻擊來欺騙ResNet,這個腳本的輸出(adversarial.png)將會存儲在硬盤中。

 

做好準備來實現你的第一個Keras和TensorFlow中的對抗攻擊了嗎?讓我們開始吧。

 

ImageNet 類別標籤/索引幫助實用程序

在我們開始執行正常的圖像分類或者對抗攻擊使用的混淆圖像分類之前,首先需要創建一個Python輔助函數來載入和解析ImageNet數據集的類別標籤。

 

我們提供了一個JSON文件包含所有的類別標籤索引、標識符和可讀的字符串,全都包含在項目目錄結構中pyimagesearch模塊的imagenet_class_index.json文件裏。

 

在這裏展示JSON文件的前幾行:

{
"0": [
"n01440764",
"tench"
],
"1": [
"n01443537",
"goldfish"
],
"2": [
"n01484850",
"great_white_shark"
],
"3": [
"n01491361",
"tiger_shark"
],
...
"106": [
"n01883070",
"wombat"
],
...

 

在這裏可以看到這個文件是一個字典格式的。字典的關鍵字是類別標籤的整數值索引,而值由二元元組組成:

  1. ImageNet標籤的唯一標識符;

  2. 有可讀性的類別標籤。

 

我們的目標是實現一個Python函數可以解析JSON文件:

  1. 接收一個輸入標籤;

  2. 轉化成其對應標籤的類別標籤整數值索引。

 

實際上,我們就是轉換imagenet_class_index.json文件中的鍵與值(key/value)的關係。

 

讓我們來實現這個輔助函數吧。

 

打開pyimagesearch模塊的utils.py文件,插入下列代碼:

# importnecessary packages
import json
import os
defget_class_idx(label):
# build the path to theImageNet class label mappings file
labelPath = os.path.join(os.path.dirname(__file__),
"imagenet_class_index.json")

 

第2行和第3行引入我們所需要的Python包。我們要用到json Python模塊來載入JSON文件,而os包用於構建文件路徑,這與你使用的是什麼操作系統並無關係。

 

接下來我們去定義get_class_idx輔助函數。這個函數的目標是接收一個輸入類別標籤,然後獲得預測(即在ImageNet上所訓練出的模型在1000個標籤類別中做出的預測)的標籤類別的整數值索引。

 

第7行是組成能夠載入 pyimagesearch模塊中的imagenet_class_index.json的路徑。

 

現在載入JSON文件中的內容:

# open theImageNet class mappings file and load the mappings as
# a dictionary with the human-readable class label as the keyand
# the integerindex as the value
withopen(labelPath)as f:
imageNetClasses = {labels[1]: int(idx)for(idx, labels)in
json.load(f).items()}
# check to see if the inputclass label has a corresponding
# integer index value, and if so return it; otherwise return
# a None-type value
return imageNetClasses.get(label, None)

 

第4-6行,打開labelPath文件,然後將鍵值對的關係進行轉換,這樣鍵纔是可讀的標籤字符串,而值是那個標籤的整數值索引。

 

爲了獲得輸入標籤的整數值索引,可以調用imageNetClasses字典中的.get方法(最後一行),會返回:

  • 如果在字典中存在改標籤的話,則返回該標籤的整數值索引;

  • 否則返回None。

 

這個值返回到調用函數。

 

讓我們在下一章節來構建get_class_idx輔助函數。

 

利用Keras和TensorFlow在沒有對抗攻擊的情況下進行圖片分類

在實現了ImageNet 類標籤/索引輔助函數後,讓我們構造一個圖像分類腳本,在沒有對抗攻擊的情況下實現基本分類的圖像分類腳本。

 

這個腳本可以證明我們的ResNet模型表現正常(做出正確地預測)。在這篇教程的後半部分,你們將會發現如何構建一張對抗圖像用於混淆ResNet。

 

讓我們開始基本的圖像分類腳本——在你的工程文件結構中打開predict_normal.py文件,並插入以下代碼:

# import necessarypackages
from pyimagesearch.utils import get_class_idx
from tensorflow.keras.applications import ResNet50
from tensorflow.keras.applications.resnet50 import decode_predictions
from tensorflow.keras.applications.resnet50 import preprocess_input
import numpy as np
import argparse
import imutils
import cv2

 

在第2-9行,我們引入了所需的Python包。如果你之前使用過Keras,TensorFlow和OpenCV的話,這些對你來說都是常規操作。

 

如果你是Keras和TensorFlow的新手,我強烈建議你去看一下我的這篇《KerasTutorial: How to get started with Keras, Deep Learning, and Python》教程。另外,你或許想要讀一讀我的書《DeepLearning for Computer Vision with Python》加深你對訓練自定義神經網絡的理解。

 

在第2行,我們引入了在上一章節中定義的get_class_idx函數,這個函數可以獲得ResNet50模型中最高預測標籤的整數索引值。

 

讓我們定義下 preprocess_image輔助函數:

defpreprocess_image(image):
# swap color channels,preprocess the image, and add in a batch
# dimension
image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
image = preprocess_input(image)
image = cv2.resize(image, (224, 224))
image = np.expand_dims(image, axis=0)
# return the preprocessed image
return image

 

preprocess_image 方法接收一個單一的必需參數,那就是我們想要預處理的圖片。

 

對這張圖片進行預處理有以下幾步:

  1. 將圖片的BGR通道組合轉化爲RGB;

  2. 執行preprocess_input函數,用於完成ResNet50中特別的預處理和比例縮放過程;

  3. 將圖片大小調整爲224×224;

  4. 增加一個批次維度。

 

這張預處理好的圖像會被返回到調用函數中。

 

接下來,讓我們解析下指令參數:

# construct the argument parser and parsethe arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image",required=True,
help="pathto input image")
args = vars(ap.parse_args())

 

我們在這裏只需要一個命令行參數,--image,即輸入圖像存放在硬盤上的路徑。

 

如果你從來沒有處理過命令行參數和argparse ,我建議你看一下這篇教程。

 

接下來讓載入輸入圖像並進行預處理:

# load image fromdisk and make a clone for annotation
print("[INFO] loadingimage...")
image = cv2.imread(args["image"])
output = image.copy()
# preprocess the input image
output = imutils.resize(output, width=400)
preprocessedImage = preprocess_image(image)

 

通過調用cv2.imread來載入輸入圖片。在第4行對這張圖片進行了複製,以便後期在輸出結果上面畫框並標註上預測結果類別標籤。

 

我們調整下輸出圖片的尺寸,讓它的寬變爲400像素,這樣可以適配我們的電腦屏幕。在這裏同樣使用preprocess_image函數將其處理爲可用於ResNet進行分類的輸入圖片。

 

加上我們預處理好的圖片,接着載入ResNet並對圖片進行分類:

# load thepre-trained ResNet50 model
print("[INFO] loadingpre-trained ResNet50 model...")
model = ResNet50(weights="imagenet")
# makepredictions on the input image and parse the top-3 predictions
print("[INFO] makingpredictions...")
predictions =model.predict(preprocessedImage)
predictions = decode_predictions(predictions, top=3)[0]

 

第3行,載入ResNet和該模型用ImageNet數據集預訓練好的權重。

 

第6和7行,針對預處理好的圖片進行預測,然後再用 Keras/TensorFlow 中的decode_predictions輔助函數對圖片進行解碼。

 

現在讓我們看看神經網絡預測的Top 3 (置信度前三)類別以及所展示的類別標籤:

# loop over thetop three predictions
for(i, (imagenetID, label,prob))inenumerate(predictions):
# print the ImageNet class label ID of the top prediction to our
# terminal (we'll need thislabel for our next script which will
# perform the actual adversarial attack)
if i == 0:
print("[INFO] {} => {}".format(label, get_class_idx(label)))
# display the prediction to our screen
print("[INFO] {}.{}: {:.2f}%".format(i + 1, label, prob * 100))

 

第2行開始循環Top-3預測結果。

 

如果這是第一個預測結果(即Top-1預測結果),在輸出終端顯示可讀的標籤,然後利用 get_class_idx函數找出該標籤對應的ImageNet的整數值索引。

 

還可以在終端上展示Top-3的標籤和對應的概率值。

 

最終一步就是將Top-1預測結果標註在輸出圖片中:

# draw thetop-most predicted label on the image along with the
# confidence score
text = "{}:{:.2f}%".format(predictions[0][1],
predictions[0][2] * 100)
cv2.putText(output, text, (3, 20),cv2.FONT_HERSHEY_SIMPLEX, 0.8,
(0, 255, 0), 2)
# show the output image
cv2.imshow("Output", output)
cv2.waitKey(0)

 

輸出圖像在終端顯示,如果你單擊OpenCV窗口或按下任意鍵,輸出圖像將會關閉。

 

非對抗圖像的分類結果

現在可以用ResNet來執行基本的圖像分類(非對抗攻擊)了。

 

首先在“下載“頁獲取源代碼和圖像範例。

 

從這開始,打開一個終端並執行以下命令:

$ pythonpredict_normal.py --image pig.jpg
[INFO] loading image...
[INFO] loadingpre-trained ResNet50 model...
[INFO] making predictions...
[INFO] hog => 341
[INFO] 1. hog: 99.97%
[INFO] 2.wild_boar: 0.03%
[INFO] 3. piggy_bank: 0.00%

圖五:預訓練好的ResNet模型可以正確地將這張圖片分類爲“豬(hog)”。

 

在這裏你可以看到我們將一張豬的圖片進行了分類,置信度爲99.97%。

 

另外,這裏還加上了hog標籤的ID(341)。我們將會在下一章用到這個標籤ID,我們會針對這張豬的輸入圖片進行一次對抗攻擊。

 

在Keras和TensorFlow上實現對抗圖像和對抗攻擊

接下來就要學習如何在Keras和TensorFlow上實現對抗圖像和對抗攻擊。

 

打開generate_basic_adversary.py文件,插入以下代碼:

# import necessary packages
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.applications import ResNet50
from tensorflow.keras.losses importSparseCategoricalCrossentropy
from tensorflow.keras.applications.resnet50 import decode_predictions
from tensorflow.keras.applications.resnet50 import preprocess_input
import tensorflow as tf
import numpy as np
import argparse
import cv2

 

在第2-10行中引入我們所需的Python包。你會注意到我們再次用到了ResNet50 架構,以及對應的preprocess_input函數(用於預處理/縮放輸入圖像)和 decode_predictions用於解碼預測輸出和顯示可讀的ImageNet標籤。

 

SparseCategoricalCrossentropy 用於計算標籤和預測值之間的分類交叉熵損失。利用稀疏版本的分類交叉熵,我們不需要像使用scikit-learn的LabelBinarizer或者Keras/TensorFlow的to_categorical功能一樣用one-hot對類標籤編碼。

 

例如在predict_normal.py腳本中有preprocess_image 功能,我們在這個腳本上同樣需要:

defpreprocess_image(image):
# swap color channels, resizethe input image, and add a batch
# dimension
image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
image = cv2.resize(image, (224, 224))
image = np.expand_dims(image, axis=0)
# return the preprocessedimage
return image

 

除了省略了調用preprocess_input函數,這一段代碼與上一段代碼相同,當我們開始創建對抗圖像時,你們馬上就會明白爲什麼省去調用這一函數。

 

接下來,我們有一個簡單的輔助程序,clip_eps:

defclip_eps(tensor, eps):
# clip the values of thetensor to a given range and return it
return tf.clip_by_value(tensor,clip_value_min=-eps,
clip_value_max=eps)

 

這個函數的目的就是接受一個輸入張量tensor,然後在範圍值 [-eps, eps]內對輸入進行截取。

 

被截取後的tensor會被返回到調用函數。

 

接下來看看generate_adversaries 函數,這是對抗攻擊的靈魂:

defgenerate_adversaries(model, baseImage,delta, classIdx, steps=50):
# iterate over the number ofsteps
for step inrange(0, steps):
# record our gradients
with tf.GradientTape()as tape:
# explicitly indicate thatour perturbation vector should
# be tracked for gradient updates
tape.watch(delta)

 

generate_adversaries 方法是整個腳本的核心。這個函數接收四個必需的參數,以及第五個可選參數:

  1. model:ResNet50模型(如果你願意,你可以換成其他預訓練好的模型,例如VGG16,MobileNet等等);

  2. baseImage:原本沒有被幹擾的輸入圖像,我們有意針對這張圖像創建對抗攻擊,導致model參數對它進行錯誤的分類。

  3. delta:噪聲向量,將會被加入到baseImage中,最終導致錯誤分類。我們將會用梯度下降均值來更新這個delta 向量。

  4. classIdx:通過predict_normal.py腳本所獲得的類別標籤整數值索引。

  5. steps:梯度下降執行的步數(默認爲50步)。

 

第3行開始循環設定好的步數。

 

接下來用GradientTape來記錄梯度。在tape上調用 .watch方法指出擾動向量是可以用來追蹤更新的。

 

現在可以建造對抗圖像了:

# add our perturbation vector to the base image and
# preprocess the resulting image
adversary = preprocess_input(baseImage + delta)
# run this newly constructed image tensor through our
# model and calculate theloss with respect to the
# *original* class index
predictions = model(adversary,training=False)
loss = -sccLoss(tf.convert_to_tensor([classIdx]),
predictions)
# check to see if we arelogging the loss value, and if
# so, display it to our terminal
if step % 5 == 0:
print("step: {},loss: {}...".format(step,
loss.numpy()))
# calculate the gradients ofloss with respect to the
# perturbation vector
gradients = tape.gradient(loss, delta)
# update the weights, clipthe perturbation vector, and
# update its value
optimizer.apply_gradients([(gradients, delta)])
delta.assign_add(clip_eps(delta, eps=EPS))
# return the perturbationvector
return delta

 

第3行將delta擾動向量加入至baseImage的方式來組建對抗圖片,所得到的結果將放入ResNet50的preprocess_input函數中來進行比例縮放和結果對抗圖像進行歸一化。

 

接下來幾行的意義是:

  • 第7行用model參數導入的模型對新創建的對抗圖像進行預測。

  • 第8和9行鍼對原有的classIdx(通過運行predict_normal.py得到的top-1 ImageNet類別標籤整數值索引)計算損失。

  • 第12-14行表示每5步就顯示一次損失值。

 

第17行,在with聲明外根據擾動向量計算梯度損失。

 

接着,可以更新delta向量,截取掉超出 [-EPS,EPS] 範圍外的值。

 

最終,把得到的擾動向量返回至調用函數——即最終的delta值,該值能讓我們建立用來欺騙模型的對抗攻擊。

 

在對抗腳本的核心實現後,接下來就是解析命令行參數:

# construct the argumentparser and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--input", required=True,
help="path tooriginal input image")
ap.add_argument("-o", "--output", required=True,
help="path tooutput adversarial image")
ap.add_argument("-c", "--class-idx", type=int,required=True,
help="ImageNetclass ID of the predicted label")
args = vars(ap.parse_args())

 

我們的對抗攻擊Python腳本需要三個指令行參數:

  1. --input: 輸入圖像的磁盤路徑(例如pig.jpg);

  2. --output: 在構建進攻後的對抗圖像輸出(例如adversarial.png);

  3. --class-idex:ImageNet數據集中的類別標籤整數值索引。我們可以通過執行在“非對抗圖像的分類結果”章節中提到的predict_normal.py來獲得這一索引。

 

接下來是幾個變量的初始化,以及加載/預處理--input圖像:

# define theepsilon and learning rate constants
EPS = 2 / 255.0
LR = 0.1
# load the inputimage from disk and preprocess it
print("[INFO] loadingimage...")
image = cv2.imread(args["input"])
image = preprocess_image(image)

 

第2行定義了用於在構建對抗圖像時來裁剪tensor的epsilon值(EPS)。2 / 255.0 是EPS的一個用於對抗類刊物或教程的標準值和指導值(如果你想要了解更多的默認值,你可以參考這份指導)。

 

在第3行定義了學習速率。經驗之談,LR的初始值一般設爲0.1,在創建你自己的對抗圖像時可能需要調整這個值。

 

最後兩行載入輸入圖像,利用preprocess_image輔助函數來對其進行預處理。

 

接下來,可以載入ResNet模型:

# load thepre-trained  ResNet50 model for running inference
print("[INFO] loadingpre-trained ResNet50 model...")
model = ResNet50(weights="imagenet")
# initializeoptimizer and loss function
optimizer = Adam(learning_rate=LR)
sccLoss = SparseCategoricalCrossentropy()

 

第3行載入在ImageNet數據集上訓練好的ResNet50模型。

 

我們將會用到Adam優化器,以及稀疏的分類損失用於更新我們的擾動向量。

 

讓我們來構建對抗圖像:

# create a tensorbased off the input image and initialize the
# perturbation vector (we will update this vector via training)
baseImage = tf.constant(image,dtype=tf.float32)
delta = tf.Variable(tf.zeros_like(baseImage), trainable=True)
# generate the perturbation vector to create an adversarialexample
print("[INFO]generating perturbation...")
deltaUpdated = generate_adversaries(model, baseImage,delta,
args["class_idx"])
# create theadversarial example, swap color channels, and save the
# output image to disk
print("[INFO]creating adversarial example...")
adverImage = (baseImage +deltaUpdated).numpy().squeeze()
adverImage = np.clip(adverImage, 0, 255).astype("uint8")
adverImage = cv2.cvtColor(adverImage,cv2.COLOR_RGB2BGR)
cv2.imwrite(args["output"], adverImage)

 

第3行根據輸入圖像構建了一個tensor,第4行初始化擾動向量delta。

 

我們可以把ResNet50、輸入圖像、初始化後的擾動向量、及類標籤整數值索引作爲參數,用來調用generate_adversaries並更新delta向量。

 

在 generate_adversaries函數執行時,會一直更新delta擾動向量,生成最終的噪聲向量deltaUpdated。

 

在倒數第4行,在baseImage上加入deltaUpdated 向量,就生成了最終的對抗圖像(adverImage)。

 

然後,對生成的對抗圖像進行以下三步後處理:

  1. 將超出[0,255] 範圍的值裁剪掉;

  2. 將圖片轉化成一個無符號8-bit(unsigned 8-bit)整數(這樣OpenCV才能對圖片進行處理);

  3. 將通道順序從RGB轉換成BGR。

 

在經過這些處理步驟後,就可以把對抗圖像寫入到硬盤裏了。

 

真正的問題來了,我們新創建的對抗圖像能夠欺騙我們的ResNet模型嗎?

 

下一段代碼就可以回答這一問題:

# run inferencewith this adversarial example, parse the results,
# and display the top-1 predicted result
print("[INFO]running inference on the adversarial example...")
preprocessedImage = preprocess_input(baseImage +deltaUpdated)
predictions =model.predict(preprocessedImage)
predictions = decode_predictions(predictions, top=3)[0]
label = predictions[0][1]
confidence = predictions[0][2] * 100
print("[INFO] label:{} confidence: {:.2f}%".format(label,
confidence))
# draw the top-most predicted label on the adversarial imagealong
# with theconfidence score
text = "{}: {:.2f}%".format(label, confidence)
cv2.putText(adverImage, text, (3, 20),cv2.FONT_HERSHEY_SIMPLEX, 0.5,
(0, 255, 0), 2)
# show the output image
cv2.imshow("Output", adverImage)
cv2.waitKey(0)

 

在第4行又一次創建了一個對抗圖像,方式還是在原始輸入圖像中加入delta噪聲向量,但這次我們利用ResNet的preprocess_input功能來處理。

 

生成的預處理圖像進入到ResNet,然後會得到top-3預測結果並對他們進行解碼(第5和6行)。

 

接着我們獲取到top-1標籤和對應的概率/置信度,並將這些值顯示在終端上(第7-10行)。

 

最後一步就是把最高的預測值標在輸出的對抗圖像上,並展示在屏幕上。

對抗圖像和攻擊的結果

準備好見證一次對抗攻擊了嗎?

 

從這裏開始,你就可以打開終端並執行下列代碼了:

$ python generate_basic_adversary.py --inputpig.jpg --output adversarial.png --class-idx 341
[INFO] loading image...
[INFO] loading pre-trained ResNet50 model...
[INFO] generatingperturbation...
step: 0, loss:-0.0004124982515349984...
step: 5, loss:-0.0010656398953869939...
step: 10, loss:-0.005332294851541519...
step: 15, loss: -0.06327803432941437...
step: 20, loss: -0.7707189321517944...
step: 25, loss: -3.4659299850463867...
step: 30, loss: -7.515471935272217...
step: 35, loss: -13.503922462463379...
step: 40, loss: -16.118188858032227...
step: 45, loss: -16.118192672729492...
[INFO] creating adversarial example...
[INFO] running inference on theadversarial example...
[INFO] label: wombat confidence: 100.00%

圖六:之前,這張輸入圖片被正確地分在了“豬(hog)”類別中,但現在因爲對抗攻擊而被分在了“袋熊(wombat)”類別裏!

 

我們的輸入圖片 pig.jpg 之前被正確地分在了“豬(hog)”類別中,但現在它的標籤卻成爲了“袋熊(wombat)”!

 

將原始圖片和用generate_basic_adversary.py腳本生成的對抗圖片放在一起進行對比:

圖七:在左邊是原始圖片,分類結果正確。右邊將是對抗圖片,被錯誤地分在了“袋熊(wombat)”類別中。而對於人類的眼睛來看完全分辨不出兩張圖片的有什麼區別。

 

左邊是最初豬的圖像,在右邊是輸出的對抗圖像,這張圖像被錯誤的分在了“袋熊(wombat)”類別。

 

就像你看到的一樣,這兩張圖片沒有任何可感知的差別,我們人類的眼睛看不出任何區別,但對於ResNet來說確實完全不同的。

 

這很好,但我們無法清晰地掌控對抗圖像被最終識別的類別標籤。這會引起以下問題:

我們有可能掌控輸入圖片的最終類別標籤嗎?答案是肯定的,這會成爲我下一篇教程的主題。

 

總結來說,對抗圖像和對抗攻擊真的是令人細思極恐。但如果等我們看到下一篇教程,就可以提前防禦這種類型的進攻。稍後再詳細說明下。

致謝

如果沒有Goodfellow, Szegedy和其他深度學習的研究者的工作,這篇教程就無法完成。

 

另外,這篇教程所用到的實現代碼靈感來自於TensorFlow官方實現的《Fast Gradient Signed Method》。我強烈建議你去看看其他的示例,每一段代碼都比這篇教程中的在理論和數學上更加明確。

 

總結

在這篇教程中,你學到關於對抗攻擊的知識,關於他們是怎樣工作的,以及隨着人工智能和深度神經網絡與這個世界的關聯越來越高,對抗攻擊就會構成更大的威脅。

接着我們利用深度學習庫Keras 和TensorFlow實現了基本的對抗攻擊算法。

利用對抗攻擊,我們可以蓄意擾亂一張輸入圖片,例如:

  1. 這張輸入圖片會被錯誤分類。

  2. 然而,肉眼看上去被擾亂的圖片還是和之前一樣。

利用這篇教程所使用的方法,我們並不能控制圖片最終被判別的類別標籤——我們所做的只是創造一個噪聲向量,並將其嵌入到輸入圖像中,導致深度神經網絡對其錯誤分類。

如果我們能夠控制最終的類別標籤會怎樣呢?比如說,我們拿一張“狗”的圖片,然後製造一次對抗攻擊,讓卷積神經網絡認爲這是一張“貓”的圖片,這有沒有可能呢?

答案是肯定的——我們會在下一篇教程中來談論這一話題。

原文鏈接:

https://www.pyimagesearch.com/2020/10/19/adversarial-images-and-attacks-with-keras-and-tensorflow/

原文標題:Adversarial images andattacks with Keras and TensorFlow

譯者簡介:吳振東,法國洛林大學計算機與決策專業碩士。現從事人工智能和大數據相關工作,以成爲數據科學家爲終生奮鬥目標。來自山東濟南,不會開挖掘機,但寫得了Java、Python和PPT。

END

版權聲明:本號內容部分來自互聯網,轉載請註明原文鏈接和作者,如有侵權或出處有誤請和我們聯繫。


合作請加QQ:365242293  

數據分析(ID : ecshujufenxi )互聯網科技與數據圈自己的微信,也是WeMedia自媒體聯盟成員之一,WeMedia聯盟覆蓋5000萬人羣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章