```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.


 **Chapter 4: [Spectroscopy](CH4-Spectroscopy.ipynb)**

# Analysing Low-Loss Spectra with Drude Theory

[Download](https://raw.githubusercontent.com/gduscher/MSE672-Introduction-to-TEM/main/Spectroscopy/CH4_03-Drude.ipynb)

[](
    https://colab.research.google.com/github/gduscher/MSE672-Introduction-to-TEM/blob/main/Spectroscopy/CH4_03-Drude.ipynb)

part of

 **[MSE672:  Introduction to Transmission Electron Microscopy](../_MSE672_Intro_TEM.ipynb)**

by Gerd Duscher, Spring 2021

Microscopy Facilities

Joint Institute of Advanced Materials

Materials Science & Engineering

The University of Tennessee, Knoxville

Background and methods to analysis and quantification of data acquired with transmission electron microscopes.

## Content
The main feature in a low-loss EELS spectrum is the ``volume plasmon`` peak.

This ``volume plasmon`` and all other features in the ``low-loss`` region of an EELS spectrum are described by Dielectric Theory of Electrodynamics.

The simplest theory to interprete this energy range is the Drude theory.

Another easy to observe component is the multiple scattering of this plasmon peak, which we can correct for or use for thickness determination.

## Load important packages

### Check Installed Packages

```python
import sys
from pkg_resources import get_distribution, DistributionNotFound

def test_package(package_name):
    """Test if package exists and returns version or -1"""
    try:
        version = get_distribution(package_name).version
    except (DistributionNotFound, ImportError) as err:
        version = '-1'
    return version

# Colab setup ------------------
if 'google.colab' in sys.modules:
    !pip install pyTEMlib -q
# pyTEMlib setup ------------------
else:
    if test_package('sidpy') < '0.0.5':
        print('installing pyTEMlib')
        !{sys.executable} -m pip install  --upgrade pyTEMlib -q
    if test_package('pyTEMlib') < '0.2021.4.10':
        print('installing pyTEMlib')
        !{sys.executable} -m pip install  --upgrade pyTEMlib -q
# ------------------------------
print('done')
```

    done

### Import all relevant libraries

Please note that the EELS_tools package from pyTEMlib is essential.

```python
import sys
if 'google.colab' in sys.modules:
    %pylab --no-import-all inline
else:
    %pylab --no-import-all notebook
    %gui qt

import warnings
warnings.filterwarnings('ignore')

# additional package
import ipywidgets as ipyw
from scipy.optimize import leastsq  ## fitting routine of scipy

# Import libraries from the book
import pyTEMlib
import pyTEMlib.file_tools as ft          # File input/ output library
from pyTEMlib import eels_tools
import pyTEMlib.KinsCat as ks         # Kinematic sCattering Library
                             # Atomic form factors from Kirklands book

# For archiving reasons it is a good idea to print the version numbers out at this point
print('pyTEM version: ',pyTEMlib.__version__)
```

    Populating the interactive namespace from numpy and matplotlib
    Using KinsCat library version  0.5  by G.Duscher
    spglib not installed; Symmetry functions of spglib disabled
    pyTEM version:  0.2021.04.02

## Dielectric Theory

### Comparison to Optical Spectroscopy

The interaction of a transmitted electron with a solid is here described in terms of
a dielectric response function $\varepsilon(q, \omega))$.

The same response function $\varepsilon(q, \omega))$ describes
the interaction of any electro-magnetic wave (such as photons)  with a solid, so this formalism allows energy-loss data to be compared with the results of optical measurements.

The difference is that there is no momentum transfer in an optical transition and, therefore, the relevant dielectric function is $\varepsilon(q=0, \omega))$.

The optical dielectric function (permittivity) is a transverse property of the medium, in the sense that the electric field of an electromagnetic wave displaces electrons in a direction perpendicular to the direction of propagation, the electron density remaining
unchanged.

The relation between the ac conductivity $\sigma$ (for dc conductivity the in metals $\varepsilon$ can go to $\inf$ and is therefore not well described) and $\varepsilon$ is :
$$ \varepsilon = 1 + \frac{4\pi \ i \sigma}{\omega} $$

The relation between the complex refractive index $n+i\kappa$ and dielectric function is:
$$ n^2-\kappa^2 ={\bf Re}(\varepsilon), \ \ 2n\kappa = {\bf Im}(\varepsilon) $$

Optical absorption spectrum is obtained through
ABS= ${\bf Im}( \varepsilon(q→0,\omega) )$

>
>So, the dielectric function describes the electrical and optical response of a material almost completely.
>

### Dispersion
From the Fourier representation of the Maxwell equations in the source free case, we get:
$$\frac{\epsilon_i \mu_i}{c^2} = k^2$$

which we transform into:

$$k = \frac{\omega}{c} \sqrt{\varepsilon_1 \mu}$$

which gives for non-magnetic materials:

$$k = \frac{\omega}{c} \sqrt{\varepsilon_1 }$$

for $\varepsilon_1 > 0$ the wavenumber $k$ is a real function of real $\omega$.

while for $\varepsilon_1 < 0$ the wavenumber $k$ is purely imaginary.

for light  in vacuum the permittivity $\varepsilon_1$ is a constant with value 1 and we get the equation for light line:

$$k = \frac{\omega}{c}$$

### Cross Section

$$ \frac{d^2\sigma}{dE d\Omega} = \frac{1}{\pi a_0 m_0 v^2 n_a}
        {\rm Im} \left[ \frac{-1}{\varepsilon(q,E)} \right]
         \left( \frac{1}{\theta^2+\theta_E^2}\right)$$

The partial cross section  $\frac{d^2\sigma}{dE d\Omega}$ gives us the probability that an incident electron will be scattered into angle $q$ with energy-loss $E$.

There are three compenents, first a term that depends on the  ``atom density`` $n_a$ (atoms per volume).

And the third term is the (Lorentzian) ``angle dependence`` with the characteristic angle $$\theta_E = E_E/(\gamma m_0v^2)$$

The second term is the ``loss-function``.

The loss function of a dielectric function $\varepsilon = \varepsilon_1+ i*\varepsilon_2$ is
$$ {\rm Im} \left[ \frac{-1}{\varepsilon(q,E)} \right] = \frac{\varepsilon_1(q,E)}{\varepsilon_1^2(q,E) + \varepsilon_2^2(q,E)}$$
At large energy loss and $q\approx 0$, $\varepsilon_2$ is small
and $\varepsilon_1$ close to 1, the loss function becomes proportional to
$\varepsilon_2$ and (apart from a factor of E$^{−3}$) the energy-loss spectrum is proportional to the X-ray absorption spectrum.

## Load and plot a spectrum
please see [Loading an EELS Spectrum](LoadEELS.ipynb) for details

```python
# Load file
filename = '../example_data/AL-DFoffset0.00.dm3'
eels_dataset = ft.open_file(filename)
if eels_dataset.data_type.name != 'SPECTRUM':
    print('We need an EELS spectrum for this notebook')

