From 92da5805999df34c79efc87bd9eea0b4ff7c7762 Mon Sep 17 00:00:00 2001 From: tmayer868 Date: Thu, 7 May 2020 15:30:03 -0600 Subject: [PATCH] Add files via upload --- adversarial_examples.ipynb | 4283 +++++++++++++++++++++++++++--------- 1 file changed, 3273 insertions(+), 1010 deletions(-) diff --git a/adversarial_examples.ipynb b/adversarial_examples.ipynb index 5405e2a..2506328 100644 --- a/adversarial_examples.ipynb +++ b/adversarial_examples.ipynb @@ -1,1012 +1,3275 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "from collections import namedtuple\n", - "\n", - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "import torch\n", - "import torch.nn as nn\n", - "import torchvision as tv\n", - "import tqdm\n", - "from scipy.optimize import Bounds, minimize\n", - "from torch import optim\n", - "from torch.autograd import Variable\n", - "from torch.functional import F\n", - "from torch.utils.data import DataLoader, Dataset, TensorDataset, random_split\n", - "from torchvision import datasets, transforms" - ] + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + }, + "colab": { + "name": "adversarial_examples.ipynb", + "provenance": [], + "collapsed_sections": [] + }, + "accelerator": "GPU", + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "4bd664c6474c48d9ab30f97e976c7f8a": { + "model_module": "@jupyter-widgets/controls", + "model_name": "HBoxModel", + "state": { + "_view_name": "HBoxView", + "_dom_classes": [], + "_model_name": "HBoxModel", + "_view_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_view_count": null, + "_view_module_version": "1.5.0", + "box_style": "", + "layout": "IPY_MODEL_2639f416befa453896f213fcb57ef859", + "_model_module": "@jupyter-widgets/controls", + "children": [ + "IPY_MODEL_d886e997192d4b58ac168d6ebda00166", + "IPY_MODEL_d55e87faaf1f493ca6eef267985d763c" + ] + } + }, + "2639f416befa453896f213fcb57ef859": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "state": { + "_view_name": "LayoutView", + "grid_template_rows": null, + "right": null, + "justify_content": null, + "_view_module": "@jupyter-widgets/base", + "overflow": null, + "_model_module_version": "1.2.0", + "_view_count": null, + "flex_flow": null, + "width": null, + "min_width": null, + "border": null, + "align_items": null, + "bottom": null, + "_model_module": "@jupyter-widgets/base", + "top": null, + "grid_column": null, + "overflow_y": null, + "overflow_x": null, + "grid_auto_flow": null, + "grid_area": null, + "grid_template_columns": null, + "flex": null, + "_model_name": "LayoutModel", + "justify_items": null, + "grid_row": null, + "max_height": null, + "align_content": null, + "visibility": null, + "align_self": null, + "height": null, + "min_height": null, + "padding": null, + "grid_auto_rows": null, + "grid_gap": null, + "max_width": null, + "order": null, + "_view_module_version": "1.2.0", + "grid_template_areas": null, + "object_position": null, + "object_fit": null, + "grid_auto_columns": null, + "margin": null, + "display": null, + "left": null + } + }, + "d886e997192d4b58ac168d6ebda00166": { + "model_module": "@jupyter-widgets/controls", + "model_name": "IntProgressModel", + "state": { + "_view_name": "ProgressView", + "style": "IPY_MODEL_1da9f979d2b94ad6868b28a53831df83", + "_dom_classes": [], + "description": "", + "_model_name": "IntProgressModel", + "bar_style": "info", + "max": 1, + "_view_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "value": 1, + "_view_count": null, + "_view_module_version": "1.5.0", + "orientation": "horizontal", + "min": 0, + "description_tooltip": null, + "_model_module": "@jupyter-widgets/controls", + "layout": "IPY_MODEL_e6824aa0701240b1a9fa163b40342f00" + } + }, + "d55e87faaf1f493ca6eef267985d763c": { + "model_module": "@jupyter-widgets/controls", + "model_name": "HTMLModel", + "state": { + "_view_name": "HTMLView", + "style": "IPY_MODEL_7974262e11ca430b9dd8ba704abcac1a", + "_dom_classes": [], + "description": "", + "_model_name": "HTMLModel", + "placeholder": "​", + "_view_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "value": " 9920512/? [00:20<00:00, 725943.61it/s]", + "_view_count": null, + "_view_module_version": "1.5.0", + "description_tooltip": null, + "_model_module": "@jupyter-widgets/controls", + "layout": "IPY_MODEL_d715272dfc5e4ecb9198fee692e6ff32" + } + }, + "1da9f979d2b94ad6868b28a53831df83": { + "model_module": "@jupyter-widgets/controls", + "model_name": "ProgressStyleModel", + "state": { + "_view_name": "StyleView", + "_model_name": "ProgressStyleModel", + "description_width": "initial", + "_view_module": "@jupyter-widgets/base", + "_model_module_version": "1.5.0", + "_view_count": null, + "_view_module_version": "1.2.0", + "bar_color": null, + "_model_module": "@jupyter-widgets/controls" + } + }, + "e6824aa0701240b1a9fa163b40342f00": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "state": { + "_view_name": "LayoutView", + "grid_template_rows": null, + "right": null, + "justify_content": null, + "_view_module": "@jupyter-widgets/base", + "overflow": null, + "_model_module_version": "1.2.0", + "_view_count": null, + "flex_flow": null, + "width": null, + "min_width": null, + "border": null, + "align_items": null, + "bottom": null, + "_model_module": "@jupyter-widgets/base", + "top": null, + "grid_column": null, + "overflow_y": null, + "overflow_x": null, + "grid_auto_flow": null, + "grid_area": null, + "grid_template_columns": null, + "flex": null, + "_model_name": "LayoutModel", + "justify_items": null, + "grid_row": null, + "max_height": null, + "align_content": null, + "visibility": null, + "align_self": null, + "height": null, + "min_height": null, + "padding": null, + "grid_auto_rows": null, + "grid_gap": null, + "max_width": null, + "order": null, + "_view_module_version": "1.2.0", + "grid_template_areas": null, + "object_position": null, + "object_fit": null, + "grid_auto_columns": null, + "margin": null, + "display": null, + "left": null + } + }, + "7974262e11ca430b9dd8ba704abcac1a": { + "model_module": "@jupyter-widgets/controls", + "model_name": "DescriptionStyleModel", + "state": { + "_view_name": "StyleView", + "_model_name": "DescriptionStyleModel", + "description_width": "", + "_view_module": "@jupyter-widgets/base", + "_model_module_version": "1.5.0", + "_view_count": null, + "_view_module_version": "1.2.0", + "_model_module": "@jupyter-widgets/controls" + } + }, + "d715272dfc5e4ecb9198fee692e6ff32": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "state": { + "_view_name": "LayoutView", + "grid_template_rows": null, + "right": null, + "justify_content": null, + "_view_module": "@jupyter-widgets/base", + "overflow": null, + "_model_module_version": "1.2.0", + "_view_count": null, + "flex_flow": null, + "width": null, + "min_width": null, + "border": null, + "align_items": null, + "bottom": null, + "_model_module": "@jupyter-widgets/base", + "top": null, + "grid_column": null, + "overflow_y": null, + "overflow_x": null, + "grid_auto_flow": null, + "grid_area": null, + "grid_template_columns": null, + "flex": null, + "_model_name": "LayoutModel", + "justify_items": null, + "grid_row": null, + "max_height": null, + "align_content": null, + "visibility": null, + "align_self": null, + "height": null, + "min_height": null, + "padding": null, + "grid_auto_rows": null, + "grid_gap": null, + "max_width": null, + "order": null, + "_view_module_version": "1.2.0", + "grid_template_areas": null, + "object_position": null, + "object_fit": null, + "grid_auto_columns": null, + "margin": null, + "display": null, + "left": null + } + }, + "35dd20c07f6243b4b0649f854c87b9c0": { + "model_module": "@jupyter-widgets/controls", + "model_name": "HBoxModel", + "state": { + "_view_name": "HBoxView", + "_dom_classes": [], + "_model_name": "HBoxModel", + "_view_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_view_count": null, + "_view_module_version": "1.5.0", + "box_style": "", + "layout": "IPY_MODEL_e91138ae826346f4996964b7012b2756", + "_model_module": "@jupyter-widgets/controls", + "children": [ + "IPY_MODEL_cb8d16f35d30428ab2a7679d534b78ec", + "IPY_MODEL_744cad24eba3434e9a30b2f7cafd20f9" + ] + } + }, + "e91138ae826346f4996964b7012b2756": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "state": { + "_view_name": "LayoutView", + "grid_template_rows": null, + "right": null, + "justify_content": null, + "_view_module": "@jupyter-widgets/base", + "overflow": null, + "_model_module_version": "1.2.0", + "_view_count": null, + "flex_flow": null, + "width": null, + "min_width": null, + "border": null, + "align_items": null, + "bottom": null, + "_model_module": "@jupyter-widgets/base", + "top": null, + "grid_column": null, + "overflow_y": null, + "overflow_x": null, + "grid_auto_flow": null, + "grid_area": null, + "grid_template_columns": null, + "flex": null, + "_model_name": "LayoutModel", + "justify_items": null, + "grid_row": null, + "max_height": null, + "align_content": null, + "visibility": null, + "align_self": null, + "height": null, + "min_height": null, + "padding": null, + "grid_auto_rows": null, + "grid_gap": null, + "max_width": null, + "order": null, + "_view_module_version": "1.2.0", + "grid_template_areas": null, + "object_position": null, + "object_fit": null, + "grid_auto_columns": null, + "margin": null, + "display": null, + "left": null + } + }, + "cb8d16f35d30428ab2a7679d534b78ec": { + "model_module": "@jupyter-widgets/controls", + "model_name": "IntProgressModel", + "state": { + "_view_name": "ProgressView", + "style": "IPY_MODEL_132f4710cf6848988dfbe11b4979e486", + "_dom_classes": [], + "description": " 0%", + "_model_name": "IntProgressModel", + "bar_style": "info", + "max": 1, + "_view_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "value": 0, + "_view_count": null, + "_view_module_version": "1.5.0", + "orientation": "horizontal", + "min": 0, + "description_tooltip": null, + "_model_module": "@jupyter-widgets/controls", + "layout": "IPY_MODEL_4d193173cd2f4310b333c9bf87fe0c25" + } + }, + "744cad24eba3434e9a30b2f7cafd20f9": { + "model_module": "@jupyter-widgets/controls", + "model_name": "HTMLModel", + "state": { + "_view_name": "HTMLView", + "style": "IPY_MODEL_6abd415380aa44b2b8dbf52442819bbf", + "_dom_classes": [], + "description": "", + "_model_name": "HTMLModel", + "placeholder": "​", + "_view_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "value": " 0/28881 [00:00<?, ?it/s]", + "_view_count": null, + "_view_module_version": "1.5.0", + "description_tooltip": null, + "_model_module": "@jupyter-widgets/controls", + "layout": "IPY_MODEL_18cade4aaaf44a598568cb312682b828" + } + }, + "132f4710cf6848988dfbe11b4979e486": { + "model_module": "@jupyter-widgets/controls", + "model_name": "ProgressStyleModel", + "state": { + "_view_name": "StyleView", + "_model_name": "ProgressStyleModel", + "description_width": "initial", + "_view_module": "@jupyter-widgets/base", + "_model_module_version": "1.5.0", + "_view_count": null, + "_view_module_version": "1.2.0", + "bar_color": null, + "_model_module": "@jupyter-widgets/controls" + } + }, + "4d193173cd2f4310b333c9bf87fe0c25": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "state": { + "_view_name": "LayoutView", + "grid_template_rows": null, + "right": null, + "justify_content": null, + "_view_module": "@jupyter-widgets/base", + "overflow": null, + "_model_module_version": "1.2.0", + "_view_count": null, + "flex_flow": null, + "width": null, + "min_width": null, + "border": null, + "align_items": null, + "bottom": null, + "_model_module": "@jupyter-widgets/base", + "top": null, + "grid_column": null, + "overflow_y": null, + "overflow_x": null, + "grid_auto_flow": null, + "grid_area": null, + "grid_template_columns": null, + "flex": null, + "_model_name": "LayoutModel", + "justify_items": null, + "grid_row": null, + "max_height": null, + "align_content": null, + "visibility": null, + "align_self": null, + "height": null, + "min_height": null, + "padding": null, + "grid_auto_rows": null, + "grid_gap": null, + "max_width": null, + "order": null, + "_view_module_version": "1.2.0", + "grid_template_areas": null, + "object_position": null, + "object_fit": null, + "grid_auto_columns": null, + "margin": null, + "display": null, + "left": null + } + }, + "6abd415380aa44b2b8dbf52442819bbf": { + "model_module": "@jupyter-widgets/controls", + "model_name": "DescriptionStyleModel", + "state": { + "_view_name": "StyleView", + "_model_name": "DescriptionStyleModel", + "description_width": "", + "_view_module": "@jupyter-widgets/base", + "_model_module_version": "1.5.0", + "_view_count": null, + "_view_module_version": "1.2.0", + "_model_module": "@jupyter-widgets/controls" + } + }, + "18cade4aaaf44a598568cb312682b828": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "state": { + "_view_name": "LayoutView", + "grid_template_rows": null, + "right": null, + "justify_content": null, + "_view_module": "@jupyter-widgets/base", + "overflow": null, + "_model_module_version": "1.2.0", + "_view_count": null, + "flex_flow": null, + "width": null, + "min_width": null, + "border": null, + "align_items": null, + "bottom": null, + "_model_module": "@jupyter-widgets/base", + "top": null, + "grid_column": null, + "overflow_y": null, + "overflow_x": null, + "grid_auto_flow": null, + "grid_area": null, + "grid_template_columns": null, + "flex": null, + "_model_name": "LayoutModel", + "justify_items": null, + "grid_row": null, + "max_height": null, + "align_content": null, + "visibility": null, + "align_self": null, + "height": null, + "min_height": null, + "padding": null, + "grid_auto_rows": null, + "grid_gap": null, + "max_width": null, + "order": null, + "_view_module_version": "1.2.0", + "grid_template_areas": null, + "object_position": null, + "object_fit": null, + "grid_auto_columns": null, + "margin": null, + "display": null, + "left": null + } + }, + "1c2abbcdb94e49d390b8a136014c2c9e": { + "model_module": "@jupyter-widgets/controls", + "model_name": "HBoxModel", + "state": { + "_view_name": "HBoxView", + "_dom_classes": [], + "_model_name": "HBoxModel", + "_view_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_view_count": null, + "_view_module_version": "1.5.0", + "box_style": "", + "layout": "IPY_MODEL_4cc0f910eba24eb98804a3b715319b33", + "_model_module": "@jupyter-widgets/controls", + "children": [ + "IPY_MODEL_2aca8cfb1fee4035b3fd4406349aa8a4", + "IPY_MODEL_d753360b6296449b9ef26e552bf9840c" + ] + } + }, + "4cc0f910eba24eb98804a3b715319b33": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "state": { + "_view_name": "LayoutView", + "grid_template_rows": null, + "right": null, + "justify_content": null, + "_view_module": "@jupyter-widgets/base", + "overflow": null, + "_model_module_version": "1.2.0", + "_view_count": null, + "flex_flow": null, + "width": null, + "min_width": null, + "border": null, + "align_items": null, + "bottom": null, + "_model_module": "@jupyter-widgets/base", + "top": null, + "grid_column": null, + "overflow_y": null, + "overflow_x": null, + "grid_auto_flow": null, + "grid_area": null, + "grid_template_columns": null, + "flex": null, + "_model_name": "LayoutModel", + "justify_items": null, + "grid_row": null, + "max_height": null, + "align_content": null, + "visibility": null, + "align_self": null, + "height": null, + "min_height": null, + "padding": null, + "grid_auto_rows": null, + "grid_gap": null, + "max_width": null, + "order": null, + "_view_module_version": "1.2.0", + "grid_template_areas": null, + "object_position": null, + "object_fit": null, + "grid_auto_columns": null, + "margin": null, + "display": null, + "left": null + } + }, + "2aca8cfb1fee4035b3fd4406349aa8a4": { + "model_module": "@jupyter-widgets/controls", + "model_name": "IntProgressModel", + "state": { + "_view_name": "ProgressView", + "style": "IPY_MODEL_0bf4cf6348ad400583c62ad82dbcedff", + "_dom_classes": [], + "description": "", + "_model_name": "IntProgressModel", + "bar_style": "success", + "max": 1, + "_view_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "value": 1, + "_view_count": null, + "_view_module_version": "1.5.0", + "orientation": "horizontal", + "min": 0, + "description_tooltip": null, + "_model_module": "@jupyter-widgets/controls", + "layout": "IPY_MODEL_d300af4165b4494e978e4d0f506daa22" + } + }, + "d753360b6296449b9ef26e552bf9840c": { + "model_module": "@jupyter-widgets/controls", + "model_name": "HTMLModel", + "state": { + "_view_name": "HTMLView", + "style": "IPY_MODEL_cd2e1593d7144ff9a6920e6862021776", + "_dom_classes": [], + "description": "", + "_model_name": "HTMLModel", + "placeholder": "​", + "_view_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "value": " 1654784/? [00:07<00:00, 221979.13it/s]", + "_view_count": null, + "_view_module_version": "1.5.0", + "description_tooltip": null, + "_model_module": "@jupyter-widgets/controls", + "layout": "IPY_MODEL_c632e4cf62d04732b80c543df910793f" + } + }, + "0bf4cf6348ad400583c62ad82dbcedff": { + "model_module": "@jupyter-widgets/controls", + "model_name": "ProgressStyleModel", + "state": { + "_view_name": "StyleView", + "_model_name": "ProgressStyleModel", + "description_width": "initial", + "_view_module": "@jupyter-widgets/base", + "_model_module_version": "1.5.0", + "_view_count": null, + "_view_module_version": "1.2.0", + "bar_color": null, + "_model_module": "@jupyter-widgets/controls" + } + }, + "d300af4165b4494e978e4d0f506daa22": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "state": { + "_view_name": "LayoutView", + "grid_template_rows": null, + "right": null, + "justify_content": null, + "_view_module": "@jupyter-widgets/base", + "overflow": null, + "_model_module_version": "1.2.0", + "_view_count": null, + "flex_flow": null, + "width": null, + "min_width": null, + "border": null, + "align_items": null, + "bottom": null, + "_model_module": "@jupyter-widgets/base", + "top": null, + "grid_column": null, + "overflow_y": null, + "overflow_x": null, + "grid_auto_flow": null, + "grid_area": null, + "grid_template_columns": null, + "flex": null, + "_model_name": "LayoutModel", + "justify_items": null, + "grid_row": null, + "max_height": null, + "align_content": null, + "visibility": null, + "align_self": null, + "height": null, + "min_height": null, + "padding": null, + "grid_auto_rows": null, + "grid_gap": null, + "max_width": null, + "order": null, + "_view_module_version": "1.2.0", + "grid_template_areas": null, + "object_position": null, + "object_fit": null, + "grid_auto_columns": null, + "margin": null, + "display": null, + "left": null + } + }, + "cd2e1593d7144ff9a6920e6862021776": { + "model_module": "@jupyter-widgets/controls", + "model_name": "DescriptionStyleModel", + "state": { + "_view_name": "StyleView", + "_model_name": "DescriptionStyleModel", + "description_width": "", + "_view_module": "@jupyter-widgets/base", + "_model_module_version": "1.5.0", + "_view_count": null, + "_view_module_version": "1.2.0", + "_model_module": "@jupyter-widgets/controls" + } + }, + "c632e4cf62d04732b80c543df910793f": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "state": { + "_view_name": "LayoutView", + "grid_template_rows": null, + "right": null, + "justify_content": null, + "_view_module": "@jupyter-widgets/base", + "overflow": null, + "_model_module_version": "1.2.0", + "_view_count": null, + "flex_flow": null, + "width": null, + "min_width": null, + "border": null, + "align_items": null, + "bottom": null, + "_model_module": "@jupyter-widgets/base", + "top": null, + "grid_column": null, + "overflow_y": null, + "overflow_x": null, + "grid_auto_flow": null, + "grid_area": null, + "grid_template_columns": null, + "flex": null, + "_model_name": "LayoutModel", + "justify_items": null, + "grid_row": null, + "max_height": null, + "align_content": null, + "visibility": null, + "align_self": null, + "height": null, + "min_height": null, + "padding": null, + "grid_auto_rows": null, + "grid_gap": null, + "max_width": null, + "order": null, + "_view_module_version": "1.2.0", + "grid_template_areas": null, + "object_position": null, + "object_fit": null, + "grid_auto_columns": null, + "margin": null, + "display": null, + "left": null + } + }, + "6e37ec9443f7406f9fcc18a3a5e01466": { + "model_module": "@jupyter-widgets/controls", + "model_name": "HBoxModel", + "state": { + "_view_name": "HBoxView", + "_dom_classes": [], + "_model_name": "HBoxModel", + "_view_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_view_count": null, + "_view_module_version": "1.5.0", + "box_style": "", + "layout": "IPY_MODEL_0e6e86c2b233430fa57adf412d057165", + "_model_module": "@jupyter-widgets/controls", + "children": [ + "IPY_MODEL_74b4c3177f01400a99b9bfca7a995970", + "IPY_MODEL_3132be86fcaf45f8b861ce56cdbf1527" + ] + } + }, + "0e6e86c2b233430fa57adf412d057165": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "state": { + "_view_name": "LayoutView", + "grid_template_rows": null, + "right": null, + "justify_content": null, + "_view_module": "@jupyter-widgets/base", + "overflow": null, + "_model_module_version": "1.2.0", + "_view_count": null, + "flex_flow": null, + "width": null, + "min_width": null, + "border": null, + "align_items": null, + "bottom": null, + "_model_module": "@jupyter-widgets/base", + "top": null, + "grid_column": null, + "overflow_y": null, + "overflow_x": null, + "grid_auto_flow": null, + "grid_area": null, + "grid_template_columns": null, + "flex": null, + "_model_name": "LayoutModel", + "justify_items": null, + "grid_row": null, + "max_height": null, + "align_content": null, + "visibility": null, + "align_self": null, + "height": null, + "min_height": null, + "padding": null, + "grid_auto_rows": null, + "grid_gap": null, + "max_width": null, + "order": null, + "_view_module_version": "1.2.0", + "grid_template_areas": null, + "object_position": null, + "object_fit": null, + "grid_auto_columns": null, + "margin": null, + "display": null, + "left": null + } + }, + "74b4c3177f01400a99b9bfca7a995970": { + "model_module": "@jupyter-widgets/controls", + "model_name": "IntProgressModel", + "state": { + "_view_name": "ProgressView", + "style": "IPY_MODEL_40add84ad252405aa560250c61e39014", + "_dom_classes": [], + "description": "", + "_model_name": "IntProgressModel", + "bar_style": "success", + "max": 1, + "_view_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "value": 1, + "_view_count": null, + "_view_module_version": "1.5.0", + "orientation": "horizontal", + "min": 0, + "description_tooltip": null, + "_model_module": "@jupyter-widgets/controls", + "layout": "IPY_MODEL_6f67777b35264b519f59f1a58e453300" + } + }, + "3132be86fcaf45f8b861ce56cdbf1527": { + "model_module": "@jupyter-widgets/controls", + "model_name": "HTMLModel", + "state": { + "_view_name": "HTMLView", + "style": "IPY_MODEL_f25f6758ff4f40bab681d582f7c48942", + "_dom_classes": [], + "description": "", + "_model_name": "HTMLModel", + "placeholder": "​", + "_view_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "value": " 8192/? [00:06<00:00, 1233.48it/s]", + "_view_count": null, + "_view_module_version": "1.5.0", + "description_tooltip": null, + "_model_module": "@jupyter-widgets/controls", + "layout": "IPY_MODEL_8238a401c79749798c6546ac80a80b0e" + } + }, + "40add84ad252405aa560250c61e39014": { + "model_module": "@jupyter-widgets/controls", + "model_name": "ProgressStyleModel", + "state": { + "_view_name": "StyleView", + "_model_name": "ProgressStyleModel", + "description_width": "initial", + "_view_module": "@jupyter-widgets/base", + "_model_module_version": "1.5.0", + "_view_count": null, + "_view_module_version": "1.2.0", + "bar_color": null, + "_model_module": "@jupyter-widgets/controls" + } + }, + "6f67777b35264b519f59f1a58e453300": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "state": { + "_view_name": "LayoutView", + "grid_template_rows": null, + "right": null, + "justify_content": null, + "_view_module": "@jupyter-widgets/base", + "overflow": null, + "_model_module_version": "1.2.0", + "_view_count": null, + "flex_flow": null, + "width": null, + "min_width": null, + "border": null, + "align_items": null, + "bottom": null, + "_model_module": "@jupyter-widgets/base", + "top": null, + "grid_column": null, + "overflow_y": null, + "overflow_x": null, + "grid_auto_flow": null, + "grid_area": null, + "grid_template_columns": null, + "flex": null, + "_model_name": "LayoutModel", + "justify_items": null, + "grid_row": null, + "max_height": null, + "align_content": null, + "visibility": null, + "align_self": null, + "height": null, + "min_height": null, + "padding": null, + "grid_auto_rows": null, + "grid_gap": null, + "max_width": null, + "order": null, + "_view_module_version": "1.2.0", + "grid_template_areas": null, + "object_position": null, + "object_fit": null, + "grid_auto_columns": null, + "margin": null, + "display": null, + "left": null + } + }, + "f25f6758ff4f40bab681d582f7c48942": { + "model_module": "@jupyter-widgets/controls", + "model_name": "DescriptionStyleModel", + "state": { + "_view_name": "StyleView", + "_model_name": "DescriptionStyleModel", + "description_width": "", + "_view_module": "@jupyter-widgets/base", + "_model_module_version": "1.5.0", + "_view_count": null, + "_view_module_version": "1.2.0", + "_model_module": "@jupyter-widgets/controls" + } + }, + "8238a401c79749798c6546ac80a80b0e": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "state": { + "_view_name": "LayoutView", + "grid_template_rows": null, + "right": null, + "justify_content": null, + "_view_module": "@jupyter-widgets/base", + "overflow": null, + "_model_module_version": "1.2.0", + "_view_count": null, + "flex_flow": null, + "width": null, + "min_width": null, + "border": null, + "align_items": null, + "bottom": null, + "_model_module": "@jupyter-widgets/base", + "top": null, + "grid_column": null, + "overflow_y": null, + "overflow_x": null, + "grid_auto_flow": null, + "grid_area": null, + "grid_template_columns": null, + "flex": null, + "_model_name": "LayoutModel", + "justify_items": null, + "grid_row": null, + "max_height": null, + "align_content": null, + "visibility": null, + "align_self": null, + "height": null, + "min_height": null, + "padding": null, + "grid_auto_rows": null, + "grid_gap": null, + "max_width": null, + "order": null, + "_view_module_version": "1.2.0", + "grid_template_areas": null, + "object_position": null, + "object_fit": null, + "grid_auto_columns": null, + "margin": null, + "display": null, + "left": null + } + } + } + } }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Week 14: Intriguing Properties of Neural Networks\n", - "There are two types of adversarial models that we will create. First, we will reproduce some of the results of Szegedy via targeted misclassifications, and also test the cross-model susceptibility. An optional second section explores the development of a different method for generating adversarial examples called the Fast Gradient Sign Method.\n", - "\n", - "### Part 1: Targeted Examples\n", - "\n", - "Now we will find inputs that will cause the network to mispredict in such a way that it outputs what we want it to." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Config = namedtuple(\n", - " \"Config\",\n", - " (\n", - " \"batch_size\",\n", - " \"epochs\",\n", - " \"lr\",\n", - " \"lr_gamma\",\n", - " \"input_size\",\n", - " \"output_size\",\n", - " \"w_decay\",\n", - " \"r_weight\",\n", - " \"device\",\n", - " )\n", - ")\n", - "\n", - "config = Config(\n", - " batch_size=64,\n", - " epochs=25,\n", - " # Learning rate\n", - " lr=1e-4,\n", - " # Learning rate decay factor\n", - " lr_gamma=0.88,\n", - " input_size=28*28,\n", - " output_size=10,\n", - " # L2 regularization strength\n", - " w_decay=1e-4,\n", - " # c from section 4.1 of the paper\n", - " r_weight=0.15,\n", - " device=device,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Data\n", - "\n", - "We'll use the full MNIST datasets for training and testing." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "os.makedirs(\"./data\", exist_ok=True)\n", - "# MNIST Datasets\n", - "train_ds = tv.datasets.MNIST(\n", - " \"./data\",\n", - " transform=tv.transforms.ToTensor(),\n", - " train=True,\n", - " download=True\n", - ")\n", - "test_ds = tv.datasets.MNIST(\n", - " \"./data\",\n", - " transform=tv.transforms.ToTensor(),\n", - " train=False,\n", - ")\n", - "# MNIST Data Loaders\n", - "train_dl = torch.utils.data.DataLoader(\n", - " train_ds,\n", - " batch_size=config.batch_size,\n", - " drop_last=True,\n", - " shuffle=True,\n", - " num_workers=4,\n", - ")\n", - "test_dl = torch.utils.data.DataLoader(\n", - " test_ds,\n", - " batch_size=config.batch_size,\n", - " shuffle=True\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Helpers\n", - "\n", - "These are some helper functions that you don't need to worry about" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def train(model, opt, loss_func, loader, config, epoch):\n", - " it = tqdm.tqdm(loader, ncols=80, desc=f\"train: {epoch + 1}/{config.epochs}\")\n", - " for imgs, targets in it:\n", - " model.train()\n", - " opt.zero_grad()\n", - " imgs = imgs.to(config.device).double()\n", - " targets = targets.to(config.device)\n", - " out = model(imgs)\n", - " loss = loss_func(out, targets)\n", - " loss.backward()\n", - " opt.step()\n", - " \n", - " \n", - "def test(model, loader, config):\n", - " correct = 0\n", - " for imgs, targets in tqdm.tqdm(loader, ncols=80, desc=\"Test\"):\n", - " model.eval()\n", - " imgs = imgs.to(config.device).double()\n", - " targets = targets.to(config.device)\n", - " with torch.no_grad():\n", - " out = model(imgs)\n", - " pred = logits_to_label(out)\n", - " correct += (pred == targets).sum()\n", - " N = len(loader.dataset)\n", - " acc = float(correct) / N\n", - " print(f\"Test acc: {acc:.02%}\")\n", - "\n", - " \n", - "def fetch_trained_model(model_class, train_loader, test_loader, load_from_file, path, config):\n", - " \"\"\"Either fetch a stored model from disk or train one from scratch\"\"\"\n", - " model = model_class(config).double().to(config.device)\n", - " if os.path.isfile(path) and load_from_file:\n", - " model.load_state_dict(torch.load(path))\n", - " test(model, test_loader, config)\n", - " else:\n", - " opt = torch.optim.Adam(model.parameters(), config.lr, weight_decay=config.w_decay)\n", - " # Learning rate adjuster\n", - " sched = torch.optim.lr_scheduler.StepLR(opt, 1, config.lr_gamma)\n", - " criterion = nn.CrossEntropyLoss()\n", - "\n", - " for ep in range(config.epochs):\n", - " train(model, opt, criterion, train_loader, config, ep)\n", - " test(model, test_loader, config)\n", - " sched.step()\n", - " torch.save(model.state_dict(), path)\n", - " model.eval()\n", - " print(model)\n", - " return model" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Model\n", - "We'll use one of the fully connected models from the paper." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def logits_to_label(logits):\n", - " \"\"\"\n", - " Converts the logits or log-odds output of the networks\n", - " to a class label.\n", - " \"\"\"\n", - " return torch.softmax(logits, 1).argmax(1)\n", - "\n", - "\n", - "class FCNet100(nn.Module):\n", - " \"\"\"The FC100-100-10 network from the paper\"\"\"\n", - "\n", - " def __init__(self, config):\n", - " super(FCNet100, self).__init__()\n", - " self.config = config\n", - " self.model = nn.Sequential(\n", - " nn.Linear(config.input_size, 100),\n", - " nn.LeakyReLU(0.1, inplace=True),\n", - " \n", - " nn.Linear(100, 100),\n", - " nn.LeakyReLU(0.1, inplace=True),\n", - " \n", - " nn.Linear(100, config.output_size),\n", - " )\n", - " # Note that softmax is left out of the model on purpose.\n", - " \n", - " def forward(self, x):\n", - " if isinstance(x, np.ndarray):\n", - " x = torch.tensor(x).to(self.config.device)\n", - " x = x.view(x.size(0), self.config.input_size)\n", - " # Logits\n", - " return self.model(x)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For convenience, a pre-trained set of weights are included in this repository at './data/model_1.pt'.\n", - "The code below will load them for you. Pass `False` to the `load` argument to train a fresh version\n", - "of the model. Note that `double()` has been called on the model so that all of the weights are 64-bit floats\n", - "and the model is expecting 64-bit float inputs. The model will also produce 64-bit floats as outputs.\n", - "This last part is why `double()` is called and is important later." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "# Load our first model from disk or train a fresh one\n", - "model = fetch_trained_model(\n", - " FCNet100,\n", - " train_dl,\n", - " test_dl,\n", - " True,\n", - " \"./data/model_1.pt\",\n", - " config\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Finding Adversarial Examples\n", - "\n", - "The two functions below are used to find targeted examples. Together these make up the algorithm\n", - "descriibed in Section 4.1 of the paper:\n", - "\n", - "$$\\textrm{minimize} \\quad c \\left\\lvert r \\right\\rvert + \\textrm{loss}_f(x + r, l)$$\n", - "with $$x + r \\in [0, 1]^m$$" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def adv_penalty_func(x_plus_r, *args):\n", - " \"\"\"This function is simply the penalty from Section 4.1\"\"\"\n", - " x, net, c, target, device = args\n", - " #\n", - " # TODO: fill in the missing part of the penalty\n", - " # c|r| penalty portion\n", - " size_penalty = c * ???\n", - " # x + r as a tensor\n", - " x_plus_r_tensor = torch.tensor(x_plus_r.reshape(1, 28, 28)).to(device)\n", - " # loss_f(x + r, l) penalty\n", - " logits = net(x_plus_r_tensor)\n", - " #\n", - " # TODO: What loss function should go here?\n", - " class_penalty = nn.functional.???(logits, target).item()\n", - " #\n", - " # TODO: what should be returned\n", - " return ???\n", - "\n", - "\n", - "def find_adversarial_example(\n", - " net, x, target, config, max_iterations=45_000, max_penalty_function_calls=45_000\n", - "):\n", - " \"\"\"This is the optimization problem described in Section 4.1\"\"\"\n", - " # The c weight for the L1 norm of r\n", - " c = config.r_weight\n", - " # Box constraint: [0, 1]^m\n", - " box_constraint = Bounds(np.zeros_like(x.ravel()), np.ones_like(x.ravel()))\n", - " # Target label as tensor\n", - " target = torch.tensor([target]).to(config.device, dtype=torch.int64)\n", - " # Args that will be passed to the penalty function in addition to the (x + r) array.\n", - " args = (x.flatten(), net, c, target, device)\n", - " #\n", - " # TODO: What should the initial guess for x+r be?\n", - " #\n", - " x_plus_r_initial = ???\n", - " \n", - " # minimize will be calling the penalty function, and in turn the model, a LOT.\n", - " # We use torch.no_grad() to make sure that we don't have the overhead of\n", - " # torch's autograd during those calls.\n", - " with torch.no_grad():\n", - " # We'll treat scipy's minimize as a black box and simply use it as a means to an\n", - " # end. Below, we have used the recommended settings for \"L-BFGS-B\" from the\n", - " # scipy docs.\n", - " res = minimize(\n", - " # Our penalty function from above\n", - " adv_penalty_func,\n", - " # Flatten it since minimize only works on a 1D array\n", - " x_plus_r_initial.flatten(),\n", - " args,\n", - " method='L-BFGS-B',\n", - " jac=None,\n", - " bounds=box_constraint,\n", - " tol=None,\n", - " callback=None,\n", - " options={\n", - " 'disp': None,\n", - " 'maxcor': 10,\n", - " 'ftol': 2.220446049250313e-09,\n", - " 'gtol': 1e-05,\n", - " # This is the step size used by the numerical optimizer\n", - " 'eps': 1e-9,\n", - " # The maximum number of times the penalty func can be called by minimize\n", - " 'maxfun': max_penalty_function_calls,\n", - " # The maximum number of steps taken by minimize\n", - " 'maxiter': max_iterations,\n", - " 'iprint': -1,\n", - " 'maxls': 15,\n", - " }\n", - " )\n", - " # Reshape back into an image\n", - " xr = torch.tensor(res.x.reshape(x.shape))\n", - " return xr" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Question: Now that you have seen the functions used to find the adversarial examples,\n", - "why is it important for the model to output 64-bit floats?**\n", - "\n", - "**Question: Why is x (the original image) the ideal starting point for the optimizer?**" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Starting Images\n", - "Now we will select some images from the test dataset, which the model hasn't seen, to turn into adversarial\n", - "examples." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Select N images that the model correctly classifies already\n", - "N = 16\n", - "adv_target = 8\n", - "x_imgs = []\n", - "x_labels = []\n", - "for x, label in test_ds:\n", - " if label == adv_target:\n", - " continue\n", - " x = x.to(config.device).double()\n", - " # Make sure that the model correctly classifies this x\n", - " if (logits_to_label(model(x)) == label).item():\n", - " x_imgs.append(x.cpu())\n", - " x_labels.append(label)\n", - " if len(x_imgs) >= N:\n", - " break\n", - "\n", - "x_labels = np.array(x_labels).reshape(4, 4)\n", - "grid = tv.utils.make_grid(x_imgs, nrow=4, pad_value=1)\n", - "plt.figure(figsize=(8, 8))\n", - "plt.title(\"Original Inputs\")\n", - "plt.imshow(np.transpose(grid, (1, 2, 0)))\n", - "print(x_labels)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Creating Adversaries\n", - "This code will call the functions above to create adverserial examples. Unfortunately, it\n", - "runs pretty slowly so it can take some time to find them. You can try adjusting\n", - "the `max_iterations` and `max_penalty_function_calls` parameters of\n", - "`find_adversarial_example()` to speed things up. Note that if you lower them too far you\n", - "will produce poor examples that don't fool the network or that have large distortions. You can\n", - "also adjust c to weight the size of r more or less." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "xr_imgs = []\n", - "xr_labels = []\n", - "for x in tqdm.tqdm(x_imgs, ncols=80):\n", - " xr = find_adversarial_example(model, x.numpy(), adv_target, config)\n", - " xr_imgs.append(xr)\n", - " new_label = logits_to_label(model(xr.to(config.device))).item()\n", - " xr_labels.append(new_label)\n", - "xr_labels = np.array(xr_labels).reshape(4, 4)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Adversarial Results\n", - "\n", - "If everything is working properly, all of the new images should be classified as the target class." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "grid = tv.utils.make_grid(xr_imgs, nrow=4, pad_value=1)\n", - "plt.figure(figsize=(8, 8))\n", - "plt.title(\"Adversarial Examples\")\n", - "plt.imshow(np.transpose(grid, (1, 2, 0)))\n", - "print(f\"New Labels:\\n{xr_labels}\")\n", - "\n", - "# Calculate r = (x + r) - x and display them\n", - "r_imgs = [xr - x for xr, x in zip(xr_imgs, x_imgs)]\n", - "grid = tv.utils.make_grid(r_imgs, nrow=4, pad_value=1)\n", - "plt.figure(figsize=(8, 8))\n", - "plt.title(\"Adversarial Perturbations\")\n", - "plt.imshow(np.transpose(grid, (1, 2, 0)))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Cross Network Attack\n", - "Now we'll try using the examples created using one network to attack another.\n", - "Below is a model similar to the one used above that has pre-trained weights." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class FCNet200(nn.Module):\n", - " def __init__(self, config):\n", - " super(FCNet200, self).__init__()\n", - " self.config = config\n", - " self.model = nn.Sequential(\n", - " nn.Linear(config.input_size, 200),\n", - " nn.LeakyReLU(0.1, inplace=True),\n", - " \n", - " nn.Linear(200, 200),\n", - " nn.LeakyReLU(0.1, inplace=True),\n", - " \n", - " nn.Linear(200, config.output_size),\n", - " )\n", - " \n", - " def forward(self, x):\n", - " if isinstance(x, np.ndarray):\n", - " x = torch.tensor(x).to(self.config.device)\n", - " x = x.view(x.size(0), self.config.input_size)\n", - " # Logits\n", - " return self.model(x)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This loads the pre-trained weights for the model above. If you change the `True` to `False`\n", - "it will train the model from scratch." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "model2 = fetch_trained_model(\n", - " FCNet200,\n", - " train_dl,\n", - " test_dl,\n", - " True,\n", - " \"./data/model_2.pt\",\n", - " config\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "cross_model_labels = []\n", - "for xr in x_plus_r_imgs:\n", - " label = logits_to_label(model2(xr.unsqueeze(0).to(config.device))).item()\n", - " cross_model_labels.append(label)\n", - "cross_model_labels = np.array(cross_model_labels).reshape(4, 4)\n", - "\n", - "print(f\"Cross Model Adverserial Predictions:\\n{cross_model_labels}\")\n", - "print(f\"Attack Success: {(cross_model_labels == xr_labels).sum() / N:.2%}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Bonus: Try experimenting with the second model's architecture to see if it affects the attack\n", - "success rate.**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Part 2: Fast Gradient Sign Method (Optional)\n", - "\n", - "This section implements the [Fast Gradient Sign Method](https://arxiv.org/abs/1412.6572), which is a fast alternative method for finding adversarial examples. It is untargeted, in the sense that unlike the section above, you don't get to choose what your image gets classified as.\n", - "\n", - "The following cell is group of helper functions." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n", - "\n", - "def load_dataset(torchvision_dataset):\n", - " save_dir = \"./data\"\n", - " transform_list = [transforms.ToTensor()]\n", - " transform = transforms.Compose(transform_list)\n", - " kwargs = {\n", - " \"download\": True,\n", - " \"transform\": transform,\n", - " }\n", - " train = torchvision_dataset(save_dir, train=True, **kwargs)\n", - " test = torchvision_dataset(save_dir, train=False, **kwargs)\n", - " return train, test\n", - "\n", - "\n", - "def resize_dataset(dataset, dataset_len=None):\n", - " if dataset_len is None:\n", - " return dataset\n", - " assert 0 <= dataset_len <= len(dataset)\n", - " ignored_len = len(dataset) - dataset_len\n", - " sizes = (dataset_len, ignored_len)\n", - " dataset, ignored = random_split(dataset, sizes)\n", - " return dataset\n", - "\n", - "\n", - "def make_batches(train, test, batch_size=100):\n", - " assert 1 <= batch_size <= len(test)\n", - " kwargs = {\n", - " \"batch_size\": batch_size,\n", - " \"shuffle\": True,\n", - " }\n", - " train_loader = DataLoader(train, **kwargs)\n", - " test_loader = DataLoader(test, **kwargs)\n", - " return train_loader, test_loader\n", - " \n", - "def train_model(model, device, train_loader, optimizer, criterion, num_epochs=1):\n", - " for epoch in range(num_epochs):\n", - " print(\"Training Epoch\", epoch)\n", - " for i, (images, targets) in enumerate(train_loader):\n", - " images = images.to(device)\n", - " targets = targets.to(device)\n", - "\n", - " # Forward\n", - " outputs = model(images)\n", - "\n", - " # Backward\n", - " optimizer.zero_grad()\n", - " loss = criterion(outputs, targets)\n", - " loss.backward()\n", - " optimizer.step()\n", - "\n", - "def test_model(model, device, test_loader):\n", - " correct = 0\n", - " total = 0\n", - " for i, (images, targets) in enumerate(test_loader):\n", - " images = images.to(device)\n", - " targets = targets.to(device)\n", - "\n", - " # Forward\n", - " outputs = model(images)\n", - "\n", - " # Prediction\n", - " predicted = torch.argmax(outputs, 1)\n", - " total += targets.size(0)\n", - " correct += (predicted == targets).sum().item()\n", - "\n", - " accuracy = 100 * correct / total\n", - " return accuracy" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Model Definition\n", - "This is our convolutional network that we use to train both our adversarial and non-adversarial models." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class Conv_Net(nn.Module):\n", - " def __init__(self, in_height, in_width, num_targets):\n", - " super().__init__()\n", - " # Convolutional parameters\n", - " kernel_size = 4\n", - " stride = 1\n", - "\n", - " # Pool layer and its parameters\n", - " pool_kernel_size = 2\n", - " pool_stride = pool_kernel_size # When these values are the same it reduces the image by half\n", - " self.pool = nn.MaxPool2d(pool_kernel_size, pool_stride)\n", - "\n", - " in_channels_2 = 1\n", - " out_channels_2 = 10\n", - " self.conv2 = nn.Conv2d(\n", - " in_channels_2,\n", - " out_channels_2, \n", - " kernel_size=kernel_size, \n", - " stride=stride, \n", - " )\n", - " self.bn2 = nn.BatchNorm2d(out_channels_2)\n", - "\n", - " self.conv2_height = height - kernel_size + 1\n", - " self.conv2_width = width - kernel_size + 1\n", - " self.conv2_size = self.conv2_height * self.conv2_width * out_channels_2\n", - "\n", - " self.pool1_height = int(self.conv2_height / pool_kernel_size)\n", - " self.pool1_width = int(self.conv2_height / pool_kernel_size)\n", - " self.pool1_size = self.pool1_height * self.pool1_width * out_channels_2\n", - "\n", - " out_channels_3 = 10\n", - " self.conv3 = nn.Conv2d(\n", - " out_channels_2, \n", - " out_channels_3, \n", - " kernel_size=kernel_size, \n", - " stride=stride, \n", - " )\n", - " self.bn3 = nn.BatchNorm2d(out_channels_3)\n", - "\n", - " self.conv3_height = self.pool1_height - kernel_size + 1\n", - " self.conv3_width = self.pool1_width - kernel_size + 1\n", - " self.conv3_size = self.conv3_height * self.conv3_width * out_channels_3\n", - "\n", - " self.pool2_height = int(self.conv3_height / pool_kernel_size)\n", - " self.pool2_width = int(self.conv3_height / pool_kernel_size)\n", - " self.pool2_size = self.pool2_height * self.pool2_width * out_channels_2\n", - "\n", - " in_features_4 = self.pool2_size\n", - " out_features_4 = int(in_features_4 / 2)\n", - " self.fc1 = nn.Linear(in_features_4, out_features_4)\n", - " self.bn4 = nn.BatchNorm1d(out_features_4)\n", - "\n", - " out_features_5 = int(out_features_4 / 2)\n", - " self.fc2 = nn.Linear(out_features_4, out_features_5)\n", - " self.bn5 = nn.BatchNorm1d(out_features_5)\n", - "\n", - " self.fc3 = nn.Linear(out_features_5, num_targets)\n", - "\n", - " def forward(self, x):\n", - " # First convolution and pooling\n", - " x = self.conv2(x)\n", - " x = F.relu(x)\n", - " x = self.pool(x)\n", - " x = self.bn2(x)\n", - "\n", - " # Second convolution and pooling\n", - " x = self.conv3(x)\n", - " x = F.relu(x)\n", - " x = self.pool(x)\n", - " x = self.bn3(x)\n", - "\n", - " # Flattening convolutional layer for fully connected layer\n", - " batch_size = -1\n", - " x = x.view(batch_size, self.pool2_size) # This value was calculated in __init__\n", - "\n", - " # First fcl\n", - " x = self.fc1(x)\n", - " x = F.relu(x)\n", - " x = self.bn4(x)\n", - "\n", - " # Second fcl\n", - " x = self.fc2(x)\n", - " x = F.relu(x)\n", - " x = self.bn5(x)\n", - " \n", - " # Output fcl\n", - " x = self.fc3(x) \n", - " x = F.log_softmax(x, dim=1)\n", - " return x" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Non-adversarial Model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Load data\n", - "torchvision_dataset = datasets.MNIST\n", - "train, test = load_dataset(torchvision_dataset)\n", - "\n", - "# Find dimensions\n", - "height, width = train.data[0].shape\n", - "num_targets = len(set(train.targets.numpy()))\n", - "\n", - "# Setting the size to \"None\" will use all instances\n", - "train_len, test_len = 8000, 2000\n", - "train = resize_dataset(train, train_len)\n", - "test = resize_dataset(test, test_len)\n", - "\n", - "# Batch\n", - "batch_size = 100\n", - "train_loader, test_loader = make_batches(train, test, batch_size)\n", - "\n", - "# Make model\n", - "good_model = Conv_Net(height, width, num_targets).to(device)\n", - "\n", - "# Optimizer\n", - "learning_rate = 0.001\n", - "optimizer = optim.Adam(good_model.parameters(), lr = learning_rate)\n", - "\n", - "# Criterion\n", - "criterion = nn.CrossEntropyLoss()\n", - "\n", - "# Train\n", - "num_epochs = 10\n", - "train_model(good_model, device, train_loader, optimizer, criterion, num_epochs)\n", - "\n", - "# Test\n", - "accuracy = test_model(good_model, device, test_loader)\n", - "print(f\"Original Accuracy: {accuracy} %\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adversarial parameters" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "learning_rate = 0.001\n", - "# This is the length of the epsilon_list you are going to create. If your list is longer or shorter you must reflect that change in this variable.\n", - "num_epsilons = 5 \n", - "\n", - "# Make a list of epsilon values for the attack\n", - "epsilon_list = [i / (num_epsilons * 2) for i in range(num_epsilons)]\n", - "\n", - "# This is storage for samples from our adversarial model.\n", - "examples = {\n", - " epsilon: {\n", - " \"original_images\": [],\n", - " \"adversarial_images\": [],\n", - " \"original_predictions\": [],\n", - " \"adversarial_predictions\": []\n", - " }\n", - " for epsilon in epsilon_list\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Q: Below you will set the clamp min and max values on the line \"torch.clamp(perturbated_images, clamp_min, clamp_max)\". Why do we need to clamp and what would the effect of using a value outside the range be?\n", - "\n", - "A:\n", - " \n", - "Q: This is very subtle. Why are we adding our perturbations instead of subtracting them? The goal should be to go in the opposite direction of the gradient. How is addition moving us uphill?\n", - "\n", - "A:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adversarial Model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for epsilon in epsilon_list:\n", - " print(f\"Epsilon: {epsilon}\")\n", - " \n", - " # Train Attack model\n", - " attack_model = Conv_Net(height, width, num_targets).to(device)\n", - "\n", - " optimizer = optim.Adam(attack_model.parameters(), lr = learning_rate)\n", - " criterion = nn.CrossEntropyLoss()\n", - "\n", - " train_model(attack_model, device, train_loader, optimizer, criterion, num_epochs)\n", - " pre_attack_accuracy = test_model(attack_model, device, test_loader)\n", - " print(f\"Pre-attack Accuracy: {pre_attack_accuracy} %\")\n", - "\n", - " correct = 0\n", - " total = 0\n", - " sample = True \n", - " \n", - " for i, (images, targets) in enumerate(test_loader):\n", - " images = images.to(device)\n", - " # Enable finding gradient w.r.t the inputs.\n", - " images.requires_grad = True \n", - " targets = targets.to(device)\n", - "\n", - " # Forward\n", - " outputs = attack_model(images)\n", - " predicted = torch.argmax(outputs, 1)\n", - "\n", - " # Backward\n", - " optimizer.zero_grad()\n", - " loss = criterion(outputs, targets)\n", - " loss.backward()\n", - "\n", - " image_grad = images.grad \n", - "\n", - " grad_sign = image_grad.sign()\n", - " perturbation = ???\n", - " perturbed_images = ???\n", - " clamp_min = ???\n", - " clamp_max = ??? # TODO\n", - " perturbed_images = torch.clamp(perturbed_images, clamp_min, clamp_max)\n", - "\n", - " attack_output = attack_model(perturbed_images)\n", - " attack_predicted = torch.argmax(attack_output, 1)\n", - " \n", - " total += targets.size(0)\n", - " correct += (attack_predicted == targets).sum().item()\n", - " \n", - " if sample:\n", - " sample = False\n", - " \n", - " with torch.no_grad():\n", - " examples[epsilon][\"original_images\"] = images.cpu().numpy()[:num_epsilons]\n", - " examples[epsilon][\"adversarial_images\"] = perturbed_images.cpu().numpy()[:num_epsilons]\n", - " examples[epsilon][\"original_predictions\"] = predicted.cpu().numpy()[:num_epsilons]\n", - " examples[epsilon][\"adversarial_predictions\"] = attack_predicted.cpu().numpy()[:num_epsilons]\n", - " \n", - " \n", - " post_attack_accuracy = correct / total * 100\n", - " print(f\"Post-attack Accuracy: {post_attack_accuracy} %\")\n", - " print()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Q: If you were a real attacker that relied on misclassification, how would you deal with the cases where the class was retained?\n", - "\n", - "A:\n", - "\n", - "Q: Go back and look at where we set grad_sign. If we had multiplied the gradient instead of the gradient sign by the epsilon value our performance would not be as high. Why?\n", - "\n", - "A:\n", - "\n", - "Q: Our purturbations depended on the gradient of the image. Why is the gradient necessary as opposed to some non-gradient dependent function the input image?\n", - "\n", - "A:\n", - "\n", - "Q: You can misclassify an image by just mutating it. What is the point of this model? \n", - "\n", - "A:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Plot sample of adversarial images" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plot_dim = 10, 12\n", - "plt.figure(figsize=plot_dim)\n", - "\n", - "plot_index = 1\n", - "for row in range(num_epsilons):\n", - " for column in range(num_epsilons):\n", - " plt.subplot(num_epsilons, num_epsilons, plot_index)\n", - "\n", - " plt.xticks([], [])\n", - " plt.yticks([], [])\n", - "\n", - " epsilon = epsilon_list[row]\n", - " if column == 0:\n", - " plt.ylabel(\"Eps: {}\".format(epsilon), fontsize=14)\n", - "\n", - " adversarial_image = examples[epsilon][\"adversarial_images\"][column].squeeze()\n", - " original_prediction = examples[epsilon][\"original_predictions\"][column].item()\n", - " adversarial_prediction = examples[epsilon][\"adversarial_predictions\"][column].item()\n", - "\n", - " plt.title(\"{} -> {}\".format(original_prediction, adversarial_prediction))\n", - " plt.imshow(adversarial_image, cmap=\"gray\")\n", - "\n", - " plot_index += 1\n", - "plt.tight_layout()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Part 2: Bonus\n", - "Plot just the purturbations. You can modify the code from the cell above but put the new plotting code in this cell below so that both copies are retained." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.7" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} + "cells": [ + { + "cell_type": "code", + "metadata": { + "id": "390vLd1cKNaL", + "colab_type": "code", + "colab": {} + }, + "source": [ + "import os\n", + "from collections import namedtuple\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import torch\n", + "import torch.nn as nn\n", + "import torchvision as tv\n", + "import tqdm\n", + "from scipy.optimize import Bounds, minimize\n", + "from torch import optim\n", + "from torch.autograd import Variable\n", + "from torch.functional import F\n", + "from torch.utils.data import DataLoader, Dataset, TensorDataset, random_split\n", + "from torchvision import datasets, transforms" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "S1cr2BRwKNaO", + "colab_type": "text" + }, + "source": [ + "# Week 14: Intriguing Properties of Neural Networks\n", + "There are two types of adversarial models that we will create. First, we will reproduce some of the results of Szegedy via targeted misclassifications, and also test the cross-model susceptibility. An optional second section explores the development of a different method for generating adversarial examples called the Fast Gradient Sign Method.\n", + "\n", + "### Part 1: Targeted Examples\n", + "\n", + "Now we will find inputs that will cause the network to mispredict in such a way that it outputs what we want it to." + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "Qr8bRa3XKNaP", + "colab_type": "code", + "colab": {} + }, + "source": [ + "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n", + "Config = namedtuple(\n", + " \"Config\",\n", + " (\n", + " \"batch_size\",\n", + " \"epochs\",\n", + " \"lr\",\n", + " \"lr_gamma\",\n", + " \"input_size\",\n", + " \"output_size\",\n", + " \"w_decay\",\n", + " \"r_weight\",\n", + " \"device\",\n", + " )\n", + ")\n", + "\n", + "config = Config(\n", + " batch_size=64,\n", + " epochs=25,\n", + " # Learning rate\n", + " lr=1e-4,\n", + " # Learning rate decay factor\n", + " lr_gamma=0.88,\n", + " input_size=28*28,\n", + " output_size=10,\n", + " # L2 regularization strength\n", + " w_decay=1e-4,\n", + " # c from section 4.1 of the paper\n", + " r_weight=0.15,\n", + " device=device,\n", + ")" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-BN6Jc0bKNaS", + "colab_type": "text" + }, + "source": [ + "### Data\n", + "\n", + "We'll use the full MNIST datasets for training and testing." + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "aefN9SMZKNaT", + "colab_type": "code", + "outputId": "5b0004d7-2040-4edd-ce0f-2382dc4b5890", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 352, + "referenced_widgets": [ + "4bd664c6474c48d9ab30f97e976c7f8a", + "2639f416befa453896f213fcb57ef859", + "d886e997192d4b58ac168d6ebda00166", + "d55e87faaf1f493ca6eef267985d763c", + "1da9f979d2b94ad6868b28a53831df83", + "e6824aa0701240b1a9fa163b40342f00", + "7974262e11ca430b9dd8ba704abcac1a", + "d715272dfc5e4ecb9198fee692e6ff32", + "35dd20c07f6243b4b0649f854c87b9c0", + "e91138ae826346f4996964b7012b2756", + "cb8d16f35d30428ab2a7679d534b78ec", + "744cad24eba3434e9a30b2f7cafd20f9", + "132f4710cf6848988dfbe11b4979e486", + "4d193173cd2f4310b333c9bf87fe0c25", + "6abd415380aa44b2b8dbf52442819bbf", + "18cade4aaaf44a598568cb312682b828", + "1c2abbcdb94e49d390b8a136014c2c9e", + "4cc0f910eba24eb98804a3b715319b33", + "2aca8cfb1fee4035b3fd4406349aa8a4", + "d753360b6296449b9ef26e552bf9840c", + "0bf4cf6348ad400583c62ad82dbcedff", + "d300af4165b4494e978e4d0f506daa22", + "cd2e1593d7144ff9a6920e6862021776", + "c632e4cf62d04732b80c543df910793f", + "6e37ec9443f7406f9fcc18a3a5e01466", + "0e6e86c2b233430fa57adf412d057165", + "74b4c3177f01400a99b9bfca7a995970", + "3132be86fcaf45f8b861ce56cdbf1527", + "40add84ad252405aa560250c61e39014", + "6f67777b35264b519f59f1a58e453300", + "f25f6758ff4f40bab681d582f7c48942", + "8238a401c79749798c6546ac80a80b0e" + ] + } + }, + "source": [ + "os.makedirs(\"./data\", exist_ok=True)\n", + "# MNIST Datasets\n", + "train_ds = tv.datasets.MNIST(\n", + " \"./data\",\n", + " transform=tv.transforms.ToTensor(),\n", + " train=True,\n", + " download=True\n", + ")\n", + "test_ds = tv.datasets.MNIST(\n", + " \"./data\",\n", + " transform=tv.transforms.ToTensor(),\n", + " train=False,\n", + ")\n", + "# MNIST Data Loaders\n", + "train_dl = torch.utils.data.DataLoader(\n", + " train_ds,\n", + " batch_size=config.batch_size,\n", + " drop_last=True,\n", + " shuffle=True,\n", + " num_workers=4,\n", + ")\n", + "test_dl = torch.utils.data.DataLoader(\n", + " test_ds,\n", + " batch_size=config.batch_size,\n", + " shuffle=True\n", + ")" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz to ./data/MNIST/raw/train-images-idx3-ubyte.gz\n" + ], + "name": "stdout" + }, + { + "output_type": "display_data", + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "4bd664c6474c48d9ab30f97e976c7f8a", + "version_minor": 0, + "version_major": 2 + }, + "text/plain": [ + "HBox(children=(IntProgress(value=1, bar_style='info', max=1), HTML(value='')))" + ] + }, + "metadata": { + "tags": [] + } + }, + { + "output_type": "stream", + "text": [ + "Extracting ./data/MNIST/raw/train-images-idx3-ubyte.gz to ./data/MNIST/raw\n", + "Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz to ./data/MNIST/raw/train-labels-idx1-ubyte.gz\n" + ], + "name": "stdout" + }, + { + "output_type": "display_data", + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "35dd20c07f6243b4b0649f854c87b9c0", + "version_minor": 0, + "version_major": 2 + }, + "text/plain": [ + "HBox(children=(IntProgress(value=1, bar_style='info', max=1), HTML(value='')))" + ] + }, + "metadata": { + "tags": [] + } + }, + { + "output_type": "stream", + "text": [ + "Extracting ./data/MNIST/raw/train-labels-idx1-ubyte.gz to ./data/MNIST/raw\n", + "Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz to ./data/MNIST/raw/t10k-images-idx3-ubyte.gz\n" + ], + "name": "stdout" + }, + { + "output_type": "display_data", + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "1c2abbcdb94e49d390b8a136014c2c9e", + "version_minor": 0, + "version_major": 2 + }, + "text/plain": [ + "HBox(children=(IntProgress(value=1, bar_style='info', max=1), HTML(value='')))" + ] + }, + "metadata": { + "tags": [] + } + }, + { + "output_type": "stream", + "text": [ + "Extracting ./data/MNIST/raw/t10k-images-idx3-ubyte.gz to ./data/MNIST/raw\n", + "Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz to ./data/MNIST/raw/t10k-labels-idx1-ubyte.gz\n" + ], + "name": "stdout" + }, + { + "output_type": "display_data", + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "6e37ec9443f7406f9fcc18a3a5e01466", + "version_minor": 0, + "version_major": 2 + }, + "text/plain": [ + "HBox(children=(IntProgress(value=1, bar_style='info', max=1), HTML(value='')))" + ] + }, + "metadata": { + "tags": [] + } + }, + { + "output_type": "stream", + "text": [ + "Extracting ./data/MNIST/raw/t10k-labels-idx1-ubyte.gz to ./data/MNIST/raw\n", + "Processing...\n", + "Done!\n" + ], + "name": "stdout" + }, + { + "output_type": "stream", + "text": [ + "/pytorch/torch/csrc/utils/tensor_numpy.cpp:141: UserWarning: The given NumPy array is not writeable, and PyTorch does not support non-writeable tensors. This means you can write to the underlying (supposedly non-writeable) NumPy array using the tensor. You may want to copy the array to protect its data or make it writeable before converting it to a tensor. This type of warning will be suppressed for the rest of this program.\n" + ], + "name": "stderr" + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "T0j-38_PKNaV", + "colab_type": "text" + }, + "source": [ + "### Helpers\n", + "\n", + "These are some helper functions that you don't need to worry about" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "yI-VU0bBKNaW", + "colab_type": "code", + "outputId": "240451e7-2b07-492b-a151-c0e34d7d378f", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 51 + } + }, + "source": [ + "def train(model, opt, loss_func, loader, config, epoch):\n", + " it = tqdm.tqdm(loader, ncols=80, desc=f\"train: {epoch + 1}/{config.epochs}\")\n", + " for imgs, targets in it:\n", + " model.train()\n", + " opt.zero_grad()\n", + " imgs = imgs.to(config.device).double()\n", + " targets = targets.to(config.device)\n", + " out = model(imgs)\n", + " loss = loss_func(out, targets)\n", + " loss.backward()\n", + " opt.step()\n", + " \n", + " \n", + "def test(model, loader, config):\n", + " correct = 0\n", + " for imgs, targets in tqdm.tqdm(loader, ncols=80, desc=\"Test\"):\n", + " model.eval()\n", + " imgs = imgs.to(config.device).double()\n", + " targets = targets.to(config.device)\n", + " with torch.no_grad():\n", + " out = model(imgs)\n", + " pred = logits_to_label(out)\n", + " correct += (pred == targets).sum()\n", + " N = len(loader.dataset)\n", + " acc = float(correct) / N\n", + " print(f\"Test acc: {acc:.02%}\")\n", + "\n", + " \n", + "def fetch_trained_model(model_class, train_loader, test_loader, load_from_file, path, config):\n", + " \"\"\"Either fetch a stored model from disk or train one from scratch\"\"\"\n", + " model = model_class(config).double().to(config.device)\n", + " if os.path.isfile(path) and load_from_file:\n", + " model.load_state_dict(torch.load(path))\n", + " test(model, test_loader, config)\n", + " else:\n", + " opt = torch.optim.Adam(model.parameters(), config.lr, weight_decay=config.w_decay)\n", + " # Learning rate adjuster\n", + " sched = torch.optim.lr_scheduler.StepLR(opt, 1, config.lr_gamma)\n", + " criterion = nn.CrossEntropyLoss()\n", + "\n", + " for ep in range(config.epochs):\n", + " train(model, opt, criterion, train_loader, config, ep)\n", + " test(model, test_loader, config)\n", + " sched.step()\n", + " torch.save(model.state_dict(), path)\n", + " model.eval()\n", + " print(model)\n", + " return model" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "stream", + "text": [ + "\n", + "\n" + ], + "name": "stdout" + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Mh5d5NDKKNaZ", + "colab_type": "text" + }, + "source": [ + "### Model\n", + "We'll use one of the fully connected models from the paper." + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "LMXMsaxIKNaZ", + "colab_type": "code", + "colab": {} + }, + "source": [ + "def logits_to_label(logits):\n", + " \"\"\"\n", + " Converts the logits or log-odds output of the networks\n", + " to a class label.\n", + " \"\"\"\n", + " return torch.softmax(logits, 1).argmax(1)\n", + "\n", + "\n", + "class FCNet100(nn.Module):\n", + " \"\"\"The FC100-100-10 network from the paper\"\"\"\n", + "\n", + " def __init__(self, config):\n", + " super(FCNet100, self).__init__()\n", + " self.config = config\n", + " self.model = nn.Sequential(\n", + " nn.Linear(config.input_size, 100),\n", + " nn.LeakyReLU(0.1, inplace=True),\n", + " \n", + " nn.Linear(100, 100),\n", + " nn.LeakyReLU(0.1, inplace=True),\n", + " \n", + " nn.Linear(100, config.output_size),\n", + " )\n", + " # Note that softmax is left out of the model on purpose.\n", + " \n", + " def forward(self, x):\n", + " if isinstance(x, np.ndarray):\n", + " x = torch.tensor(x).to(self.config.device)\n", + " x = x.view(x.size(0), self.config.input_size)\n", + " # Logits\n", + " return self.model(x)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "xJ-Lg8w7KNac", + "colab_type": "text" + }, + "source": [ + "For convenience, a pre-trained set of weights are included in this repository at './data/model_1.pt'.\n", + "The code below will load them for you. Pass `False` to the `load` argument to train a fresh version\n", + "of the model. Note that `double()` has been called on the model so that all of the weights are 64-bit floats\n", + "and the model is expecting 64-bit float inputs. The model will also produce 64-bit floats as outputs.\n", + "This last part is why `double()` is called and is important later." + ] + }, + { + "cell_type": "code", + "metadata": { + "scrolled": true, + "id": "cYwFDESdKNac", + "colab_type": "code", + "outputId": "5834cb01-3fbc-4738-c630-06987a9a4d87", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + } + }, + "source": [ + "# Load our first model from disk or train a fresh one\n", + "model = fetch_trained_model(\n", + " FCNet100,\n", + " train_dl,\n", + " test_dl,\n", + " True,\n", + " \"./data/model_1.pt\",\n", + " config\n", + ")" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "stream", + "text": [ + "train: 1/25: 100%|███████████████████████████| 937/937 [00:06<00:00, 138.82it/s]\n", + "Test: 100%|██████████████████████████████████| 157/157 [00:00<00:00, 208.95it/s]\n", + "train: 2/25: 0%| | 0/937 [00:00= N:\n", + " break\n", + "\n", + "x_labels = np.array(x_labels).reshape(4, 4)\n", + "grid = tv.utils.make_grid(x_imgs, nrow=4, pad_value=1)\n", + "plt.figure(figsize=(8, 8))\n", + "plt.title(\"Original Inputs\")\n", + "plt.imshow(np.transpose(grid, (1, 2, 0)))\n", + "print(x_labels)" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "stream", + "text": [ + "[[7 2 1 0]\n", + " [4 1 4 9]\n", + " [9 0 6 9]\n", + " [0 1 5 9]]\n" + ], + "name": "stdout" + }, + { + "output_type": "display_data", + "data": { + "image/png": "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rZ978MEHg7JzrtbqVI78pVglqXPnzlmP7d+/f4Frk9t2222XxBdddFGQe+KJJ5LYH7euL1q2bBmUb7311iT258BLud/z6Xnw55xzThJ/8803NaliWarpne+tki6RtCxTbiJpnnNuaaY8U9Jyv6FiZr3MrNLMKufMmVPDagAAUD7ybnzN7CBJs51z41d48HI45wY75yqccxXpVWMAAKjLatLtvIekQ8ysm6Q1JK0n6TZJG5jZqpm735aSPq95NeNq3759Eqenj3z88cdJfN1110Wr08/85SVRGNtvv31Q3mmnnbIeu3Tp0iT+17/+VbA6lYP0TkU9evTIeuwpp4RfBYnd++V3M0vSCy+8kPVYv9v5+++/L1idStXFF18clNPTxKrryCOPDMpdunRJ4vSUJb+LevHixXldr9TlfefrnLvcOdfSOdda0lGSRjvnjpX0oqSemcNOlDQiyykAAKiXCjHP91JVfflqqqrGgO8pwDUAAChbtbLClXNujKQxmfgTSZ1q47wAANRFLC+5HH369Eni9JKNXbt2TeIFCxYUvC7p8ZW99toriZctW5Y+HLXgsMMOq/axzz33XAFrUl5uuummoHzccccFZX9rvsceeyxKnbLZc889g3Lz5s2TeMiQIUEuPYWsPmjVqlUSn3TSSVmPmzhxYlD2t4NMbyGY5m/dmB5Xfuihh5L4yy+/zF3ZMsXykgAAREbjCwBAZHQ7S+rZs2dQ9ncumjp1apB78803o9TpZ34XuBR2NY8ZMybIzZs3L0aV6jx/F6O09LSH9A449Vl6daP0sMgXX3yRxDGmj6y55ppB2V+p7Kyzzgpyft1PPvnkwlasDOy4445JvO666wa5V155JYn9YTBJWmONNZL4mGOOCXKXX355UN58882TeKONNgpyI0b8MknGH+qT6s5qWNz5AgAQGY0vAACR0fgCABAZY76SjjjiiKDs7w40cGD8DZlat26dxMcee2yQ++mnn5I4vSTbkiVLClqvumr33XfPWfald4p6++23C1KnuujAAw9M4vQULf/7CjX5zPljkOldlHbdddesPzds2LC8r1kX+Tt7pcfyb7nllqw/t2jRoiS+9957g1z6uzWbbbZZ1vMsXLgwiVleEgAA1AoaXwAAIqu33c7+6iq5uqPuuuuuGNUJ9OrVK4mbNm0a5KZMmZLEo0ePjlanumznnXeu9rHFGIYoF7fddltQ3meffYJyixYtkjg9ncvMkrgmO3f558m1sfsnn3wSlP1pSJCOPvrorDl/+ODJJ5+s9jkrKiqqfezYsWOTOMZKgsXAnS8AAJHR+AIAEBmNLwAAkdXbMV//q/SbbLJJkHvkkUdiVyfgL7uW9t5770WsSf2worEofxrMoEGDCl2dsuXvWiRJ7du3D8r+koVdunQJcr17907iOXPmBLn77ruv2nV44IEHkvidd97Jetxrr70WlD/++ONqX6M+ePjhh5M4PQbvf0dim222CXL+a57eHaxRo0ZB2f9cpXOnnXZaEvuvqSRNnjw5Z93LBXe+AABERuMLAEBkNL4AAERmuebCxVJRUeEqKyuXm/Pn7dUmf7sxf4ssSWrYsGES77333kGuENtZbbjhhkF51qxZWY8977zzkvjOO++s9bqsSK73S6Feq0L47W9/m8TprRlXWSX8N+n06dOTuE2bNgWtV22qK6/VyvCXLExvB+ovBXrAAQcEufQ4c2yl9lo1btw4idPPo79GQrpuuX6PF154ISifffbZSfzUU08FuS233DKJ77777iB3xhlnZL1GDCt4rcY756o1oZk7XwAAIqPxBQAgsno71ejHH39M4vQ0gx49eiTx008/HeRuvvnmvK7Xrl27oOxPJ2rVqlWQy9WtUQrDBHVBkyZNkjjdzZz2/PPPF7o6qCVXX311Eqc/K5deemkSF7ubudT5w2u///3vg5y/A5TfBZ12xx13BGX/+ZfCHZCGDx8e5C677LIkTg8R+H87y3mKGHe+AABERuMLAEBkNL4AAERWb8d8ff369QvK/tfn/e2zpHDZtZUxd+7coOyPR6W3DczlH//4R17XR6hnz55Zc/6yd9KvpzqgdBxxxBFB+YQTTkji77//Psh9/fXXUepU16SnCPmfnWOOOSbI+Z8df/xdCsd40wYMGBCUt9122yROL2/pn/fEE0/Mes5Sx50vAACR0fgCABAZ3c6SpkyZEpT9r9Z36NAhyOXacSgX/+v5aeldW4499tisx/pTpFB9LVu2DMpHH3101mNnzpwZlN98882C1Ak117Vr16y59KpJb731VqGrUy/43dDpLul8pf+uPfroo0mc7nb2Vx30V+KSCrMCYaFw5wsAQGQ0vgAAREbjCwBAZIz5rsCECRNylmvDJ598Uu1j27dvn8Tvvvturdelrtp9992Dcq4lJUeMGFHo6qCWpMd8Fy5cmMQ33XRT7OqglgwdOjSJ02O+Rx55ZBKfc845Qa5///6FrVgt4s4XAIDIaHwBAIiMbucSkN6QOtfm2XQ158ffxSgtvfrYbbfdVujqIE/pjdSbN28elGfPnp3ETC0qX8uWLUviG264IcgdeuihSdy3b98g98gjjyTxhx9+WKDa1Q7ufAEAiIzGFwCAyGh8AQCIjDHfEuDvcLS8Mmpu//33z5qbMWNGUJ4/f36hq4M8pcd805+Vp59+OuvPrrvuukncqFGjIJd+D6B0vP3220HZ39XoxhtvDHLXXXddEh9//PFBrtSW5uXOFwCAyGh8AQCIjMYXAIDIGPMtAWussUbW3KJFiyLWpG5p2LBhEm+xxRZZj0s/x0uWLClYnVBYP/30UxKnt+a84IILknjSpElB7sQTTyxsxVBr7r///iQ+/fTTg9zhhx+exOmlJidOnFjYiq0k7nwBAIiMxhcAgMjodi4BJ510UlCeN29eEg8YMCB2deoMf4m6N998M8htt912STx16tRodUJhnXrqqUl8yimnBLl77rkniflcla85c+Yk8X777Rfkpk2blsSXXnppkEsPQxQbd74AAERG4wsAQGQ0vgAARMaYbwlIj0fecsstSTx69OjY1akz/GknV155ZZDzlyVk67nyce655wbla665Jii//PLLSTxw4MAg9+233ybx4sWLC1A7xJZeFvSFF15IYn/rQUlq27ZtUJ48eXLhKlYN3PkCABAZjS8AAJFZKeygU1FR4SorK5ebM7PItUEuud4vvFalhdeqfPBa1Y711lsvid95550gd/755wflkSNH5nWNFbxW451zFdU5D3e+AABERuMLAEBkNL4AAETGVCMAQJ3w3XffJXGbNm2KWJMV484XAIDIaHwBAIiMxhcAgMhofAEAiKxGja+ZbWBmw8zsfTObYma7mVljM3vezD7K/L9RbVUWAIC6oKZ3vrdJetY5t42kHSRNkXSZpFHOuS0ljcqUAQBARt5TjcxsfUm/k/QHSXLOLZa02MwOldQ5c9h9ksZIujTf65TC8peoHl6r8sFrVT54reqmmtz5tpE0R9I/zGyCmf3dzNaW1Nw5NytzzJeSmte0kgAA1CU1aXxXldRR0kDnXAdJPyjVxeyq/sm23H+2mVkvM6s0s8o5c+bUoBoAAJSXmjS+MyXNdM6Ny5SHqaox/srMWkhS5v+zl/fDzrnBzrkK51xFs2bNalANAADKS96Nr3PuS0mfmdnWmYf2lTRZ0khJJ2YeO1HSiBrVEACAOqamazufK+khM1tN0ieSTlJVgz7UzE6RNF3S72t4DQAA6pQaNb7OubclLW/j4H1rcl4AAOqykt/VyMyKXQV4ck174LUqLbxW5YPXqnzU1tQvlpcEACAyGl8AACKj8QUAIDIaXwAAIqPxBQAgMhpfAAAio/EFACAyGl8AACKj8QUAIDIaXwAAIiv55SUB1H2rr756Ev/nP/8Jch06dEji/2dyJtYAACAASURBVPu//wty3bt3L2zFgALhzhcAgMhofAEAiIxuZ9QLe+65Z1B+7bXXknjrrbcOcgcddFBQPvDAA5P46aefznqN119/PSi/8sorK13P+sLvZpakW265JYl33HHHIOfvIjN+/PjCVgyIhDtfAAAio/EFACAyGl8AACJjzBd1xnrrrReUH3rooSTeZ599gtyPP/6YxKuttlqQW2eddbJeIz12nO2ckrRw4cIkPvPMM4PcsGHDsp6nPjjvvPOCcq9evZJ49OjRQe7qq69O4rFjxxa2YkAk3PkCABAZjS8AAJHR7Yw6489//nNQ9qcIpa255ppJPGXKlCA3Z86coPzdd99lPc8qq/zy79du3bplvcY999wT5D788MOgPHHixKzXqIs22mijrLkXXnghKNPVjLqIO18AACKj8QUAIDIaXwAAImPMt0C22GKLoNy0adOgfNhhhyVx586dg9yyZcuSeNCgQUHOXxbxo48+qmk1y952222XxD179sx63MyZM4PyCSeckMRTp04NcvPmzQvKCxYsyHpef8zXnxIjSVdeeWUSp6dB9e3bNyifeuqpSfztt99mvV5dse666wblJUuWJHF6zBflw9+BasCAAUGua9euQdn/7Ph/86RwKl6fPn2C3KxZs5J47733DnKjRo1K4vTUv1LDnS8AAJHR+AIAEBmNLwAAkTHmWwPt27cPymeffXYSH3744UEuPeZbXbvssktQXrp0aRJ/8MEHQe7VV18Nyueff34SL168OK/rlzp/7LBJkyZBzt+KLj0HeMyYMbVyfX+sql+/fkHOX7by4osvDnL+mL8k3XvvvUmca9vCcrbxxhsn8SmnnBLk/O0Y33rrrWh1wspr2LBhEu+1115BbsiQIUmcnsvtfx6l8LOTzvXo0SOJ02O3m266aRKnvy9z4oknJvGDDz64vOqXDO58AQCIjMYXAIDI6HZege233z4o+13LRx55ZJBLTyfxff7550H5lVdeSeJPP/00yF1yySVJPH78+CDXqVOnJG7cuHGQSy9v+M477yRxespSXbH66qtnzd13331JfOedd8aoTuCKK65I4vR7pU2bNkHZH6aoq93O/tSrYtttt92CcsuWLbMe63+O0suC1kcdO3ZM4meffTbrcf6UIEk655xzgrK/61daq1atsh53++23J3F6OC19zVLGnS8AAJHR+AIAEBmNLwAAkTHmuxx/+9vfkjg9JSTXlCF/abN33303yPnjf5K0aNGirOfZfffdk/jMM88Mcv6UlB133DHIffXVV0HZH+d8/PHHg1x627xylV7Czjdu3LiINcnt3//+d1A+44wzgvKuu+4aszpFkWuLx7///e+1fr2BAwdmvX6jRo2CnL/9Y5q/peQtt9wS5HK9/+oKfwlXSRo5cmTWY/2/gZdffnmQW5kpZP60tBEjRgS5DTbYIIlvvPHGrNcvddz5AgAQGY0vAACR1dtu5zXWWCOJ/ak9UrjDjJkFOb+7Nt2t5XeB/PDDD3nXzV+pqUGDBkHOX0Up/TV//+v5ddVmm20WlP3uqfnz5we5dNd/MY0ePToop7ud66K11lorKK+66i9/btJT7/yVkXLxzyGF014k6Yknnkji9ApL/i466WEXfyel9Dl/85vfJPHpp58e5O6///6gPH369Kx1L1dXXXVVUPaH3tLT4i666KIkrsmua+3atUtif6ektFxTnUodd74AAERG4wsAQGQ0vgAARFZvx3z93TB69+4d5Pxx3i+++CLI+csAvvHGG3lf3x/L9XfpkMJxpGeeeSbIpadI+NLj0w888EASz5s3L696lprjjjsuKPtjwOnpVK+99lqUOmH5/O9OSFLz5s2TePDgwdU+jz+u36tXryCXa8nK9GfX/zzcddddQW7mzJlZz+NPrUkv4dqiRYugXFfGfO++++4kPuKII4Kc/32Wyy67LMjlO87r75QkhdOU0n/XXnrppeXG5YY7XwAAIqPxBQAgsnrb7ex3+/70009Zj1uyZElQ9je379mzZ5DbZpttsp4nvSH0tttuu9xYkubOnZvEflfdiqRXuLr22muTOP17lKuj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OUEuvfQdgJrbdtttk7h79+5BbsqUKbGrE50/fUiSxo4dWyvnbdmyZbWOq63rlRrufAEAiIzGFwCAyOh2lvT2228H5d69eydxeuPm3//+90n817/+NcgVYmrP3XffHZSbN2+exI899liQW7RoUa1fv67ypxPlmlqE2nPBBRckcXrI5MEHH4xdnaxatWoVlK+99tokXnvttYNcejcgVF+PHj2KXYWi4s4XAIDIaHwBAIiMxhcAgMgY812O//znP0ns77whSUcffXQSp8d7amvM198p5LDDDgtys2fPTuL+/fvXyvXqo759+xa7CnXe4YcfHpT9HYAGDx4c5Aox5tusWbOgnP4sZct17NgxyPnj08cff3yQe//992tSxXrlN7/5TVD2/5am+TtLfffddwWrUzFx5wsAQGQ0vgAAREa383J88sknSXzllVcGud133z2J012XfjfXFVdckfMa/o4eO++8c5C75ZZbkniDDTYIcjfddFMST548Oec18Iv0dKKVmV609957J3F6ByRU3yqr/PJv/V69egU5f9rJ8OHDg5yZJXF6FyF/xyEpXIHK/zkp3KA9nfNXqnrooYeC3HXXXZf1eqi+LbbYIiivv/76WY8dMWJEEi9durRgdSom7nwBAIiMxhcAgMhofAEAiIwx3xWYNm1aUN5jjz2SOL2L0FlnnZXEXbt2DXLpY/1pQk2aNMl6/aeeeioop6dooOauueaaoNyvX7/iVKSOSY/ddunSJYnTuwP50lOC/O9SpHcR8sdxpfDzkR6ffeKJJ7Je058ytHDhwqzHIX/pqV++H3/8MSjfcccdha5O0XHnCwBAZDS+AABERuMLAEBklh4zKYaKigpXWVm53Fx6Pl4pSc9T23rrrZP4qquuCnLpMWB/vm7a448/nsTpJSuLPect1/ullF+r+ojXqnzUh9dq6NChQdmf2z1u3Lgg56+nUGpW8FqNd85VVOc83PkCABAZjS8AAJEx1agG5s+fH5TfeOONJD744INjVwcASlbPnj2Dst99O2HChNjVKTrufAEAiIzGFwCAyGh8AQCIjDFfAEDB+VtKgjtfAACio/EFACAyGl8AACKj8QUAILIVNr5mdq+ZzTaz97zHbjSz981sopk9YWYbeLnLzWyqmX1gZgcUquIAAJSr6tz5DpHUJfXY85LaOee2l/ShpMslyczaSjpK0naZn7nLzBrUWm0BAKgDVjjVyDn3spm1Tj32nFccK+nndcMOlfSIc+6/kj41s6mSOkl6Pd8KlsKuS6geXqvywWtVPnit6qbaGPM9WdK/MvEmkj7zcjMzj/2KmfUys0ozq5wzZ04tVAMAgPJQo8bXzPpIWirpoZX9WefcYOdchXOuolmzZjWpBgAAZSXvFa7M7A+SDpK0r/ulX+RzSZt6h7XMPAYAADLyuvM1sy6SLpF0iHNuoZcaKekoM1vdzNpI2lLSG8s7BwAA9dUK73zN7GFJnSU1NbOZkvqq6tvNq0t63swkaaxz7gzn3CQzGyppsqq6o892zv1UqMoDAFCOrBS+SVdRUeEqKyuLXQ0AAPJmZuOdcxXVOrYUGl8zmyNpuqSmkuYWuTqliucmN56f7HhusuO5yY7nJrflPT+tnHPV+gZxSTS+PzOzyur+q6G+4bnJjecnO56b7HhusuO5ya2mzw9rOwMAEBmNLwAAkZVa4zu42BUoYTw3ufH8ZMdzkx3PTXY8N7nV6PkpqTFfAADqg1K78wUAoM4ricbXzLpk9v+damaXFbs+xWZmm5rZi2Y22cwmmdn5mccbm9nzZvZR5v+Nil3XYjGzBmY2wcyeypTbmNm4zHvoUTNbrdh1LAYz28DMhmX2255iZrvxvvmFmV2Q+Uy9Z2YPm9ka9fW9k2Wv9uW+V6zK7ZnnaKKZdSxezQsvxj72RW98M/v93impq6S2ko7O7Atcny2VdJFzrq2kXSWdnXlOLpM0yjm3paRRmXJ9db6kKV75z5Jucc5tIelbSacUpVbFd5ukZ51z20jaQVXPEe8bSWa2iaTzJFU459pJaqCq/cfr63tniH69V3u290pXVS0XvKWkXpIGRqpjsQxRgfexL3rjq6r9fqc65z5xzi2W9Iiq9gWut5xzs5xzb2Xi71X1B3QTVT0v92UOu09S9+LUsLjMrKWkAyX9PVM2SftIGpY5pF4+N2a2vqTfSbpHkpxzi51z88T7xreqpDXNbFVJa0mapXr63nHOvSzpm9TD2d4rh0q631UZK2kDM2sRp6bxLe+5cc4955xbmimOVdXGQZK3j71z7lNJP+9jn1MpNL7V3gO4PjKz1pI6SBonqblzblYm9aWk5kWqVrHdqqqNPZZlyk0kzfM+GPX1PdRG0hxJ/8h0yf/dzNYW7xtJknPuc0l/kTRDVY3ufEnjxXvHl+29wt/pUF772PtKofFFFma2jqTHJf2vc+47P5fZxrHefVXdzA6SNNs5N77YdSlBq0rqKGmgc66DpB+U6mKur+8bScqMXx6qqn+kbCxpbf26axEZ9fm9kktN9rH3lULjyx7Ay2FmDVXV8D7knBueefirn7t6Mv+fXaz6FdEekg4xs2mqGqLYR1XjnBtkuhKl+vsemilppnNuXKY8TFWNMe+bKvtJ+tQ5N8c5t0TScFW9n3jv/CLbe4W/0wr2sT+2pvvYl0Lj+6akLTPfOFxNVQPXI4tcp6LKjGHeI2mKc+5mLzVS0omZ+ERJI2LXrdicc5c751o651qr6r0y2jl3rKQXJfXMHFZfn5svJX1mZltnHtpXVdt71vv3TcYMSbua2VqZz9jPz0+9f+94sr1XRko6IfOt510lzfe6p+sFq+197J1zRf9PUjdVfXvsY0l9il2fI74MoQAAAMNJREFUYv8n6beq6u6ZKOntzH/dVDW2OUrSR5JekNS42HUt8vPUWdJTmXizzBt+qqTHJK1e7PoV6TnZUVJl5r3zpKRGvG+C5+caSe9Lek/SA6ral7xevnckPayqse8lquo1OSXbe0WSqWpWyseS3lXVN8aL/jtEfm6mqmps9+e/yYO84/tknpsPJHWtzjVY4QoAgMhKodsZAIB6hcYXAIDIaHwBAIiMxhcAgMhofAEAiIzGFwCAyGh8AQCIjMYXAIDI/h/+vJZcyko3fQAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "tags": [], + "needs_background": "light" + } + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "B-VwpM_aKNap", + "colab_type": "text" + }, + "source": [ + "## Creating Adversaries\n", + "This code will call the functions above to create adverserial examples. Unfortunately, it\n", + "runs pretty slowly so it can take some time to find them. You can try adjusting\n", + "the `max_iterations` and `max_penalty_function_calls` parameters of\n", + "`find_adversarial_example()` to speed things up. Note that if you lower them too far you\n", + "will produce poor examples that don't fool the network or that have large distortions. You can\n", + "also adjust c to weight the size of r more or less." + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "rovZmfGoKNap", + "colab_type": "code", + "outputId": "f107ca68-431f-4d73-af5e-0c7260a9d0c1", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 34 + } + }, + "source": [ + "xr_imgs = []\n", + "xr_labels = []\n", + "for x in tqdm.tqdm(x_imgs, ncols=80):\n", + " xr = find_adversarial_example(model, x.numpy(), adv_target, config)\n", + " xr_imgs.append(xr)\n", + " new_label = logits_to_label(model(xr.to(config.device))).item()\n", + " xr_labels.append(new_label)\n", + "xr_labels = np.array(xr_labels).reshape(4, 4)" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "stream", + "text": [ + "100%|███████████████████████████████████████████| 16/16 [01:41<00:00, 6.36s/it]\n" + ], + "name": "stderr" + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "VCSsIuApKNas", + "colab_type": "text" + }, + "source": [ + "## Adversarial Results\n", + "\n", + "If everything is working properly, all of the new images should be classified as the target class." + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "7VMBT07QKNat", + "colab_type": "code", + "outputId": "62a1d9cc-81cb-4b4b-b5f5-7830f42e3ad6", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + } + }, + "source": [ + "grid = tv.utils.make_grid(xr_imgs, nrow=4, pad_value=1)\n", + "plt.figure(figsize=(8, 8))\n", + "plt.title(\"Adversarial Examples\")\n", + "plt.imshow(np.transpose(grid, (1, 2, 0)))\n", + "print(f\"New Labels:\\n{xr_labels}\")\n", + "\n", + "# Calculate r = (x + r) - x and display them\n", + "r_imgs = [xr - x for xr, x in zip(xr_imgs, x_imgs)]\n", + "grid = tv.utils.make_grid(r_imgs, nrow=4, pad_value=1)\n", + "plt.figure(figsize=(8, 8))\n", + "plt.title(\"Adversarial Perturbations\")\n", + "plt.imshow(np.transpose(grid, (1, 2, 0)))" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n" + ], + "name": "stderr" + }, + { + "output_type": "stream", + "text": [ + "New Labels:\n", + "[[7 2 8 0]\n", + " [4 8 4 8]\n", + " [9 0 6 9]\n", + " [0 8 5 9]]\n" + ], + "name": "stdout" + }, + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "" + ] + }, + "metadata": { + "tags": [] + }, + "execution_count": 10 + }, + { + "output_type": "display_data", + "data": { + "image/png": "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LxERkWfsdiYiIvIsYXe+ItJKRJaIyFIR6Z2o6xAREaWbhNz5ikgRAD8DuATAWgA/IjD3cGHcL0ZERJRmErXIRlMAS5VSywFARN5FYDpGvo1vhQoVVI0aNRJUFSIiosSbOXPmJqVUxYKPTFzjWwX2EnFrEViLNV81atRAbm5ugqpCRESUeCKyquCjApL2bWcR6Rbc8is3Ly8vWdUgIiLyLlGN7zrY67NWDf5MU0oNVUrlKKVyKlaM6C6diIgoIySq8f0RwEkiUjO4V2pnAOMTdC0iIqK0kpAxX6XUfhG5E4F9TYsAGKaUWpCIaxEREaWbhG0pqJT6BMAniTo/ERFRukr5/XxFYtkWlOIt3Lxwvlapha9V+uBrlT7itTYG13YmIiLyjI0vERGRZ2x8iYiIPGPjS0RE5BkbXyIiIs/Y+BIREXnGxpeIiMgzNr5ERESesfElIiLyjI0vERGRZym/vCRRtO6//34dlyhRwsqdeuqpOr7qqqvCnmfw4ME6/v77763cm2++GUsViShL8c6XiIjIMza+REREnrHbmTLGqFGjrHJB3cmHHDx4MGz+1ltv1fHFF19s5b766isdr169OqLrUfwccYR9/2DuOFOnTh0rt2TJEh3fc889Vu6///1vAmqXfUqWLKnjZ555xsqZnyMAmDlzpo47depk5VatWpWA2qUW3vkSERF5xsaXiIjIMza+REREnnHMl9KaOc4b6RgvACxevFjHn332mZWrVauWVb788st1XLt2bSvXpUsXHT/55JMRX58id8kll+j4iy++sHLmGK9bnjBhQshzrlu3Lk61C+20006zynPnzk34NZPthBNO0PE//vEPK+d+t+KMM87Qcdu2ba3cSy+9lIDapRbe+RIREXnGxpeIiMgzNr5ERESeccyX0kpOTo5Vbt++fchjFyxYoOMrrrjCym3atEnHO3futHLFihWzytOnT9exO45Xrly5AmpMsXLHeU3umG+ZMmV07M7z3b17t47Hjh0bp9qFlg1jvBUrVrTKw4cPT05F0hDvfImIiDxj40tERORZ1nY7m9NS3K/E//rrrzres2ePlRs5cqSON2zYYOWWLl0azypSPipXrmyVRUTHZjczALRs2VLH69evj/ga5m5IANCgQYOQx4abzkL+mcMLu3btsnJvvPFG3K934oknWuVsWGL07rvv1nG7du2sXNOmTaM65wUXXGCVzWVD3e77r7/+OqprpBre+RIREXnGxpeIiMgzNr5ERESeiftV/WTIyclRubm5+ebMMb14Wr58uY5r1KgR1Tl27Nhhld0xx0Rbu3atVX766aetcqjnNBbh3i+Jeq3CqV69uo7d12PLli1RndMdY2rYsGHIY80tBr/88suorpcoqfZamerVq2eVzeU+Y2F+l+O9996zchdddJGOza0gY3H66adb5dmzZ+vY/a7AokWLQp4nlV8r14EDB3Rc0Hac4ZjjuuHO424veM011+jY3JbQlwJeq5lKqZyQBxh450tEROQZG18iIiLPsnaqkTm9yF21aOHChTp2u44aN26s42bNmlm5s88+W8dr1qyxctWqVYu4bvv379dxXl6elXOn2pjcaQ6J6HZONW6XVDR69uxplU8++eSQx86YMSNsmUI7/vjjdWzufgPEr9vZ7MpcuXKllYvH58Gt99ChQ63yo48+quNw3czp5JNPPrHK5nMci82bN+vYXWXOHE6qWbOmlfvhhx90XKRIkbjUJRl450tEROQZG18iIiLP2PgSERF5lrVjvpMnT843dk2cODFkrmzZslbZHA92x5fOPPPMiOtmLmn5888/WzlzHMndUcecPkXhtW3bVscDBgywcu6uRhs3btRxnz59rJy7hCGFZi7b6k7nMss33nhjxOc0xwYBYNKkSTq+4447rNwff/wR8XlDKVWqlFV2lxc1x3zT2YUXXqjjunXrWjlzWlBhphoNGTLEKn/++ec63r59u5Vr3ry5jvv16xfynN27d7fKgwcPjrg+ycY7XyIiIs/Y+BIREXmWtd3O8bB161arPGXKlJDHhuvaDqdjx45W2ezq/umnn6zcu+++G9U1slFOzuFFaNxuZteoUaN0HK+VkTJRkyZNrHK41Yfc6V1md3FhuNP9hg0bpuMxY8ZYuU8//TSqa5jc4aRjjjnGKj/yyCM6dlemMqfMrFixIua6xJO7yp/5u6RChQoRn8ec+uc+/26XfLghG/M83bp1s3IVK1bUsbuq31FHHaXjF1980crt27cv5PWSgXe+REREnrHxJSIi8oyNLxERkWcc801Bxx13nI5ffvllK2cu7eZOkYl2F59s8OGHH1rlSy+9NOSxb7zxhlXu379/QuqUaWbNmmWVE7Ebj7ujzMCBA0Me676OkXLHEWvVqqVjd+csc4wX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NaJCSafdV7IdX6v0wdcqfXBXIyIiojTFxpeIiMgzNr5ERESepfxUo1QYk6bI8LVKH3yt0gdfq8zEO18iIiLP2PgSERF5xsaXiIjIMza+REREnrHxJSIi8oyNLxERkWcpP9WIS6ulFi6Dlz74WqUPvlbpI15Tv3jnS0RE5BkbXyIiIs/Y+BIREXnGxpeIiMgzNr5ERESesfElIiLyjI0vERGRZ2x8iYiIPGPjS0RE5BkbXyIiIs/Y+BIREXnGxpeIiMgzNr5ERESepfyuRkS+FStWzCoXKVJEx7t377Zy5o4z8drthCgTXXjhhVa5T58+Om7VqpXv6iQd73yJiIg8Y+NLRETkGRtfIiIizzjmW0hNmjTR8dixY61cjRo1En79Sy+9VMeLFi2ycmvWrEn49TNF/fr1dew+j3v37g35uP79+1vlAQMG6PjOO++0ckOGDNHxwYMHo6qn66qrrrLKo0ePjst5fTvuuOOs8nvvvafj7777zsoNHTpUxytXrkxovfJTunRpHV9wwQVWbuLEiVZ53759XuqUjtzPmTnOW7ZsWSu3detWL3VKJt75EhERecbGl4iIyDN2OxdSy5YtdVy8eHHv17/iiit0fNNNN1m5zp07+65O2nK7wMJp166djm+99daQx7344otWediwYTres2dPIWoXWrp2MwN21+KCBQusnNm1+9tvv1k5313NZl0AYObMmTquWLGilcvJybHKv/zyS+IqloKqVKlilc3pQ40aNbJyLVq0CHmebOhmdvHOl4iIyDM2vkRERJ6x8SUiIvKMY74FOPJI+ylq06ZNkmoSkJubq+P77rvPypUsWdIq//HHH17qlOnMKWTuGJfpnXfescp//vlnVNczx/UBYPny5TqeP39+VOdMhgoVKljlUaNG6bhcuXJW7uWXX9bxXXfdldiKFcCdTlazZk0du2P+2TbGCwDXXXedjp944gkrV61atZCPO/bYY63yli1b4luxArjvK/P7E+vXr/daF4B3vkRERN7F3PiKSBERmS0iHwfLNUVkhogsFZFRIlKsoHMQERFlk3jc+d4DwJy38W8Azyml6gDYCuDmOFyDiIgoY8Q05isiVQFcBuAJAPdJYH+15gD+FjxkBIBHAAyO5TrJdNFFF1nlc845R8dPP/207+pYY2UNGjSwckcffbRVzrYx38qVK1vlaMdx3Pnbf/vb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593dNjDcv/mYY47RsTlu7Uu494uP1ypa7mtsTp/429/+ZuXMZe8AoGXLljoO9T5NRen6WhVGp06drPKoUaN0bE4nBOwt5WbNmpXYihVSOr1W5his+9n5/fffddy/f38rF25rwBIlSljlkSNH6tjdYvKtt97S8Y033lhwheOsgNdqplIqJ+QBBt75EhERecbGl4iIyLOsnWpkcnfJML9a37hxYysXbsehcEaPHh0y5+7act1114U8NhldzemqTp06Oja7wwCgc+fOIR/nrmKVTl3N2aZ169Yhcx9//LFVTrWu5nRRo0YNq2yuInbTTRZG7XwAACAASURBVDfF5Rru7zVz+ODKK6+0cubwgY8VCBOFd75ERESesfElIiLyjI0vERGRZxzzLcDs2bPDluNh+fLlER9r7rj0008/xb0umWTp0qU6dpfIK1KkiI7dqQPjxo1LbMUobtwx3127dul44MCBvquTkVavXm2Vp0+fnvBrvv/++zp2pxqZ39cwd0YC7GVjUx3vfImIiDxj40tEROQZu51TgLuCTbgVbdjVHJ3y5cuHzG3evNkqDxo0KNHVoSjddtttVrlSpUpWeePGjTrm1KLItWvXzip/+OGHOj548KCVmzdvXsLrY17zmWeesXJmXR955BErZ05R+vnnnxNTuTjhnS8REZFnbHyJiIg8Y+NLRETkGcd8U4A71SUVdprKNJdccolVNp9jdyqFuxQlpQ53zNf9rEyYMCHkY80dwcqWLWvl3PdAphszZoxVNqf2pJo5c+ZY5YceekjH7njwE088oeMbbrjByqXa0ry88yUiIvKMjS8REZFnbHyJiIg845hvCjjqqKNC5vbs2eOxJpmlaNGiOq5Xr56VO+KIw//vPOOMM6xcixYtrPLEiRMTUDtKhAMHDujY3Zrz3nvv1fGCBQusXNeuXRNbsRTjbgVofh5i0aBBAx27WwG+++67VnnFihVRXeONN97Q8a233mrlOnTooOMBAwZYuVRbI4F3vkRERJ6x8SUiIvKM3c4p4O9//7tV3rZtm47TaZeOVGMu01m6dOmIH/f6669b5Vq1aun4nHPOsXJTpkyJsnaUCLfccouOb775Ziv32muv6TjbP1fbt29PyHnd7nyTu0zl2rVrdexOfQo33JaXl6fjiy++2MqZXdm9evWycl26dAl5zmTgnS8REZFnbHyJiIg8Y+NLRETkGcd8U8CPP/5olZ977jkdc0wxenv37tVx3759rdzw4cNDPm7JkiVWedeuXSGPDbf94/HHH6/jDRs2hDyOInfXXXdZ5UcffdQqf/311zoePHiwldu6dauOzfcGxc+0adN07C7ZaY7xAkC/fv10vGbNGitnvo7huNeYNGmSjlu2bGnlzGlQALBw4cKIrpEovPMlIiLyjI0vERGRZ5IKO+jk5OSo3NzcfHPhuvXIv3Dvl2x4rZo3b26VU3lYINtfq3TC1yo+SpYsqWN3Rat77rnHKn/00UdRXaOA12qmUionkvPwzpeIiMgzNr5ERESesfElIiLyjFONiAohlcd4ibLdH3/8oWNzWdhUxDtfIiIiz9j4EhERecbGl4iIyDM2vkRERJ7F1PiKSBkRGS0ii0VkkYicIyLlROQLEfkl+HfZeFWWiIgoE8R65zsIwESlVD0ApwFYBKA3gMlKqZMATA6WiYiIKCjqqUYiUhrABQBuBACl1F4Ae0XkSgDNgoeNADAVQK9or5MKy19SZPhapQ++VumDr1VmiuXOtyaAPACvi8hsEXlVREoCqKSUWh88ZgOASrFWkoiIKJPE0vgeCaAJgMFKqcYA/oDTxawC/2XL979tItJNRHJFJDcvLy+GahAREaWXWBrftQDWKqVmBMujEWiMfxORygAQ/Htjfg9WSg1VSuUopXIqVqwYQzWIiIjSS9SNr1JqA4A1IlI3+KMWABYCGA+ga/BnXQGMi6mGREREGSbWtZ3vAjBSRIoBWA7g7wg06O+JyM0AVgG4OsZrEBERZZSYGl+l1BwA+W0c3CKW8xIREWWylN/VSESSXQUyhJv2wNcqtfC1Sh98rdJHvKZ+cXlJIiIiz9j4EhERecbGl4iIyDM2vkRERJ6x8SUiIvKMjS8REZFnbHyJiIg8Y+NLRETkGRtfIiIiz9j4EhEReZbyy0sSUeYrXry4jr/99lsr17hxYx1/9NFHVq5du3aJrRhRgvDOl4iIyDM2vkRERJ6x25mywvnnn2+Vv/vuOx3XrVvXyrVt29YqX3bZZTqeMGFCyGt8//33VnnatGmFrme2MLuZAeC5557T8emnn27lzF1kZs6cmdiKEXnCO18iIiLP2PgSERF5xsaXiIjIM475UsY49thjrfLIkSN13Lx5cyu3e/duHRcrVszKlSpVKuQ13LHjUOcEgF27dum4e/fuVm706NEhz5MN7r77bqvcrVs3HU+ZMsXKPfTQQzqePn16YitG5AnvfImIiDxj40tEROQZu50pY/z73/+2yuYUIVeJEiV0vGjRIiuXl5dnlX///feQ5zniiMP/f23Tpk3Ia7z22mtW7ueff7bK8+bNC3mNTHT88ceHzE2aNMkqs6uZMhHvfImIiDxj40tEROQZG18iIiLPOOabIHXq1LHKFSpUsMrt27fXcbNmzazcwYMHdTxkyBArZy6L+Msvv8RazbR3yimn6Piqq64KedzatWut8g033KDjpUuXWrlt27ZZ5Z07d4Y8rznma06JAYD+/fvr2J0G9fDDD1vlW265Rcdbt24Neb1Mccwxx1jlffv26dgd86X0Ye5A9dhjj1m51q1bW2Xzs2P+zgPsqXj9+vWzcuvXr9fxRRddZOUmT56sY3fqX6rhnS8REZFnbHyJiIg8Y+NLRETkGcd8Y9CoUSOrfMcdd+i4Q4cOVs4d843UWWedZZX379+v4yVLlli5b775xirfc889Ot67d29U10915thh+fLlrZy5FZ07B3jq1Klxub45VvXII49YOXPZyvvvv9/KmWP+ADBs2DAdh9u2MJ2dcMIJOr755putnLkd46xZs7zViQqvaNGiOr7wwgut3PDhw3XszuU2P4+A/dlxcx07dtSxO3ZbrVo1Hbvfl+natauO33rrrfyqnzJ450tEROQZG18iIiLP2O1cgFNPPdUqm13L11xzjZVzp5OY1q1bZ5WnTZum4xUrVli5Bx54QMczZ860ck2bNtVxuXLlrJy7vOHcuXN17E5ZyhTFixcPmRsxYoSOX3rpJR/VsfTt21fH7nulZs2aVtkcpsjUbmdz6lWynXPOOVa5atWqIY81P0fusqDZqEmTJjqeOHFiyOPMKUEAcOedd1plc9cvV/Xq1UMe98ILL+jYHU5zr5nKeOdLRETkGRtfIiIiz9j4EhERecYx33z873//07E7JSTclCFzabOffvrJypnjfwCwZ8+ekOc599xzddy9e3crZ05JOf30063cb7/9ZpXNcc4xY8ZYOXfbvHTlLmFnmjFjhseahPfZZ59Z5dtuu80qn3322T6rkxThtnh89dVX4369wYMHh7x+2bJlrZy5/aPL3FLyueees3Lh3n+ZwlzCFQDGjx8f8ljzd2CfPn2sXGGmkJnT0saNG2fl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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "tags": [], + "needs_background": "light" + } + }, + { + "output_type": "display_data", + "data": { + "image/png": "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27AalV/qsHDhwsKy8847r1TevHlzsjahcw01x7v99tuXyps2baq6XivckraaWbNmFeqHH354od7snyW+XhIAgMQIXwAAEmPYGS3thBNOKNRvvvnmqut+85vfLNQvu+yyquvutddepfLTTz89wtbVz6233loqH3PMMYVl9913X6m88847F5aNGTOmVF69mqv6MDK77bZboV4+1Pzyyy+PaJs//OEPC/WLLrqoUC+fFmqEKVOmDLq8/Jvkrr322sKyW265pVSufB/1Qs8XAIDECF8AABIjfAEASMyt8FHx3t7e6OvrG3BZM+42geoGO1/a9Vj95je/KdTf9ra31f01zj333EJ90qRJhfpxxx1XKlf+LEycOLFUrvzKvMF04rHqVJ14rCrf0z777FOoP/nkkymbozvvvLNQP/roo0vl008/vbDs8ssvr7qdIY7V/IjoHU576PkCAJAY4QsAQGIMO2ObdMrw2Bvf+MZSed26dYVlTz31VNXnlX/zjzTyyzBS6JRj1Q04VkXlP2et9jPGsDMAAG2K8AUAIDHCFwCAxPh6SXSlnp6eUnnjxo2FZYPN+bba/BPQqsq/vlGStmzZUqgPNnfaDT9n9HwBAEiM8AUAIDHCFwCAxJjzRVfavHlzqfz73/++iS0BOtO2zPF2I3q+AAAkRvgCAJAYw87oSuecc06pPG/evOY1BOhQDDMPjp4vAACJEb4AACRG+AIAkBhzvugKhxxySKH+/ve/v0ktATrXdtu90p+rvNQIRfR8AQBIjPAFACAxhp3RsSZOnFgqL126tIktAdrXhAkTCvXjjjuuVJ4zZ05hGUPNw0fPFwCAxAhfAAASI3wBAEiMOV90rGXLljW7CUDbW7FiRaF+zz33NKklnYWeLwAAiRG+AAAkxrAzAHS5k08+uVC//vrrS+XKy4cWLlyYpE2djp4vAACJEb4AACRG+AIAkBhzvgDQZa699tpC/eqrr25SS7oXPV8AABIjfAEASIzwBQAgMeZ80RVGjRpVqG/evLlUPuqoowrLdt1110L9l7/8ZeMaBjTBRz/60UJ9u+3q0w+bPHlyqTxjxozCsiuvvLJQX758eV1es13R8wUAIDHCFwCAxBh2RscqH2p+17veVVh26623lsoTJ04sLLv44osL9QMPPLBUPvLIIwvL7rjjjprbCaS2fv36hmx38eLFVZdVfk3l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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "tags": [], + "needs_background": "light" + } + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "oLY9v2ZrKNav", + "colab_type": "text" + }, + "source": [ + "## Cross Network Attack\n", + "Now we'll try using the examples created using one network to attack another.\n", + "Below is a model similar to the one used above that has pre-trained weights." + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "o3eH8Q4mKNaw", + "colab_type": "code", + "colab": {} + }, + "source": [ + "class FCNet200(nn.Module):\n", + " def __init__(self, config):\n", + " super(FCNet200, self).__init__()\n", + " self.config = config\n", + " self.model = nn.Sequential(\n", + " nn.Linear(config.input_size, 200),\n", + " nn.LeakyReLU(0.1, inplace=True),\n", + " \n", + " nn.Linear(200, 200),\n", + " nn.LeakyReLU(0.1, inplace=True),\n", + " \n", + " nn.Linear(200, config.output_size),\n", + " )\n", + " \n", + " def forward(self, x):\n", + " if isinstance(x, np.ndarray):\n", + " x = torch.tensor(x).to(self.config.device)\n", + " x = x.view(x.size(0), self.config.input_size)\n", + " # Logits\n", + " return self.model(x)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "fJOBPrE_KNay", + "colab_type": "text" + }, + "source": [ + "This loads the pre-trained weights for the model above. If you change the `True` to `False`\n", + "it will train the model from scratch." + ] + }, + { + "cell_type": "code", + "metadata": { + "scrolled": true, + "id": "7iC3q1IPKNaz", + "colab_type": "code", + "outputId": "68da79de-abf8-4a96-abae-46f0e4f4ebb4", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + } + }, + "source": [ + "model2 = fetch_trained_model(\n", + " FCNet200,\n", + " train_dl,\n", + " test_dl,\n", + " True,\n", + " \"./data/model_2.pt\",\n", + " config\n", + ")" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "stream", + "text": [ + "train: 1/25: 100%|███████████████████████████| 937/937 [00:06<00:00, 136.81it/s]\n", + "Test: 100%|██████████████████████████████████| 157/157 [00:00<00:00, 192.24it/s]\n", + "train: 2/25: 0%| | 0/937 [00:00 {}\".format(original_prediction, adversarial_prediction))\n", + " plt.imshow(adversarial_image, cmap=\"gray\")\n", + "\n", + " plot_index += 1\n", + "plt.tight_layout()\n", + "plt.show()" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "zsTLDbhnKNbM", + "colab_type": "text" + }, + "source": [ + "### Part 2: Bonus\n", + "Plot just the purturbations. You can modify the code from the cell above but put the new plotting code in this cell below so that both copies are retained." + ] + } + ] +} \ No newline at end of file