```python
import sys
sys.path = ['/Users/sebastian/github/mlxtend/'] + sys.path
```

```python
#load watermark
%load_ext watermark
%watermark -a 'Gopala KR' -u -d -v -p watermark,numpy,pandas,matplotlib,nltk,sklearn,tensorflow,theano,mxnet,chainer,seaborn,keras,tflearn,bokeh,gensim
```

    The watermark extension is already loaded. To reload it, use:
      %reload_ext watermark
    Gopala KR
    last updated: 2018-02-13

    CPython 3.6.3
    IPython 6.2.1

    watermark 1.6.0
    numpy 1.13.1
    pandas 0.20.3
    matplotlib 2.0.2
    nltk 3.2.5
    sklearn 0.19.0
    tensorflow 1.3.0
    theano 1.0.1
    mxnet 1.0.0
    chainer 3.3.0
    seaborn 0.8.1
    keras 2.1.3
    tflearn n
    bokeh 0.12.14
    gensim 3.3.0

# Artificial Neurons and Single-Layer Neural Networks

## - How Machine Learning Algorithms Work Part 1

**This article offers a brief glimpse of the history and basic concepts of machine learning. We will take a look at the first algorithmically described neural network and the gradient descent algorithm in context of adaptive linear neurons, which will not only introduce the principles of machine learning but also serve as the basis for modern multilayer neural networks in future articles.**

## Sections