#######################################
## Important Experimental Parameters ##
#######################################
eels_dataset.metadata = eels_tools.read_dm3_eels_info(eels_dataset.original_metadata)

sum_spectrum = eels_dataset.sum()

eels_dataset = eels_dataset/sum_spectrum*100.
eels_dataset.units = '%'
eels_dataset.quantity = 'scattering probability'

eels_dataset.plot()
```

    Cannot overwrite file. Using:  AL_DFoffset0.00-1.hf5

    <IPython.core.display.Javascript object>

### Fix energy scale and resolution function
please see [Fitting the Zero-Loss Peak](FitZeroLoss.ipynb) for details

### Relative Thickness Determination

The probabaility of an low-loss function in a solid angle is then:
$$\frac{\partial^2 P}{(\partial E \partial \Omega) }= t*  \frac{e}{\pi^2 a_0 m_0 v^2} {\rm Im} \left[ \frac{-1}{\varepsilon(q,E)} \right]
         \left( \frac{1}{\theta^2+\theta_E^2}\right)$$

Please see [Kroeger Formalism](CH4_04-Kroeger.ipynb) for inclusion of retardation effects, and surface plasmons.

The integration over the (effective) collection angle gives:
$$\frac{\partial P}{\partial E} = \int_0^{\theta_c}\frac{\partial^2 P}{(\partial E \partial \Omega) }(E,\theta)\sin \theta\ d \theta$$

So we need to get the loss-function, calculate $\frac{\partial^2 P}{(\partial E \partial \Omega) }$ and then integrate over the angles, then we fit this to the spectrum with the sole variable of the thickness $t$.

The specimen thickness $t$, it is actually the total scattering
and mass thickness that is measured by EELS. If the physical density of a
material were reduced by a factor $f$, the scattering per atom would remain the same
(according to an atomic model) and the mean free path should increase by a factor $f$.

The big problem hoewver, is that one has to know the **dielectric function**.
In the case of the Drude theory, that means we need to know the electron density.

Any approximation, therefore, needs to approximate this dielectric function, which cannot be generally applicable.

The relative thickness $ t_{rel} = t/\lambda$, in contrast, is relatively easy to determine. All the above problems are hidden in value of the inelastic mean free path (IMFP) $\lambda$, which is the inverse of the cross section above.

>When you use a tabulated value for the IMFP, be aware that this value depends on:
> * acceleration voltage
> * effective collection angle
> * material density

>**and may not be applicable for your experimental setup.**

The measurement of the relative thickness $t_{rel}$ is relative easy:

We already did this in the  [Fit Zero-Loss](FitZeroLoss.ipynb) part.

The inelastic scattering can be viewed in terms of independent collisions, whose occurrences obey Poisson statistics. The probability that a transmitted electron suffers $n$ collisions is
\begin{equation} \Large
P_n = (1/n!)m^n \exp(-m)
\end{equation}

where $m$ is the number of average collisions for electrons travel through this sample area. The number $m$ can be set to the scattering parameter $t/\lambda$. $P_n$ is represented in the EELS spectrum by the ration of the energy-integrated intensity $I_n$ of $n$-fold scattering divided by the {\bf total} integrated intensity $I_t$:
\begin{equation} \Large
\label{equ:poisson}
P_n =    I_n/I_t (1/n!) (t/\lambda)^n \exp(-t/\lambda)
\end{equation}

For a given order $n$ of scattering, the intensity  is highest when $t/\lambda =n$
In the case of the unscattered (n=0) component (zero--loss peak), the intensity is highest at t=0 and decreases exponentially with specimen thickness. For $n=0$, equation \ref{equ:poisson} gives equation
\ref{equ:relative_thickness}
\begin{equation} \Large
\frac{t}{\lambda}= - \ln\left[ \frac{I_{\mbox{total}}}{I_{zl}}\right]
\end{equation}
I just replaced $I_{zl}$ with $I_0$.

```python
FWHM, energy_shift = eels_tools.fix_energy_scale(np.array(eels_dataset), eels_dataset.energy_loss)

