欺騙神經網路:建立你自己的對抗樣本

AI前線發表於2018-02-26
本文由 「AI前線」原創,原文連結:欺騙神經網路:建立你自己的對抗樣本
作者|Daniel Geng and Rishi Veerapaneni
譯者|孫浩
編輯|Emily

AI 前線導讀:通過神經網路進行暗殺。聽起來瘋狂嗎? 也許有一天這樣的事會以你想不到的方式發生。當然,神經網路可以訓練無人機或操縱其他大規模殺傷性武器,即使是通過無害 (以當前的狀況來看) 網路訓練後去駕駛一輛汽車,它也有可能違背其主人的意圖。這是因為神經網路非常容易受“對抗樣本”的影響。


“對抗樣本”是神經網路的輸入,它會導致網路不正確的輸出。這裡將通過一個例子來更好的說明這種狀況。我們可以從左邊的熊貓圖片開始,一些網路認為它有 57.7% 的可信度是“熊貓”。熊貓類別也是所有類別中可信度最高的類別,因此網路得出的結論是,影象中的物體是一隻熊貓。但是,通過新增少量精心構造的噪音,我們可以得到一張在人類看來與之前完全相同的影象,但網路認為它有 99.3% 的可信度是“長臂猿”。這是件很瘋狂的事情!

欺騙神經網路:建立你自己的對抗樣本

來自 Goodfellow 的《理解和控制對抗樣本》

那麼,如何通過對抗樣本來進行暗殺呢? 想象一下,用一個對抗樣本來代替一個停止標誌,這是一個人類可以立即識別的訊號,但是神經網路可能都不會注意這點。現在想象一下,在一個繁忙的十字路口放置一個對抗的停車標誌。當自動駕駛汽車接近十字路口時,車載神經網路將無法看到停車標誌,進而繼續駛入迎面而來的車流中,將它的乘客帶入死亡的邊緣 (理論上)。

這可能只是一個令人費解的稍微有點聳人聽聞的例子,但它說明了人們如何利用對抗樣本來傷害他人,這裡還有更多的例子。例如,iPhone X 的“人臉識別”解鎖功能依賴於神經網路識別人臉,因此也容易受到對抗攻擊。人們可以構建對抗影象來繞過人臉識別的安全特性。其他生物識別安全系統也將面臨風險,非法或錯誤的內容可能使用對抗樣本繞過基於神經網路的內容過濾器。這些對抗樣本的存在意味著,結合深度學習模型的系統實際上有很高的安全風險。

欺騙神經網路:建立你自己的對抗樣本


你可以通過把它們看作是神經網路的視覺幻象來理解對抗樣本。視覺錯覺可以欺騙人類的大腦,同理,對抗樣本也可以欺騙神經網路。

上面提到的關於熊貓的對抗樣本是一個有針對性的例子。在影象中新增了少量的精心構造的噪聲,導致神經網路對影象進行了錯誤的分類,儘管對於人類來說影象看起來與之前相同。也有一些無針對性的例子,它們試圖找到任何能欺騙神經網路的輸入。這個輸入對於人類來說可能看起來像白噪音,但是因為我們沒有被限制去尋找在人類看來相似的輸入,問題就簡單多了。

我們可以為任何神經網路找到對抗樣本,即使是被稱為具有“超人類”能力的最先進的模型,這有點令人不安。事實上,建立對抗樣本很容易,我們將在本文中向你展示如何做到這一點。你需要的所有程式碼和相關依賴都可以在這個 GitHub 的 repo 中找到。

欺騙神經網路:建立你自己的對抗樣本

病毒模仿,完美展現對抗樣本的有效性

MNIST 上的對抗樣本這部分的程式碼可以在下面的 GitHub repo 中找到 (下載程式碼對理解本文並非必需):GitHub Repo

我們將嘗試在 MNIST 的資料集上訓練一個前饋神經網路。MNIST 是一個 28*28 畫素的手寫數字影象的資料集。他們的樣式如下:

欺騙神經網路:建立你自己的對抗樣本

6 張並排的 MNIST 圖片

在開始之前,我們首先應該匯入我們需要的庫。