- [Introduction](#Introduction)
- [Artificial Neurons and the McCulloch-Pitts Model](#Artificial-Neurons-and-the-McCulloch-Pitts-Model)
- [Frank Rosenblatt's Perceptron](#Frank-Rosenblatt's-Perceptron)
    - [The Unit Step Function](#The-Unit-Step-Function)
    - [The Perceptron Learning Rule](#The-Perceptron-Learning-Rule)
    - [Implementing the Perceptron Rule in Python](#Implementing-the-Perceptron-Rule-in-Python)
    - [Problems with Perceptrons](#Problems-with-Perceptrons)
- [Adaptive Linear Neurons and the Delta Rule](#Adaptive-Linear-Neurons-and-the-Delta-Rule)
    - [Gradient Descent](#Gradient-Descent)
    - [The Gradient Descent Rule in Action](#The-Gradient-Descent-Rule-in-Action)
    - [Online Learning via Stochastic Gradient Descent](#Online-Learning-via-Stochastic-Gradient-Descent)
- [What's Next?](#What's-Next?)
- [References](#References)

# Introduction

[[back to top](#Sections)]

Machine learning is one of the hottest and most exciting fields in the modern age of technology. Thanks to machine learning, we enjoy robust email spam filters, convenient text and voice recognition, reliable web search engines, challenging chess players, and, hopefully soon, safe and efficient self-driving cars.

Without any doubt, machine learning has become a big and popular field, and sometimes it may be challenging to see the (random) forest for the (decision) trees. Thus, I thought that it might be worthwhile to explore different machine learning algorithms in more detail by not only discussing the theory but also by implementing them step by step.

To briefly summarize what machine learning is all about: "[Machine learning is the] field of study that gives computers the ability to learn without being explicitly programmed" (Arthur Samuel, 1959). Machine learning is about the development and use of algorithms that can recognize patterns in data in order to make decisions based on statistics, probability theory, combinatorics, and optimization.

The first article in this series will introduce perceptrons and the adaline (ADAptive LINear NEuron), which fall into the category of single-layer neural networks. The perceptron is not only the first algorithmically described learning algorithm [[1](#References)], but it is also very intuitive, easy to implement, and a good entry point to the (re-discovered) modern state-of-the-art machine learning algorithms: Artificial neural networks (or "deep learning" if you like). As we will see later, the adaline is a consequent improvement of the perceptron algorithm and offers a good opportunity to learn about a popular optimization algorithm in machine learning: gradient descent.

# Artificial Neurons and the McCulloch-Pitts Model

[[back to top](#Sections)]

The initial idea of the perceptron dates back to the work of Warren McCulloch and Walter Pitts in 1943 [[2](#References)], who drew an analogy between biological neurons and simple logic gates with binary outputs. In more intuitive terms, neurons can be understood as the subunits of a neural network in a biological brain. Here, the signals of variable magnitudes arrive at the dendrites. Those input signals are then accumulated in the cell body of the neuron, and if the accumulated signal exceeds a certain threshold, a output signal is generated that which will be passed on by the axon.

# Frank Rosenblatt's Perceptron

[[back to top](#Sections)]

To continue with the story, a few years after McCulloch and Walter Pitt, Frank Rosenblatt published the first concept of the Perceptron learning rule [[1](#References)]. The main idea was to define an algorithm in order to learn the values of the weights $w$ that are then multiplied with the input features in order to make a decision whether a neuron fires or not. In context of pattern classification, such an algorithm could be useful to determine if a sample belongs to one class or the other.

To put the perceptron algorithm into the broader context of machine learning: The perceptron belongs to the category of supervised learning algorithms, single-layer binary linear classifiers to be more specific. In brief, the task is to predict to which of two possible categories a certain data point belongs based on a set of input variables. In this article, I don't want to discuss the concept of predictive modeling and classification in too much detail, but if you prefer more background information, please see my previous article "[Introduction to supervised learning](http://sebastianraschka.com/Articles/2014_intro_supervised_learning.html)".

## The Unit Step Function

[[back to top](#Sections)]

Before we dive deeper into the algorithm(s) for learning the weights of the artificial neuron, let us take a brief look at the basic notation. In the following sections, we will label the *positive* and *negative* class in our binary classification setting as "1" and "-1", respectively. Next, we define an activation function $g(\mathbf{z})$ that takes a linear combination of the input values $\mathbf{x}$ and weights $\mathbf{w}$ as input ($\mathbf{z} = w_1x_{1} + \dots + w_mx_{m}$), and if $g(\mathbf{z})$ is greater than a defined threshold $\theta$ we predict 1 and -1 otherwise; in this case, this activation function $g$ is an alternative form of a simple "unit step function," which is sometimes also called "Heaviside step function."

*(Please note that the *unit step* is classically defined as being equal to 0 if $ z < 0$ and 1 for $z \ge 0$; nonetheless, we will refer to the following piece-wise linear function with -1 if $z < \theta$ and 1 for $z \ge \theta$ as *unit step function* for simplicity).*

$$
 g(\mathbf{z}) =\begin{cases}
    1 & \text{if $\mathbf{z} \ge \theta$}\\
    -1 & \text{otherwise}.
  \end{cases}
$$

where

$$\mathbf{z} =  w_1x_{1} + \dots + w_mx_{m} = \sum_{j=1}^{m} x_{j}w_{j} \\ = \mathbf{w}^T\mathbf{x}$$

$\mathbf{w}$ is the feature vector, and $\mathbf{x}$ is an $m$-dimensional sample from the training dataset:

$$
\mathbf{w} = \begin{bmatrix}
    w_{1}  \\
    \vdots \\
    w_{m}
\end{bmatrix}
\quad  \mathbf{x} = \begin{bmatrix}
    x_{1}  \\
    \vdots \\
    x_{m}
\end{bmatrix}$$

In order to simplify the notation, we bring $\theta$ to the left side of the equation and define $w_0 = -\theta  \text{ and } x_0=1$

so that

$$\begin{equation}
 g({\mathbf{z}}) =\begin{cases}
    1 & \text{if $\mathbf{z} \ge 0$}\\
    -1 & \text{otherwise}.
  \end{cases}
\end{equation}$$

and

$$\mathbf{z} = w_0x_{0} + w_1x_{1} + \dots + w_mx_{m} = \sum_{j=0}^{m} x_{j}w_{j} \\ = \mathbf{w}^T\mathbf{x}.$$

## The Perceptron Learning Rule

[[back to top](#Sections)]

It might sound like extreme case of a reductionist approach, but the idea behind this "thresholded" perceptron was to mimic how a single neuron in the brain works: It either "fires" or not. To summarize the main points from the previous section: A perceptron receives multiple input signals, and if the sum of the input signals exceed a certain threshold it either returns a signal or  remains "silent" otherwise. What made this a "machine learning" algorithm was Frank Rosenblatt's idea of the perceptron learning rule: The perceptron algorithm is about learning the weights for the input signals in order to draw linear decision boundary that allows us to discriminate between the two linearly separable classes +1 and -1.

Rosenblatt's initial perceptron rule is fairly simple and can be summarized by the following steps:

1. Initialize the weights to 0 or small random numbers.
2. For each training sample $\mathbf{x^{(i)}}$:
    2. Calculate the *output* value.
    2. Update the weights.

The output value is the class label predicted by the unit step function that we defined earlier (output $=g(\mathbf{z})$) and the weight update can be written more formally as  $w_j := w_j + \Delta w_j$.

The value for updating the weights at each increment is calculated by the learning rule

$\Delta w_j = \eta \; (\text{target}^{(i)} - \text{output}^{(i)})\;x^{(i)}_{j}$

where $\eta$ is the learning rate (a constant between 0.0 and 1.0), "target" is the true class label, and the "output" is the predicted class label.

It is important to note that all weights in the weight vector are being updated simultaneously. Concretely, for a 2-dimensional dataset, we would write the update as:

$\Delta w_0 = \eta(\text{target}^{(i)} - \text{output}^{(i)})$
$\Delta w_1 = \eta(\text{target}^{(i)} - \text{output}^{(i)})\;x^{(i)}_{1}$
$\Delta w_2 = \eta(\text{target}^{(i)} - \text{output}^{(i)})\;x^{(i)}_{2}$

Before we implement the perceptron rule in Python, let us make a simple thought experiment to illustrate how beautifully simple this learning rule really is. In the two scenarios where the perceptron predicts the class label correctly, the weights remain unchanged:

- $\Delta w_j = \eta(-1^{(i)} - -1^{(i)})\;x^{(i)}_{j} = 0$
- $\Delta w_j = \eta(1^{(i)} - 1^{(i)})\;x^{(i)}_{j} = 0$

However, in case of a wrong prediction, the weights are being "pushed" towards the direction of the positive or negative target class, respectively:

- $\Delta w_j = \eta(1^{(i)} - -1^{(i)})\;x^{(i)}_{j} = \eta(2)\;x^{(i)}_{j}$
- $\Delta w_j = \eta(-1^{(i)} - 1^{(i)})\;x^{(i)}_{j} = \eta(-2)\;x^{(i)}_{j}$

It is important to note that the convergence of the perceptron is only guaranteed if the two classes are linearly separable. If the two classes can't be separated by a linear decision boundary, we can set a maximum number of passes over the training dataset ("epochs") and/or a threshold for the number of tolerated misclassifications.

## Implementing the Perceptron Rule in Python

[[back to top](#Sections)]

In this section, we will implement the simple perceptron learning rule in Python to classify flowers in the Iris dataset.
Please note that I omitted some "safety checks" for clarity, for a more "robust" version please see the following [code on GitHub](https://github.com/rasbt/mlxtend/blob/master/mlxtend/classifier/perceptron.py).

```python
import numpy as np

class Perceptron(object):

    def __init__(self, eta=0.01, epochs=50):
        self.eta = eta
        self.epochs = epochs

    def train(self, X, y):

        self.w_ = np.zeros(1 + X.shape[1])
        self.errors_ = []

        for _ in range(self.epochs):
            errors = 0
            for xi, target in zip(X, y):
                update = self.eta * (target - self.predict(xi))
                self.w_[1:] +=  update * xi
                self.w_[0] +=  update
                errors += int(update != 0.0)
            self.errors_.append(errors)
        return self

    def net_input(self, X):
        return np.dot(X, self.w_[1:]) + self.w_[0]

    def predict(self, X):
        return np.where(self.net_input(X) >= 0.0, 1, -1)
```

For the following example, we will load the Iris data set from the [UCI Machine Learning Repository](http://archive.ics.uci.edu/ml/) and only focus on the two flower species *Setosa* and *Versicolor*. Furthermore, we will only use the two features *sepal length* and *petal length* for visualization purposes.

```python
import pandas as pd
df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data', header=None)

# setosa and versicolor
y = df.iloc[0:100, 4].values
y = np.where(y == 'Iris-setosa', -1, 1)

# sepal length and petal length
X = df.iloc[0:100, [0,2]].values
```

```python
!pip install mlxtend
```

    Collecting mlxtend
      Downloading mlxtend-0.10.0-py2.py3-none-any.whl (1.3MB)
        100% |████████████████████████████████| 1.3MB 927kB/s ta 0:00:01
    Requirement already satisfied: scikit-learn>=0.18 in /srv/venv/lib/python3.6/site-packages (from mlxtend)
    Requirement already satisfied: setuptools in /srv/venv/lib/python3.6/site-packages (from mlxtend)
    Requirement already satisfied: matplotlib>=1.5.1 in /srv/venv/lib/python3.6/site-packages (from mlxtend)
    Requirement already satisfied: numpy>=1.10.4 in /srv/venv/lib/python3.6/site-packages (from mlxtend)
    Requirement already satisfied: pandas>=0.17.1 in /srv/venv/lib/python3.6/site-packages (from mlxtend)
    Requirement already satisfied: scipy>=0.17 in /srv/venv/lib/python3.6/site-packages (from mlxtend)
    Requirement already satisfied: pyparsing!=2.0.0,!=2.0.4,!=2.1.2,!=2.1.6,>=1.5.6 in /srv/venv/lib/python3.6/site-packages (from matplotlib>=1.5.1->mlxtend)
    Requirement already satisfied: pytz in /srv/venv/lib/python3.6/site-packages (from matplotlib>=1.5.1->mlxtend)
    Requirement already satisfied: python-dateutil in /srv/venv/lib/python3.6/site-packages (from matplotlib>=1.5.1->mlxtend)
    Requirement already satisfied: cycler>=0.10 in /srv/venv/lib/python3.6/site-packages (from matplotlib>=1.5.1->mlxtend)
    Requirement already satisfied: six>=1.10 in /srv/venv/lib/python3.6/site-packages (from matplotlib>=1.5.1->mlxtend)
    Installing collected packages: mlxtend
    Successfully installed mlxtend-0.10.0

```python
%matplotlib inline
import matplotlib.pyplot as plt
from mlxtend.plotting import plot_decision_regions

ppn = Perceptron(epochs=10, eta=0.1)

ppn.train(X, y)
print('Weights: %s' % ppn.w_)
plot_decision_regions(X, y, clf=ppn)
plt.title('Perceptron')
plt.xlabel('sepal length [cm]')
plt.ylabel('petal length [cm]')
plt.show()

plt.plot(range(1, len(ppn.errors_)+1), ppn.errors_, marker='o')
plt.xlabel('Iterations')
plt.ylabel('Misclassifications')
plt.show()

```

As we can see, the perceptron converges after the 6th iteration and separates the two flower classes perfectly.

## Problems with Perceptrons

[[back to top](#Sections)]

Although the perceptron classified the two Iris flower classes perfectly, convergence is one of the biggest problems of the perceptron. Frank Rosenblatt proofed mathematically that the perceptron learning rule converges if the two classes can be separated by linear hyperplane, but problems arise if the classes cannot be separated perfectly by a linear classifier. To demonstrate this issue, we will use two different classes and features from the Iris dataset.

```python
# versicolor and virginica
y2 = df.iloc[50:150, 4].values
y2 = np.where(y2 == 'Iris-virginica', -1, 1)

# sepal width and petal width
X2 = df.iloc[50:150, [1,3]].values

ppn = Perceptron(epochs=25, eta=0.01)
ppn.train(X2, y2)

plot_decision_regions(X2, y2, clf=ppn)
plt.show()

plt.plot(range(1, len(ppn.errors_)+1), ppn.errors_, marker='o')
plt.xlabel('Iterations')
plt.ylabel('Misclassifications')
plt.show()
```

```python
print('Total number of misclassifications: %d of 100' % (y2 != ppn.predict(X2)).sum())
```

    Total number of misclassifications: 43 of 100

Even at a lower training rate, the perceptron failed to find a good decision boundary since one or more samples will always be misclassified in every epoch so that the learning rule never stops updating the weights.

It may seem paradoxical in this context that another shortcoming of the perceptron algorithm is that it stops updating the weights as soon as all samples are classified correctly. Our intuition tells us that a decision boundary with a large margin between the classes (as indicated by the dashed line in the figure below) likely has a better generalization error than the decision boundary of the perceptron. But large-margin classifiers such as Support Vector Machines are a topic for another time.

# Adaptive Linear Neurons and the Delta Rule

[[back to top](#Sections)]

The perceptron surely was very popular at the time of its discovery, however, it only took a few years until Bernard Widrow and his doctoral student Tedd Hoff proposed the idea of the Adaptive Linear Neuron (adaline) [[3](#References)].

In contrast to the perceptron rule, the delta rule of the adaline (also known as Widrow-Hoff" rule or Adaline rule) updates the weights based on a linear activation function rather than a unit step function; here, this linear activation function $g(\mathbf{z})$ is just the identity function of the net input $g(\mathbf{w}^T\mathbf{x}) = \mathbf{w}^T\mathbf{x}$. In the next section, we will see why this linear activation is an improvement over the perceptron update and where the name "delta rule" comes from.

## Gradient Descent

[[back to top](#Sections)]

Being a continuous function, one of the biggest advantages of the linear activation function over the unit step function is that it is differentiable. This property allows us to define a cost function $J(\mathbf{w})$ that we can minimize in order to update our weights. In the case of the linear activation function, we can define the cost function $J(\mathbf{w})$ as the *sum of squared errors* (SSE), which is similar to the cost function that is minimized in ordinary least squares (OLS) linear regression.

$$J(\mathbf{w})  = \frac{1}{2} \sum_{i} (\text{target}^{(i)} - \text{output}^{(i)})^2 \quad \quad \text{output}^{(i)} \in \mathbb{R}$$

(The fraction $\frac{1}{2}$ is just used for convenience to derive the gradient as we will see in the next paragraphs.)

In order to minimize the SSE cost function, we will use gradient descent, a simple yet useful optimization algorithm that is often used in machine learning to find the local minimum of linear systems.

Before we get to the fun part (calculus), let us consider a convex cost function for one single weight. As illustrated in the figure below, we can describe the principle behind gradient descent as "climbing down a hill" until a local or global minimum is reached. At each step, we take a step into the opposite direction of the gradient, and the step size is determined by the value of the learning rate as well as the slope of the gradient.

Now, as promised, onto the fun part -- deriving the Adaline learning rule.
As mentioned above, each update is updated by taking a step into the opposite direction of the gradient $\Delta \mathbf{w} = - \eta \nabla J(\mathbf{w})$, thus, we have to compute the partial derivative of the cost function for each weight in the weight vector:  $\Delta w_j = - \eta \frac{\partial J}{\partial w_j}$.

The partial derivative of the SSE cost function for a particular weight can be calculated as follows:

$$\begin{equation}
 \frac{\partial J}{\partial w_j} = \frac{\partial }{\partial w_j} \frac{1}{2} \sum_i  (t^{(i)} - o^{(i)})^2 \\
= \frac{1}{2} \sum_i \frac{\partial}{\partial w_j} (t^{(i)} - o^{(i)})^2 \\
= \frac{1}{2} \sum_i 2 (t^{(i)} - o^{(i)}) \frac{\partial}{\partial w_j} (t^{(i)} - o^{(i)}) \\
=  \sum_i (t^{(i)} - o^{(i)}) \frac{\partial}{\partial w_j} \bigg(t^{(i)} - \sum_j w_j x^{(i)}_{j}\bigg) \\
= \sum_i  (t^{(i)} - o^{(i)})(-x^{(i)}_{j})
\end{equation}$$

(t = target, o = output)

And if we plug the results back into the learning rule, we get

$\Delta w_j = - \eta \frac{\partial J}{\partial w_j} = - \eta \sum_i  (t^{(i)} - o^{(i)})(- x^{(i)}_{j}) = \eta \sum_i (t^{(i)} - o^{(i)})x^{(i)}_{j}$,

Eventually, we can apply a simultaneous weight update similar to the perceptron rule:

$\mathbf{w} := \mathbf{w} + \Delta \mathbf{w}$.

**Although, the learning rule above looks identical to the perceptron rule, we shall note the two main differences:**

1. Here, the output "o" is a real number and not a class label as in the perceptron learning rule.
2. The weight update is calculated based on all samples in the training set (instead of updating the weights incrementally after each sample), which is why this approach is also called "batch" gradient descent.

## The Gradient Descent Rule in Action

[[back to top](#Sections)]

Now, it's time to implement the gradient descent rule in Python.

```python
import numpy as np

class AdalineGD(object):

    def __init__(self, eta=0.01, epochs=50):
        self.eta = eta
        self.epochs = epochs

    def train(self, X, y):

        self.w_ = np.zeros(1 + X.shape[1])
        self.cost_ = []

        for i in range(self.epochs):
            output = self.net_input(X)
            errors = (y - output)
            self.w_[1:] += self.eta * X.T.dot(errors)
            self.w_[0] += self.eta * errors.sum()
            cost = (errors**2).sum() / 2.0
            self.cost_.append(cost)
        return self

    def net_input(self, X):
        return np.dot(X, self.w_[1:]) + self.w_[0]

    def activation(self, X):
        return self.net_input(X)

    def predict(self, X):
        return np.where(self.activation(X) >= 0.0, 1, -1)
```

In practice, it often requires some experimentation to find a good learning rate for optimal convergence, thus, we will start by plotting the cost for two different learning rates.

```python
ada = AdalineGD(epochs=10, eta=0.01).train(X, y)
plt.plot(range(1, len(ada.cost_)+1), np.log10(ada.cost_), marker='o')
plt.xlabel('Iterations')
plt.ylabel('log(Sum-squared-error)')
plt.title('Adaline - Learning rate 0.01')
plt.show()

ada = AdalineGD(epochs=10, eta=0.0001).train(X, y)
plt.plot(range(1, len(ada.cost_)+1), ada.cost_, marker='o')
plt.xlabel('Iterations')
plt.ylabel('Sum-squared-error')
plt.title('Adaline - Learning rate 0.0001')
plt.show()
```

The two plots above nicely emphasize the importance of plotting learning curves by illustrating two most common problems with gradient descent:

1. If the learning rate is too large, gradient descent will overshoot the minima and diverge.
2. If the learning rate is too small, the algorithm will require too many epochs to converge and can become trapped in local minima more easily.

Gradient descent is also a good example why feature scaling is important for many machine learning algorithms.
It is not only easier to find an appropriate learning rate if the features are on the same scale, but it also often leads to faster convergence and can prevent the weights from becoming too small (numerical stability).

A common way of feature scaling is standardization

$$\mathbf{x}_{j, std} = \frac{\mathbf{x}_j - \mathbf{\mu}_j}{\mathbf{\sigma}_j}$$

where $\mathbf{\mu}_j$ is the sample mean of the feature $\mathbf{x}_{j}$ and $\mathbf{\sigma}_j$ the standard deviation, respectively. After standardization, the features will have unit variance and are centered around mean zero.

```python
# standardize features
X_std = np.copy(X)
X_std[:,0] = (X[:,0] - X[:,0].mean()) / X[:,0].std()
X_std[:,1] = (X[:,1] - X[:,1].mean()) / X[:,1].std()
```

```python
%matplotlib inline
import matplotlib.pyplot as plt
from mlxtend.evaluate import plot_decision_regions

ada = AdalineGD(epochs=15, eta=0.01)

ada.train(X_std, y)
plot_decision_regions(X_std, y, clf=ada)
plt.title('Adaline - Gradient Descent')
plt.xlabel('sepal length [standardized]')
plt.ylabel('petal length [standardized]')
plt.show()

plt.plot(range(1, len( ada.cost_)+1), ada.cost_, marker='o')
plt.xlabel('Iterations')
plt.ylabel('Sum-squared-error')
plt.show()
```

## Online Learning via Stochastic Gradient Descent

[[back to top](#Sections)]

The previous section was all about "batch" gradient descent learning. The "batch" updates refers to the fact that the cost function is minimized based on the complete training data set. If we think back to the perceptron rule, we remember that it performed the weight update incrementally after each individual training sample. This approach is also called "online" learning, and in fact, this is also how Adaline was first described by Bernard Widrow et al. [[3](#References)]

The process of incrementally updating the weights is also called "stochastic" gradient descent since it approximates the minimization of the cost function. Although the stochastic gradient descent approach might sound inferior to gradient descent due its "stochastic" nature and the "approximated" direction (gradient), it can have certain advantages in practice. Often, stochastic gradient descent converges much faster than gradient descent since the updates are applied immediately after each training sample; stochastic gradient descent is computationally more efficient, especially for very large datasets. Another advantage of online learning is that the classifier can be immediately updated as new training data arrives, e.g., in web applications, and old training data can be discarded if storage is an issue. In large-scale machine learning systems, it is also common practice to use so-called "mini-batches", a compromise with smoother convergence than stochastic gradient descent.

In the interests of completeness let us also implement the stochastic gradient descent Adaline and confirm that it converges on the linearly separable iris dataset.

```python
import numpy as np

class AdalineSGD(object):

    def __init__(self, eta=0.01, epochs=50):
        self.eta = eta
        self.epochs = epochs

    def train(self, X, y, reinitialize_weights=True):

        if reinitialize_weights:
            self.w_ = np.zeros(1 + X.shape[1])
        self.cost_ = []

        for i in range(self.epochs):
            for xi, target in zip(X, y):
                output = self.net_input(xi)
                error = (target - output)
                self.w_[1:] += self.eta * xi.dot(error)
                self.w_[0] += self.eta * error

            cost = ((y - self.activation(X))**2).sum() / 2.0
            self.cost_.append(cost)
        return self

    def net_input(self, X):
        return np.dot(X, self.w_[1:]) + self.w_[0]

    def activation(self, X):
        return self.net_input(X)

    def predict(self, X):
        return np.where(self.activation(X) >= 0.0, 1, -1)
```

One more advice before we let the adaline learn via stochastic gradient descent is to shuffle the training dataset to iterate over the training samples in random order.

We shall note that the "standard" stochastic gradient descent algorithm uses sampling "with replacement," which means that at each iteration, a training sample is chosen randomly from the entire training set. In contrast, sampling "without replacement," which means that each training sample is evaluated exactly once in every epoch, is not only easier to implement but also shows a better performance in empirical comparisons. A more detailed discussion about this topic can be found in Benjamin Recht and Christopher Re's paper *Beneath the valley of the noncommutative arithmetic-geometric mean inequality: conjectures, case-studies, and consequences* [[4](#References)].

```python
ada = AdalineSGD(epochs=15, eta=0.01)

# shuffle data
np.random.seed(123)
idx = np.random.permutation(len(y))
X_shuffled, y_shuffled =  X_std[idx], y[idx]

# train and adaline and plot decision regions
ada.train(X_shuffled, y_shuffled)
plot_decision_regions(X_shuffled, y_shuffled, clf=ada)
plt.title('Adaline - Gradient Descent')
plt.xlabel('sepal length [standardized]')
plt.ylabel('petal length [standardized]')
plt.show()

plt.plot(range(1, len(ada.cost_)+1), ada.cost_, marker='o')
plt.xlabel('Iterations')
plt.ylabel('Sum-squared-error')
plt.show()
```

# What's Next?

[[back to top](#Sections)]

Although we covered many different topics during this article, we just scratched the surface of artificial neurons.

In later articles, we will take a look at different approaches to dynamically adjust the learning rate, the concepts of "One-vs-All" and "One-vs-One" for multi-class classification, regularization to overcome overfitting by introducing additional information, dealing with nonlinear problems and multilayer neural networks, different activation functions for artificial neurons, and related concepts such as logistic regression and support vector machines.

### References

[[back to top](#Sections)]

[1] F. Rosenblatt. The perceptron, a perceiving and recognizing automaton Project Para. Cornell Aeronautical Laboratory, 1957.

[2] W. S. McCulloch and W. Pitts. A logical calculus of the ideas immanent in nervous activity. The bulletin of mathematical biophysics, 5(4):115–133, 1943.

[3] B. Widrow et al. Adaptive ”Adaline” neuron using chemical ”memistors”. Number Technical Report 1553-2. Stanford Electron. Labs., Stanford, CA, October 1960.

[4] B. Recht and C. R ́e. Beneath the valley of the noncommutative arithmetic-geometric mean inequality: conjectures, case-studies, and consequences. arXiv preprint arXiv:1202.4184, 2012.

```python

```

```python

```

```python

```

```python
tested; gopal
```


```python
%matplotlib inline
```

```python
!ls \content
```

    ls: cannot access 'content': No such file or directory

Adversarial Example Generation
==============================

**Author:** `Nathan Inkawhich <https://github.com/inkawhich>`__

If you are reading this, hopefully you can appreciate how effective some
machine learning models are. Research is constantly pushing ML models to
be faster, more accurate, and more efficient. However, an often
overlooked aspect of designing and training models is security and
robustness, especially in the face of an adversary who wishes to fool
the model.

This tutorial will raise your awareness to the security vulnerabilities
of ML models, and will give insight into the hot topic of adversarial
machine learning. You may be surprised to find that adding imperceptible
perturbations to an image *can* cause drastically different model
performance. Given that this is a tutorial, we will explore the topic
via example on an image classifier. Specifically we will use one of the
first and most popular attack methods, the Fast Gradient Sign Attack
(FGSM), to fool an MNIST classifier.

Threat Model
------------

For context, there are many categories of adversarial attacks, each with
a different goal and assumption of the attacker’s knowledge. However, in
general the overarching goal is to add the least amount of perturbation
to the input data to cause the desired misclassification. There are
several kinds of assumptions of the attacker’s knowledge, two of which
are: **white-box** and **black-box**. A *white-box* attack assumes the
attacker has full knowledge and access to the model, including
architecture, inputs, outputs, and weights. A *black-box* attack assumes
the attacker only has access to the inputs and outputs of the model, and
knows nothing about the underlying architecture or weights. There are
also several types of goals, including **misclassification** and
**source/target misclassification**. A goal of *misclassification* means
the adversary only wants the output classification to be wrong but does
not care what the new classification is. A *source/target
misclassification* means the adversary wants to alter an image that is
originally of a specific source class so that it is classified as a
specific target class.

In this case, the FGSM attack is a *white-box* attack with the goal of
*misclassification*. With this background information, we can now
discuss the attack in detail.

Fast Gradient Sign Attack
-------------------------

One of the first and most popular adversarial attacks to date is
referred to as the *Fast Gradient Sign Attack (FGSM)* and is described
by Goodfellow et. al. in `Explaining and Harnessing Adversarial
Examples <https://arxiv.org/abs/1412.6572>`__. The attack is remarkably
powerful, and yet intuitive. It is designed to attack neural networks by
leveraging the way they learn, *gradients*. The idea is simple, rather
than working to minimize the loss by adjusting the weights based on the
backpropagated gradients, the attack *adjusts the input data to maximize
the loss* based on the same backpropagated gradients. In other words,
the attack uses the gradient of the loss w.r.t the input data, then
adjusts the input data to maximize the loss.

Before we jump into the code, let’s look at the famous
`FGSM <https://arxiv.org/abs/1412.6572>`__ panda example and extract
some notation.

.. figure:: /_static/img/fgsm_panda_image.png
   :alt: fgsm_panda_image

From the figure, $\mathbf{x}$ is the original input image
correctly classified as a “panda”, $y$ is the ground truth label
for $\mathbf{x}$, $\mathbf{\theta}$ represents the model
parameters, and $J(\mathbf{\theta}, \mathbf{x}, y)$ is the loss
that is used to train the network. The attack backpropagates the
gradient back to the input data to calculate
$\nabla_{x} J(\mathbf{\theta}, \mathbf{x}, y)$. Then, it adjusts
the input data by a small step ($\epsilon$ or $0.007$ in the
picture) in the direction (i.e.
$sign(\nabla_{x} J(\mathbf{\theta}, \mathbf{x}, y))$) that will
maximize the loss. The resulting perturbed image, $x'$, is then
*misclassified* by the target network as a “gibbon” when it is still
clearly a “panda”.

Hopefully now the motivation for this tutorial is clear, so lets jump
into the implementation.

```python
from __future__ import print_function
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
import numpy as np
import matplotlib.pyplot as plt
from torch.utils.data import DataLoader, TensorDataset

import torchvision.utils
from torchvision import models
import torchvision.datasets as dsets
import torchvision.transforms as transforms

```

Implementation
--------------

In this section, we will discuss the input parameters for the tutorial,
define the model under attack, then code the attack and run some tests.

Inputs
~~~~~~

There are only three inputs for this tutorial, and are defined as
follows:

-  **epsilons** - List of epsilon values to use for the run. It is
   important to keep 0 in the list because it represents the model
   performance on the original test set. Also, intuitively we would
   expect the larger the epsilon, the more noticeable the perturbations
   but the more effective the attack in terms of degrading model
   accuracy. Since the data range here is $[0,1]$, no epsilon
   value should exceed 1.

-  **pretrained_model** - path to the pretrained MNIST model which was
   trained with
   `pytorch/examples/mnist <https://github.com/pytorch/examples/tree/master/mnist>`__.
   For simplicity, download the pretrained model `here <https://drive.google.com/drive/folders/1fn83DF14tWmit0RTKWRhPq5uVXt73e0h?usp=sharing>`__.

-  **use_cuda** - boolean flag to use CUDA if desired and available.
   Note, a GPU with CUDA is not critical for this tutorial as a CPU will
   not take much time.

```python
from google.colab import drive
drive.mount('/gdrive')

```

    Go to this URL in a browser: https://accounts.google.com/o/oauth2/auth?client_id=947318989803-6bn6qk8qdgf4n4g3pfee6491hc0brc4i.apps.googleusercontent.com&redirect_uri=urn%3aietf%3awg%3aoauth%3a2.0%3aoob&response_type=code&scope=email%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdocs.test%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdrive%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdrive.photos.readonly%20https%3a%2f%2fwww.googleapis.com%2fauth%2fpeopleapi.readonly

    Enter your authorization code:
    ··········
    Mounted at /gdrive

```python
pretrained_model = '/gdrive/My Drive/Tmp/cifar_net.pth' #pretrained_model = "lenet_mnist_model.pth"
use_cuda=True
```

Model Under Attack
~~~~~~~~~~~~~~~~~~

As mentioned, the model under attack is the same MNIST model from
`pytorch/examples/mnist <https://github.com/pytorch/examples/tree/master/mnist>`__.
You may train and save your own MNIST model or you can download and use
the provided model. The *Net* definition and test dataloader here have
been copied from the MNIST example. The purpose of this section is to
define the model and dataloader, then initialize the model and load the
pretrained weights.

```python
# transform = transforms.Compose(
#     [transforms.ToTensor(),
#     transforms.Normalize((0.5,0.5,0.5), (0.5,0.5,0.5))])

# normalize to range [0 1]; also the model has been trained on this range. Later I found this is not very imortant and [-1 1] also works fine,
transform = transforms.Compose(
    [transforms.ToTensor()])

# cifar10_train = dsets.CIFAR10(root='./data', train=True,
#                                        download=True, transform=transform)
cifar10_test  = dsets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)

test_loader = torch.utils.data.DataLoader(cifar10_test, batch_size=1,
                                        shuffle=False, num_workers=1)

classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

```

    Files already downloaded and verified

```python
# simply define a silu function
def srelu(input, slope):
    return slope * F.relu(input)

class SReLU(nn.Module):
    def __init__(self, slope):
        super().__init__() # init the base class
        self.slope = slope

    def forward(self, input):
        return srelu(input, self.slope) # simply apply already implemented SiLU
```

```python
class Net(nn.Module):
    def __init__(self, slope):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)
        self.slope = slope

    def forward(self, x):
        x = self.pool(srelu(self.conv1(x), self.slope))
        x = self.pool(srelu(self.conv2(x), self.slope))
        x = x.view(-1, 16 * 5 * 5)
        x = srelu(self.fc1(x), self.slope)
        x = srelu(self.fc2(x), self.slope)
        x = self.fc3(x)
        return F.log_softmax(x, dim=1)

# Define what device we are using
print("CUDA Available: ",torch.cuda.is_available())
device = torch.device("cuda" if (use_cuda and torch.cuda.is_available()) else "cpu")

# # Initialize the network
model = Net(1).to(device)

# # Load the pretrained model
model.load_state_dict(torch.load(pretrained_model, map_location='cpu'))

# # Set the model in evaluation mode. In this case this is for the Dropout layers
model.eval()
```

    CUDA Available:  True

    Net(
      (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
      (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
      (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
      (fc1): Linear(in_features=400, out_features=120, bias=True)
      (fc2): Linear(in_features=120, out_features=84, bias=True)
      (fc3): Linear(in_features=84, out_features=10, bias=True)
    )

FGSM Attack
~~~~~~~~~~~

Now, we can define the function that creates the adversarial examples by
perturbing the original inputs. The ``fgsm_attack`` function takes three
inputs, *image* is the original clean image ($x$), *epsilon* is
the pixel-wise perturbation amount ($\epsilon$), and *data_grad*
is gradient of the loss w.r.t the input image
($\nabla_{x} J(\mathbf{\theta}, \mathbf{x}, y)$). The function
then creates perturbed image as

\begin{align}perturbed\_image = image + epsilon*sign(data\_grad) = x + \epsilon * sign(\nabla_{x} J(\mathbf{\theta}, \mathbf{x}, y))\end{align}

Finally, in order to maintain the original range of the data, the
perturbed image is clipped to range $[0,1]$.

```python
# FGSM attack code
def fgsm_attack(image, epsilon, data_grad):
    # Collect the element-wise sign of the data gradient
    sign_data_grad = data_grad.sign()
    # Create the perturbed image by adjusting each pixel of the input image
    perturbed_image = image + epsilon*sign_data_grad
    # Adding clipping to maintain [0,1] range
    perturbed_image = torch.clamp(perturbed_image, 0, 1)
    # Return the perturbed image
    return perturbed_image
```

Testing Function
~~~~~~~~~~~~~~~~

Finally, the central result of this tutorial comes from the ``test``
function. Each call to this test function performs a full test step on
the MNIST test set and reports a final accuracy. However, notice that
this function also takes an *epsilon* input. This is because the
``test`` function reports the accuracy of a model that is under attack
from an adversary with strength $\epsilon$. More specifically, for
each sample in the test set, the function computes the gradient of the
loss w.r.t the input data ($data\_grad$), creates a perturbed
image with ``fgsm_attack`` ($perturbed\_data$), then checks to see
if the perturbed example is adversarial. In addition to testing the
accuracy of the model, the function also saves and returns some
successful adversarial examples to be visualized later.

```python
def test_scale( model, device, test_loader, epsilon, scale=1 ):

    # Accuracy counter
    correct = 0
    adv_examples = []

    # Loop over all examples in test set
    for data, target in test_loader:

        # scaling the inout
        data = scale * data
        data = torch.clamp(data, 0, 1)

        # Send the data and label to the device
        data, target = data.to(device), target.to(device)

        # Set requires_grad attribute of tensor. Important for Attack
        data.requires_grad = True

        # Forward pass the data through the model
        output = model(data)
        init_pred = output.max(1, keepdim=True)[1] # get the index of the max log-probability

        # If the initial prediction is wrong, dont bother attacking, just move on
        # import pdb; pdb.set_trace()
        if init_pred.item() != target.item():
            continue

        # Calculate the loss
        loss = F.nll_loss(output, target)

        # Zero all existing gradients
        model.zero_grad()

        # Calculate gradients of model in backward pass
        loss.backward()

        # Collect datagrad
        data_grad = data.grad.data

        # Call FGSM Attack
        perturbed_data = fgsm_attack(data, epsilon, data_grad)

        # Re-classify the perturbed image
        output = model(perturbed_data)

        # Check for success
        final_pred = output.max(1, keepdim=True)[1] # get the index of the max log-probability
        if final_pred.item() == target.item():
            correct += 1
            # Special case for saving 0 epsilon examples
            if (epsilon == 0) and (len(adv_examples) < 5):
                adv_ex = perturbed_data.squeeze().detach().cpu().numpy()
                adv_examples.append( (init_pred.item(), final_pred.item(), adv_ex) )
        else:
            # Save some adv examples for visualization later
            if len(adv_examples) < 5:
                adv_ex = perturbed_data.squeeze().detach().cpu().numpy()
                adv_examples.append( (init_pred.item(), final_pred.item(), adv_ex) )

    # Calculate final accuracy for this epsilon
    final_acc = correct/float(len(test_loader))
    print("Epsilon: {}\tTest Accuracy = {} / {} = {}".format(epsilon, correct, len(test_loader), final_acc))

    # Return the accuracy and an adversarial example
    return final_acc, adv_examples
```

Run Attack
~~~~~~~~~~

The last part of the implementation is to actually run the attack. Here,
we run a full test step for each epsilon value in the *epsilons* input.
For each epsilon we also save the final accuracy and some successful
adversarial examples to be plotted in the coming sections. Notice how
the printed accuracies decrease as the epsilon value increases. Also,
note the $\epsilon=0$ case represents the original test accuracy,
with no attack.

```python
epsilons = [0, 0.003, 0.007, 0.01, 0.05, 0.1 ]

scales = [.5, 1, 2, 5, 10, 100]
examples = []

all_res = []
# Run test for each epsilon
for s in scales:
    print(f'sclae={s}')
    accuracies = []
    for eps in epsilons:
        acc, ex = test_scale(model, device, test_loader, eps, s)
        accuracies.append(acc)
#         examples.append(ex)
    all_res.append(accuracies)
```

    sclae=0.5
    Epsilon: 0	Test Accuracy = 3753 / 10000 = 0.3753
    Epsilon: 0.003	Test Accuracy = 1871 / 10000 = 0.1871
    Epsilon: 0.007	Test Accuracy = 814 / 10000 = 0.0814
    Epsilon: 0.01	Test Accuracy = 444 / 10000 = 0.0444
    Epsilon: 0.05	Test Accuracy = 60 / 10000 = 0.006
    Epsilon: 0.1	Test Accuracy = 148 / 10000 = 0.0148
    sclae=1
    Epsilon: 0	Test Accuracy = 5739 / 10000 = 0.5739
    Epsilon: 0.003	Test Accuracy = 4155 / 10000 = 0.4155
    Epsilon: 0.007	Test Accuracy = 2650 / 10000 = 0.265
    Epsilon: 0.01	Test Accuracy = 1936 / 10000 = 0.1936
    Epsilon: 0.05	Test Accuracy = 124 / 10000 = 0.0124
    Epsilon: 0.1	Test Accuracy = 91 / 10000 = 0.0091
    sclae=2
    Epsilon: 0	Test Accuracy = 4726 / 10000 = 0.4726
    Epsilon: 0.003	Test Accuracy = 3775 / 10000 = 0.3775
    Epsilon: 0.007	Test Accuracy = 2811 / 10000 = 0.2811
    Epsilon: 0.01	Test Accuracy = 2271 / 10000 = 0.2271
    Epsilon: 0.05	Test Accuracy = 249 / 10000 = 0.0249
    Epsilon: 0.1	Test Accuracy = 90 / 10000 = 0.009
    sclae=5
    Epsilon: 0	Test Accuracy = 2223 / 10000 = 0.2223
    Epsilon: 0.003	Test Accuracy = 1782 / 10000 = 0.1782
    Epsilon: 0.007	Test Accuracy = 1245 / 10000 = 0.1245
    Epsilon: 0.01	Test Accuracy = 1009 / 10000 = 0.1009
    Epsilon: 0.05	Test Accuracy = 153 / 10000 = 0.0153
    Epsilon: 0.1	Test Accuracy = 52 / 10000 = 0.0052
    sclae=10
    Epsilon: 0	Test Accuracy = 1465 / 10000 = 0.1465
    Epsilon: 0.003	Test Accuracy = 1187 / 10000 = 0.1187
    Epsilon: 0.007	Test Accuracy = 624 / 10000 = 0.0624
    Epsilon: 0.01	Test Accuracy = 494 / 10000 = 0.0494
    Epsilon: 0.05	Test Accuracy = 86 / 10000 = 0.0086
    Epsilon: 0.1	Test Accuracy = 43 / 10000 = 0.0043
    sclae=100
    Epsilon: 0	Test Accuracy = 1018 / 10000 = 0.1018
    Epsilon: 0.003	Test Accuracy = 938 / 10000 = 0.0938
    Epsilon: 0.007	Test Accuracy = 118 / 10000 = 0.0118
    Epsilon: 0.01	Test Accuracy = 95 / 10000 = 0.0095
    Epsilon: 0.05	Test Accuracy = 14 / 10000 = 0.0014
    Epsilon: 0.1	Test Accuracy = 9 / 10000 = 0.0009

Results
-------

Accuracy vs Epsilon
~~~~~~~~~~~~~~~~~~~

The first result is the accuracy versus epsilon plot. As alluded to
earlier, as epsilon increases we expect the test accuracy to decrease.
This is because larger epsilons mean we take a larger step in the
direction that will maximize the loss. Notice the trend in the curve is
not linear even though the epsilon values are linearly spaced. For
example, the accuracy at $\epsilon=0.05$ is only about 4% lower
than $\epsilon=0$, but the accuracy at $\epsilon=0.2$ is 25%
lower than $\epsilon=0.15$. Also, notice the accuracy of the model
hits random accuracy for a 10-class classifier between
$\epsilon=0.25$ and $\epsilon=0.3$.

```python
symbs = ['*-', 'o-', 's-', 'd-', '+-', 'x-', '^-', '<-']
plt.figure(figsize=(5,5))

for idx, accuracies in enumerate(all_res):
  plt.plot( accuracies, symbs[idx])
plt.yticks(np.arange(0, 1, step=0.1))
plt.xticks(np.arange(0, len(epsilons), step=1), epsilons)
plt.title("Accuracy vs Epsilon")
plt.xlabel("Epsilon")
plt.ylabel("Accuracy")
plt.legend(scales)
plt.show()
```

```python
for name, param in model.named_parameters():
    print(name, '\t\t', param)
```

    conv1.weight 		 Parameter containing:
    tensor([[[[ 0.0716, -0.2105, -0.3368, -0.3054, -0.4158],
              [ 0.2207,  0.0393, -0.0686, -0.1781, -0.2624],
              [ 0.2308,  0.3688,  0.3894,  0.1577, -0.0135],
              [ 0.0110,  0.2730,  0.3224,  0.4715,  0.2458],
              [-0.2249, -0.2656, -0.1713, -0.1037,  0.0250]]],

            [[[ 0.1627,  0.3068,  0.0103, -0.4056, -0.1544],
              [ 0.2326,  0.3716, -0.0010, -0.4189, -0.3831],
              [ 0.1694,  0.4279, -0.1721, -0.2089, -0.3222],
              [ 0.3818,  0.0690,  0.0631, -0.2041, -0.2434],
              [ 0.2582,  0.1175,  0.1383, -0.1191, -0.0858]]],

            [[[-0.4009, -0.2780,  0.0117,  0.0297,  0.2906],
              [-0.1245, -0.1157,  0.0199,  0.3325,  0.1503],
              [-0.2538,  0.1658,  0.3959,  0.4191, -0.1492],
              [ 0.0242,  0.1532,  0.2859, -0.0572, -0.2331],
              [ 0.1785,  0.0378,  0.0625, -0.1391, -0.1094]]],

            [[[ 0.2123,  0.3867,  0.3543,  0.0238,  0.1834],
              [-0.1520, -0.0358,  0.1342,  0.1333,  0.2333],
              [-0.1841, -0.2525,  0.0497, -0.0087,  0.1459],
              [ 0.0662, -0.2662, -0.1838, -0.0887, -0.2658],
              [-0.0270,  0.0353, -0.1582, -0.2547, -0.2047]]],

            [[[ 0.3354,  0.0766,  0.1723,  0.0181, -0.2436],
              [-0.1808,  0.3147,  0.0959,  0.0672, -0.0186],
              [-0.0161,  0.3644,  0.3018, -0.1804,  0.0325],
              [ 0.1156,  0.3093,  0.2729, -0.0490, -0.0082],
              [-0.1838,  0.0518,  0.3013,  0.0050,  0.2397]]],

            [[[-0.2074, -0.3270, -0.1741,  0.2165,  0.3955],
              [-0.1506,  0.0792,  0.2740,  0.3807,  0.2204],
              [ 0.2410,  0.1996,  0.0631, -0.0692, -0.2378],
              [-0.0067, -0.0351, -0.2246,  0.0627,  0.1113],
              [-0.0117,  0.0261,  0.0133,  0.1745,  0.2427]]],

            [[[-0.1348,  0.1092, -0.0244,  0.0292, -0.2357],
              [-0.2222, -0.1731, -0.0452,  0.2187, -0.1051],
              [-0.2438,  0.0446,  0.1167,  0.1665, -0.1699],
              [-0.0126, -0.0873, -0.0293, -0.1430, -0.0684],
              [-0.0143,  0.0911,  0.0647,  0.1692,  0.3910]]],

            [[[ 0.1434,  0.2022,  0.1325, -0.1210,  0.2094],
              [-0.2349, -0.2177, -0.4011, -0.3501, -0.3614],
              [ 0.0977, -0.2784, -0.3112, -0.1948, -0.2123],
              [ 0.2357,  0.1336,  0.1090,  0.1476,  0.2865],
              [-0.0037,  0.3885,  0.4732,  0.4028,  0.0174]]],

            [[[ 0.0279, -0.1312,  0.2581,  0.0561,  0.1460],
              [-0.0524, -0.0660,  0.0468,  0.2466,  0.2751],
              [-0.0179, -0.0107, -0.2264,  0.2436,  0.4001],
              [ 0.0906,  0.0360, -0.2536, -0.1249,  0.3145],
              [-0.3844, -0.0830, -0.4069, -0.4139, -0.0060]]],

            [[[-0.1255, -0.0125,  0.3053,  0.3956,  0.3941],
              [-0.0981, -0.2020,  0.3015,  0.1462,  0.1056],
              [-0.0702, -0.2145,  0.1430,  0.2274, -0.0538],
              [-0.2880, -0.1260, -0.0125, -0.0389, -0.1249],
              [-0.1637, -0.4058, -0.3674, -0.2811,  0.0233]]]], device='cuda:0',
           requires_grad=True)
    conv1.bias 		 Parameter containing:
    tensor([ 0.0208, -0.1223, -0.0758,  0.0572,  0.1769,  0.0333,  0.1023,  0.0913,
            -0.0270,  0.0105], device='cuda:0', requires_grad=True)
    conv2.weight 		 Parameter containing:
    tensor([[[[ 3.2406e-03,  2.3492e-02, -1.5451e-01, -3.8980e-02,  3.2560e-02],
              [-5.6062e-02, -3.6786e-02, -1.1818e-01,  3.1350e-02, -1.2680e-02],
              [ 7.0198e-02, -1.4161e-03, -3.1375e-02,  5.6263e-02,  3.1962e-02],
              [-8.3054e-02, -4.4973e-02,  1.6788e-02,  1.2552e-02,  2.7096e-02],
              [ 1.5355e-02,  5.4694e-02,  7.2467e-02,  2.3650e-02, -6.8467e-02]],

             [[-2.3582e-02, -9.7704e-02, -8.8937e-03,  1.9265e-02, -4.0476e-03],
              [-3.5773e-02, -8.9463e-02,  5.7462e-02,  7.4515e-02, -1.1988e-02],
              [-8.7023e-02,  1.1307e-02,  1.9738e-02,  1.1747e-02, -1.2718e-02],
              [-7.1679e-02, -3.8960e-03,  2.0039e-02, -1.7698e-02, -3.4618e-02],
              [-8.4022e-03,  2.1286e-03,  5.1770e-03, -7.5517e-02, -9.0349e-02]],

             [[ 6.1617e-02, -1.9899e-02,  3.5535e-02,  4.7708e-02,  1.3664e-01],
              [-7.5929e-02,  3.7885e-02,  8.0744e-02,  1.0877e-01,  2.2666e-02],
              [-5.3058e-02,  1.3982e-02, -1.2811e-03,  6.6976e-03, -3.6921e-02],
              [-1.0464e-01, -1.0586e-01, -4.8237e-02, -1.0337e-01, -6.8164e-02],
              [ 2.5132e-02, -7.9188e-02, -7.0288e-02, -7.7731e-02, -7.8140e-02]],

             ...,

             [[-7.1625e-02,  3.4197e-02, -4.0996e-02, -9.0841e-03,  6.6187e-02],
              [ 1.5555e-03,  1.0294e-01, -1.1165e-01,  5.5047e-03,  4.1969e-02],
              [-7.7031e-03, -4.6209e-02, -2.1860e-02, -5.0044e-02,  3.7437e-03],
              [ 5.0873e-02,  2.0071e-02,  7.9846e-02,  7.3587e-02,  1.1663e-01],
              [ 1.3951e-01,  3.4201e-02,  1.3915e-02,  3.2151e-02, -8.7847e-02]],

             [[ 3.3142e-02, -7.6181e-02, -2.5871e-04,  4.8773e-03, -1.9490e-02],
              [ 6.1528e-03, -4.4825e-02, -4.8179e-02,  1.4189e-02, -1.6885e-02],
              [ 4.7568e-02,  5.9331e-03,  9.6932e-02,  6.5544e-02,  2.1964e-02],
              [ 6.6608e-02, -2.2974e-02,  6.3493e-02,  9.8286e-03, -1.1296e-01],
              [-7.3091e-02, -9.7398e-02, -2.2807e-02, -5.1994e-02, -6.6852e-02]],

             [[ 9.1175e-02, -7.4183e-02,  5.7258e-02, -9.9369e-03, -2.3709e-02],
              [ 5.3960e-02, -1.5873e-02,  8.4859e-03, -1.0613e-01,  2.8231e-02],
              [ 5.7768e-02,  7.6650e-02, -4.3169e-02, -3.7843e-02,  2.8252e-02],
              [ 2.9920e-02,  2.0983e-02, -1.2960e-02,  1.3957e-02, -3.1601e-02],
              [-1.0488e-01, -7.4026e-02, -5.8408e-02,  3.8878e-02, -7.5910e-02]]],

            [[[-2.9365e-02,  3.2399e-02, -2.5986e-02,  8.8294e-02,  1.2441e-01],
              [-7.2747e-02, -5.8488e-02, -1.8048e-02, -5.8308e-03,  4.5541e-02],
              [-9.5670e-02, -5.6461e-02,  4.9058e-02,  1.3813e-02,  4.0005e-02],
              [-5.8488e-03,  1.2354e-01,  7.9167e-02,  2.5777e-02,  6.3247e-04],
              [ 1.1749e-01,  7.6435e-02,  4.1616e-02, -5.3279e-02, -9.6389e-02]],

             [[-1.2692e-02,  4.4093e-03, -1.4037e-01, -1.5158e-01,  5.0030e-02],
              [ 2.1660e-02, -1.0190e-01, -6.0065e-02, -2.4457e-02,  1.5927e-02],
              [-5.7182e-03, -5.3580e-02,  6.5555e-02, -1.5922e-02,  6.8660e-02],
              [-4.0793e-02, -4.0750e-02,  1.8933e-02, -1.2689e-01, -7.8944e-02],
              [-3.2061e-02,  1.7704e-02, -1.1314e-01, -1.7181e-01, -1.2024e-01]],

             [[ 1.1056e-02, -3.2912e-02, -4.7793e-02, -3.5121e-02,  6.6325e-02],
              [ 1.9364e-03,  3.4033e-02,  5.5499e-02,  1.3518e-01,  3.8330e-02],
              [ 7.0822e-02,  8.0836e-02, -1.2617e-03, -6.7602e-04, -5.9030e-02],
              [-5.4586e-02, -3.5652e-02, -6.5056e-02, -9.0833e-03, -7.8283e-02],
              [-6.3437e-02, -7.5626e-02, -1.2237e-01,  1.6368e-02, -2.7867e-02]],

             ...,

             [[-2.1205e-02,  6.3997e-02,  7.4062e-02,  5.0037e-02, -1.7404e-02],
              [ 2.7888e-02, -1.2465e-02, -5.5007e-02, -4.0537e-02, -1.0537e-02],
              [-1.0281e-01,  1.7290e-02,  1.0763e-02,  7.4117e-02,  1.1354e-01],
              [-2.8639e-02,  1.6626e-02,  4.7252e-02,  7.3116e-02,  9.9302e-04],
              [ 7.7133e-02,  8.5157e-02,  1.0495e-01,  4.5579e-02, -1.2862e-01]],

             [[-3.9319e-02, -8.3736e-02, -1.1566e-02, -4.8101e-02, -7.5922e-02],
              [-6.6929e-02, -2.9602e-02,  2.6696e-02,  1.0357e-02, -2.7448e-02],
              [ 2.9584e-02,  4.6510e-02,  5.8329e-02, -7.3835e-02,  3.4107e-04],
              [ 7.0233e-02,  1.8823e-02, -3.9542e-03,  9.3295e-03, -6.7738e-02],
              [ 5.6451e-02,  3.3854e-02, -3.1170e-02, -9.0905e-02, -6.8238e-02]],

             [[ 6.0264e-02,  1.3489e-02, -1.1083e-01, -6.4834e-02, -1.0347e-01],
              [-1.3426e-01, -4.9525e-02,  2.7510e-02,  4.3950e-02, -3.8565e-02],
              [-3.4958e-02,  7.6161e-02,  1.0131e-01,  4.6756e-02, -5.1791e-02],
              [-1.1140e-02,  5.0631e-02,  7.2451e-02, -3.4299e-02, -5.4583e-02],
              [-3.6768e-02,  6.0417e-02,  4.5989e-02, -6.2761e-02, -9.5459e-02]]],

            [[[ 4.4563e-02,  5.9872e-02,  1.2271e-01,  4.8217e-02,  6.7621e-02],
              [ 9.6950e-02,  5.0030e-02,  4.5794e-02,  5.4723e-03,  4.7155e-02],
              [ 7.9957e-03,  3.5117e-02, -6.6099e-02,  2.8642e-02,  1.3039e-01],
              [ 2.8450e-02,  2.1658e-02, -6.6176e-02, -7.6192e-02, -4.8325e-02],
              [-7.5177e-02,  9.1468e-03, -3.2752e-03,  1.7911e-02, -2.2739e-02]],

             [[-4.5614e-02,  2.9085e-02, -4.1997e-02,  1.0645e-03, -1.8657e-04],
              [-7.9652e-02, -2.5191e-02, -2.6619e-02, -9.5521e-03,  1.1337e-01],
              [-7.2420e-02, -7.8455e-03, -6.8409e-02, -4.2262e-02, -4.7118e-02],
              [-6.1591e-02,  7.5016e-02, -1.6441e-02,  3.4968e-02, -9.9923e-02],
              [ 2.9125e-03,  6.4524e-02,  2.0775e-02, -9.1303e-02, -1.1663e-01]],

             [[ 1.1815e-02, -1.1561e-01, -3.5057e-02, -4.1431e-02,  4.5217e-02],
              [-2.7642e-02,  4.6790e-02,  4.1553e-02, -1.6990e-02,  1.0332e-02],
              [ 2.7691e-02,  5.1338e-02, -8.6081e-02, -1.6986e-02, -2.7818e-02],
              [-4.1001e-02, -7.5508e-02, -3.6931e-02, -4.6065e-02, -3.2646e-02],
              [ 5.5465e-02,  6.4031e-02,  4.5948e-02, -5.4590e-02, -5.0784e-02]],

             ...,

             [[ 5.1806e-03,  8.6599e-02,  4.4183e-02, -7.3262e-03,  3.2709e-02],
              [ 5.8628e-04,  9.2975e-02,  5.4805e-02,  9.3968e-03,  8.1422e-02],
              [ 3.2659e-02, -4.6189e-02,  1.0316e-03, -3.3372e-02,  1.4532e-02],
              [ 5.0147e-02, -2.1381e-02,  2.5049e-02,  6.1027e-02, -7.7898e-02],
              [-3.0626e-03,  1.0922e-01,  4.9009e-02,  2.9281e-02, -3.3414e-02]],

             [[ 5.1430e-02, -1.7307e-02, -4.0158e-02, -4.6593e-02, -2.2410e-02],
              [ 9.3532e-02,  6.0234e-02,  5.3747e-02,  1.4397e-01, -9.7555e-02],
              [ 1.2916e-02, -3.4177e-02, -3.1555e-02,  3.9536e-02, -4.0185e-02],
              [-9.2673e-02, -4.6246e-02, -7.6708e-02,  2.9954e-02,  1.6910e-02],
              [-3.2925e-02,  5.3984e-03, -3.1208e-02,  1.1060e-02, -5.3339e-02]],

             [[-1.4054e-02, -8.2115e-02, -1.6085e-02, -1.2302e-01, -1.0704e-01],
              [ 6.8058e-02,  1.1936e-01,  7.1887e-02,  1.8280e-03, -4.4231e-02],
              [ 1.4987e-01,  7.1005e-02, -5.6956e-02,  2.1013e-03,  3.7827e-02],
              [ 5.2238e-02, -6.3953e-02, -4.9763e-02, -2.6169e-02, -2.4818e-03],
              [ 2.9697e-02,  5.0945e-03, -7.9696e-02, -5.5902e-02, -1.0575e-01]]],

            ...,

            [[[ 1.1673e-01,  6.8688e-02,  3.8541e-02, -1.8145e-02, -4.0311e-02],
              [-5.5185e-02, -5.1283e-02, -7.8085e-02, -8.6606e-02, -2.3905e-02],
              [-6.5268e-02, -8.4449e-02, -2.9057e-02,  1.3444e-01,  4.2075e-02],
              [-9.2482e-02, -1.0340e-01, -2.3502e-02,  1.0009e-01,  8.2379e-02],
              [-4.2585e-02,  6.7819e-03,  6.3555e-02, -8.0110e-03, -5.1712e-02]],

             [[-1.8320e-02, -9.5806e-02, -3.2208e-02, -7.5349e-02, -9.4703e-02],
              [-2.2735e-02,  4.3093e-02,  1.5593e-01,  5.1182e-02,  2.6505e-03],
              [-6.5782e-02, -3.2418e-02,  2.0098e-01,  1.3105e-01, -1.8755e-04],
              [-7.5738e-02, -1.9528e-02,  2.0169e-01,  7.8797e-02,  3.4014e-02],
              [-3.0056e-02,  1.2195e-03, -4.8983e-03, -2.6672e-02,  2.2467e-02]],

             [[-1.4346e-02, -4.9363e-03,  2.4653e-03,  1.0575e-01,  5.2716e-03],
              [-1.7676e-02, -8.9843e-02, -6.4424e-02,  5.6984e-02, -3.8588e-02],
              [-9.4159e-02, -1.4147e-01, -3.4852e-02, -5.4545e-02,  9.6373e-03],
              [ 4.6069e-03, -9.2915e-02, -1.2575e-01, -1.1252e-01, -4.9302e-03],
              [ 6.5314e-02, -4.6186e-03,  3.7907e-03,  5.0026e-03, -5.2141e-02]],

             ...,

             [[-5.1717e-02, -1.1548e-02, -3.5722e-02, -4.8252e-02, -2.3624e-03],
              [-1.0316e-02, -1.0061e-01, -9.6746e-02, -2.2608e-02,  4.8633e-02],
              [-1.5764e-02, -1.1463e-01, -1.1854e-01,  2.3761e-02,  1.0033e-01],
              [-2.1364e-02, -3.6315e-02,  1.1347e-02,  1.2085e-03,  4.8508e-03],
              [ 1.5899e-02, -7.0534e-04,  1.5393e-02,  2.7154e-02, -4.5235e-02]],

             [[-5.3407e-03,  3.5194e-02, -5.4456e-02, -1.3259e-03, -2.5566e-02],
              [ 5.5321e-02,  9.6244e-02,  2.3939e-02, -4.4724e-02, -7.4791e-02],
              [ 3.4828e-02,  7.5973e-02,  1.6468e-01,  3.1776e-02, -1.5392e-02],
              [-1.2091e-01,  7.3690e-02,  7.7322e-02,  1.0333e-01, -5.8316e-02],
              [-8.3618e-02,  2.2486e-02, -3.8987e-02,  1.1463e-02, -5.3135e-02]],

             [[-4.7398e-02, -1.7160e-02, -1.1741e-01, -1.0121e-01, -2.3814e-02],
              [ 4.2046e-02, -5.2907e-02, -1.1056e-01,  4.4180e-03, -1.2532e-01],
              [ 3.8826e-02, -1.4651e-01,  3.9591e-02,  5.9154e-03, -3.1876e-02],
              [ 2.9826e-02,  1.7856e-02, -3.9436e-03,  4.9777e-02, -6.0402e-03],
              [-1.0284e-01, -4.6473e-02,  1.2968e-02, -2.0338e-02,  1.5976e-04]]],

            [[[ 1.5193e-02,  5.7096e-02, -3.5322e-02, -5.5820e-02, -1.3253e-01],
              [ 8.0359e-02,  1.7342e-02,  1.0950e-02, -4.6353e-02, -9.9457e-02],
              [ 8.3726e-02, -5.7086e-04, -5.4463e-02, -7.2609e-02, -8.2260e-02],
              [ 3.1282e-02, -8.8818e-02, -9.3672e-02, -1.1095e-01, -1.2400e-01],
              [-6.8372e-02, -1.7500e-01, -1.0916e-01, -2.1871e-02, -3.1304e-02]],

             [[ 5.6444e-02,  4.9185e-02,  7.5450e-02,  8.8123e-02,  2.6439e-02],
              [ 1.2896e-02,  1.1045e-01,  1.2714e-01,  2.6480e-02,  1.2326e-01],
              [-4.1653e-02,  5.0474e-02,  1.3976e-01,  5.9167e-02,  1.1080e-01],
              [ 4.3210e-02,  9.1162e-02,  6.3861e-02, -1.8224e-02, -4.6584e-02],
              [ 9.1414e-02, -7.1644e-03,  4.7173e-02,  2.0085e-02,  5.5844e-02]],

             [[ 3.4813e-02, -9.3072e-02, -7.1623e-02, -3.7221e-02,  6.3834e-04],
              [-8.5928e-02,  3.6634e-05, -1.9533e-02,  2.4342e-02, -3.0719e-02],
              [-9.2936e-03,  4.5266e-02,  9.5609e-02,  7.5996e-02, -1.0007e-01],
              [ 8.0353e-02,  3.1478e-02,  1.3575e-01,  1.4468e-02,  1.2206e-02],
              [ 5.4383e-02,  6.1522e-02,  3.8471e-02, -1.2687e-02,  9.7042e-02]],

             ...,

             [[ 7.9088e-03, -2.5339e-02, -5.0461e-02, -8.1445e-02, -1.8118e-01],
              [ 3.3992e-02,  1.9507e-02, -2.1310e-02, -1.1999e-01, -1.5138e-01],
              [ 7.4818e-02, -8.3039e-02, -1.6725e-02, -1.1600e-01, -3.6865e-02],
              [ 6.0182e-02, -5.8928e-02, -1.3846e-01,  3.6211e-03, -3.7769e-02],
              [-2.1468e-02, -5.6421e-02, -6.0800e-03,  9.5311e-02,  1.2233e-01]],

             [[ 7.8180e-02,  2.7584e-02,  6.7619e-02, -6.1883e-02, -2.2267e-02],
              [ 5.2021e-02,  5.8063e-02,  2.0244e-02, -5.2788e-02, -9.3708e-02],
              [-5.8100e-02,  9.6376e-03, -6.5814e-02, -8.8950e-02, -1.0554e-01],
              [ 4.5192e-02, -5.5434e-02, -1.1351e-01, -1.4918e-01, -7.2342e-02],
              [-1.0280e-02, -8.3596e-02, -9.5348e-02, -8.1241e-02, -4.6028e-02]],

             [[ 1.4847e-02, -7.2905e-03, -4.3224e-02, -3.9776e-02,  1.6482e-02],
              [-6.5112e-02, -2.9044e-02,  2.5568e-02,  2.0679e-02, -2.8948e-02],
              [-7.6850e-02,  3.2196e-02,  7.3615e-02,  6.7641e-02, -4.7219e-02],
              [-3.0013e-02, -1.7610e-02,  1.9233e-02, -2.1600e-02, -1.2320e-01],
              [-1.5888e-02, -3.2151e-02, -3.0425e-02, -7.2162e-02,  3.2722e-02]]],

            [[[ 3.8342e-02, -7.1643e-02, -3.8899e-02, -3.1054e-02, -9.2070e-02],
              [ 5.8230e-02,  3.8005e-03, -3.3761e-02, -1.0233e-01, -1.3204e-01],
              [ 3.8958e-03, -4.6927e-02, -7.7617e-02, -1.2274e-01, -5.3947e-02],
              [-1.0023e-01, -7.0598e-02,  1.4389e-02, -1.2138e-01, -2.7234e-02],
              [-1.4343e-01, -1.5076e-01, -1.1692e-01, -6.0956e-02,  5.2540e-03]],

             [[ 1.5956e-02,  9.3919e-03,  6.4916e-02, -1.0452e-02, -4.2572e-02],
              [ 7.6396e-02,  7.9740e-02,  3.8097e-03, -1.1123e-03, -8.2374e-02],
              [ 8.1111e-02,  4.3804e-02,  9.6647e-02,  4.2713e-02,  7.0432e-02],
              [ 9.5397e-02,  3.5690e-02,  8.9411e-02,  2.9462e-02,  5.3192e-02],
              [ 1.2352e-01,  6.9035e-02,  1.3839e-01, -2.0425e-02,  1.3936e-01]],

             [[-5.0452e-02, -7.5217e-02, -3.7385e-02,  5.8201e-02,  4.0725e-02],
              [-8.7560e-02, -5.4459e-02,  6.3860e-02, -2.8118e-02,  4.0431e-02],
              [ 4.1790e-02,  6.6813e-02,  4.0199e-02, -9.2723e-02,  1.8208e-01],
              [ 2.0361e-01,  3.3444e-02, -8.7835e-02, -1.3059e-02,  8.6943e-02],
              [ 1.2563e-01,  8.6099e-02, -6.7836e-03, -2.1245e-02, -1.3146e-03]],

             ...,

             [[ 2.7495e-02, -8.7357e-02, -1.0211e-01, -4.8019e-02, -7.6701e-02],
              [ 2.2642e-02, -6.4198e-02, -4.1811e-02, -6.7755e-02, -3.4063e-02],
              [ 1.4155e-02, -9.0433e-02, -9.1400e-02, -5.8746e-02, -4.8009e-02],
              [-1.1856e-02, -8.3386e-02, -1.5353e-01, -7.6114e-02, -7.6436e-02],
              [-3.4464e-02, -6.2143e-02,  2.7012e-02,  7.0886e-04, -4.4748e-03]],

             [[ 9.4607e-02,  1.8445e-02, -4.7002e-02, -2.2003e-02, -1.3177e-02],
              [ 2.0734e-02, -2.2912e-02, -8.0467e-02, -2.7710e-02, -2.8224e-03],
              [-6.8572e-03,  1.0440e-02, -3.5518e-02,  4.1372e-03, -2.0476e-02],
              [ 2.9808e-02, -9.7879e-02, -4.7983e-03,  9.0547e-02,  1.2234e-01],
              [-3.5634e-02, -1.3524e-01, -1.4973e-01, -7.2582e-02, -4.4997e-03]],

             [[ 3.9441e-02, -3.8255e-02,  1.9479e-02,  1.6807e-02, -1.3818e-02],
              [ 1.9176e-02,  5.0845e-02,  5.4972e-02,  2.7127e-02, -6.7752e-04],
              [-2.5190e-02,  1.3671e-01, -2.2166e-03, -8.9856e-02,  1.0663e-01],
              [-1.3365e-03,  1.0261e-02, -7.5918e-02, -7.2554e-02,  1.6060e-01],
              [ 3.5451e-02, -1.0672e-01, -1.1914e-01, -1.1996e-02,  1.1065e-01]]]],
           device='cuda:0', requires_grad=True)
    conv2.bias 		 Parameter containing:
    tensor([ 0.0367, -0.0361,  0.0427, -0.0476, -0.0237, -0.0378, -0.0382, -0.0035,
            -0.0622, -0.0295,  0.0189, -0.0617, -0.0585,  0.0742, -0.0272, -0.0856,
             0.0027, -0.0772, -0.0160,  0.0287], device='cuda:0',
           requires_grad=True)
    fc1.weight 		 Parameter containing:
    tensor([[-0.0432, -0.0451, -0.0249,  ...,  0.0730,  0.1586,  0.0473],
            [-0.0010, -0.0796, -0.0384,  ...,  0.0267,  0.0399,  0.0416],
            [-0.0410,  0.0048, -0.0199,  ...,  0.0208,  0.0421,  0.0883],
            ...,
            [ 0.0060,  0.0307,  0.0497,  ...,  0.0487,  0.0009, -0.1537],
            [ 0.0091, -0.0079,  0.0224,  ...,  0.0122,  0.0358,  0.0011],
            [ 0.0710,  0.0095, -0.0194,  ..., -0.0837, -0.0367, -0.0426]],
           device='cuda:0', requires_grad=True)
    fc1.bias 		 Parameter containing:
    tensor([ 0.0613,  0.0407,  0.0155,  0.0150,  0.0309,  0.0106,  0.0054, -0.0210,
             0.0523,  0.0213,  0.0222,  0.0860, -0.0160,  0.0199,  0.0120, -0.0289,
             0.0813,  0.0469,  0.0737, -0.0129,  0.0415,  0.0347, -0.0314,  0.0559,
             0.0566, -0.0036,  0.0901,  0.0016, -0.0195, -0.0144, -0.0129,  0.0454,
             0.0228, -0.0215, -0.0232,  0.0994,  0.0363, -0.0543,  0.0440, -0.0139,
             0.0309,  0.0982,  0.0504, -0.0240,  0.0036,  0.0419, -0.0238, -0.0261,
             0.0587,  0.0734], device='cuda:0', requires_grad=True)
    fc2.weight 		 Parameter containing:
    tensor([[-0.0770, -0.1595,  0.1351,  0.2024, -0.2046, -0.0559, -0.2167, -0.1508,
             -0.1405, -0.2049, -0.2397, -0.1481, -0.1323,  0.2162,  0.2514,  0.1620,
              0.1507,  0.2602,  0.0773, -0.1474, -0.1545, -0.1898, -0.1575, -0.2840,
             -0.1066, -0.2594, -0.2433, -0.2415, -0.0065,  0.2663, -0.1490, -0.1689,
              0.2494,  0.1705, -0.1686,  0.2222,  0.1110,  0.0254,  0.1353, -0.1895,
             -0.1554, -0.2286,  0.1516, -0.1718, -0.0450,  0.2561, -0.1185, -0.1254,
              0.2117,  0.2056],
            [-0.0848,  0.1615, -0.2497,  0.1739,  0.2169,  0.2023, -0.2229,  0.1366,
             -0.0154, -0.1704,  0.0203,  0.2291, -0.1161, -0.1862, -0.0103, -0.2400,
              0.0898, -0.0933, -0.2255,  0.1298, -0.1904, -0.0456,  0.2154,  0.1804,
              0.2146, -0.2637, -0.2916,  0.2599,  0.1945,  0.0025,  0.2085, -0.1374,
             -0.2011, -0.2394,  0.2196, -0.0438, -0.1560, -0.1370,  0.1107,  0.2482,
             -0.0658,  0.1573, -0.1563, -0.2429,  0.2284, -0.1126, -0.1980,  0.2051,
             -0.2658,  0.0168],
            [-0.1404, -0.0575, -0.0845,  0.0459, -0.1437,  0.2079,  0.1644,  0.0901,
              0.2253,  0.0987, -0.0707,  0.2670,  0.2101,  0.1916,  0.1845,  0.1214,
              0.0693, -0.0954, -0.1760, -0.2644, -0.0889,  0.2360, -0.1462, -0.1980,
             -0.1323, -0.0768, -0.2093,  0.0373, -0.2495, -0.0680, -0.0913, -0.0934,
              0.0082, -0.1509,  0.2156,  0.1513, -0.0354, -0.0843,  0.1131,  0.2053,
              0.1530,  0.2225, -0.1567, -0.0362,  0.1276,  0.2084, -0.0470,  0.2165,
             -0.1776,  0.1935],
            [ 0.0686,  0.2199,  0.1628, -0.2300, -0.0377, -0.1835,  0.1080,  0.0971,
              0.0159,  0.1072, -0.0561,  0.2397, -0.1396, -0.0541,  0.0243, -0.0987,
             -0.0591, -0.0057, -0.0581,  0.1788,  0.2598,  0.2532,  0.0263, -0.0743,
             -0.2499, -0.1444,  0.1748, -0.0812,  0.2674, -0.1471, -0.2817,  0.2889,
             -0.1538,  0.1557,  0.0027,  0.0118,  0.1995,  0.0018, -0.2316, -0.0878,
              0.1925,  0.2329, -0.1893,  0.1155,  0.1720, -0.0418, -0.1206,  0.0616,
             -0.1526,  0.1656],
            [ 0.1730,  0.1422, -0.1862,  0.0096,  0.2001,  0.1987,  0.1697, -0.1730,
             -0.1993, -0.1957,  0.2586,  0.0346,  0.2881, -0.1817, -0.0468,  0.1523,
             -0.2472, -0.2026, -0.1039, -0.1498, -0.1765, -0.1061,  0.2256,  0.2049,
              0.1544,  0.2332,  0.0706, -0.0103, -0.0603, -0.1319,  0.1248,  0.2409,
             -0.2055, -0.2770, -0.0963, -0.2107, -0.1407,  0.3512,  0.1461, -0.0733,
             -0.2674, -0.1546,  0.1557,  0.0370, -0.1891, -0.0432, -0.1020, -0.2026,
              0.2063, -0.1723],
            [ 0.1598, -0.0730,  0.1404, -0.1735, -0.0179, -0.1517, -0.1803, -0.1802,
              0.2437, -0.2384,  0.2235, -0.0767, -0.1797, -0.1854, -0.1730, -0.1093,
             -0.2429,  0.2715,  0.2525,  0.1776,  0.2537, -0.1637, -0.1420,  0.0761,
              0.0716, -0.1806,  0.1693, -0.1261,  0.2359,  0.1229, -0.0288,  0.2561,
             -0.0967,  0.1562,  0.0155,  0.2064,  0.2224,  0.0146, -0.1664, -0.0880,
             -0.1780, -0.0518,  0.1445, -0.1330, -0.0926, -0.0189,  0.3200, -0.0487,
              0.1479, -0.1094],
            [-0.3489, -0.1620, -0.2170, -0.0465, -0.1727,  0.1675, -0.3525,  0.1213,
              0.1768, -0.2231,  0.2609, -0.1075, -0.0460, -0.2762,  0.1885,  0.1626,
             -0.3093,  0.3085,  0.2147,  0.1412,  0.0041, -0.1516, -0.1457, -0.1537,
             -0.1125, -0.1490, -0.2117, -0.1257,  0.2457, -0.0554,  0.2266, -0.1785,
             -0.0963, -0.1160, -0.1390,  0.2343, -0.1983,  0.3407,  0.1277,  0.2792,
             -0.3473, -0.3041,  0.2161, -0.0725, -0.1508,  0.1769,  0.3222, -0.1744,
              0.2086, -0.0700],
            [ 0.1591,  0.1763,  0.1291,  0.0565,  0.2101, -0.1105,  0.1304, -0.0745,
             -0.0414,  0.1086, -0.1657,  0.1979,  0.2833,  0.1809, -0.0767, -0.1970,
              0.0938, -0.1430, -0.1066, -0.2779, -0.1583, -0.0441, -0.1019,  0.1044,
             -0.1490,  0.1786, -0.2080,  0.2644, -0.0823,  0.2341, -0.1082, -0.0964,
              0.2276,  0.0994,  0.1615, -0.2566,  0.0278,  0.0046, -0.3285, -0.0794,
              0.1802,  0.2310, -0.0778,  0.3170,  0.1598, -0.1058, -0.0574,  0.2149,
             -0.0050, -0.1879],
            [ 0.1664, -0.1925, -0.1088, -0.1680, -0.1033, -0.0306,  0.1316,  0.1653,
              0.2366, -0.1538,  0.0030, -0.0815, -0.0727, -0.0674,  0.2187, -0.1217,
              0.1088, -0.0614,  0.2317,  0.1549,  0.2566, -0.1011, -0.1174,  0.1953,
              0.1449,  0.0785,  0.1695, -0.1087, -0.1225, -0.0174,  0.2054, -0.0347,
             -0.0904, -0.0991, -0.0884,  0.2446, -0.2397, -0.1355, -0.0758,  0.0294,
              0.1791,  0.2207, -0.0206, -0.0545, -0.1589,  0.2189, -0.0284, -0.0618,
              0.1710,  0.1154],
            [ 0.2035, -0.1901,  0.1341, -0.0971,  0.1880, -0.2517,  0.1564, -0.1532,
             -0.2116,  0.1176,  0.2459, -0.1790, -0.0989, -0.0323, -0.1042,  0.1491,
              0.1211, -0.0858,  0.2408, -0.0567,  0.1421, -0.1653,  0.1628,  0.1945,
              0.0267,  0.1994,  0.1740, -0.0651,  0.0132,  0.0120, -0.1598,  0.2490,
             -0.0856,  0.1622, -0.1611, -0.1080,  0.0214, -0.0880,  0.0757, -0.0944,
              0.1685, -0.0691, -0.2191,  0.3712, -0.2077, -0.1536, -0.1108, -0.0291,
              0.1971, -0.1638]], device='cuda:0', requires_grad=True)
    fc2.bias 		 Parameter containing:
    tensor([ 0.0205,  0.1291, -0.0242, -0.1210, -0.0541,  0.1059, -0.2361, -0.1569,
             0.1555, -0.1171], device='cuda:0', requires_grad=True)

```python
param
```

    Parameter containing:
    tensor([ 0.0205,  0.1291, -0.0242, -0.1210, -0.0541,  0.1059, -0.2361, -0.1569,
             0.1555, -0.1171], device='cuda:0', requires_grad=True)

Sample Adversarial Examples
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Remember the idea of no free lunch? In this case, as epsilon increases
the test accuracy decreases **BUT** the perturbations become more easily
perceptible. In reality, there is a tradeoff between accuracy
degredation and perceptibility that an attacker must consider. Here, we
show some examples of successful adversarial examples at each epsilon
value. Each row of the plot shows a different epsilon value. The first
row is the $\epsilon=0$ examples which represent the original
“clean” images with no perturbation. The title of each image shows the
“original classification -> adversarial classification.” Notice, the
perturbations start to become evident at $\epsilon=0.15$ and are
quite evident at $\epsilon=0.3$. However, in all cases humans are
still capable of identifying the correct class despite the added noise.

```python
# Plot several examples of adversarial samples at each epsilon
cnt = 0
plt.figure(figsize=(8,10))
for i in range(len(epsilons)):
    for j in range(len(examples[i])):
        cnt += 1
        plt.subplot(len(epsilons),len(examples[0]),cnt)
        plt.xticks([], [])
        plt.yticks([], [])
        if j == 0:
            plt.ylabel("Eps: {}".format(epsilons[i]), fontsize=14)
        orig,adv,ex = examples[i][j]
        plt.title("{} -> {}".format(orig, adv))
        plt.imshow(ex, cmap="gray")
plt.tight_layout()
plt.show()
```

Where to go next?
-----------------

Hopefully this tutorial gives some insight into the topic of adversarial
machine learning. There are many potential directions to go from here.
This attack represents the very beginning of adversarial attack research
and since there have been many subsequent ideas for how to attack and
defend ML models from an adversary. In fact, at NIPS 2017 there was an
adversarial attack and defense competition and many of the methods used
in the competition are described in this paper: `Adversarial Attacks and
Defences Competition <https://arxiv.org/pdf/1804.00097.pdf>`__. The work
on defense also leads into the idea of making machine learning models
more *robust* in general, to both naturally perturbed and adversarially
crafted inputs.

Another direction to go is adversarial attacks and defense in different
domains. Adversarial research is not limited to the image domain, check
out `this <https://arxiv.org/pdf/1801.01944.pdf>`__ attack on
speech-to-text models. But perhaps the best way to learn more about
adversarial machine learning is to get your hands dirty. Try to
implement a different attack from the NIPS 2017 competition, and see how
it differs from FGSM. Then, try to defend the model from your own
attacks.