print(f'Zero Loss with energy resolution of {FWHM:.2f} eV at position {energy_shift:.3f} eV')
eels_dataset.energy_loss -= energy_shift

zero_loss, _ = eels_tools.resolution_function(eels_dataset.energy_loss, eels_dataset, .4)
print(zero_loss)
plt.figure()
plt.plot(eels_dataset.energy_loss, eels_dataset, label='spectrum')
plt.plot(eels_dataset.energy_loss, zero_loss, label = 'zero-loss')
plt.plot(eels_dataset.energy_loss, np.array(eels_dataset)-zero_loss , label = 'difference')

plt.title ('Lorentzian Product Fit of Zero-Loss Peak')
#plt.xlim(-5,30)
plt.legend();
Izl = zero_loss.sum()
Itotal = np.array(eels_dataset).sum()
tmfp = np.log(Itotal/Izl)
print(f'Sum of Zero-Loss: {Izl:.0f} counts')
print(f'Sum of Spectrum: {Itotal:.0f} counts')
print (f'thickness [IMFP]: {tmfp:.5f}')

```

    Zero Loss with energy resolution of 0.18 eV at position -0.134 eV
    energy_loss:  energy-loss (energy_loss) of size (2048,)

    <IPython.core.display.Javascript object>

    Sum of Zero-Loss: 84 counts
    Sum of Spectrum: 100 counts
    thickness [IMFP]: 0.17585

## Single Scattering Deconvolution

For thicker samples (relative thickness larger than half the inelastic MFP), the scattered electrons are likely to be scattered a second time by plasmon excitations. This results for thicknesses around 0.5 MFP in more intensity in the high energy tail of the plasmon peak. For thicker areas a second plasmon peak will appear at double the energy loss and for very thick areas we have a whole series of plasmon peaks.
In spectra of very thick areas the zero-loss peak may completely vanish. Multiple scattering is easily recognized by the positions of the peaks (multiple of $E_{max}$).

Multiple scattering influences the high energy side of the first plasmon peak, and makes it difficult to quantify the spectra, whether it is in the low-loss or core--loss region. Figure \ref{fig:ssd} shows how the correction improves the determination of the width of the plasmon width. Fortunately, we can correct this multiple scattering, because mathematically, it is a kind of self-convolution. The procedure is called single scattering deconvolution (SSD).

Electrons that lost energy can interact with the sample again. The average path between two interaction is the above introduced IMFP $\lambda$. This multiple inelaxtic scattering is most obvious in the strongest energ-loss spectrum: the volume plasmon. This multiple scatering follows the Poisson statistic and the $n^{\rm th}$ scattering has the intensity:
$$I_n = I_0 P_n = (I_0/n!)(t/λ)^n \exp(−t/λ)$$

It can be shown the the single scattering spectrum in fourier space $s(v)$ can be derived by:

$$s(v) = I_0 \ln[j(v)/z(v)]$$

$z(v)$ beeing the Fourier transformed zero-loss peak (resolution function) and $j(v$ the spectrum in Fourier space.

The above formula would also correct for any instument broadening and a very noisy spectrum would result, so we need to convolute (multiplication in Fourier space) a new broadening function, which could be a well behaved Gaussian with slightly smaller energy width than the original zero-loss peak.

```python
# Single scattering deconvolution

# Use resolution Function as ZL

j = np.fft.fft(np.array(eels_dataset))
z = np.fft.fft(zero_loss)
z2 = z ## Could be a different zl i.e. extracted from Spectrum
j1 = z2*np.log(j/z)
ssd_low_loss =np.fft.ifft(j1).real#,'fourier-log deconvolution')

