Serangan satu piksel. Atau cara menipu jaringan saraf

Mari berkenalan dengan salah satu serangan pada jaringan saraf, yang mengarah pada kesalahan klasifikasi dengan pengaruh eksternal minimal. Bayangkan sejenak bahwa jaringan saraf itu adalah Anda. Dan saat ini, sambil minum secangkir kopi aromatik, Anda mengklasifikasikan gambar kucing dengan akurasi lebih dari 90 persen tanpa curiga bahwa "serangan satu-pixel" mengubah semua "kucing" Anda menjadi truk.

Dan sekarang kita akan berhenti sebentar, memindahkan kopi, mengimpor semua perpustakaan yang kita butuhkan dan menganalisis bagaimana serangan satu piksel tersebut bekerja.

Tujuan serangan ini adalah membuat algoritma (neural network) memberikan jawaban yang salah. Di bawah ini kita akan melihat ini dengan beberapa model jaringan saraf convolutional yang berbeda. Dengan menggunakan salah satu metode optimisasi matematis multidimensi - evolusi diferensial, kami menemukan piksel khusus yang dapat mengubah gambar sehingga jaringan saraf mulai mengklasifikasikan gambar ini secara salah (terlepas dari kenyataan bahwa sebelumnya algoritme โ€œmengenaliโ€ gambar yang sama dengan benar dan dengan akurasi tinggi).

Impor perpustakaan:

# Python Libraries
%matplotlib inline
import pickle
import numpy as np
import pandas as pd
import matplotlib
from keras.datasets import cifar10
from keras import backend as K

# Custom Networks
from networks.lenet import LeNet
from networks.pure_cnn import PureCnn
from networks.network_in_network import NetworkInNetwork
from networks.resnet import ResNet
from networks.densenet import DenseNet
from networks.wide_resnet import WideResNet
from networks.capsnet import CapsNet

# Helper functions
from differential_evolution import differential_evolution
import helper

matplotlib.style.use('ggplot')

Untuk percobaan kami, kami akan memuat dataset CIFAR-10 yang berisi gambar dunia nyata dibagi menjadi 10 kelas.

(x_train, y_train), (x_test, y_test) = cifar10.load_data()

class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']

Mari kita lihat gambar apa saja berdasarkan indeksnya. Misalnya, di sini di atas kuda ini.

image_id = 99 # Image index in the test set
helper.plot_image(x_test[image_id])



Kami harus mencari piksel yang sangat kuat yang dapat mengubah respons jaringan saraf, yang berarti saatnya untuk menulis fungsi untuk mengubah satu atau lebih piksel gambar.