import network.network as network
import network.mnist_loader as mnist_loader
import pickle
import matplotlib.pyplot as plt
import numpy as np
複製程式碼

有 50000 張訓練影象和 10000 張測試影象。我們首先載入預先訓練的神經網路 (這是厚著臉皮從這篇很棒的關於神經網路的文章中拿來的):

with open('trained_network.pkl', 'rb') as f:  
    net = pickle.load(f)  

training_data, validation_data, test_data = mnist_loader.load_data_wrapper()
複製程式碼

對於那些不熟悉 pickle 的人解釋一下,這是 python 序列化資料 (如寫入到磁碟) 的一種方式,本質上就是儲存類和物件。使用 pickle.load() 可以開啟已儲存的網路層版本。

關於這個預先訓練過的神經網路。它有 784 個輸入神經元 (每個對應 28*28=784 畫素),一層有 30 個隱藏的神經元和 10 個輸出神經元 (每個數字對應一個)。它的所有活態都是 s 形的;它的輸出是一個表示網路預測的單熱向量,它通過最小化均方差誤差損失來訓練。

為了證明神經網路是經過訓練的我們可以寫一個簡單的測試小函式:

def predict(n):
    # Get the data from the test set
    x = test_data[n][0]

    # Get output of network and prediction
    activations = net.feedforward(x)
    prediction = np.argmax(activations)

    # Print the prediction of the network
    print('Network output: ')
    print(activations)

    print('Network prediction: ')
    print(prediction)

    print('Actual image: ')

    # Draw the image
    plt.imshow(x.reshape((28,28)), cmap='Greys')
複製程式碼

該方法從測試集中選擇樣本,顯示它,然後在神經網路中使用 net.feedforward(x) 方法執行它。以下是一些圖片的輸出:


欺騙神經網路:建立你自己的對抗樣本



欺騙神經網路:建立你自己的對抗樣本



欺騙神經網路:建立你自己的對抗樣本


左邊是 MNIST 的影象。右側是神經網路的 3 個輸出,稱為活態。輸出訊號越大,神經網路就越認為影象就是那個數字。

現在我們有一個訓練有素的網路,但是我們如何去騙它呢? 我們將首先從一個簡單的無針對性的方法開始,一旦我們得到預期目的,我們就可以使用一個很酷的技巧來修改這個方法,將其改為一種有針對性的方法。

無針對性攻擊這個思路是生成一些影象,以便使神經網路產生特定的輸出。例如,我們的目標標籤 / 輸出是:

也就是說,我們想要得到一個影象,它經過神經網路得到的輸出結果是上面的向量。換句話說,找到一個影象,讓神經網路認為該影象是一個 5(提醒一下,我們是零索引)。事實證明,我們可以把這當作一個優化問題,就像我們訓練網路一樣。我們將我們想要生成的影象稱為 (一個 784 維向量,為了計算簡便,我們把 28*28 畫素的影象拉平了)。我們將定義一個代價函式:

是 L2 正規化的平方。是我們從上面獲得的目標標籤。我們的影象經過神經網路的輸出是。我們可以看到,如果我們的影象經過神經網路的輸出結果非常接近我們的目標標籤,那麼相應的代價就會很低。如果網路的輸出結果與我們的目標相去甚遠,那麼代價值就會很高。因此,找到一個向量使代價值 C 最小,神經網路預測的影象就是我們的目標標籤。我們現在的問題是找到這個向量。請注意,這個問題與我們訓練神經網路的方式非常相似,在這裡我們定義了一個代價函式,然後選擇權重和偏差 (又稱引數) 來最小化代價函式。在生成對抗樣本的情況下,我們不使用權重和偏差來最小化成本,而是保持權重和偏差不變 (本質上是保持整個網路常量),並選擇一個輸入使代價最小化。

為了做到這一點,我們將採用與訓練神經網路相同的方法。也就是說,我們將使用梯度下降法!我們可以使用反向傳播求出輸入的代價函式的導數,,,然後使用梯度下降更新找到使代價最小化的最佳的。

反向傳播通常用於計算權重和代價偏差的梯度,但通常而言反向傳播只是一種演算法,它可以有效地計算出計算圖上的梯度 (這就是神經網路)。因此,它也可以用於計算神經網路輸入的代價函式的梯度。