plt.figure()
#plt.plot(s.tags['ene'][start:end],  zLoss[start:end]/sumSpec*1e2)
plt.plot(eels_dataset.energy_loss,  ssd_low_loss)
plt.plot(eels_dataset.energy_loss, eels_dataset)
#plt.xlim(-4,40)
plt.ylim(-.1,.1);
eels_dataset.metadata['resolution_function'] = zero_loss
```

    <IPython.core.display.Javascript object>

## Volume Plasmon Peak

Most of the inelastically scattered electron arise from interaction with outer shell electrons. These interactions, therefore, have a high intensity and are easy to obtain. The quantification of this region ($<$ 100eV)  in an EELS spectrum is rather hard.

The valence electrons in a solid can be considered as coupled oscillators which interact with each others and with the transmitted electrons. In a first approximation the valence electrons behave like a free electron gas (Fermi sea, jellium). The behavior of the electron gas is described in terms of the dielectric function $\varepsilon$.

The energy-loss function $F_{el}$ on the other hand is determined by the dielectric function $\varepsilon$ through:

$$
F_{el} = \Im \left[\frac{-1}{\varepsilon(\omega)} \right]
$$

The maximum of the energy-loss function is the plasmon--peak, which is given by:
$$
E_{\mbox{max}} = \left[(E_p)^2 - (\Delta E_p/2)^2\right]^{1/2}
$$

### Dielectric Theory

We investigate the plasmon excitation in the so called jellium model. This model is enough to explain the essence of this excitation, but is not as complicated as the real solid state.

The displacement of a {\it quasifree} electron (with an effective mass $m$ and not the rest mass $m_0$) due to an electric field $\vec{E}$ must fulfill the equation :
\begin{equation} \Large
\label{equ:motion}
m\vec{x}'' + m\Gamma \vec{x}'  =  -e\vec{E}
\end{equation}

The usage of an effective mass approximates the interaction between the valence electrons and the ion--cores. For the same reason, we introduce $\Gamma$ a damping constant. Instead of $\Gamma$, we could also use its reciprocal value $\tau = 1/\Gamma$ as in the Drude theory. This is similar to the electronic theory of conduction.

For an oscillatory field $\vec{E}= \vec{E} \exp(-i\omega t)$ above equation \ref{equ:motion} has the following solution:
\begin{equation} \Large
\label{equ:motion_solution}
\vec{x} = (e\vec{E}/m)(\omega ^2+i\Gamma \omega)^{-1}
\end{equation}

The displacement gives raise to a polarization $\vec{P}  = -en\vec{x} =\varepsilon_0\chi\vec{E}$, with $\chi$ electric susceptibility and $n$ the the number of electron per unit volume. The dielectric function $\varepsilon(\omega)=1+\chi$ can be expressed as:
\begin{equation} \Large
\label{equ:dielectric_function}
\varepsilon(\omega)=\varepsilon_1+i\varepsilon_2 = 1-\frac{\omega_p^2}{\omega^2+\Gamma^2}+\frac{i\Gamma\omega^2_p}{\omega(\gamma^2+\Gamma^2)}
\end{equation}
Where $\omega$ is the frequency (in rad/s) of forced oscillation and $\omega_p$ is the Eigen or resonance frequency of the free electron gas, which is given by:
\begin{equation} \Large
\label{equ:plasmon_frequency}
\omega_p =\left[ \frac{ne^2}{\varepsilon_0 m} \right]^{1/2}
\end{equation}

A sudden impulse such as a transmitted electron will contain all angular frequencies (Fourier components). It will also excite the resonance frequency of the jellium. Such an oscillation with frequency $\omega_p$ can be viewed as a ``pseudoparticle`` with (plasmon) energy $E_p =\hbar \omega_p.$ We call this ``pseudoparticle``  a **plasmon**.

### Energy-Loss Function
The energy-loss function (elf) is given as shown in the beginning by:
\begin{equation} \Large
\label{equ:eels_function2}
E_{el}= \left[\frac{1}{\varepsilon(\omega)}\right] = \frac{\varepsilon_2}{\varepsilon_1^2+\varepsilon_2^2}=\frac{\omega\Gamma\omega_p^2}{(\omega^2-\omega_p^2)^2 +(\omega\Gamma)^2}
\end{equation}
The energy-loss is represented by $E=\hbar \omega$ and we can rewrite equation
\ref{equ:eels_function2} as:
\begin{equation}
\label{equ:eels_function3}
 \left[\frac{1}{\varepsilon(E)}\right]  =\frac{(E\hbar/\tau)E_p^2}{(E^2-E^2)^2 +(E\hbar/\tau)^2} %
  =\frac{(E \quad \Delta E_p)E_p^2}{(E^2-E^2)^2 +(E \quad \Delta E_p)^2}
\end{equation}

The relaxation time $\tau = 1/\Gamma$ is directly connected to the FWHM of the energy-loss function which is given by $\Delta E_p =\hbar\Gamma = \hbar/\tau$. The effect of the effective mass on the energy-loss function is rather small.\\
The maximum of the energy-loss function is given in equation \ref{equ:plasmon-maximum}.

The relaxation time $\tau$ represents the time for plasmon oscillations to decay in energy by  a factor $\exp(-1)=0.37$. The number of oscillations which occur within this time is $\omega_p\tau/(2\pi)=E_p/(2\pi/\Delta E_p)$. Using experimental values for $E_p$ and $\Delta E_p$ result in 5 to 0.4 oscillations. The plasmon oscillations are, therefore, heavily  damped, which depends on the band structure of the material.

### Plasmon Energies

The calculated plasmon energies of the Jellium model with equation \ref{equ:plasmon_frequency} are already quite good as can be seen in table \ref{tbl:plasmon_frequencies}.
Only the number of valence electrons per unit area $n$ and the dielectric constant $\varepsilon_0$ are necessary for these calculations.

| Material |  $E_p$ (experimental) | $E_P$ (theoretical)|
|----------|-----------------------|--------------------|
|Li | 7.1 eV | 8.0 eV|
|Diamond | 34 eV | 31 eV|
|Si | 16.5 eV | 16.6 eV|
|Ge | 16.0 eV | 15.6 eV|
|InSb | 12.9 eV | 12.7 eV|
|GaAs | 15.8 eV | 15.7 eV|
|NaCl | 15.5 eV | 15.7 eV|

A more sophisticated way of comparing the valence loss spectra to theory is to calculate the dielectric function. {\it Ab initio} density functional theory can be used to do these calculations. To obtain the dielectric function the valence band has to be convoluted with the conduction band. A comparison between experiment and theory is shown in the figure below.

*Plasmon--Loss peak of Li (black circles), Gibbson et al Phys.~Rev.~B {\bf 13}:2451 (1979) and calculated energy-loss function by S.~Ramachandran et al. (unpublished)}*

The plasmon--loss enables us to determine the plasmon energy, a materials parameter.
The width plasmon peak is also a characteristic of the material and is needed to determine the plasmon energy.

*Plasmon--Loss peak of SrTiO$_3$ with highlighted FWHM*

The spectrum here is quite complicated due to the surface plasmon--peak which shows up to the left of the volume plasmon--peak. We measure the energy of the maximum of the plasmon--peak (here $E_{\mbox{max}} = 31$ eV) and the width of the plasmon--peak (here $\Delta E_P = 5.3$ eV). While it is easy to determine the plasmon--peak energy , the plasmon--peak width can only be determined approximately, because we don't know how to subtract the background.

Using equation above, we find that the plasmon energy is 30.5 eV for SrTiO$_3$.

This spectrum shows the high dynamic range of an EELS spectrum. The valence loss part of the spectrum is noisy, but the zero-loss peak is not saturated. The surface plasmon peak is strong, which indicates a rather thin area. May be a little too thin. Without loss of information we can get spectra from thicker areas.

Not only the plasmon energy (or the plasmon maximum) can be used for an identification of materials,
but also the shape, especially the width of the plasmon peak.

As seen above, it is rather difficult to determine the FWHM of a plasmon peak. However, it is much easier after subtraction of the zero-loss peak, as can be seen in figure below.

*Plasmon--Loss peak of  Ni (black) after subtraction of the zero-loss peak and with highlighted FWHM. The original spectrum and the zero-loss peak are displayed in the background.*

A comparison of the plasmon peak width of Ni (above) and Si (figure  below)
 makes it apparent  how different the shape of plasmon peaks can be. The Si plasmon peak is sharp (FWHM: 7.3 eV) and has a well defined maximum, while the Ni plasmon peak is very broad (FWHM: 40 eV).

*Plasmon--Loss peak of  Si (black) after subtraction of the zero-loss peak and with highlighted FWHM. The original spectrum and the zero-loss peak are displayed in the background.*

The Si plasmon peak is obtained from a rather thin area, which also makes it easier to determine the width, because the right hand tail is not broadened by the thicker sample (please see section above about single scattering deconvolution).

## Drude Function

The dielectric function in the Drude theory is given by two input parameters the position of the plasmon energy $E_p$
and the width of the plasmon $\Gamma$

$$ ε(ω) = ε1 + iε2 = 1 + χ = 1 − \frac{\omega_p^2}{\omega^2+\Gamma^2} + \frac{i\Gamma \omega_p^2}{\omega(\omega^2+\Gamma^2)}$$
Here $\omega$ is the angular frequency (rad/s) of forced oscillation and $\omega_p$ is the natural or resonance frequency for plasma oscillation, given by
$$ ω_p = \sqrt{\frac{ne^2}{(ε_0m_0)}} $$
A transmitted electron represents a sudden impulse of applied electric field, containing
all angular frequencies (Fourier components). Setting up a plasma oscillation of the loosely bound outer-shell electrons in a solid is equivalent to creating a pseudoparticle of energy $E_p = \hbar \omega_p$, known as a plasmon (Pines, 1963).

```python
E_p = plasmon_energy = 13. # in eV
E_w = plasmon_gamma = 3. # in eV
energy_scale = eels_dataset.energy_loss+1e-18 #= np.linspace(0,50,1024)+1e-18

