{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Quickstart\n", "\n", "This notebook demonstrates how you can find adversarial examples for a pre-trained example network on the MNIST dataset.\n", "\n", "We suggest having the `Gurobi` solver installed, since its performance is significantly faster. If this is not possible, the `HiGHS` solver is another option.\n", "\n", "The `Images` package is only necessary for visualizing the sample images." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "using MIPVerify\n", "# If you have an academic license, use `Gurobi` instead.\n", "using HiGHS\n", "using Images" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup\n", "\n", "### MNIST dataset\n", "\n", "We begin by loading the MNIST dataset. The data is provided as a Julia `struct` for easy access. The training images and test images are provided as a 4-dimensional array of size `(num_samples, height, width, num_channels)`." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "mnist:\n", " `train`: {LabelledImageDataset}\n", " `images`: 60000 images of size (28, 28, 1), with pixels in [0.0, 1.0].\n", " `labels`: 60000 corresponding labels, with 10 unique labels in [0, 9].\n", " `test`: {LabelledImageDataset}\n", " `images`: 10000 images of size (28, 28, 1), with pixels in [0.0, 1.0].\n", " `labels`: 10000 corresponding labels, with 10 unique labels in [0, 9]." ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "mnist = MIPVerify.read_datasets(\"MNIST\")" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{LabelledImageDataset}\n", " `images`: 60000 images of size (28, 28, 1), with pixels in [0.0, 1.0].\n", " `labels`: 60000 corresponding labels, with 10 unique labels in [0, 9]." ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "mnist.train" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(60000, 28, 28, 1)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "size(mnist.train.images)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "60000-element Vector{UInt8}:\n", " 0x05\n", " 0x00\n", " 0x04\n", " 0x01\n", " 0x09\n", " 0x02\n", " 0x01\n", " 0x03\n", " 0x01\n", " 0x04\n", " ⋮\n", " 0x02\n", " 0x09\n", " 0x05\n", " 0x01\n", " 0x08\n", " 0x03\n", " 0x05\n", " 0x06\n", " 0x08" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "mnist.train.labels" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Sample Neural Network\n", "\n", "We import a sample pre-trained neural network. " ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "sequential net MNIST.n1\n", " (1) Flatten(): flattens 4 dimensional input, with dimensions permuted according to the order [4, 3, 2, 1]\n", " (2) Linear(784 -> 40)\n", " (3) ReLU()\n", " (4) Linear(40 -> 20)\n", " (5) ReLU()\n", " (6) Linear(20 -> 10)\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "n1 = MIPVerify.get_example_network_params(\"MNIST.n1\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`MIPVerify.frac_correct` allows us to verify that the network has a reasonable accuracy on the test set of 96.95%. (This step is crucial when working with your own neural net parameters; since the training is done outside of Julia, a common mistake is to transfer the parameters incorrectly.)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.9695" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "MIPVerify.frac_correct(n1, mnist.test, 10000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We feed the first image into the neural net, obtaining the activations of the final softmax layer. \n", "\n", "Note that the image must be specified as a 4-dimensional array with size `(1, height, width, num_channels)`. We provide a helper function `MIPVerify.get_image` that extracts the image from the dataset while preserving all four dimensions." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1×28×28×1 Array{Float64, 4}:\n", "[:, :, 1, 1] =\n", " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 … 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n", "\n", "[:, :, 2, 1] =\n", " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 … 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n", "\n", "[:, :, 3, 1] =\n", " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 … 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n", "\n", ";;; … \n", "\n", "[:, :, 26, 1] =\n", " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 … 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n", "\n", "[:, :, 27, 1] =\n", " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 … 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n", "\n", "[:, :, 28, 1] =\n", " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 … 0.0 0.0 0.0 0.0 0.0 0.0 0.0" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "sample_image = MIPVerify.get_image(mnist.test.images, 1)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10-element Vector{Float64}:\n", " -0.02074390040759505\n", " -0.017499541361042703\n", " 0.16707187742051954\n", " -0.05323712887827292\n", " -0.019291011852467455\n", " -0.07951546424946399\n", " 0.06191130931372918\n", " 4.833970937815984\n", " 0.46706000134294867\n", " 0.40145201599055125" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "output_activations = sample_image |> n1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The category that has the largest activation is category 8, corresponding to a label of 7." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "7" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "(output_activations |> MIPVerify.get_max_index) - 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This matches the true label." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "7" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "MIPVerify.get_label(mnist.test.labels, 1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Finding an Adversarial Example\n", "\n", "We now try to find the closest $L_{\\infty}$ norm adversarial example to the first image, setting the target category as index `10` (corresponding to a true label of 9). Note that we restrict the search space to a distance of `0.05` around the original image via the specified `pp`." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[36m[notice | MIPVerify]: Attempting to find adversarial example. Neural net predicted label is 8, target labels are [10]\u001b[39m\n", "\u001b[36m[notice | MIPVerify]: Determining upper and lower bounds for the input to each non-linear unit.\u001b[39m\n", "Running HiGHS 1.4.2 [date: 1970-01-01, git hash: f797c1ab6]\n", "Copyright (c) 2022 ERGO-Code under MIT licence terms\n", "Presolving model\n", "1770 rows, 1621 cols, 51874 nonzeros\n", "1770 rows, 1621 cols, 51874 nonzeros\n", "\n", "Solving MIP model with:\n", " 1770 rows\n", " 1621 cols (26 binary, 0 integer, 0 implied int., 1595 continuous)\n", " 51874 nonzeros\n", "\n", " Nodes | B&B Tree | Objective Bounds | Dynamic Constraints | Work \n", " Proc. InQueue | Leaves Expl. | BestBound BestSol Gap | Cuts InLp Confl. | LpIters Time\n", "\n", " 0 0 0 0.00% 0 inf inf 0 0 0 0 0.0s\n", " 0 0 0 0.00% 0.000619835635 inf inf 0 0 8 2021 0.1s\n", " 0 0 0 0.00% 0.00338385518 inf inf 5652 14 30 2097 5.2s\n", " 0 0 0 0.00% 0.00343896617 inf inf 5989 12 38 40946 25.4s\n", " B 15 0 3 12.55% 0.00343896617 0.0460846816 92.54% 5994 12 47 55284 28.3s\n", "\n", "Solving report\n", " Status Optimal\n", " Primal bound 0.0460846815889\n", " Dual bound 0.0460846815889\n", " Gap 0% (tolerance: 0.01%)\n", " Solution status feasible\n", " 0.0460846815889 (objective)\n", " 0 (bound viol.)\n", " 0 (int. viol.)\n", " 0 (row viol.)\n", " Timing 32.80 (total)\n", " 0.02 (presolve)\n", " 0.00 (postsolve)\n", " Nodes 27\n", " LP iterations 60751 (total)\n", " 14654 (strong br.)\n", " 751 (separation)\n", " 38835 (heuristics)\n" ] }, { "data": { "text/plain": [ "Dict{Any, Any} with 11 entries:\n", " :TargetIndexes => [10]\n", " :SolveTime => 32.8068\n", " :TotalTime => 60.2054\n", " :Perturbation => [_[1] _[2] … _[27] _[28];;; _[29] _[30] … _[55] _[56];…\n", " :PerturbedInput => [_[785] _[786] … _[811] _[812];;; _[813] _[814] … _[83…\n", " :TighteningApproach => \"mip\"\n", " :PerturbationFamily => linf-norm-bounded-0.05\n", " :SolveStatus => OPTIMAL\n", " :Model => A JuMP Model…\n", " :Output => JuMP.AffExpr[-0.012063867412507534 _[1601] + 0.0758192…\n", " :PredictedIndex => 8" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "target_label_index = 10\n", "d = MIPVerify.find_adversarial_example(\n", " n1, \n", " sample_image, \n", " target_label_index, \n", " HiGHS.Optimizer, \n", " Dict(),\n", " norm_order = Inf,\n", " pp=MIPVerify.LInfNormBoundedPerturbationFamily(0.05)\n", ")" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1×28×28×1 Array{Float64, 4}:\n", "[:, :, 1, 1] =\n", " 0.0460847 0.0 0.0 0.0460847 0.0 … 0.0 0.0 0.0 0.0 0.0 0.0460847\n", "\n", "[:, :, 2, 1] =\n", " 0.0 0.0 0.0 0.0460847 0.0460847 0.0 … 0.0 0.0 0.0 0.0 0.0460847\n", "\n", "[:, :, 3, 1] =\n", " 0.0 0.0 0.0 0.0 0.0 0.0 0.0460847 … 0.0 0.0 0.0460847 0.0 0.0\n", "\n", ";;; … \n", "\n", "[:, :, 26, 1] =\n", " 0.0 0.0 0.0460847 0.0 0.0 0.0 0.0 … 0.0460847 0.0460847 0.0 0.0\n", "\n", "[:, :, 27, 1] =\n", " 0.0460847 0.0460847 0.0 0.0 0.0 0.0 … 0.0460847 0.0 0.0460847\n", "\n", "[:, :, 28, 1] =\n", " 0.0460847 0.0 0.0460847 0.0 0.0 0.0 … 0.0 0.0 0.0 0.0460847 0.0" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "using JuMP\n", "\n", "perturbed_sample_image = JuMP.value.(d[:PerturbedInput])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As a sanity check, we feed the perturbed image into the neural net and inspect the activation in the final layer. We verify that the perturbed image does maximize the activation of the target label index, which is 10." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10-element Vector{Float64}:\n", " 0.6749450628745558\n", " 0.6179790360668576\n", " 0.3930321598089386\n", " 0.29656185967035986\n", " 0.2410105349548307\n", " 0.1606002120357421\n", " 0.5428526100447275\n", " 4.288351484573889\n", " -0.2264301823307634\n", " 4.288351484573882" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "perturbed_sample_image |> n1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We visualize the perturbed image and compare it to the original image. Since we are minimizing the $L_{\\infty}$-norm, changes are made to many pixels but the change to each pixels is not very noticeable." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAHAAAABwCAAAAADji6uXAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAwtJREFUaAW9wb/rrgUZB+AruqmU24aCCMGkoYZAQiJocGhTjKCppd2mlkNnCSSqPyFoaAkCwVkkooaQwB9LGGRZVEPZ0NRwPoPggfrCfeDx8X3Poem+rmrEWRtBOwTtEGeNoI2gEbRRlpVlZVnFoREjzmLEaKMdYsR1QVlWlpVl5X2Cxl20ETTaIc6CdmjcRRmNoFGWlWVlWbURI2gEjUbQiEOMOLQRNOIsKMvKsrKsgjYacRY04sHi0IjryrKyrCyrRhwaMYJG0Ij/XxtxVpaVZWVZBe1SI0YjRiNo1wVxqRGUZWVZWVaNoJ09hefxO7yLF4y/ui5GO8RZoywry8qyiut+jo/hCTyCb+MO3nJ/n8Y7uIW30QjaoSwry8qyatd9HV/EH/EFPImv4iv4Jx5zuIsP4+N4Av/AdxG0Q1CWlWVlWbmi8QbeMt7ET/EYnsTL+DI+hP/iXfwFf8In8CriLEZZVpaVZRWjjaDRzhr/wUvGG2iHb+Jx/B0vohEjDmVZWVaWlXtitPuL0c4+hZ8YP8RHjDbaCMqysqwsKx8QtEsx2lnwAzyM9/BnZ3FWlpVlZVm1ETSCOGu0EbTD07hlfAN/QBzaWVlWlpVlFYcYjaBdamfPGq/jNYd2FqMsK8vKsvI+jRjtELRLD+EZ4/t4z2iHoNFGWVaWlWXlRjs0gkYcgnZ2G5/Hb/GqETSCNoI2yrKyrCwrN4JGHOJS0MbXcBv/wo8QNBpBI2hnZVlZVpaVG+3BGjGCj+J7xi/wayMOcRY0yrKyrCwrN4JGIy7F2S/xWbyJ5z1Y0GijLCvLyrJqhzg04tKj+JJxC/92aASNoJ0FZVlZVpZV0C7FpU/iV8Zt/MZoBDHiLGg0yrKyrCwrN+LQRoxGjOfwGeMVh6DdXxtBWVaWlWXlA+LQCBqfw3fwCO44awRtBO26sqwsK8vKPY0YbQSN4Ck07uBviEPQiNHurywry8qyck9cagTt8Ht8C+84NGI0YjTi0CjLyrKyrNoIGkGMNoKf4ceui9GI6xpBWVaWlWX/A06qydY5JLkuAAAAAElFTkSuQmCC", "text/html": [ "" ], "text/plain": [ "28×28 reinterpret(reshape, Gray{Float64}, ::Matrix{Float64}) with eltype Gray{Float64}:\n", " Gray{Float64}(0.0460847) … Gray{Float64}(0.0460847)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0460847)\n", " Gray{Float64}(0.0460847) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0460847) … Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0460847) Gray{Float64}(0.0460847)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0460847)\n", " Gray{Float64}(0.0460847) Gray{Float64}(0.0)\n", " ⋮ ⋱ \n", " Gray{Float64}(0.0) Gray{Float64}(0.0460847)\n", " Gray{Float64}(0.0) … Gray{Float64}(0.0460847)\n", " Gray{Float64}(0.0460847) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0460847)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) … Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0460847)\n", " Gray{Float64}(0.0460847) Gray{Float64}(0.0)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "colorview(Gray, perturbed_sample_image[1, :, :, 1])" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAHAAAABwCAAAAADji6uXAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAdBJREFUaAW9wb1qlgcABtCDeToUXLRU6FB/cOtSgggFWyh0EV0EvYXUoXQpBFxCQYdAxg7egeAFlBIKKXTRJYv4UyrGwYoIhQS0Q1ChDu8QBL/4vfnCc06URVmURVmURVmURVmURVmURVmURVmURVmURVmURVmURVmURVmURVmURVlMcAkLeIZt3MBzPDKbKIuyKIsJVnDcjst4ift29xQrWPd+URZlURYTLOBLPMAXmMe3+Ar/4HM73uBffGbwBOveL8qiLMpigjWsGawaHMI81nHajm08xF84jMcmi7Ioi7IYYQt/GKx510Ucwl3cNFmURVmUxT44gus4gKvYNFmURVmUxT74AZ9iC3/bXZRFWZTFjM7gisEF3LO7KIuyKIsZncNHWMNtHxZlURZlMYOPcRav8DNe+7Aoi7IoixksYh6ruGU6URZlURZ7dB5LeIFrphdlURZlsQef4BfM4TfcNr0oi7Ioi5HmsIoT2MCScaIsyqIsRjqJUwY/YcM4URZlURYjHMPvBov41XhRFmVRFiN8j6MGf+J/40VZlEVZTOkb/Gh2URZlURZT+hoHDTbwn72JsiiLshjpDr7Dpr2JsiiLspjSMpbNLsqiLMreApamPWWOWvFrAAAAAElFTkSuQmCC", "text/html": [ "" ], "text/plain": [ "28×28 reinterpret(reshape, Gray{Float64}, ::Matrix{Float64}) with eltype Gray{Float64}:\n", " Gray{Float64}(0.0) Gray{Float64}(0.0) … Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0) … Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0) Gray{Float64}(0.0)\n", " ⋮ ⋱ \n", " Gray{Float64}(0.0) Gray{Float64}(0.0) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0) … Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0) … Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0) Gray{Float64}(0.0)\n", " Gray{Float64}(0.0) Gray{Float64}(0.0) Gray{Float64}(0.0)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "colorview(Gray, sample_image[1, :, :, 1])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That concludes this quickstart! The next tutorial will introduce you to each of the layers, and show how you can import your own neural network parameters." ] } ], "metadata": { "kernelspec": { "display_name": "Julia 1.7.1", "language": "julia", "name": "julia-1.7" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "1.7.1" } }, "nbformat": 4, "nbformat_minor": 2 }