現在讓我們看看生成對抗樣本的程式碼:

def adversarial(net, n, steps, eta):
"""
net : network object
    neural network instance to use
n : integer
    our goal label (just an int, the function transforms it into a one-hot vector)
steps : integer
    number of steps for gradient descent
eta : integer
    step size for gradient descent
"""
# Set the goal output
goal = np.zeros((10, 1))
goal[n] = 1

# Create a random image to initialize gradient descent with
x = np.random.normal(.5, .3, (784, 1))

# Gradient descent on the input
for i in range(steps):
    # Calculate the derivative
    d = input_derivative(net,x,goal)

    # The GD update on x
    x -= eta * d

return x
複製程式碼


首先我們建立,在程式碼中稱為 goal。接下來,我們初始化作為一個隨機的 784 維向量。有了這個向量,我們就可以開始使用梯度下降法了,這實際上只有兩行程式碼。第一行 d = input_derivative(net,x,goal) 使用反向傳播計算 (筆記中完整的程式碼是為需要的人編寫的,但在這裡我們就不做描述了,因為它實際上只是一大堆的數學知識。如果你想要獲得對反向傳播更好的描述 (input_derivative 所做的事情),可以到這個網站查閱 (順便說一下,我們從那裡得到了神經網路的實現))。梯度下降迴圈的第二行和最後一行,x- = eta * d 是對上 GD 的更新。我們沿著梯度方向與階梯大小的 eta 方向移動。

以下是針對每個類的無針對性對抗樣本以及神經網路的預測:

無針對性"O"


欺騙神經網路:建立你自己的對抗樣本


無針對性"3"


欺騙神經網路:建立你自己的對抗樣本


無針對性"5"


欺騙神經網路:建立你自己的對抗樣本


左邊是無針對性的對抗測試 (一個 28 X 28 畫素的影象)。當給出影象的時候,右邊就繪製了網路的活態。

令人難以置信的是,神經網路認為有些影象是某些數字的可信度非常高。“3”和“5”就是很好的例子。對於大多數其他數字,神經網路表現出的活態就很低,這表明神經網路有些混亂了。效果看起來很不錯!

在這一點上,可能會有一些事情讓你感到困擾。如果我們想要做出一個對應於“5”的對抗樣本那麼我們想要找到一個,將其輸入到神經網路後,輸出的結果就會盡可能接近代表“5”的一維向量。然而,為什麼梯度下降沒有找到“5”的影象呢? 畢竟,神經網路幾乎肯定會認為“5”的影象實際上是“5”(因為它確實是“5”)。關於為什麼會發生這種情況的一個可能的理論是:

所有可用的 28×28 畫素影象的間隔是巨大的。有種不同的 28×28 畫素的黑白影象。下面作個對比,我們對可觀測宇宙中原子數量的普遍估計數量是。如果宇宙中的每個原子都包含另一個宇宙那麼我們就會有個原子。如果每個原子包含另一個也包含宇宙的原子,以此相互包含大約 23 次,那麼我們幾乎可以得到個原子。所以大體上可以看出,可分析影象的數量是驚人地龐大。

在所有這些照片中,只有極少的一部分能夠被人類的眼睛識別成數字。面對很多圖片時,神經網路能將大部分的影象識別成數字 (部分原因是因為我們的神經網路從沒有在看起來不像數字的影象上訓練過,所以,假如是一張看起來不像數字的影象,神經網路輸出的結果幾乎會是隨機的)。所以當我們開始尋找一個對於神經網路來說像數字的影象時,我們更有可能找到一個看起來像噪音或靜電干擾的影象,而不是僅僅通過概率找到一個對於人類來說像數字的影象。

針對性攻擊

這些對抗樣本很酷,但對人類來說,它們看起來就像噪音。如果我們能有一個看起來像某種東西的對抗樣本,那不是更酷嗎?也許一張“2”的影象神經網路會認為是“5”?事實證明這是可能的!我們只對原始程式碼做了非常小的修改。我們給我們最小化的代價函式加了一項。新代價函式如下:

就是我們所期望的對抗樣本的樣子 (是一個 784 維向量,和我們輸入的維度相同)。所以我們現在要做的就是同時最小化這兩項。左邊的項我們已經見過了。一旦給定,最小化這項將會使神經網路輸出。最小化第二項將試圖促使我們的對抗影象儘可能的接近 (因為兩個向量越接近標準值會越小),這恰恰就是我們想要的!另外,前面的λ是一個超引數,它決定了哪項更重要。和大多數的超參一樣,我們在反覆試驗後發現.05 是λ一個非常恰當的值。

如果你瞭解嶺迴歸你可能就會對上面的代價函式非常熟悉。事實上,我們可以將上面的代價函式解釋為,在我們的模型中為我們的對抗樣本放置了一個先驗的例子。

如果你對正規化 Regularization 不瞭解,可以通過搜尋引擎瞭解更多的資訊。

實現最小化新代價函式的程式碼幾乎與原始程式碼相同 (我們稱這個函式為 sneaky_adversarial(),因為我們使用針對性攻擊是偷偷摸摸的。命名一直是程式設計中最難的部分。)

def sneaky_adversarial(net, n, x_target, steps, eta, lam=.05):
    """
    net : network object
        neural network instance to use
    n : integer
        our goal label (just an int, the function transforms it into a one-hot vector)
    x_target : numpy vector
        our goal image for the adversarial example
    steps : integer
        number of steps for gradient descent
    eta : integer
        step size for gradient descent
    lam : float
        lambda, our regularization parameter. Default is .05
    """

    # Set the goal output
    goal = np.zeros((10, 1))
    goal[n] = 1

    # Create a random image to initialize gradient descent with
    x = np.random.normal(.5, .3, (784, 1))

    # Gradient descent on the input
    for i in range(steps):
        # Calculate the derivative
        d = input_derivative(net,x,goal)

        # The GD update on x, with an added penalty 
        # to the cost function
        # ONLY CHANGE IS RIGHT HERE!!!
        x -= eta * (d + lam * (x - x_target))

    return x
複製程式碼

我們唯一改變的是梯度下降更新:

x -= eta
 (d + lam
 (x - x_target)) 。
複製程式碼

eta 項是我們代價函式的新項。來看看新迭代的方法產出的結果:

針對性“7”[x_target=3]


欺騙神經網路:建立你自己的對抗樣本


針對性“9”[x_target=5]


欺騙神經網路:建立你自己的對抗樣本


針對性“2”[x_target=8]


欺騙神經網路:建立你自己的對抗樣本


左邊是有針對性的對抗樣本 (一個 28 X 28 畫素的影象)。當給出影象的時候,右邊就繪製了網路的活態。

需要注意的是,與非目標攻擊一樣,這裡有兩種行為。要麼是神經網路被完全欺騙,而我們想要的數字的活態是非常高的 (例如“針對性 5”影象),要麼是網路被困惑住了,所有的活態都很低 (例如“針對性 7”影象)。有趣的是,現在有更多的影象在前一個類別中,完全欺騙了神經網路,而不只是讓它困惑。看起來,在梯度下降的過程中,那些被規範化為“數字類”的對抗樣本更容易使收斂效果更好。

防止對手的攻擊

太不可思議了! 我們剛剛建立了一些影象來欺騙神經網路。我們的下一個問題是,我們是否能夠防止這些型別的攻擊。如果你仔細觀察原始影象和對抗樣本,你會發現對抗樣本中有一些灰色背景。

目標影象


欺騙神經網路:建立你自己的對抗樣本


原始影象


欺騙神經網路:建立你自己的對抗樣本


背景中有噪聲的一個對抗樣本。

我們可以先嚐試一種比較簡單的做法,使用二進位制閾值來徹底地將背景色完全變白:

def binary_thresholding(n, m):
"""
n: int 0-9, the target number to match
m: index of example image to use (from the test set)
"""

# Generate adversarial example
x = sneaky_generate(n, m)

# Binarize image
x = (x > .5).astype(float)

print("With binary thresholding: ")

plt.imshow(x.reshape(28,28), cmap="Greys")
plt.show()

# Get binarized output and prediction
binary_activations = net.feedforward(x)
binary_prediction = np.argmax(net.feedforward(x))