def Drude(E,E_p,E_w):
    eps = 1 - E_p**2/(E**2+E_w**2) +1j* E_w* E_p**2/E/(E**2+E_w**2)
    elf = (-1/eps).imag
    return eps,elf

eps,elf = Drude(energy_scale, plasmon_energy, plasmon_gamma)

plt.figure()
plt.plot(energy_scale, eps.real, label='Re($\epsilon_{Drude}$)')
plt.plot(energy_scale, eps.imag, label='Im($\epsilon_{Drude}$)')
plt.plot(energy_scale, elf, label='loss function$_{Drude}$')
plt.plot([0,energy_scale[-1]],[0,0],c='black')

plt.legend()
plt.gca().set_ylim(-2,max(elf)*1.05);
plt.xlim(-1,40);
plt.xlabel('energy loss (eV)')
```

    <IPython.core.display.Javascript object>

    Text(0.5, 0, 'energy loss (eV)')

## Fitting a Drude Function to Plasmon

The position and the width are important materials parameters and we can derive them by fitting the Drude function to the volume plasmon region.

```python
from scipy.optimize import leastsq

def Drude(E,Ep,Ew):
    eps = 1 - Ep**2/(E**2+Ew**2) +1j* Ew* Ep**2/E/(E**2+Ew**2)
    elf = (-1/eps).imag
    return eps,elf

