在本教程中,我們將看到如何創建使用JAX訓練神經網絡的對抗示例。
首先,讓我們看一些定義。有哪些例子?簡而言之,對抗性示例是神經網絡的輸入,這些輸入經過優化以欺騙算法,即導致目標變量分類錯誤。通過向目標變量添加「適當的」噪聲,我們可以對目標變量進行錯誤分類。下圖演示了該概念。
本教程的重點是演示如何創建對抗示例。我們將使用快速梯度符號法生成。
在這種方法中,如果x是輸入圖像,我們將x修改為
其中對抗輸入是通過輸入輸入圖像x的交叉熵損失的梯度的符號並將其添加到原始圖像而獲得的。ε是此處的超參數。
在本教程中,我們將使用流行的MNIST數據集。如果您不知道什麼是MNIST數據集,建議轉到以下連結。
為了訓練我們的模型並生成對抗示例,我們將使用JAX模塊。JAX是自動差分(AD)工具箱,在訓練大規模數據集(如MNIST)時非常方便。有人恰當地將JAX描述為類固醇的Numpy!因為這不是「 JAX入門」教程,所以不會對其進行更深入的研究。
現在讓我們開始進行編碼。
我提供的代碼是基於以下GitHub存儲庫構建的。進行了必要的更改,並添加了一些新功能以使其適合手邊的應用程式。
首先,我們將導入所有重要的庫。
import array
import gzip
import itertools
import numpy
import numpy.random as npr
import os
import struct
import time
from os import path
import urllib.request
import jax.numpy as np
from jax.api import jit, grad
from jax.config import config
from jax.scipy.special import logsumexp
from jax import random
import matplotlib.pyplot as plt
接下來,我們將下載並加載MNIST數據。
_DATA = "/tmp/"
def _download(url, filename):
"""Download a url to a file in the JAX data temp directory."""
if not path.exists(_DATA):
os.makedirs(_DATA)
out_file = path.join(_DATA, filename)
if not path.isfile(out_file):
urllib.request.urlretrieve(url, out_file)
print("downloaded {} to {}".format(url, _DATA))
def _partial_flatten(x):
"""Flatten all but the first dimension of an ndarray."""
return numpy.reshape(x, (x.shape[0], -1))
def _one_hot(x, k, dtype=numpy.float32):
"""Create a one-hot encoding of x of size k."""
return numpy.array(x[:, None] == numpy.arange(k), dtype)
def mnist_raw():
"""Download and parse the raw MNIST dataset."""
# CVDF mirror of http://yann.lecun.com/exdb/mnist/
base_url = "https://storage.googleapis.com/cvdf-datasets/mnist/"
def parse_labels(filename):
with gzip.open(filename, "rb") as fh:
_ = struct.unpack(">II", fh.read(8))
return numpy.array(array.array("B", fh.read()), dtype=numpy.uint8)
def parse_images(filename):
_, num_data, rows, cols = struct.unpack(">IIII", fh.read(16))
return numpy.array(array.array("B", fh.read()),
dtype=numpy.uint8).reshape(num_data, rows, cols)
for filename in ["train-images-idx3-ubyte.gz", "train-labels-idx1-ubyte.gz",
"t10k-images-idx3-ubyte.gz", "t10k-labels-idx1-ubyte.gz"]:
_download(base_url + filename, filename)
train_images = parse_images(path.join(_DATA, "train-images-idx3-ubyte.gz"))
train_labels = parse_labels(path.join(_DATA, "train-labels-idx1-ubyte.gz"))
test_images = parse_images(path.join(_DATA, "t10k-images-idx3-ubyte.gz"))
test_labels = parse_labels(path.join(_DATA, "t10k-labels-idx1-ubyte.gz"))
return train_images, train_labels, test_images, test_labels
def mnist(create_outliers=False):
"""Download, parse and process MNIST data to unit scale and one-hot labels."""
train_images, train_labels, test_images, test_labels = mnist_raw()
train_images = _partial_flatten(train_images) / numpy.float32(255.)
test_images = _partial_flatten(test_images) / numpy.float32(255.)
train_labels = _one_hot(train_labels, 10)
test_labels = _one_hot(test_labels, 10)
if create_outliers:
mum_outliers = 30000
perm = numpy.random.RandomState(0).permutation(mum_outliers)
train_images[:mum_outliers] = train_images[:mum_outliers][perm]
def shape_as_image(images, labels, dummy_dim=False):
target_shape = (-1, 1, 28, 28, 1) if dummy_dim else (-1, 28, 28, 1)
return np.reshape(images, target_shape), labels
train_images, train_labels, test_images, test_labels = mnist(create_outliers=False)
num_train = train_images.shape[0]
現在,我們將定義一個函數,該函數將通過遍歷其所有層,獲取輸入/上一層的激活並應用tanh激活來計算其全連接神經網絡的輸出。
請記住,對於我們使用的輸出,z =wx+ b
def predict(params, inputs):
activations = inputs
for w, b in params[:-1]:
outputs = np.dot(activations, w) + b
activations = np.tanh(outputs)
final_w, final_b = params[-1]
logits = np.dot(activations, final_w) + final_b
return logits - logsumexp(logits, axis=1, keepdims=True)
在本教程中,我們將使用交叉熵損失。以下函數將使我們損失模型。
# loss function for calculating predictions and accuracy before pertubation
def loss(params, batch, test=0):
inputs, targets = batch
logits = predict(params, inputs)
preds = stax.logsoftmax(logits)
if(test==1):
print('Prediction Vector before softmax')
print(logits)
print("____________________________________________________________________________________")
print('Prediction Vector after softmax')
print(preds)
return -(1/(preds.shape[0]))*np.sum(targets*preds)
# loss function for calculating gradients of loss w.r.t. input image
def lo(batch,params):
以下單元格定義了模型的準確性以及如何初始化其參數。
def accuracy(params, batch):
target_class = np.argmax(targets, axis=1)
predicted_class = np.argmax(predict(params, inputs), axis=1)
return np.mean(predicted_class == target_class), target_class, predicted_class
現在,我們必須生成一批訓練數據。為此,我們將為數據集創建一個Python生成器。它一次輸出一批n個訓練示例。
batch_size = 128
num_complete_batches, leftover = divmod(num_train, batch_size)
num_batches = num_complete_batches + bool(leftover)
def data_stream():
rng = npr.RandomState(0)
whileTrue:
perm = rng.permutation(num_train)
for i in range(num_batches):
batch_idx = perm[i * batch_size:(i + 1) * batch_size]
yield train_images[batch_idx], train_labels[batch_idx]
batches = data_stream()
接下來,我們的工作是使用'stax'創建一個完全連接的神經網絡體系結構。Stax是一個神經網絡規範庫。在這裡,我們將詳細介紹卷積神經網絡中各層的規範。
init_random_params, predict = stax.serial(
stax.Conv(64, (7,7), padding='SAME'),
stax.Relu,
stax.Conv(32, (4, 4), padding='SAME'),
stax.MaxPool((3, 3)),
stax.Flatten,
stax.Dense(128),
stax.Dense(10),
)
現在,我們必須定義迷你批處理SGD優化器。優化器為我們提供了3件事。
1]方法opt_init,它接受init_fun返回的一組初始參數值,並返回初始優化器狀態opt_state,
2]一種方法opt_update,它採用梯度和參數並通過應用一個優化步驟來更新優化器狀態,並且
3]方法get_params進入優化器狀態並返回當前參數值。
learning_rate = 0.14
opt_init, opt_update, get_params = optimizers.sgd(learning_rate)
@jit
def update(_, i, opt_state, batch):
params = get_params(opt_state)
return opt_update(i, grad(loss)(params, batch), opt_state)
接下來,我們將在訓練示例中訓練我們的模型。在訓練結束時,我們將獲得「參數」,我們將使用這些「參數」來計算測試圖像的損失函數的梯度。
num_epochs = 1
key = random.PRNGKey(123)
_, init_params = init_random_params(key, (-1, 28, 28, 1))
opt_state = opt_init(init_params)
itercount = itertools.count()
for _ in range(num_batches):
opt_state= update(key, next(itercount), opt_state, shape_as_image(*next(batches)))
params = get_params(opt_state)
最後,我們定義函數,該函數將通過測試輸入返回損耗函數的梯度。另外,此函數將計算測試損失並預測目標變量的類別。
# This function calculates, loss, predictions and gradients
def covnet(t,params):
test_acc,target_class, predicted_class = accuracy(params, shape_as_image(test_images, test_labels))
test_loss = loss(params, shape_as_image(test_images, test_labels),test=t)
grads = grad(lo)(shape_as_image(test_images, test_labels),params)
if(t==1):
print('Test set loss, accuracy (%): ({:.2f}, {:.2f})'.format(test_loss, 100 * test_acc))
print('predicted_class,target_class', predicted_class,target_class)
return grads, test_acc
現在是時候測試我們的模型了。
首先,讓我們接受一個測試輸入。在這裡,我們選擇屬於「 7」類的圖像。
讓我們可視化原始圖像。
def display(image):
img = image[0].reshape((28,28))
plt.imshow(img, cmap="Greys")
plt.show()
return
display(a)
上面的代碼為我們提供了以下輸出。
讓我們看看我們訓練有素的模型是否可以預測此圖像的準確分類。
# load desired image and its label in test set
def load_img(image,img_label):
img = np.array(image)
img = img.reshape(1,784)
label = np.array(img_label)
label = label.reshape(1,10)
return img, label
img, label = load_img(test_images[0],test_labels[0])
test_images = img
test_labels = label
#Predictions Before Pertubation
grads,acc = covnet(1,params)
運行上面的代碼後,我們得到以下輸出。
我們看到我們的模型已經正確預測了輸入圖像的類別。