print("Prediction with binary thresholding: ")
print(binary_prediction)

print("Network output: ")
print(binary_activations)
複製程式碼

下面是結果:

對抗圖片:


欺騙神經網路:建立你自己的對抗樣本


二進位制化的影象


欺騙神經網路:建立你自己的對抗樣本


二進位制閾值對 MNIST 對抗影象的影響。左邊是影象,右邊是神經網路的輸出。

結果表明二進位制的閾值起作用了!但是這種保護對抗攻擊的方法不是很好。並不是所有的圖片都有白色背景。例如,在這篇文章的開頭,看看熊貓的圖象。對影象進行二進位制閾值的處理可能會消除噪聲,但大程度的干擾了熊貓的影象。甚至可能到了連網路 (和人類) 都不能認出它是一隻熊貓的地步。

欺騙神經網路:建立你自己的對抗樣本

對熊貓進行二進位制的閾值處理會產生一個滿是斑點的影象

我們可以嘗試的另一種更普遍的方法是,同時在正確標註的對抗樣本和原始訓練測試集上訓練一個新的神經網路。在 ipython notebook 中有實現程式碼 (注意,程式碼需要大約 15 分鐘的時間來執行)。這樣做之後,可以使所有對抗影象的測試集的準確率達到 94%,這樣的效果非常好。但是,這種方法有它自己的侷限性。在現實生活中,你不太可能知道你的攻擊者是如何產生對抗樣本的。

還有很多其他的方法可以保護我們免受對抗攻擊,在這篇介紹性的文章中我們沒有涉足,但是這個問題仍然是一個開放的研究課題,如果你感興趣的話,有很多關於這個主題的論文。

黑盒攻擊

對對抗樣本的一個有趣但很重要的觀察結果是,它們通常沒有特定的模型或架構。為一個神經網路架構生成的對抗樣本可以很好的移植到另一個架構上。換句話說,如果你想要欺騙一個模型,你可以根據它建立你自己的模型和對抗樣本。那麼,這些相同的對抗樣本很可能也會欺騙其它的模型。

這具有重要的含義,因為這意味著我們有可能為一個完整的黑盒模型建立一個對抗樣本,而我們對其內部機制事先不必瞭解。事實上,伯克利的一個團隊成功地利用這種方法對一個商業人工智慧分類系統發起了一次攻擊。

結論

在我們邁向未來的過程中,我們的日常生活中包含了越來越多的神經網路和深度學習演算法,我們必須非常小心並且記住這些模型很容易被愚弄。儘管神經網路在某種程度上從生物學上獲得了啟發,並且在各種各樣的任務中具有接近 (甚至超過) 人類的能力,但對抗樣本告訴我們,它們的運作方式與真實的生物是不一樣的。正如我們所看到的那樣,神經網路很容易就會失敗,而且是災難性的,這種狀況對我們人類來說是完全陌生的。

我們不完全理解神經網路,所以用我們人類的直覺來描述神經網路是不明智的。例如,你經常會聽到人們說“神經網路認為這張影象是貓的,因為它是橙色的。”問題是神經網路並不會像人類那樣思考。它們本質上只是一系列的矩陣與一些相加的非線性資料的乘積。正如對抗樣本所展現的那樣,這些模型的輸出是非常脆弱的。我們必須注意,不能把讓機器實現人類品質的希望寄託於神經網路上,儘管他們有人類的能力。也就是說,我們不能將機器學習模式擬人化。


欺騙神經網路:建立你自己的對抗樣本


一個被訓練用來檢測啞鈴的神經網路“相信”“啞鈴”有時會與一個脫離身體的手臂配對。這顯然不是我們所期望的。來自 Google Research。

總而言之,對抗樣本的存在應該讓我們保持謙遜。他們向我們表明,儘管我們取得了巨大的進步,但仍有許多我們不知道的東西。

英文原文連結:

ml.berkeley.edu/blog/2018/0…

更多幹貨內容,可關注AI前線,ID:ai-front,後臺回覆「AI」、「TF」、「大資料」可獲得《AI前線》系列PDF迷你書和技能圖譜。


相關文章