def perturb_image(xs, img):
    # If this function is passed just one perturbation vector,
    # pack it in a list to keep the computation the same
    if xs.ndim < 2:
        xs = np.array([xs])
    
    # Copy the image n == len(xs) times so that we can 
    # create n new perturbed images
    tile = [len(xs)] + [1]*(xs.ndim+1)
    imgs = np.tile(img, tile)
    
    # Make sure to floor the members of xs as int types
    xs = xs.astype(int)
    
    for x,img in zip(xs, imgs):
        # Split x into an array of 5-tuples (perturbation pixels)
        # i.e., [[x,y,r,g,b], ...]
        pixels = np.split(x, len(x) // 5)
        for pixel in pixels:
            # At each pixel's x,y position, assign its rgb value
            x_pos, y_pos, *rgb = pixel
            img[x_pos, y_pos] = rgb
    
    return imgs

Coba lihat ?! Ubah satu piksel kuda kita dengan koordinat (16, 16) menjadi kuning.

image_id = 99 # Image index in the test set
pixel = np.array([16, 16, 255, 255, 0]) # pixel = x,y,r,g,b
image_perturbed = perturb_image(pixel, x_test[image_id])[0]

helper.plot_image(image_perturbed)



Untuk mendemonstrasikan serangan, Anda perlu mengunduh model-model jaringan saraf pra-terlatih pada dataset CIFAR-10 kami. Kami akan menggunakan dua model lenet dan resnet, tetapi Anda dapat menggunakan yang lain untuk percobaan Anda dengan menghapus komentar pada baris kode yang sesuai.

lenet = LeNet()
resnet = ResNet()

models = [lenet, resnet]

Setelah memuat model, perlu untuk mengevaluasi gambar uji masing-masing model untuk memastikan bahwa kami hanya menyerang gambar yang diklasifikasikan dengan benar. Kode di bawah ini menampilkan akurasi dan jumlah parameter untuk masing-masing model.

network_stats, correct_imgs = helper.evaluate_models(models, x_test, y_test)

correct_imgs = pd.DataFrame(correct_imgs, columns=['name', 'img', 'label', 'confidence', 'pred'])

network_stats = pd.DataFrame(network_stats, columns=['name', 'accuracy', 'param_count'])

network_stats
Evaluating lenet
Evaluating resnet

Out[11]:


	name        accuracy    param_count
0      lenet        0.748       62006
1      resnet       0.9231      470218


Semua serangan seperti itu dapat dibagi menjadi dua kelas: WhiteBox dan BlackBox. Perbedaan di antara mereka adalah bahwa dalam kasus pertama, kita semua tahu tentang algoritma, model yang kita pakai. Dalam kasus BlackBox, yang kita butuhkan adalah input (gambar) dan output (probabilitas ditugaskan ke salah satu kelas). Satu serangan pixel mengacu pada BlackBox.

Dalam artikel ini, kami mempertimbangkan dua opsi untuk menyerang satu piksel: tidak bertarget dan bertarget. Dalam kasus pertama, sama sekali tidak masalah untuk kelas mana jaringan saraf kucing kita akan menjadi milik, yang paling penting, bukan untuk kelas kucing. Serangan yang ditargetkan berlaku ketika kita ingin kucing kita menjadi sebuah truk dan hanya sebuah truk.

Tetapi bagaimana cara menemukan piksel yang perubahannya akan menyebabkan perubahan pada kelas gambar? Bagaimana menemukan piksel dengan mengubah serangan satu piksel yang menjadi mungkin dan berhasil? Mari kita coba merumuskan masalah ini sebagai masalah optimisasi, tetapi hanya dengan kata-kata yang sangat sederhana: dengan serangan yang tidak ditargetkan, kita harus meminimalkan kepercayaan pada kelas yang diinginkan, dan dengan target, memaksimalkan kepercayaan pada kelas target.

Saat melakukan serangan seperti itu, sulit untuk mengoptimalkan fungsi menggunakan gradien. Algoritme pengoptimalan harus digunakan yang tidak bergantung pada kelancaran fungsi.

Ingatlah bahwa untuk percobaan kami, kami menggunakan dataset CIFAR-10, yang berisi gambar dunia nyata, berukuran 32 x 32 piksel, dibagi menjadi 10 kelas. Ini berarti bahwa kami memiliki nilai integer diskrit dari 0 hingga 31 dan intensitas warna dari 0 hingga 255, dan fungsinya tidak diharapkan mulus, melainkan bergerigi, seperti yang ditunjukkan di bawah ini:



Itulah sebabnya kami menggunakan algoritma evolusi diferensial.

Tetapi kembali ke kode dan tulis fungsi yang mengembalikan probabilitas keandalan model. Jika kelas target benar, maka kami ingin meminimalkan fungsi ini sehingga modelnya yakin dengan kelas lain (yang tidak benar).

def predict_classes(xs, img, target_class, model, minimize=True):
    # Perturb the image with the given pixel(s) x and get the prediction of the model
    imgs_perturbed = perturb_image(xs, img)
    predictions = model.predict(imgs_perturbed)[:,target_class]
    # This function should always be minimized, so return its complement if needed
    return predictions if minimize else 1 - predictions

image_id = 384
pixel = np.array([16, 13,  25, 48, 156])
model = resnet

true_class = y_test[image_id, 0]
prior_confidence = model.predict_one(x_test[image_id])[true_class]
confidence = predict_classes(pixel, x_test[image_id], true_class, model)[0]

print('Confidence in true class', class_names[true_class], 'is', confidence)
print('Prior confidence was', prior_confidence)
helper.plot_image(perturb_image(pixel, x_test[image_id])[0])

Confidence in true class bird is 0.00018887444
Prior confidence was 0.70661753



Kami akan membutuhkan fungsi berikutnya untuk mengkonfirmasi kriteria untuk keberhasilan serangan, itu akan mengembalikan Benar ketika perubahan sudah cukup untuk menipu model.

def attack_success(x, img, target_class, model, targeted_attack=False, verbose=False):
    # Perturb the image with the given pixel(s) and get the prediction of the model
    attack_image = perturb_image(x, img)
    confidence = model.predict(attack_image)[0]
    predicted_class = np.argmax(confidence)
    
    # If the prediction is what we want (misclassification or 
    # targeted classification), return True
    if verbose:
        print('Confidence:', confidence[target_class])
    if ((targeted_attack and predicted_class == target_class) or
        (not targeted_attack and predicted_class != target_class)):
        return True
    # NOTE: return None otherwise (not False), due to how Scipy handles its callback function

Mari kita lihat karya fungsi kriteria kesuksesan. Untuk menunjukkan, kami menganggap serangan non-target.

image_id = 541
pixel = np.array([17, 18, 185, 36, 215])
model = resnet

true_class = y_test[image_id, 0]
prior_confidence = model.predict_one(x_test[image_id])[true_class]
success = attack_success(pixel, x_test[image_id], true_class, model, verbose=True)

print('Prior confidence', prior_confidence)
print('Attack success:', success == True)
helper.plot_image(perturb_image(pixel, x_test[image_id])[0])
Confidence: 0.07460087
Prior confidence 0.50054216
Attack success: True



Sudah waktunya untuk mengumpulkan semua teka-teki menjadi satu gambar. Kami akan menggunakan modifikasi kecil dari implementasi evolusi diferensial dalam Scipy.

def attack(img_id, model, target=None, pixel_count=1, 
           maxiter=75, popsize=400, verbose=False):
    # Change the target class based on whether this is a targeted attack or not
    targeted_attack = target is not None
    target_class = target if targeted_attack else y_test[img_id, 0]
    
    # Define bounds for a flat vector of x,y,r,g,b values
    # For more pixels, repeat this layout
    bounds = [(0,32), (0,32), (0,256), (0,256), (0,256)] * pixel_count
    
    # Population multiplier, in terms of the size of the perturbation vector x
    popmul = max(1, popsize // len(bounds))
    
    # Format the predict/callback functions for the differential evolution algorithm
    def predict_fn(xs):
        return predict_classes(xs, x_test[img_id], target_class, 
                               model, target is None)
    
    def callback_fn(x, convergence):
        return attack_success(x, x_test[img_id], target_class, 
                              model, targeted_attack, verbose)
    
    # Call Scipy's Implementation of Differential Evolution
    attack_result = differential_evolution(
        predict_fn, bounds, maxiter=maxiter, popsize=popmul,
        recombination=1, atol=-1, callback=callback_fn, polish=False)

    # Calculate some useful statistics to return from this function
    attack_image = perturb_image(attack_result.x, x_test[img_id])[0]
    prior_probs = model.predict_one(x_test[img_id])
    predicted_probs = model.predict_one(attack_image)
    predicted_class = np.argmax(predicted_probs)
    actual_class = y_test[img_id, 0]
    success = predicted_class != actual_class
    cdiff = prior_probs[actual_class] - predicted_probs[actual_class]

    # Show the best attempt at a solution (successful or not)
    helper.plot_image(attack_image, actual_class, class_names, predicted_class)

    return [model.name, pixel_count, img_id, actual_class, predicted_class, success, cdiff, prior_probs, predicted_probs, attack_result.x]

Sudah waktunya untuk membagikan hasil penelitian (serangan) dan melihat bagaimana mengubah hanya satu piksel akan mengubah katak menjadi anjing, kucing menjadi katak, dan mobil menjadi pesawat terbang. Tetapi semakin banyak titik gambar yang dibiarkan berubah, semakin tinggi kemungkinan serangan berhasil pada gambar apa pun.



Peragakan serangan yang berhasil pada gambar katak menggunakan model resnet. Kita harus melihat kepercayaan pada penurunan kelas yang sebenarnya setelah beberapa iterasi.

image_id = 102
pixels = 1 # Number of pixels to attack
model = resnet

_ = attack(image_id, model, pixel_count=pixels, verbose=True)

Confidence: 0.9938618
Confidence: 0.77454716
Confidence: 0.77454716
Confidence: 0.77454716
Confidence: 0.77454716
Confidence: 0.77454716
Confidence: 0.53226393
Confidence: 0.53226393
Confidence: 0.53226393
Confidence: 0.53226393
Confidence: 0.4211318



Ini adalah contoh serangan yang tidak ditargetkan, dan sekarang kami akan melakukan serangan yang ditargetkan dan memilih kelas mana yang kami inginkan model untuk mengklasifikasikan gambar. Tugasnya jauh lebih rumit daripada yang sebelumnya, karena kita akan membuat jaringan saraf mengklasifikasikan citra kapal sebagai mobil, dan kuda sebagai kucing.



Di bawah ini kita akan mencoba mendapatkan lenet untuk mengklasifikasikan citra kapal sebagai mobil.

image_id = 108
target_class = 1 # Integer in range 0-9
pixels = 3
model = lenet

print('Attacking with target', class_names[target_class])
_ = attack(image_id, model, target_class, pixel_count=pixels, verbose=True)
Attacking with target automobile
Confidence: 0.044409167
Confidence: 0.044409167
Confidence: 0.044409167
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.081972085
Confidence: 0.081972085
Confidence: 0.081972085
Confidence: 0.081972085
Confidence: 0.1537778
Confidence: 0.1537778
Confidence: 0.1537778
Confidence: 0.22246778
Confidence: 0.23916133
Confidence: 0.25238588
Confidence: 0.25238588
Confidence: 0.25238588
Confidence: 0.44560355
Confidence: 0.44560355
Confidence: 0.44560355
Confidence: 0.5711696



Setelah menangani satu kasus serangan, kami akan mengumpulkan statistik menggunakan arsitektur jaringan saraf konvolusional ResNet, melalui masing-masing model, mengubah 1, 3 atau 5 piksel dari setiap gambar. Pada artikel ini, kami menunjukkan kesimpulan akhir tanpa mengganggu pembaca untuk membiasakan diri dengan setiap iterasi, karena itu membutuhkan banyak waktu dan sumber daya komputasi.

def attack_all(models, samples=500, pixels=(1,3,5), targeted=False, 
               maxiter=75, popsize=400, verbose=False):
    results = []
    for model in models:
        model_results = []
        valid_imgs = correct_imgs[correct_imgs.name == model.name].img
        img_samples = np.random.choice(valid_imgs, samples, replace=False)
        
        for pixel_count in pixels:
            for i, img_id in enumerate(img_samples):
                print('\n', model.name, '- image', img_id, '-', i+1, '/', len(img_samples))
                targets = [None] if not targeted else range(10)
                
                for target in targets:
                    if targeted:
                        print('Attacking with target', class_names[target])
                        if target == y_test[img, 0]:
                            continue
                    result = attack(img_id, model, target, pixel_count, 
                                    maxiter=maxiter, popsize=popsize, 
                                    verbose=verbose)
                    model_results.append(result)
                    
        results += model_results
        helper.checkpoint(results, targeted)
    return results

untargeted = attack_all(models, samples=100, targeted=False)

targeted = attack_all(models, samples=10, targeted=False)

Untuk menguji kemungkinan diskon jaringan, suatu algoritma dikembangkan dan pengaruhnya terhadap kualitas perkiraan solusi pengenalan pola diukur.

Mari kita lihat hasil akhirnya.

untargeted, targeted = helper.load_results()

columns = ['model', 'pixels', 'image', 'true', 'predicted', 'success', 'cdiff', 'prior_probs', 'predicted_probs', 'perturbation']

untargeted_results = pd.DataFrame(untargeted, columns=columns)
targeted_results = pd.DataFrame(targeted, columns=columns)

Tabel di bawah ini menunjukkan bahwa menggunakan jaringan saraf ResNet dengan akurasi 0,9231, mengubah beberapa piksel gambar, kami mendapatkan persentase yang sangat baik dari gambar yang berhasil diserang (attack_success_rate).

helper.attack_stats(targeted_results, models, network_stats)
Out[26]:
	model	accuracy   pixels	attack_success_rate
0	resnet	0.9231	    1	        0.144444
1	resnet	0.9231	    3	        0.211111
2	resnet	0.9231	    5	        0.222222

helper.attack_stats(untargeted_results, models, network_stats)
Out[27]:
	model	accuracy   pixels	attack_success_rate
0	resnet	0.9231	   1	        0.34
1	resnet	0.9231	   3	        0.79
2	resnet	0.9231	   5	        0.79

Dalam percobaan Anda, Anda bebas untuk menggunakan arsitektur lain dari jaringan saraf tiruan, karena saat ini ada banyak sekali dari mereka.



Jaringan saraf telah menyelimuti dunia modern dengan utas yang tak terlihat. Layanan telah lama ditemukan di mana, menggunakan AI (kecerdasan buatan), pengguna mendapatkan foto yang diproses dengan gaya mirip dengan karya seniman hebat, dan hari ini algoritma sudah dapat menggambar sendiri, membuat karya musik, menulis buku, dan bahkan naskah untuk film.

Bidang-bidang seperti penglihatan komputer, pengenalan wajah, kendaraan tak berawak, diagnosa penyakit - membuat keputusan penting dan tidak memiliki hak untuk melakukan kesalahan, dan gangguan dengan pengoperasian algoritme akan menyebabkan konsekuensi bencana.

Serangan satu piksel adalah salah satu cara untuk menipu serangan. Untuk menguji kemungkinan diskon jaringan, suatu algoritma dikembangkan dan pengaruhnya terhadap kualitas perkiraan solusi pengenalan pola diukur. Hasil penelitian menunjukkan bahwa arsitektur jaringan saraf convolutional yang digunakan rentan terhadap algoritma serangan One pixel terlatih, yang menggantikan satu piksel, untuk mendiskreditkan algoritma pengakuan.

Artikel ini disiapkan oleh Alexander Andronic dan Adrey Cherny-Tkach sebagai bagian dari magang di Data4 .

All Articles