對(duì)抗圖像和攻擊在Keras和TensorFlow上的實(shí)現(xiàn)
點(diǎn)擊上方“小白學(xué)視覺”,選擇加"星標(biāo)"或“置頂”
重磅干貨,第一時(shí)間送達(dá)
本文轉(zhuǎn)自:計(jì)算機(jī)視覺聯(lián)盟


第一段Python腳本是加載ImageNet數(shù)據(jù)集并解析類別標(biāo)簽的使用助手。 第二段Python腳本是利用在ImageNet數(shù)據(jù)集上預(yù)訓(xùn)練好的ResNet模型來實(shí)現(xiàn)基本的圖像分類(由此來演示“標(biāo)準(zhǔn)”的圖像分類)。 最后一段Python腳本用于執(zhí)行一次對(duì)抗攻擊,并且組成一張故意混淆我們的ResNet模型的對(duì)抗圖像,而這兩張圖像對(duì)于肉眼來說看上去是一樣的。



如何在Ubuntu系統(tǒng)下配置TensorFlow2.0 ?(How to installTensorFlow 2.0 on Ubuntu) 如何在macOS系統(tǒng)下配置TensorFlow2.0 ?How to install TensorFlow 2.0 on macOS
tree --dirsfirst.├── pyimagesearch│ ├── __init__.py│ ├── imagenet_class_index.json│ └── utils.py├── adversarial.png├── generate_basic_adversary.py├── pig.jpg└── predict_normal.py1 directory, 7 files
imagenet_class_index.json: 一個(gè)JSON文件,將ImageNet類別標(biāo)簽標(biāo)記為可讀的字符串。我們將會(huì)利用這個(gè)JSON文件來決定這樣一組特殊標(biāo)簽的整數(shù)值索引,在構(gòu)建對(duì)抗圖像攻擊時(shí),這個(gè)索引將會(huì)給予我們幫助。 utils.py: 包含簡(jiǎn)單的Python輔助函數(shù),用于載入和解析imagenet_class_index.json
predict_normal.py: 接收一張輸入圖像(pig.jpg),載入ResNet50模型,對(duì)輸入圖像進(jìn)行分類。這個(gè)腳本的輸出會(huì)是預(yù)測(cè)類別標(biāo)簽在ImageNet的類別標(biāo)簽索引。 generate_basic_adversary.py:利用predict_normal.py腳本中的輸出,我們將構(gòu)建一次對(duì)抗攻擊來欺騙ResNet,這個(gè)腳本的輸出(adversarial.png)將會(huì)存儲(chǔ)在硬盤中。
{"0": ["n01440764","tench"],"1": ["n01443537","goldfish"],"2": ["n01484850","great_white_shark"],"3": ["n01491361","tiger_shark"],..."106": ["n01883070","wombat"],...
ImageNet標(biāo)簽的唯一標(biāo)識(shí)符; 有可讀性的類別標(biāo)簽。
接收一個(gè)輸入標(biāo)簽; 轉(zhuǎn)化成其對(duì)應(yīng)標(biāo)簽的類別標(biāo)簽整數(shù)值索引。
# importnecessary packagesimport jsonimport osdefget_class_idx(label):# build the path to theImageNet class label mappings filelabelPath = os.path.join(os.path.dirname(__file__),"imagenet_class_index.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 valuewithopen(labelPath)as f:imageNetClasses = {labels[1]: int(idx)for(idx, labels)injson.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 valuereturn imageNetClasses.get(label, None)
如果在字典中存在改標(biāo)簽的話,則返回該標(biāo)簽的整數(shù)值索引; 否則返回None。
# import necessarypackagesfrom pyimagesearch.utils import get_class_idxfrom tensorflow.keras.applications import ResNet50from tensorflow.keras.applications.resnet50 import decode_predictionsfrom tensorflow.keras.applications.resnet50 import preprocess_inputimport numpy as npimport argparseimport imutilsimport cv2
defpreprocess_image(image):# swap color channels,preprocess the image, and add in a batch# dimensionimage = 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 imagereturn image
將圖片的BGR通道組合轉(zhuǎn)化為RGB; 執(zhí)行preprocess_input函數(shù),用于完成ResNet50中特別的預(yù)處理和比例縮放過程; 將圖片大小調(diào)整為224×224; 增加一個(gè)批次維度。
# construct the argument parser and parsethe argumentsap = argparse.ArgumentParser()ap.add_argument("-i", "--image",required=True,help="pathto input image")args = vars(ap.parse_args())
# load image fromdisk and make a clone for annotationprint("[INFO] loadingimage...")image = cv2.imread(args["image"])output = image.copy()# preprocess the input imageoutput = imutils.resize(output, width=400)preprocessedImage = preprocess_image(image)
# load thepre-trained ResNet50 modelprint("[INFO] loadingpre-trained ResNet50 model...")model = ResNet50(weights="imagenet")# makepredictions on the input image and parse the top-3 predictionsprint("[INFO] makingpredictions...")predictions =model.predict(preprocessedImage)predictions = decode_predictions(predictions, top=3)[0]
# loop over thetop three predictionsfor(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 screenprint("[INFO] {}.{}: {:.2f}%".format(i + 1, label, prob * 100))
# draw thetop-most predicted label on the image along with the# confidence scoretext = "{}:{:.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 imagecv2.imshow("Output", output)cv2.waitKey(0)
$ pythonpredict_normal.py --image pig.jpg[] loading image...[] loadingpre-trained ResNet50 model...[] making predictions...[] hog => 341[] 1. hog: 99.97%[] 2.wild_boar: 0.03%[] 3. piggy_bank: 0.00%

# import necessary packagesfrom tensorflow.keras.optimizers import Adamfrom tensorflow.keras.applications import ResNet50from tensorflow.keras.losses importSparseCategoricalCrossentropyfrom tensorflow.keras.applications.resnet50 import decode_predictionsfrom tensorflow.keras.applications.resnet50 import preprocess_inputimport tensorflow as tfimport numpy as npimport argparseimport cv2
defpreprocess_image(image):# swap color channels, resizethe input image, and add a batch# dimensionimage = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)image = cv2.resize(image, (224, 224))image = np.expand_dims(image, axis=0)# return the preprocessedimagereturn image
defclip_eps(tensor, eps):# clip the values of thetensor to a given range and return itreturn tf.clip_by_value(tensor,clip_value_min=-eps,clip_value_max=eps)
defgenerate_adversaries(model, baseImage,delta, classIdx, steps=50):# iterate over the number ofstepsfor step inrange(0, steps):# record our gradientswith tf.GradientTape()as tape:# explicitly indicate thatour perturbation vector should# be tracked for gradient updatestape.watch(delta)
model:ResNet50模型(如果你愿意,你可以換成其他預(yù)訓(xùn)練好的模型,例如VGG16,MobileNet等等); baseImage:原本沒有被干擾的輸入圖像,我們有意針對(duì)這張圖像創(chuàng)建對(duì)抗攻擊,導(dǎo)致model參數(shù)對(duì)它進(jìn)行錯(cuò)誤的分類。 delta:噪聲向量,將會(huì)被加入到baseImage中,最終導(dǎo)致錯(cuò)誤分類。我們將會(huì)用梯度下降均值來更新這個(gè)delta 向量。 classIdx:通過predict_normal.py腳本所獲得的類別標(biāo)簽整數(shù)值索引。 steps:梯度下降執(zhí)行的步數(shù)(默認(rèn)為50步)。
# add our perturbation vector to the base image and# preprocess the resulting imageadversary = preprocess_input(baseImage + delta)# run this newly constructed image tensor through our# model and calculate theloss with respect to the# *original* class indexpredictions = 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 terminalif step % 5 == 0:print("step: {},loss: {}...".format(step,loss.numpy()))# calculate the gradients ofloss with respect to the# perturbation vectorgradients = tape.gradient(loss, delta)# update the weights, clipthe perturbation vector, and# update its valueoptimizer.apply_gradients([(gradients, delta)])delta.assign_add(clip_eps(delta, eps=EPS))# return the perturbationvectorreturn delta
第7行用model參數(shù)導(dǎo)入的模型對(duì)新創(chuàng)建的對(duì)抗圖像進(jìn)行預(yù)測(cè)。 第8和9行針對(duì)原有的classIdx(通過運(yùn)行predict_normal.py得到的top-1 ImageNet類別標(biāo)簽整數(shù)值索引)計(jì)算損失。 第12-14行表示每5步就顯示一次損失值。
# construct the argumentparser and parse the argumentsap = 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())
--input: 輸入圖像的磁盤路徑(例如pig.jpg); --output: 在構(gòu)建進(jìn)攻后的對(duì)抗圖像輸出(例如adversarial.png); --class-idex:ImageNet數(shù)據(jù)集中的類別標(biāo)簽整數(shù)值索引。我們可以通過執(zhí)行在“非對(duì)抗圖像的分類結(jié)果”章節(jié)中提到的predict_normal.py來獲得這一索引。
# define theepsilon and learning rate constantsEPS = 2 / 255.0LR = 0.1# load the inputimage from disk and preprocess itprint("[INFO] loadingimage...")image = cv2.imread(args["input"])image = preprocess_image(image)
# load thepre-trained ResNet50 model for running inferenceprint("[INFO] loadingpre-trained ResNet50 model...")model = ResNet50(weights="imagenet")# initializeoptimizer and loss functionoptimizer = Adam(learning_rate=LR)sccLoss = SparseCategoricalCrossentropy()
# 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 adversarialexampleprint("[INFO]generating perturbation...")deltaUpdated = generate_adversaries(model, baseImage,delta,args["class_idx"])# create theadversarial example, swap color channels, and save the# output image to diskprint("[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)
將超出[0,255] 范圍的值裁剪掉; 將圖片轉(zhuǎn)化成一個(gè)無符號(hào)8-bit(unsigned 8-bit)整數(shù)(這樣OpenCV才能對(duì)圖片進(jìn)行處理); 將通道順序從RGB轉(zhuǎn)換成BGR。
# run inferencewith this adversarial example, parse the results,# and display the top-1 predicted resultprint("[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] * 100print("[INFO] label:{} confidence: {:.2f}%".format(label,confidence))# draw the top-most predicted label on the adversarial imagealong# with theconfidence scoretext = "{}: {:.2f}%".format(label, confidence)cv2.putText(adverImage, text, (3, 20),cv2.FONT_HERSHEY_SIMPLEX, 0.5,(0, 255, 0), 2)# show the output imagecv2.imshow("Output", adverImage)cv2.waitKey(0)
$ 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)”類別中,但現(xiàn)在因?yàn)閷?duì)抗攻擊而被分在了“袋熊(wombat)”類別里!

這張輸入圖片會(huì)被錯(cuò)誤分類。 然而,肉眼看上去被擾亂的圖片還是和之前一樣。
交流群
歡迎加入公眾號(hào)讀者群一起和同行交流,目前有SLAM、三維視覺、傳感器、自動(dòng)駕駛、計(jì)算攝影、檢測(cè)、分割、識(shí)別、醫(yī)學(xué)影像、GAN、算法競(jìng)賽等微信群(以后會(huì)逐漸細(xì)分),請(qǐng)掃描下面微信號(hào)加群,備注:”昵稱+學(xué)校/公司+研究方向“,例如:”張三 + 上海交大 + 視覺SLAM“。請(qǐng)按照格式備注,否則不予通過。添加成功后會(huì)根據(jù)研究方向邀請(qǐng)進(jìn)入相關(guān)微信群。請(qǐng)勿在群內(nèi)發(fā)送廣告,否則會(huì)請(qǐng)出群,謝謝理解~
評(píng)論
圖片
表情