def errfDrude(p, y, x):
    eps,elf = Drude(x,p[0],p[1])
    err = y - p[2]*elf
    #print (p,sum(np.abs(err)))
    return np.abs(err)#/np.sqrt(y)

pin2 = np.array([15,1,.7])
E = energy_scale
startFit =np.argmin(abs(energy_scale-13))
endFit = np.argmin(abs(energy_scale-18))

p2, lsq = leastsq(errfDrude, pin2, args=(ssd_low_loss[startFit:endFit], energy_scale[startFit:endFit]), maxfev=2000)

eps, elf =Drude(energy_scale,p2[0],p2[1])
drudePSD = p2[2]* elf
plt.figure()

plt.plot(energy_scale,eels_dataset)
plt.plot(energy_scale,drudePSD)
plt.plot(energy_scale,eels_dataset-drudePSD)
plt.axhline(0, color='black')

plt.gca().set_xlim(0,40)
plt.gca().set_ylim(-0.01,0.2)
print(f"Drude Theory with Plamson Energy: {p2[0]:2f} eV and plasmon Width {p2[1]:.2f} eV")
print(f"Max of Plasmon at {energy_scale[drudePSD.argmax(0)]:.2f} eV")
print(f"Amplitude of  {p2[2]:.2f} was deteremined by fit ")

```

    <IPython.core.display.Javascript object>

    Drude Theory with Plamson Energy: 15.047837 eV and plasmon Width 0.72 eV
    Max of Plasmon at 15.04 eV
    Amplitude of  0.01 was deteremined by fit

```python
plt.figure()
plt.title ('Drude Fit: Dielectric Function - Permittivity')
plt.plot(energy_scale,eps.real,label = 'Re($\epsilon)$')
plt.plot(energy_scale,eps.imag,label = 'Im($\epsilon)$')
plt.plot(energy_scale,drudePSD,label = 'loss-function$_{Drude}$')
plt.plot(energy_scale,eels_dataset,label = 'loss-function$_{exp}$')
plt.axhline(0, color='black')

plt.gca().set_xlim(0,40)
plt.gca().set_ylim(-2.5,5.3)

plt.legend();
```

    <IPython.core.display.Javascript object>

### Plasmon Frequency and Phase Identification

Plasmon peaks occurring in EELS is directly related to valence electron density and can thereby be used as a means for materials characterization and phase identification.  Free-electron metals have very sharp plasmon peaks as compared to semiconductors and insulators with broader peaks, where the valence electrons are no longer free (covalent or ionic bonding).  While many materials properties are a function of valence electron density, knowing the plasmon energy can prove to be a useful tool in identifying micro structures.

Microstructural phases are observed by shifts in the plasmon energy. Examples of phase identification are observed easily in alloys and also precipitate structures.  A common textbook example is EELS low loss comparison of diamond and graphite.  The example given in Figure is a line scan showing the variation of plasmon energies with position.  The plasmon energies range from 21 to 23 eV, corresponding to SiC (20.8 eV) and SiN3 (22.5 eV) respectively.   Figure \ref{plasmon-ls2}  is a reconstructed map of the plasmon energies.  Blue regions correspond to SiC, while the red regions correspond to SiN3.

Appendix C in Egerton's Electron Energy Loss Spectroscopy in the Electron Microscope lists plasmon energies of some elements and compounds [Egerton-1999].

## Surface Plasmon

Spectra from thin specimen show the excitations of the surface plasmons on each side of the specimen. For any normal specimen these surface plasmons do interact, but this is not true for extremely thick specimen ($>> 10$nm).
The surface plasmon frequency $\omega_S$ for thin specimen  is related to the bulk plasmon frequency $\omega_P$ by Ritchie [Ritchie-PR1957]:
$$
\omega_S=\omega_P\left[  \frac{1\pm \exp(-q_st) }{1+\varepsilon} \right]^{1/2}
$$

The symmetric mode, where like charges face one another, corresponds to the higher angular frequency $q_s$. Please note, that this relationship does only apply for large $q_s$

The differential probability for surface excitation at both surfaces of a sample with thickness $t$ can be expressed (normal incident, no retardation effects) by:
$$
\frac{d^2 P_s}{d\Omega d E}=\frac{2\hbar}{\pi^2 \gamma a_0 m_0^2 \mu^3}\frac{\theta}{(\theta^2+\theta^2_E)^2} \Im\left[  \frac{(\varepsilon_a - \varepsilon_b)^2 } {\varepsilon_a^2 \varepsilon_b}\right]
$$
with
$$
R_c = \frac{\varepsilon_a \sin^2(tE/2\hbar\mu)}{\varepsilon_b + \varepsilon_z }\tanh (q_s t/2)
+ \frac{\varepsilon_a \cos^2(tE/2\hbar\mu)}{\varepsilon_b + \varepsilon_a} \coth (q_s t/2)
$$
and $\varepsilon_a$ and $\varepsilon_b$ are the permitivities of the two surfaces.

A secondary effect of the surface excitation is the reduced intensity of the bulk plasmon peak. The effect is usually smaller than 1\%, but can be larger for spectra with small collection angle, because the preferred scattering of surfuce losses into small angles.
The correction for surface plasmon will be discussed in the Kramers--Kronig Analysis.

## Summary

The beauty of ``Low--Loss spectroscopy`` is its derivation of the dielectric function to high energies without prior knowledge of the composition. The signal is strong and the acquisition time is mostly restricted by the dynamic range of the spectrum.

>
>Think of low-loss spectroscopy as Electrodynamics
>

The advantages of EELS is the derivation of these values spatially resolved.
And from a linescan across an Si/SiO$_2$ interface the dielectric function per pixel can be obtained. From that we can calculate the dielectric polarizability $\alpha_e (E)$, which may be  a measure of the dielectric strength.

We obtain more or less easily:
- relative thickness
- absolute thickness
- inelastic mean free path
- plasmon frequency
- plasmon width
- band gap
- dielectric function
- reflectivity
- absorption
- effective number of electrons per atoms

The analysis of the optical data requires the exact knowledge of the zero-loss peak. Because of the weighting in the Fourier Analysis, the low energy part contributes heavily to the dielectric function. Therefore, energy resolution is critical for an exact determination of all the optical values from EELS. The new monochromated TEMs are now able to achieve an energy resolution of 10 meV (one is at the oak Ridge National Laboratory), which allows for a sharper zero-loss peak. Such a sharp zero-loss peak will enable us to extract this low energy data more accurately. The dielectric function and the parameters derived from it, can be more precisely determined from such EELS spectra.

## Navigation
-   **Up Chapter 4: [Imaging](CH4_00-Spectroscopy.ipynb)**
-   **Back: [Overview](CH4_02-Fit_Zero_Loss.ipynb)**
-   **Next: [Simulation of Momentum Resolved EELS](CH4_04-Kroeger.ipynb)**
-   **List of Content: [Front](../_MSE672_Intro_TEM.ipynb)**

```python

