如何用 Caffe 生成對抗樣本?這篇文章告訴你一個更高效的演算法
AI 研習社按:在此前發布的文章《雜談CNN:如何通過優化求解輸入圖像》中我們曾提到對抗樣本的問題,本文作為其延續,將討論如何用Fast Gradient Sign方法在Caffe中生成對抗樣本。原文作者達聞西,載於知乎專欄,雷鋒網 AI 研習社經授權發布。
Fast Gradient Sign方法
先回顧一下《雜談CNN:如何通過優化求解輸入圖像》中通過加噪音生成對抗樣本的方法,出自Christian Szegedy的論文《Intriguing properties of neural networks》:
其中n是要求的噪音,α是相應的係數,L是x+n屬於某個類別的loss,c是某個錯誤類別的標籤。論文中用來得到圖像雜訊的辦法是L-BFGS,這個方法雖然穩定有效,但是很考驗算力的,Christian在Google反正機器多又強,用這個方法產生對抗樣本自然沒有問題,但如果不是土豪的話就不太合適了。針對這個問題,這篇文章的第六作者,生成式對抗網路的發明人Ian Goodfellow在《Explaining and Harnessing Adversarial Examples》中提出了一種更快速方便的方法來產生對抗樣本:
這種方法的思想非常簡單,就是讓輸入圖像朝著讓類別置信度降低的方向上移動一個在各個維度上都是ε這麼大小的一步。因為輸入通常是高維的(比如224x224),再加上現在的主流神經網路結構都是ReLU系的激活函數,線性程度其實很高,所以即使是很小的ε,每個維度的效果加一塊,通常也足以對結果產生很大的影響,比如下面這樣:
在計算上,這種方法優勢巨大,因為只需要一次前向和一次後向梯度計算就可以了。Ian Goodfellow稱之為FastGradientSign method。
用Caffe生成對抗樣本
FGS法因為非常簡單,用任何框架都很容易實現,Ian Goodfellow 有個作為完整工具包的官方實現,基於 TensorFlow,詳細鏈接:
http://t.cn/RKAXoUz
這裡給出Caffe的Python介面實現的例子。
首先需要準備要攻擊的模型,這裡我們用在ImageNet數據集上預訓練好的SqueezeNet v1.0作為例子:
http://t.cn/RKAXWrl
需要下載兩個文件就夠了:
http://t.cn/RKAXRQ7
http://t.cn/RKAX3RZ
因為需要進行後向計算,所以把deploy.prototxt下載後,第一件事是加入下面的一句:
force_backward: true
首先在Caffe中裝載準備好的模型定義和參數文件,並初始化讀取三通道彩色圖片的transformer:
# model to attack
model_definition = /path/to/deploy.prototxt
model_weights = /path/to/squeezenet_v1.0.caffemodel
channel_means = numpy.array([104., 117., 123.])
# initialize net
net = caffe.Net(model_definition, model_weights, caffe.TEST)
n_channels, height, width = net.blobs[ data ].shape[-3:]
net.blobs[ data ].reshape(1, n_channels, height, width)
# initialize transformer
transformer = caffe.io.Transformer({ data : net.blobs[ data ].data.shape})
transformer.set_transpose( data , (2, 0, 1))
transformer.set_mean( data , channel_means)
transformer.set_raw_scale( data , 255)
transformer.set_channel_swap( data , (2, 1, 0))
因為只是演示如何製作對抗樣本,為了方便,每次只處理一張圖片,接下來就是讀取圖片並進行前向計算類別置信度,和後向計算梯度,我們用下面的白色小土狗的照片作為輸入:
代碼如下:
# Load image & forward
transformed_img = transformer.preprocess( data , img)
net.blobs[ data ].data[0] = transformed_img
net.forward()
# Get predicted label index
pred = numpy.argmax(net.blobs[ prob ].data.flatten())
# Set gradient direction to reduce the current prediction
net.blobs[ prob ].diff[0][pred] = -1.
# Generate attack image with fast gradient sign method
diffs = net.backward()
diff_sign_mat = numpy.sign(diffs[ data ])
adversarial_noise = 1.0 * diff_sign_mat
這樣用於疊加在原始圖片上的對抗樣本雜訊就好了,在這個代碼中,我們執行的是生成一個對抗樣本降低當前模型預測類別的,其中每個像素在梯度方向上的前進幅度是1.0。如果要生成一個對抗樣本使模型預測圖片為一個指定的類別,則需要把給梯度賦值的語句改成下面這句:
net.blobs[prob_blob].diff[0][label_index]=1.
# clip exceeded values
attack_hwc = transformer.deprocess(data_blob, transformed_img + adversarial_noise[0])
attack_hwc[attack_hwc > 1] = 1.
attack_hwc[attack_hwc < 0] = 0.
attack_img = transformer.preprocess(data_blob, attack_hwc)
attack_img就是和Caffe的blob形狀一致的對抗樣本了,attack_hwc是維度按照圖片高度,圖片寬度,圖片通道順序的格式,可以用matplotlib直接可視化。
可視化和簡單分析
為了方便分析,我們把產生對抗樣本的過程打包到一個函數里:
def make_n_test_adversarial_example(
img, net, transformer, epsilon,
data_blob= data , prob_blob= prob ,
label_index=None, top_k=5):
# Load image & forward
transformed_img = transformer.preprocess(data_blob, img)
net.blobs[data_blob].data[0] = transformed_img
net.forward()
probs = [x for x in enumerate(net.blobs[prob_blob].data.flatten())]
num_classes = len(probs)
sorted_probs = sorted(probs, key=itemgetter(1), reverse=True)
top_preds = sorted_probs[:top_k]
pred = sorted_probs[0][0]
# if label_index is set,
# generate a adversarial example toward the label,
# else
# reduce the probability of predicted label
net.blobs[prob_blob].diff[...] = 0
if type(label_index) is int and 0
net.blobs[prob_blob].diff[0][label_index] = 1.
else:
net.blobs[prob_blob].diff[0][pred] = -1.
# generate attack image with fast gradient sign method
diffs = net.backward()
diff_sign_mat = numpy.sign(diffs[data_blob])
adversarial_noise = epsilon * diff_sign_mat
# clip exceeded values
attack_hwc = transformer.deprocess(data_blob, transformed_img + adversarial_noise[0])
attack_hwc[attack_hwc > 1] = 1.
attack_hwc[attack_hwc < 0] = 0.
attack_img = transformer.preprocess(data_blob, attack_hwc)
net.blobs[data_blob].data[0] = attack_img
net.forward()
probs = [x for x in enumerate(net.blobs[prob_blob].data.flatten())]
sorted_probs = sorted(probs, key=itemgetter(1), reverse=True)
top_attacked_preds = sorted_probs[:top_k]
return attack_hwc, top_preds, top_attacked_preds
上面函數的結果可以用下面函數可視化:
def visualize_attack(title, original_img, attack_img, original_preds, attacked_preds, labels):
pred = original_preds[0][0]
attacked_pred = attacked_preds[0][0]
k = len(original_preds)
fig_name = {}: {} to {} .format(title, labels[pred], labels[attacked_pred])
pyplot.figure(fig_name)
for img, plt0, plt1, preds in [
(original_img, 231, 234, original_preds),
(attack_img, 233, 236, attacked_preds)
]:
pyplot.subplot(plt0)
pyplot.axis( off )
pyplot.imshow(img)
ax = pyplot.subplot(plt1)
pyplot.axis( off )
ax.set_xlim([0, 2])
bars = ax.barh(range(k-1, -1, -1), [x[1] for x in preds])
for i, bar in enumerate(bars):
x_loc = bar.get_x() + bar.get_width()
y_loc = k - i - 1
label = labels[preds[i][0]]
ax.text(x_loc, y_loc, {}: {:.2f}% .format(label, preds[i][1]*100))
pyplot.subplot(232)
pyplot.axis( off )
noise = attack_img - original_img
pyplot.imshow(255 * noise)
這段代碼會同時顯示原始圖片及模型預測的類別和置信度,對抗樣本圖片及模型預測的類別和置信度,還有疊加在原始圖片上的雜訊。另外為了方便直觀理解,需要輸入每類別的名字,對於ImageNet的數據,可以下載Caffe自帶的synset_words.txt,然後把裡面的類別按順序讀取到一個列表裡即可,下面例子中我們假設這個列表就是labels。
萬事俱備,來看看效果,首先嘗試用一個幅度為1的雜訊降低模型預測的置信度:
attack_img, original_preds, attacked_preds =
make_n_test_adversarial_example(img, net, transformer, 1.0)
visualize_attack( example0 , img, attack_img, original_preds, attacked_preds, labels)
得到結果如下:
因為中華田園犬並不在ImageNet的類別里,所以模型預測的結果是大白熊犬(Great Pyrenees),考慮到小土狗的毛色和外形,這個結果合理,說明SqueezeNet v1.0還是不錯的。而經過了1個像素的噪音疊加後,模型預測結果變成了黃鼠狼(weasel)……
接下來試試生成讓模型預測為指定類別的對抗樣本,既然原始類別是大白熊犬,不妨試試直接預測為真的大白熊,也就是北極熊(ice bear):
attack_img, original_preds, attacked_preds =
make_n_test_adversarial_example(img, net, transformer, 1.0, label_index=296)
visualize_attack( example1 , img, attack_img, original_preds, attacked_preds, labels)
從結果來看還是很不錯的,而且是個非常高的置信度,不過黃鼠狼又排在了第二。無論是大白熊犬,北極熊還是黃鼠狼,都是哺乳動物,其實外形還是比較類似的,接下來試個難一點的,嘗試用幅度為1的雜訊把小白狗預測為鴕鳥(ostrich),代碼就是把上段代碼的label_index換掉,就不再貼了:
仍然是黃鼠狼,所以嘗試用更強的雜訊,把雜訊幅度設為2.0:
成功了,雖然置信度並不是很高,進一步提升雜訊幅度到6.0:
預測為鴕鳥的置信度大幅提升!那麼是不是雜訊幅度越大,預測為鴕鳥的置信度就越高呢,按照Ian的論文中的圖(Fig. 4)似乎是這樣的:
變成蛤蟆了……Ian的論文中一個主要論點是,在現在流行的深度網路中,對抗樣本存在的主因是因為模型的線性程度很高,佐證一個是上面出現過的論文中的fig. 4,還有就是對抗樣本在不同模型之間可以泛化。不過為什麼線性就是主因了?Ian似乎並沒有給出量化的,特別令人信服的證據。事實上原文的fig 4隻是在mnist上的一個圖示,稍微複雜些的數據上線性程度已經有所減弱,比如 Ian 自己為 kdnuggets 寫的文章 Deep Learning Adversarial Examples - Clarifying Misconceptions 中的配圖。文章詳情:
http://t.cn/RLVzahm
究其本質,對抗樣本的存在還是因為高維空間搜索是不可行的,在數據和模型無法觸及的角落,對抗樣本的出現是很自然的事情。雖然感覺上模型的線性程度,及相應的對輸入空間的劃分是對抗樣本存在的主因,但歸因於其他因素的對抗樣本也不是可以忽略的,比如小狗變蛤蟆的例子。畢竟神經網路作為universal approximator的根本是源於非線性。
利用迭代更好地生成對抗樣本
分類模型雖然沒有距離這個概念,但類別間在輸入空間上顯然還是相似的類別會更近一些,通過上部分的例子也可以看到,狗變成熊或者黃鼠狼相對容易一些,變成鴕鳥就難一點了,變成其他更不相似的比如球拍(Racket)就會更難。我們把鴕鳥對抗樣本的四個幅度(1.0, 2.0, 6.0, 18.0)也在生成球拍的對抗樣本上試試,結果如下。Racket 相關鏈接如下:
https://racket-lang.org/
經歷了黑足鼬(black-footed ferret)、黃鼠狼、丁鯛(tench),最後又變成了蛤蟆。說明線性大法對於這個和小狗差異很大的球拍並不靈。事實上如果用單純的FGS在很多情況下造對抗樣本都是不靈的,也許是因為兩個類別差異過大;也許是某個類別類內差異性過大(比如把所ImageNet中所有狗算一類,其他算一類的二分類);甚至最極端的某個類別可能處在ReLU都小於0的「Dead Zone」內。只考慮前兩種情況的話,需要比FGS更好更實用的方法。既然FGS直接前進一大步可能是錯的,很自然的一個想法是借鑒梯度下降的思路,一步步迭代前進。雖然這樣(從梯度方向上)很不線性,而且還要多次計算,不過比起L-BFGS法還是要簡單,而且效果拔群。Ian Goodfellow在ICLR 2017的論文《Adversarial Examples in The Physical World》中描述了這種方法,並進一步細分為兩種:1)減小預測為原始類別的置信度;2)增大原來被預測為最小可能類別的置信度。
基於這個思路,我們把第二種方法變通一下,嘗試用迭代法增大球拍的置信度,每次迭代0.1,迭代十次:
attack_img, original_preds, attacked_preds =
make_n_test_adversarial_example(img, net, transformer, 0.1, label_index=752)
for i in range(9):
attack_img, _, attacked_preds =
make_n_test_adversarial_example(attack_img, net, transformer, 0.1, label_index=752)
visualize_attack( racket_try1 .format(i), img, attack_img, original_preds, attacked_preds, labels)
需要注意外部調用進行迭代的寫法效率是不高的,並且每次都包含一次冗餘的前向計算。這裡這樣寫只是為了簡單,迭代完的結果如下:
成功得到了球拍。另附文中完整代碼:
http://t.cn/RKAYOdE
點擊展開全文
※一文詳解 DNN 在聲學應用中的模型訓練
※深度學習下的醫學圖像分析(三)
※今日頭條成功的核心技術秘訣是什麼?
※深度學習下的醫學圖像分析(一)
※讓ML決定貸款:由人工智慧判斷貸款風險
TAG:唯物 |
※這樣公司的offer為什麼不能接?這篇文章說出了本質
※不看這篇文章,你肯定不知道 Live Photo 這麼好用
※三款新iPhone該如何選?看完這篇文章就會有答案
※Incabloc避震器是怎麼來的?看這篇文章就知道了
※當你看完這篇文章後,你會對 Alan Walker 有一個全新的了解
※蘋果發了一篇文章,想告訴你 iPad Pro 為什麼會「變彎」
※pubmed官網停更後,怎麼查文獻?看這一篇文章就足夠了
※Adidas椰子Yeezy為何那麼走俏,這篇文章告訴你答案!
※蘋果HomePod值得現在入手嗎?看完這篇文章你就有答案了
※Nike,非常值得看的一篇文章
※你所不知道的 OPPO Find X 細節,這篇文章給你答案
※老鐵,3招教你如何合情合理的拒絕offer,快看這篇文章
※Louis Vuitton為什麼找了個潮牌設計師來當掌門?一篇文章告訴你所有答案
※一篇文章搞定Markdown
※你想入門Python,還是得看這篇文章
※你的iPhone敢升級iOS12系統嗎?這篇文章可以給你答案
※怎樣才能確定Ta愛不愛你,這篇文章告訴你答案
※忘了linux系統密碼怎麼辦,這篇文章告訴你!
※讀了這篇文章,讓你和「尬聊」say No
※這篇文章告訴你iPhone XR與iPhone X該怎麼選