```


##### Copyright 2020 The Cirq Developers

```
#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
```

# Operators and observables

    View on QuantumAI

    Run in Google Colab

    View source on GitHub

    Download notebook

```
try:
    import cirq
except ImportError:
    print("installing cirq...")
    !pip install --quiet cirq
    print("installed cirq.")
    import cirq

import numpy as np
import sympy.parsing.sympy_parser as sympy_parser
```

This guide is directed at those already familiar with quantum operations (operators) and observables who want to know how to use them in Cirq. The following table shows an overview of operators.

| Operator | Cirq representation | Guides | Examples |
|-----|-------|--------|-----|
| Unitary operators | Any class implementing the `_unitary_` and `_has_unitary_` protocol | [Protocols](protocols.ipynb), [Gates and operations](gates.ipynb), [Custom gates](custom_gates.ipynb) | `cirq.Gate`
 `cirq.X(qubit)`
 `cirq.CNOT(q0, q1)`
 `cirq.MatrixGate.on(qubit)`
 `cirq.Circuit` (if it only contains unitary operations)  |
| Measurements | `cirq.measure` and `cirq.MeasurementGate`  | [Gates and operations](gates.ipynb) | `cirq.measure(cirq.LineQubit(0))` |
| Quantum channels |

Kraus operators (any class implementing the `_kraus_` and `_has_kraus_` protocol)

Unitary mixtures (any class implementing the `_mixture_` and `_has_mixture_` protocol)

 | [Protocols](protocols.ipynb) | `cirq.DepolarizingChannel(p=0.2)(q0)`
 `cirq.X.with_probability(0.5)`|

Cirq also supports observables on qubits that can be used to calculate expectation values on a given state.

* You can use `cirq.PauliString` to express them,
* Or you can use `cirq.PauliSum` with real coefficients.

## Operators

Quantum operations (or just *operators*) include unitary gates, measurements, and noisy channels. Operators that act on a given set of qubits implement `cirq.Operation` which supports the Kraus operator representation

$$
\rho \mapsto \sum_{k} A_k \rho A_k^\dagger .
$$

Here, $\sum_{k} A_k^\dagger A_k = I$ and $\rho$ is a quantum state. Operators are defined in the `cirq.ops` module.

### Unitary operators

Standard unitary operators used in quantum information can be found in `cirq.ops`, for example Pauli-$X$ as shown below.

```
qubit = cirq.LineQubit(0)
unitary_operation = cirq.ops.X.on(qubit)  # cirq.X can also be used for cirq.ops.X
print(unitary_operation)
```

Cirq makes a distinction between gates (independent of qubits) and operations (gates acting on qubits). Thus `cirq.X` is a gate where `cirq.X.on(qubit)` is an operation. See the [guide on gates](gates.ipynb) for more details and additional common unitaries defined in Cirq.

> **Note**: The method `cirq.X.on_each` is a utility to apply `cirq.X` to multiple qubits. Similarly for other operations.

Every `cirq.Operation` supports the `cirq.channel` protocol which returns its Kraus operators. (Read more about [protocols](protocols.ipynb) in Cirq.)

```
kraus_ops = cirq.kraus(unitary_operation)
print(f"Kraus operators of {unitary_operation.gate} are:", *kraus_ops, sep="\n")
```

Unitary operators also support the `cirq.unitary` protocol.

```
unitary = cirq.unitary(cirq.ops.X)
print(f"Unitary of {unitary_operation.gate} is:\n", unitary)
```

Unitary gates can be raised to powers, for example to implement a $\sqrt{X}$ operation.

```
sqrt_not = cirq.X ** (1 / 2)
print(cirq.unitary(sqrt_not))
```

Any gate can be controlled via `cirq.ControlledGate` as follows.

```
controlled_hadamard = cirq.ControlledGate(sub_gate=cirq.H, num_controls=1)
print(cirq.unitary(controlled_hadamard).round(3))
```

Custom gates can be defined as described in [this guide](custom_gates.ipynb). Some common subroutines which consist of several operations are pre-defined - e.g., `cirq.qft` returns the operations to implement the quantum Fourier transform.

### Measurements

Cirq supports measurements in the computational basis.

```
measurement = cirq.MeasurementGate(num_qubits=1, key="key")
print("Measurement:", measurement)
```

The `key` can be used to identify results of measurements when [simulating circuits](simulation.ipynb). A measurement gate acting on a qubit forms an operation.

```
measurement_operation = measurement.on(qubit)
print(measurement_operation)
```

> **Note**: The function `cirq.measure` is a utility to measure a single qubit, and the function `cirq.measure_each` is a utility to measure multiple qubits.

Again measurement operations implement `cirq.Operation` so the `cirq.channel` protocol can be used to get the Kraus operators.

```
kraus_ops = cirq.kraus(measurement)
print(f"Kraus operators of {measurement} are:", *kraus_ops, sep="\n\n")
```

The functions `cirq.measure_state_vector` and `cirq.measure_density_matrix` can be used to perform computational basis measurements on state vectors and density matrices, respectively, represented by NumPy arrays.

```
psi = np.ones(shape=(2,)) / np.sqrt(2)
print("Wavefunction:\n", psi.round(3))
```

```
results, psi_prime = cirq.measure_state_vector(psi, indices=[0])

print("Measured:", results[0])
print("Resultant state:\n", psi_prime)
```

```
rho = np.ones(shape=(2, 2)) / 2.0
print("State:\n", rho)
```

```
measurements, rho_prime = cirq.measure_density_matrix(rho, indices=[0])

print("Measured:", measurements[0])
print("Resultant state:\n", rho_prime)
```

These functions do not modify the input state (`psi` or `rho`) unless the optional argument `out` is provided as the input state.

### Noisy channels

Like common unitary gates, Cirq defines many common noisy channels, for example the depolarizing channel below.

```
depo_channel = cirq.DepolarizingChannel(p=0.01, n_qubits=1)
print(depo_channel)
```

Just like unitary gates and measurements, noisy channels implement `cirq.Operation`, and we can always use `cirq.channel` to get the Kraus operators.

```
kraus_ops = cirq.kraus(depo_channel)
print(f"Kraus operators of {depo_channel} are:", *[op.round(2) for op in kraus_ops], sep="\n\n")
```

Some channels can be written

$$
\rho \mapsto \sum_k p_k U_k \rho U_k ^\dagger
$$

where real numbers $p_k$ form a probability distribution and $U_k$ are unitary. Such a *probabilistic mixture* of unitaries supports the `cirq.mixture` protocol which returns $p_k$ and $U_k$. An example is shown below for the bit-flip channel $\rho \mapsto (1 - p) \rho + p X \rho X$.

```
bit_flip = cirq.bit_flip(p=0.05)
probs, unitaries = cirq.mixture(bit_flip)

for prob, unitary in cirq.mixture(bit_flip):
    print(f"With probability {prob}, apply \n{unitary}\n")
```

> **Note**: Any unitary gate/operation supports `cirq.mixture` because it can be interpreted as applying a single unitary with probability one.

Custom noisy channels can be defined as described in [this guide](noise.ipynb).

### In circuits

Any `cirq.Operation` (pre-defined or user-defined) can be placed in a `cirq.Circuit`. An example with a unitary, noisy channel, and measurement is shown below.

```
circuit = cirq.Circuit(
    cirq.H(qubit),
    cirq.depolarize(p=0.01).on(qubit),
    cirq.measure(qubit)
)
print(circuit)
```

The general input to the circuit constructor is a `cirq.OP_TREE`, i.e., an operation or nested collection of operations. Circuits can be manipulated as described in the [circuits guide](circuits.ipynb) and simulated as described in the [simulation guide](simulation.ipynb).

### Alternate representations

In addition to the above representations for operators. Cirq also supports some more non-standard representations as well. To convert a set of kraus operators to a choi representation you can do:

```
depo_channel = cirq.DepolarizingChannel(p=0.01, n_qubits=1)
kraus_rep = cirq.kraus(depo_channel)
print(kraus_rep)
```

```
choi_rep = cirq.kraus_to_choi(kraus_rep)
print(choi_rep)
```

And to get the superoperator representation you can do:

```
super_rep = cirq.kraus_to_superoperator(kraus_rep)
print(super_rep)
```