## Pyro code for examples in MBML book by John Winn
#### Based on version 0.7 of the early access version (www.mbmlbook.com)
Implementation and testing of ideas from the book

```python
import matplotlib.pyplot as plt
import numpy as np
import torch

import pyro
import pyro.infer
import pyro.optim
import pyro.distributions as dist
from pyro import param
from pyro.poutine import trace
from pyro import condition
from pyro.optim import Adam, ClippedAdam
from pyro.infer import SVI, Trace_ELBO
from pyro.contrib.autoguide import AutoDiagonalNormal, AutoMultivariateNormal, AutoContinuous

from torch import tensor
from pprint import pprint

import pandas as pd
import matplotlib.pyplot as plt

from os.path import join, exists, basename

display('pyro version: {}'.format(pyro.__version__))

pyro.set_rng_seed(101)
```

    'pyro version: 1.3.0'

# Some warmup exercises to understand distributions

#### Page 17, bullet 4

Define a Bernoulli distribution with a probability estimate of 0.3, then look at frequency of True/False in this disribution

```python
murderer = dist.Bernoulli(0.3)

print('num True:', int(sum([pyro.sample('murderer', murderer) for s in range(100)])))
```

    num True: 30

#### Page 23, bullet 3

Make the probability of being late conditioned on whether traffic is good or bad

```python
late = {'bad':dist.Bernoulli(0.5), 'good':dist.Bernoulli(0.05)}

print('late in bad traffic:', int(sum([pyro.sample('bad', late['bad']) for s in range(100)])),
      'late in good traffic:', int(sum([pyro.sample('good', late['good']) for s in range(100)])))
```

    late in bad traffic: 42 late in good traffic: 1

#### Page 31, bullet 4

Print out 1000 joint samples of both variables

```python
late = {'bad':dist.Bernoulli(0.5), 'good':dist.Bernoulli(0.05)}

print('bad traffic and late', int(sum([pyro.sample('badlate', late['bad']) for s in range(1000)])))
print('bad traffic and on time', 1000-int(sum([pyro.sample('badontime', late['bad']) for s in range(1000)])))
print('good traffic and late', int(sum([pyro.sample('goodlate', late['good']) for s in range(1000)])))
print('good traffic and on time', 1000-int(sum([pyro.sample('goodontime', late['good']) for s in range(1000)])))
```

    bad traffic and late 531
    bad traffic and on time 505
    good traffic and late 53
    good traffic and on time 945

# Chapter 1
## A murder mystery
#### Pages 13-46

**Problem statement:** Mr Black lies dead on the floor. The two murder suspects are Miss Auburn and Major Grey. There are two possible murder weapons, a revolver and a knife. Finally, a grey hair is found and this could either have been dropped by Grey if he is the killer or it could have placed by Auburn if she is the killer. The goal of this problem is to incorporate evidence and update beliefs regarding who the killer might be.

\
**Prior:** Initial suspicion is toward Miss Auburn with a probability of 70%, giving:

\begin{align}
P(murderer=Auburn)=0.7 \\
P(murderer=Grey)=0.3
\end{align}

\
**Weapon:** There are two possible murder weapons, a revolver and a dagger. The probabilities of each being used is conditional on the killer, giving:

\begin{align}
P(weapon=revolver|murderer=Auburn)=0.2 \\
P(weapon=knife|murderer=Auburn)=0.8 \\
P(weapon=revolver|murderer=Grey)=0.9 \\
P(weapon=knife|murderer=Grey)=0.1
\end{align}

\
**Hair:** A hair is found at the crime scene. Indicating Major Grey. However it could also have been planted by Auburn. We think there is a 50% chance that Grey would drop the hair if he is the killer, but only a 5% chance that Auburn would think to plant a grey hair in the crime scene.
\begin{align}
P(hair=true|murderer=Auburn)=0.05 \\
P(hair=false|murderer=Auburn)=0.95 \\
P(hair=true|murderer=Grey)=0.5 \\
P(hair=false|murderer=Grey)=0.5
\end{align}

**Factor graph:**

```python
# build murder mystery model

murderer_dict = {0:'Grey', 1:'Auburn'}
weapon_dict = {0:'Knife', 1:'Revolver'}
hair_dict = {0:'Not found', 1:'Found'}

def murder_case():
    '''
    The murder case model. Weapon and hair
    conditional on the murderer.
    '''

    # probability of murderer (0 is Grey and 1 is Auburn)
    murderer = pyro.sample('murderer', dist.Bernoulli(0.7)).item()

    # probability of weapon conditional on murderer (0 is knife and 1 is revolver)
    weapon = pyro.sample('weapon', {'Grey':dist.Bernoulli(0.9), 'Auburn':dist.Bernoulli(0.2)}[murderer_dict[murderer]])

    # probability of hair found conditional on murderer (0 is not found and 1 is found)
    hair = pyro.sample('hair', {'Grey':dist.Bernoulli(0.5), 'Auburn':dist.Bernoulli(0.05)}[murderer_dict[murderer]])

```

### We can explore the model using repeated sampling

**We first look at all the variables in the model.** $P(murderer)$, $P(weapon)$, $P(hair)$

These correspond to our initial beliefs.

```python

# make plots of the various probabilities involved
num_samples = 1000
traces = []

for _ in range(num_samples):
    tr = trace(murder_case).get_trace()
    values = {
        name: props['value'].item()
        for (name, props) in tr.nodes.items()
        if props['type'] == 'sample'
    }
    traces.append(values)

_ = pd.DataFrame(traces).hist()

```

**We can condition the model** and ask what the joint probability for an outcome is. For example, how likely is the outcome that Auburn is the killer, the revolver was used and the hair is found $P(murderer=Auburn, weapon=Revolver, hair=true)$? Note that this is *not* the likelyhood that Auburn is the killer given the evidence, it is the likelyhood of the overall outcome at the outset.

```python
# Condition the model
cond_model = condition(murder_case, {
    "murderer": tensor(1.),
    "weapon": tensor(1.),
    "hair": tensor(1.)
})

# setup trace
tr = trace(cond_model).get_trace()

print('Joint probability for this particular outcome: {:.2}'.format(tr.log_prob_sum().exp().item()))
```

    Joint probability for this particular outcome: 0.007

**To find the relative probability** of the two potential murderers given the evidence of the revolver and hair we need to compute both. $P(murderer=Auburn|weapon=Revolver, hair=true)$ and $P(murderer=Grey|weapon=Revolver, hair=true)$

```python
# Condition the model for Auburn
cond_model = condition(murder_case, {
    "murderer": tensor(1.),
    "weapon": tensor(1.),
    "hair": tensor(1.)
})

tr = trace(cond_model).get_trace()
auburn = tr.log_prob_sum().exp()

# Condition the model for Grey
cond_model = condition(murder_case, {
    "murderer": tensor(0.),
    "weapon": tensor(1.),
    "hair": tensor(1.)
})

# Compute the probabilities
tr = trace(cond_model).get_trace()
grey = tr.log_prob_sum().exp()

# calculate the relative probabilities
auburn_prob = auburn/(auburn+grey)
grey_prob = grey/(auburn+grey)

print('After seeing evidence the probability for Auburn being the murderer is {:.2} and Grey is {:.2}.'.format(auburn_prob, grey_prob))
```

    After seeing evidence the probability for Auburn being the murderer is 0.049 and Grey is 0.95.

### We can also use variational inference to look at latent variables
This will become useful for larger models.

**To find the probability** of Auborn being the murderer, after seeing the revolver evidence. $P(murderer=Auburn|weapon=Revolver)$

```python

def case_guide():
    '''
    Guide function used to find optimal values of latent variables.
    '''
    # Constraints ensure facts always remain true during optimization,
    # e.g. that the parameter of a Bernoulli is always between 0 and 1
    valid_prob = dist.constraints.interval(0., 1.)
    mu_p = param('mu_p', tensor(0.7), constraint=valid_prob)
    murderer = pyro.sample('murderer', dist.Bernoulli(mu_p))

    # probability of weapon conditional on murderer (0 is knife and 1 is revolver)
    weapon = pyro.sample('weapon', {'Grey':dist.Bernoulli(0.9), 'Auburn':dist.Bernoulli(0.2)}[murderer_dict[murderer.item()]])

    # probability of hair found conditional on murderer (0 is not found and 1 is found)
    hair = pyro.sample('hair', {'Grey':dist.Bernoulli(0.5), 'Auburn':dist.Bernoulli(0.05)}[murderer_dict[murderer.item()]])

# remove previous
pyro.clear_param_store()

# Condition the model on having seen the revolver
cond_model = condition(murder_case, {
    "weapon": tensor(1.)
})
# Condition the guide on having seen the revolver
cond_case_guide = condition(case_guide, {
    "weapon": tensor(1.)
})

adam = Adam({"lr": 0.005, "betas": (0.90, 0.999)})
svi = SVI(cond_model, case_guide, adam, loss=Trace_ELBO())

param_vals = []
for _ in range(2000):
    svi.step()
    param_vals.append({k: param(k).item() for k in ["mu_p"]})

# plot the parameter
pd.DataFrame(param_vals).plot(subplots=True)

# make plots of the various probabilities involved
traces = []
for _ in range(10000):
    tr = trace(case_guide).get_trace()
    values = {
        name: props['value'].item()
        for (name, props) in tr.nodes.items()
        if props['type'] == 'sample'
    }
    traces.append(values)

_ = pd.DataFrame(traces).hist()

# what is the murderer probability now?
tr = trace(case_guide).get_trace()
print('Probability for Auburn being the murderer is now {:.2}'.format(tr.nodes['mu_p']['value'].item()))
```

**To find the probability** of Auborn being the murderer, after seeing the revolver *and* hair evidence $P(murderer=Auburn|weapon=Revolver, hair=true)$. This will be the same value (or at least very similar, depending on how many iterations are used) as the 0.049 from a few cells above where we computed relative probability.

```python
%%time

# remove previous
pyro.clear_param_store()

# Condition the model on having seen the revolver and hair
cond_model = condition(murder_case, {
    "weapon": tensor(1.),
    "hair": tensor(1.)
})

# Condition the guide on having seen the revolver and hair
cond_case_guide = condition(case_guide, {
    "weapon": tensor(1.),
    "hair": tensor(1.)
})

adam = Adam({"lr": 0.01, "betas": (0.90, 0.999)})
svi = SVI(cond_model, cond_case_guide, adam, loss=Trace_ELBO())

param_vals = []
for _ in range(5000):
    svi.step()
    param_vals.append({k: param(k).item() for k in ["mu_p"]})

# plot the loss
pd.DataFrame(param_vals).plot(subplots=True)

# make plots of the various probabilities involved
traces = []
for _ in range(10000):
    tr = trace(cond_case_guide).get_trace()
    values = {
        name: props['value'].item()
        for (name, props) in tr.nodes.items()
        if props['type'] == 'sample'
    }
    traces.append(values)

_ = pd.DataFrame(traces).hist()

# what is the murderer probability now?
tr = trace(cond_case_guide).get_trace()
print('Probability for Auburn being the murderer is now {:.2}'.format(tr.nodes['mu_p']['value'].item()))
```

# Chapter 2
## Assessing people's skils
#### Pages 47-y

**Problem statement:**
The problem of assessing candidates for a job that requires certain skills is adressed. The idea is that candidates will take a multiple-choice test and we will use model-based machine learning to determine which skills each candidate has (and with what probability) given their answers in the test. We can then use this for tasks such as selecting a shortlist of candidates very likely to have a set of essential skills.

**Prior:** Several assumptions are used to construct the model.
1. Each candidate has either mastered each skill or not.
2. Before seeing any test results, it is equally likely that each candidate does or doesn't have any particular skill.
3. If a candidate has all of the skills neede for a question then they will get the question right, except one time in ten they will make a mistake.
4. If a candidate doesn't have all the skills needed for a question, they will pick an answer at random. Because this is a multiple-choice exam with five answers, there's one in five chance that they get the question right.
5. Whether the candidate gets a question right depends only on what skills that candidate has and not on anything else.

**Factor graph:** To capture these assumptions, for two skills and a single individual, a factor graph is constructed.

**Implement the model**

```python
def skill_model():
    '''
    A model representing the first three questions of the exam.
    '''
    csharp = pyro.sample('csharp', dist.Bernoulli(0.5))
    sql = pyro.sample('sql', dist.Bernoulli(0.5))

    hasskills = tensor(1.) if csharp and sql == tensor(1.) else tensor(0.)

    iscorrect1 = pyro.sample('iscorrect1', dist.Bernoulli(0.9 if csharp == tensor(1.) else 0.2))
    iscorrect2 = pyro.sample('iscorrect2', dist.Bernoulli(0.9 if sql == tensor(1.) else 0.2))
    iscorrect3 = pyro.sample('iscorrect3', dist.Bernoulli(0.9 if hasskills == tensor(1.) else 0.2))
```

**Let's take a look at the variables in the model**

```python
%%time

traces = []
for _ in range(10000):
    tr = trace(skill_model).get_trace()
    values = {
        name: props['value'].item()
        for (name, props) in tr.nodes.items()
        if props['type'] == 'sample'
    }
    traces.append(values)

_ = pd.DataFrame(traces).hist()
plt.tight_layout()
```

## Variational inference

**Define a guide function to help me solve conditioned models**

```python
def skill_guide():
    '''
    Guide function used to find optimal values of latent variables.
    '''
    valid_prob = dist.constraints.interval(0., 1.)
    cs_p = param('cs_p', tensor(0.5), constraint=valid_prob)
    sq_p = param('sq_p', tensor(0.5), constraint=valid_prob)

    csharp = pyro.sample('csharp', dist.Bernoulli(cs_p))
    sql = pyro.sample('sql', dist.Bernoulli(sq_p))

    hasskills = tensor(1.) if csharp and sql == tensor(1.) else tensor(0.)

    iscorrect1 = pyro.sample('iscorrect1', dist.Bernoulli(0.9 if csharp == tensor(1.) else 0.2))
    iscorrect2 = pyro.sample('iscorrect2', dist.Bernoulli(0.9 if sql == tensor(1.) else 0.2))
    iscorrect3 = pyro.sample('iscorrect3', dist.Bernoulli(0.9 if hasskills == tensor(1.) else 0.2))
```

**Now get the latent variable probabilities for all outcomes**

```python
%%time

data = {'iscorrect1':[], 'iscorrect2':[], 'iscorrect3':[], 'csharp':[], 'sql':[]}

for k in (tensor(0.), tensor(1.)):
    for j in (tensor(0.), tensor(1.)):
        for i in (tensor(0.), tensor(1.)):
            print(i,j,k)

            # remove previous
            pyro.clear_param_store()

            # Condition the model
            cond_model = condition(skill_model, {
                "iscorrect1": i,
                "iscorrect2": j,
                "iscorrect3": k
            })

#             cond_test_guide = condition(test_guide, {
#                 "iscorrect1": i,
#                 "iscorrect2": j,
#                 "iscorrect3": k
#             })

            adam = Adam({"lr": 0.005, "betas": (0.90, 0.999)})
            svi = SVI(model=cond_model,
                      guide=skill_guide,
                      optim=adam,
                      loss=Trace_ELBO())

            param_vals = []
            losses = []
            cs = []
            sq = []
            for _ in range(5000):
                losses.append(svi.step())
                cs.append(pyro.param("cs_p").item())
                sq.append(pyro.param("sq_p").item())
                #param_vals.append({p: param(p).item() for p in ["cs_p", "sq_p"]})

            # plot the parameter value
            #pd.DataFrame(param_vals).plot(subplots=True)

            # plot losses
            plt.plot(losses)
            plt.title("ELBO {} {} {}".format(i, j, k))
            plt.xlabel("step")
            plt.ylabel("loss")
            plt.show()

            plt.subplot(1,2,1)
            plt.plot(cs)
            plt.ylabel('cs_p')

            plt.subplot(1,2,2)
            plt.plot(sq)
            plt.ylabel('sq_p')
            plt.tight_layout()
            plt.show()

            # what is the probabilities now?
            tr = trace(skill_guide).get_trace()

            data['iscorrect1'].append(True if i == tensor(1.) else False)
            data['iscorrect2'].append(True if j == tensor(1.) else False)
            data['iscorrect3'].append(True if k == tensor(1.) else False)
            data['csharp'].append(tr.nodes['cs_p']['value'].item())
            data['sql'].append(tr.nodes['sq_p']['value'].item())

df = pd.DataFrame(data)

```

```python
display(df)
```

      iscorrect1
      iscorrect2
      iscorrect3
      csharp
      sql

      0
      False
      False
      False
      0.100951
      0.089976

      1
      True
      False
      False
      0.808066
      0.042736

      2
      False
      True
      False
      0.035051
      0.821119

      3
      True
      True
      False
      0.576358
      0.588680

      4
      False
      False
      True
      0.135753
      0.198998

      5
      True
      False
      True
      0.868326
      0.296901

      6
      False
      True
      True
      0.332842
      0.869456

      7
      True
      True
      True
      0.929578
      0.940011

Looking at the optimization traces there is room for improvement. The values for some of the latent variables do not stabilize before the process is over. Even so, the results nicely agree with the ones in Table 2.4 on page 67 of the book.

## Moving to real data
#### Page 78

```python

```

```python
### First load the data
```

```python
#
table26 = pd.read_csv(join('ch2', 'RawResponsesAsDictionary.csv'), sep=',', index_col=0)
display(table26.head())

#
skills = pd.read_csv(join('ch2', 'SkillsQuestionsMask.csv'), sep=',', header=None)
skillsT = skills.transpose()

display(skills.head())
display(skillsT.head())

#
questions = pd.read_csv(join('ch2', 'IsCorrect.csv'), sep=',', header=None)
questionsT = questions.transpose()

display(questions.head())
display(questionsT.head())
```

      S1
      S2
      S3
      S4
      S5
      S6
      S7
      Q1
      Q2
      Q3
      ...
      Q39
      Q40
      Q41
      Q42
      Q43
      Q44
      Q45
      Q46
      Q47
      Q48

      #

      ANS
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN
      2
      4
      3
      ...
      2
      1
      5
      4
      2
      5
      2
      2
      2
      1

      P1
      True
      True
      True
      True
      False
      True
      True
      2
      4
      3
      ...
      2
      1
      5
      4
      2
      5
      2
      2
      2
      1

      P2
      True
      True
      True
      True
      False
      False
      True
      1
      4
      3
      ...
      5
      1
      1
      4
      3
      3
      4
      4
      3
      1

      P3
      False
      False
      False
      True
      False
      True
      False
      3
      4
      5
      ...
      2
      1
      5
      4
      2
      5
      5
      3
      3
      1

      P4
      True
      True
      False
      True
      False
      False
      False
      2
      4
      3
      ...
      2
      5
      5
      4
      2
      5
      4
      2
      3
      1

5 rows × 55 columns

      0
      1
      2
      3
      4
      5
      6

      0
      True
      False
      False
      False
      False
      False
      False

      1
      True
      False
      False
      False
      False
      False
      False

      2
      True
      False
      False
      False
      False
      False
      True

      3
      True
      False
      False
      False
      False
      False
      True

      4
      True
      False
      False
      False
      False
      False
      False

      0
      1
      2
      3
      4
      5
      6
      7
      8
      9
      ...
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47

      0
      True
      True
      True
      True
      True
      True
      True
      False
      False
      False
      ...
      False
      False
      False
      False
      False
      False
      False
      False
      False
      False

      1
      False
      False
      False
      False
      False
      False
      False
      True
      True
      True
      ...
      False
      False
      False
      False
      False
      False
      False
      False
      False
      False

      2
      False
      False
      False
      False
      False
      False
      False
      False
      False
      False
      ...
      False
      False
      False
      False
      False
      False
      False
      False
      False
      False

      3
      False
      False
      False
      False
      False
      False
      False
      False
      False
      False
      ...
      False
      False
      False
      False
      False
      False
      False
      False
      False
      False

      4
      False
      False
      False
      False
      False
      False
      False
      False
      False
      False
      ...
      True
      True
      False
      False
      False
      False
      False
      False
      False
      False

5 rows × 48 columns

#### Make the plot in figure 2.14

```python
#
fig, ax2 = plt.subplots(figsize=(6, 20))
img = ax2.imshow(skillsT,
                 interpolation='nearest',
                 cmap = 'magma',
                vmin=0,
                vmax=1)
plt.axis('off')
fig.tight_layout()

#
fig, ax1 = plt.subplots(figsize=(6, 3))
img = ax1.imshow(questionsT,
                 interpolation='nearest',
                 cmap = 'magma',
                vmin=0,
                vmax=1)
plt.axis('off')
fig.tight_layout()

display(skillsT.shape)
display(questionsT.shape)
```

```python
# convert skills needed to a list of indexes
skills_needed_list = [skillsT.index[skillsT[s] == True].tolist() for s in skillsT.columns]
skills_needed_list
```

    [[0],
     [0],
     [0, 6],
     [0, 6],
     [0],
     [0],
     [0, 6],
     [1, 6],
     [1, 6],
     [1],
     [1],
     [1, 6],
     [1, 6],
     [1, 6],
     [1],
     [1, 6],
     [1, 6],
     [1],
     [2],
     [2],
     [2],
     [2],
     [2],
     [0],
     [0],
     [0],
     [3],
     [3],
     [3],
     [3],
     [3],
     [3],
     [3],
     [3],
     [4],
     [1, 4],
     [4],
     [4],
     [4],
     [4],
     [5],
     [5],
     [5],
     [5],
     [5],
     [5],
     [5],
     [5]]

#### Build the model using pyro plates

```python
def test_model_without_plate_one_person(data, skillsneeded, num_skills):
    '''
    A model representing all questions of the exam.
    '''
    num_exams_taken = 1 # how many exams were taken, i.e. how many people
    num_questions = data.shape[0] # number of questions in each exam
    print(num_questions)

    skills = []
    for i in range(num_skills):
        skills.append(pyro.sample('skill{}'.format(i), dist.Bernoulli(0.5)))
    skills = np.array(skills)

    # now sample probability of having the questions right
    hasskills = []
    iscorrect = []
    for i in range(num_questions):
        hasskills.append(all(skills[skillsneeded[i]]))
        iscorrect.append(pyro.sample('iscorrect{}'.format(i),
                                dist.Bernoulli(0.9 if hasskills[i] == True else 0.2), obs=data[i]))

    return iscorrect

```

```python
%%time

def test_model_without_plate(data, skillsneeded):
    '''
    A model representing all questions of the exam.
    '''
    num_exams_taken = data.shape[0] # how many exams were taken, i.e. how many people
    num_questions = data.shape[1] # number of questions in each exam

    skills = []
    for i in range(num_exams_taken):
        skills.append(pyro.sample('skill{}'.format(i), dist.Bernoulli(0.5)))

    # now sample probability of having the questions right
    hasskills = []
    iscorrect = []
    for i in range(num_exams_taken):
        for j in range(num_questions):
            hasskills.append(all(skills[skillsneeded[i]]))
            iscorrect.append(pyro.sample('iscorrect{}'.format(i),
                                    dist.Bernoulli(0.9 if hasskills[i] == True else 0.2), obs=data[i][j]))

def guide(data, skillsneeded):
    '''
    '''
    num_exams_taken = data.shape[0] # how many exams were taken, i.e. how many people
    num_questions = data.shape[1] # number of questions in each exam

    valid_prob = dist.constraints.interval(0., 1.)

    params = []
    for i in range(num_exams_taken):
        params.append(param('skill_param{}'.format(i), tensor(0.5), constraint=valid_prob))

    skills = []
    for i in range(num_exams_taken):
        skills.append(pyro.sample('skill{}'.format(i), dist.Bernoulli(params[i])))

    # now sample probability of having the questions right
    hasskills = []
    iscorrect = []
    for i in range(num_exams_taken):
        for j in range(num_questions):
            hasskills.append(all(skills[skillsneeded[i]]))
            iscorrect.append(pyro.sample('iscorrect{}'.format(i),
                                    dist.Bernoulli(0.9 if hasskills[i] == True else 0.2), obs=data[i][j]))

# prepare data for the Pyro model
data = questionsT.to_numpy()
person_idx = data.shape[0]
question_idx = data.shape[1]
skills_needed_list = [torch.tensor(s).long() for s in skills_needed_list]

# make guide function
# guide = AutoContinuous(test_model_without_plate)

# Reset parameter values
pyro.clear_param_store()

# Define the number of optimization steps
n_steps = 12000

# Setup the optimizer
adam_params = {"lr": 0.01}
optimizer = Adam(adam_params)

# Setup the inference algorithm
elbo = Trace_ELBO(num_particles=3)
svi = SVI(test_model_without_plate, guide, optimizer, loss=elbo)

# Do gradient steps
for step in range(n_steps):
    elbo = svi.step(data, skills_needed_list)
    if step % 500 == 0:
        print("[%d] ELBO: %.1f" % (step, elbo))
```

```python
data[1][2]
```

    True

```python
data, skills_needed_list, 7, 1, question_idx-1
```

```python
test_model_without_plate_one_person(questions.to_numpy()[:, 1], skills_needed_list, 7)
```

```python

```

```python

# def test_model_plate(data, skillsneeded, num_skills, person_idx, question_idx):
#     '''
#     A model representing all questions of the exam.
#     '''
#     num_exams_taken = data.shape[0] # how many exams were taken, i.e. how many people
#     num_questions = data.shape[1] # number of questions in each exam

#     # priors for the skills as plate
#     with pyro.plate('person', num_exams_taken):
#         skills = pyro.sample('skills', dist.Bernoulli(0.5*torch.ones(num_skills)))

#     # now sample probability of having the questions right
#     with pyro.plate('data', num_questions):
#         hasskills = list(map(all, skills[person_idx][skillsneeded[question_idx]])) # apply the and operation for skills needed
#         iscorrect = pyro.sample('iscorrect',
#                                 dist.Bernoulli(0.9 if hasskills[question_idx] == True else 0.2), obs=data)

#     return iscorrect

def test_model_plate(data, skillsneeded, num_skills, person_idx, question_idx):
    '''
    A model representing all questions of the exam.
    '''
    num_exams_taken = data.shape[0] # how many exams were taken, i.e. how many people
    num_questions = data.shape[1] # number of questions in each exam

    # priors for a persons skills as plate
    with pyro.plate('sk', num_skills):
        skills = pyro.sample('skills', dist.Bernoulli(0.5))

    # now sample probability of having the questions right
#     with pyro.plate('data', num_questions):

#         # apply the and operation for skills needed
#         hasskills = pyro.sample('hasskills',
#                                 dist.Bernoulli(1.0 if all(skills[skillsneeded[question_idx]]) == True else 0.0),
#                                 obs=torch.tensor(data[person_idx]))
#         print(hasskills)
#         iscorrect = pyro.sample('iscorrect',
#                                 dist.Bernoulli(0.9 if hasskills == True else 0.2),
#                                 obs=torch.tensor(data[person_idx]))

    for i in pyro.plate("data_loop", len(data[person_idx])):

        # apply the and operation for skills needed
        hasskills = pyro.sample('hasskills{}'.format(i),
                                dist.Bernoulli(1.0 if all(skills[skillsneeded[question_idx]]) == True else 0.0),
                                obs=torch.tensor(data[person_idx][i]))
        print(hasskills)
        iscorrect = pyro.sample('iscorrect{}'.format(i),
                                dist.Bernoulli(0.9 if hasskills == True else 0.2),
                                obs=torch.tensor(data[person_idx][i]))

    #return iscorrect

def guide(data, skillsneeded, num_skills, person_idx, question_idx):
    '''
    A model representing all questions of the exam.
    '''
    num_exams_taken = data.shape[0] # how many exams were taken, i.e. how many people
    num_questions = data.shape[1] # number of questions in each exam

    valid_prob = dist.constraints.interval(0., 1.)

    # priors for a persons skills as plate
    with pyro.plate('sk', num_skills):
        mu_p = param('mu_p', tensor(0.5), constraint=valid_prob)
        skills = pyro.sample('skills', dist.Bernoulli(mu_p))

#     # now sample probability of having the questions right
#     with pyro.plate('data', num_questions):
#         hasskills = torch.tensor(all(skills[skillsneeded[question_idx]])) # apply the and operation for skills needed
#         iscorrect = pyro.sample('iscorrect',
#                                 dist.Bernoulli(0.9 if hasskills == True else 0.2), obs=torch.tensor(data[person_idx]))

#     return dist.Categorical(iscorrect)

```

```python
# prepare data for the Pyro model
data = questionsT.to_numpy()
person_idx = data.shape[0]
question_idx = data.shape[1]
skills_needed_list = [torch.tensor(s).long() for s in skills_needed_list]

```

    /home/martin/miniconda3/envs/pt/lib/python3.7/site-packages/ipykernel_launcher.py:5: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).
      """

```python
y_hat = test_model_plate(data=data,
                 skillsneeded=skills_needed_list,
                 num_skills=7,
                 person_idx=1,
                 question_idx=1)

```

```python
AutoDiagonalNormal(test_model_plate)
```

    AutoDiagonalNormal()

```python
%%time

# get exam data from first person
data = questionsT.to_numpy()

# make guide function
guide = AutoContinuous(test_model_plate)

# Reset parameter values
pyro.clear_param_store()

# Define the number of optimization steps
n_steps = 120

# Setup the optimizer
adam_params = {"lr": 0.01}
optimizer = ClippedAdam(adam_params)

# Setup the inference algorithm
elbo = Trace_ELBO(num_particles=3)
svi = SVI(test_model_plate, guide, optimizer, loss=elbo)

# Do gradient steps
for step in range(n_steps):
    elbo = svi.step(data, skills_needed_list, 7, 1, question_idx-1)
    if step % 500 == 0:
        print("[%d] ELBO: %.1f" % (step, elbo))
```

```python

```

```python
skills = pyro.plate('x', pyro.sample('skills', dist.Bernoulli(0.5*torch.ones(5))))
skills
```

```python
p = pyro.param("p", torch.randn(5, 4, 3, 2).exp(),
               constraint=dist.constraints.simplex)
x = pyro.sample("x", dist.Categorical(torch.ones(4)))
y = pyro.sample("y", dist.Categorical(torch.ones(3)))
with pyro.plate("z_plate", 5):
    p_xy = p[..., x, y, :]  # Not compatible with enumeration!
    z = pyro.sample("z", dist.Categorical(p_xy))
```

```python
z
```

    tensor([1, 1, 0, 0, 0])

```python

```

```python

```

# Code scraps

```python

```

```python

```

```python
import xml.etree.ElementTree as ET
tree = ET.parse('./mbml/ch2/InputData.objml')
root = tree.getroot()

for child in root:
    print(child.tag)

# get a list of the skills
print('\n<getting list of skills>')
print(root[0].tag)
print(root[0][0].tag)
questions = []
for child in root[0][0]:
    #print(child.text)
    questions.append(child.text)
print(questions)

# get which skills are needed for each question
print('\n<getting skills for questions>')
print(root[0].tag)
print(root[0][1].tag)
skillsforq = []
for child in root[0][1]:
    temp = []
    for grandchild in child:
        #print(grandchild.text)
        temp.append(int(grandchild.text))
    skillsforq.append(temp)
print(skillsforq)

# get raw responses
print('\n<getting raw responses>')
print(root[1].tag)
raw = []
for child in root[1]:
    temp = []
    for grandchild in child:
        #print(grandchild.text)
        temp.append(int(grandchild.text))
    raw.append(temp)
print(raw)

# get whether response is correct or not
print('\n<getting response correctness>')
print(root[2].tag)
iscorrect = []
for child in root[2]:
    temp = []
    for grandchild in child:
        #print(grandchild.text)
        temp.append(True if grandchild.text == 'True' else False)
    iscorrect.append(temp)
print(iscorrect)

# get correct answers for the questions
print('\n<getting correct answers>')
print(root[3].tag)
correct = []
for child in root[3]:
    correct.append(int(child.text))
print(correct)

# get which skills participants state they have
print('\n<getting stated skills>')
print(root[4].tag)
haveskill = []
for child in root[4]:
    temp = []
    for grandchild in child:
        temp.append(True if grandchild.text == 'True' else False)
    haveskill.append(temp)
print(haveskill)

### Start putting the stuff together ###

# add in a row of NaN to stated skills (since the first row aren't participant answers)
haveskill.insert(0, ['NaN', 'NaN', 'NaN', 'NaN', 'NaN', 'NaN', 'NaN'])
print(haveskill)

# add in the correct answers in front of the given answers
raw.insert(0, correct)
print(raw)
```

    Quiz
    RawResponses
    IsCorrect
    CorrectAnswers
    StatedSkills

    <getting list of skills>
    Quiz
    SkillNames
    ['1: Core programming skills (i.e. storage, data types, decision structures, error handling, algorithms)', '2: Object Oriented Programming (i.e. classes, inheritance, polymorphism, encapsulation)', '3: Application Life Cycle Management', '4: Web Applications (development, hosting, services)', '5: Microsoft Windows desktop applications', '6: Databases & SQL', '7: C#']

    <getting skills for questions>
    Quiz
    SkillsForQuestion
    [[0], [0], [0, 6], [0, 6], [0], [0], [0, 6], [1, 6], [1, 6], [1], [1], [1, 6], [1, 6], [1, 6], [1], [1, 6], [1, 6], [1], [2], [2], [2], [2], [2], [0], [0], [0], [3], [3], [3], [3], [3], [3], [3], [3], [4], [1, 4], [4], [4], [4], [4], [5], [5], [5], [5], [5], [5], [5], [5]]

    <getting raw responses>
    RawResponses
    [[2, 4, 3, 3, 4, 3, 4, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 5, 2, 2, 2, 4, 2, 3, 3, 2, 1, 4, 4, 5, 3, 4, 2, 1, 2, 1, 5, 4, 2, 5, 2, 2, 2, 1], [1, 4, 3, 3, 4, 1, 4, 5, 1, 5, 1, 5, 1, 4, 3, 3, 5, 3, 2, 3, 4, 5, 5, 2, 2, 4, 2, 3, 3, 2, 2, 1, 4, 5, 4, 4, 3, 1, 5, 1, 1, 4, 3, 3, 4, 4, 3, 1], [3, 4, 5, 2, 4, 5, 4, 5, 1, 5, 5, 3, 2, 5, 5, 1, 2, 1, 2, 3, 1, 5, 1, 1, 4, 4, 2, 3, 3, 5, 2, 1, 4, 5, 3, 1, 3, 2, 2, 1, 5, 4, 2, 5, 5, 3, 3, 1], [2, 4, 3, 3, 4, 3, 4, 5, 1, 5, 1, 1, 2, 4, 3, 1, 2, 3, 2, 3, 2, 2, 2, 2, 2, 4, 2, 3, 3, 2, 2, 4, 2, 5, 4, 3, 2, 1, 2, 5, 5, 4, 2, 5, 4, 2, 3, 1], [2, 4, 3, 3, 4, 1, 4, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 5, 2, 2, 2, 4, 2, 3, 3, 2, 1, 1, 4, 5, 3, 4, 2, 1, 2, 1, 5, 4, 2, 5, 5, 2, 2, 5], [1, 3, 3, 5, 3, 4, 5, 2, 5, 2, 1, 4, 2, 2, 4, 4, 5, 1, 3, 2, 1, 3, 1, 2, 3, 5, 5, 1, 4, 4, 5, 5, 2, 3, 5, 3, 3, 1, 4, 4, 4, 2, 4, 3, 1, 5, 1, 3], [2, 4, 3, 3, 4, 1, 4, 5, 1, 5, 1, 1, 1, 2, 3, 1, 2, 3, 2, 3, 4, 5, 2, 2, 2, 4, 2, 3, 3, 5, 1, 4, 4, 5, 4, 4, 2, 1, 2, 1, 5, 4, 2, 5, 2, 2, 2, 1], [2, 4, 5, 2, 4, 1, 4, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 4, 2, 2, 2, 4, 2, 3, 3, 1, 1, 1, 4, 5, 3, 4, 1, 4, 2, 5, 5, 4, 2, 5, 4, 2, 2, 1], [2, 4, 1, 3, 4, 1, 4, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 2, 2, 1, 2, 5, 2, 3, 3, 2, 2, 4, 4, 5, 3, 4, 2, 1, 2, 1, 2, 4, 2, 5, 2, 2, 2, 1], [2, 4, 3, 3, 4, 1, 4, 5, 1, 5, 1, 1, 2, 4, 3, 1, 2, 3, 2, 2, 1, 5, 2, 2, 2, 4, 2, 3, 3, 2, 2, 4, 4, 5, 3, 4, 2, 1, 2, 1, 5, 4, 2, 5, 2, 1, 3, 1], [1, 4, 3, 3, 4, 3, 4, 5, 1, 5, 3, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 5, 4, 2, 2, 4, 2, 3, 3, 2, 2, 4, 4, 5, 3, 5, 2, 1, 2, 4, 5, 4, 2, 4, 2, 1, 2, 4], [1, 1, 1, 3, 4, 1, 4, 5, 1, 5, 1, 5, 5, 2, 2, 1, 5, 3, 2, 3, 4, 5, 2, 2, 2, 4, 2, 3, 3, 2, 1, 1, 2, 2, 4, 4, 2, 1, 2, 1, 5, 2, 1, 4, 2, 2, 5, 5], [2, 4, 3, 3, 4, 3, 4, 5, 1, 5, 1, 1, 1, 2, 3, 1, 2, 3, 2, 3, 4, 2, 2, 2, 2, 4, 2, 3, 3, 5, 4, 4, 4, 5, 3, 3, 2, 1, 2, 4, 5, 4, 2, 5, 2, 2, 5, 1], [2, 5, 3, 3, 5, 5, 4, 5, 1, 5, 1, 1, 5, 2, 3, 1, 2, 3, 2, 3, 2, 4, 2, 3, 2, 4, 2, 3, 3, 2, 2, 1, 4, 5, 3, 4, 3, 1, 5, 5, 5, 4, 2, 5, 4, 2, 5, 5], [2, 4, 3, 3, 4, 3, 4, 5, 1, 5, 4, 5, 1, 2, 3, 5, 2, 3, 2, 4, 4, 1, 2, 3, 2, 4, 2, 3, 3, 5, 1, 4, 4, 5, 4, 4, 2, 1, 2, 4, 5, 4, 2, 5, 2, 2, 5, 1], [2, 4, 3, 5, 4, 1, 4, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 5, 5, 2, 2, 4, 2, 3, 3, 4, 2, 4, 4, 5, 4, 4, 2, 1, 2, 5, 5, 4, 2, 5, 2, 2, 2, 1], [2, 4, 3, 3, 4, 1, 4, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 3, 3, 4, 5, 2, 2, 2, 4, 2, 3, 3, 2, 1, 4, 4, 5, 3, 4, 2, 1, 2, 5, 5, 4, 2, 5, 2, 2, 5, 1], [2, 4, 3, 3, 4, 3, 4, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 2, 2, 2, 2, 4, 2, 3, 3, 2, 5, 4, 4, 5, 3, 4, 1, 1, 2, 1, 5, 4, 2, 2, 2, 2, 3, 5], [2, 4, 5, 3, 4, 3, 4, 2, 1, 5, 1, 5, 1, 4, 3, 1, 2, 3, 2, 3, 4, 3, 2, 2, 2, 4, 2, 3, 3, 2, 2, 4, 4, 5, 4, 4, 2, 1, 2, 5, 5, 4, 2, 5, 4, 1, 5, 1], [2, 4, 3, 3, 4, 1, 4, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 4, 2, 2, 2, 4, 2, 3, 3, 5, 2, 4, 4, 5, 4, 4, 2, 1, 2, 1, 5, 4, 2, 5, 2, 2, 2, 1], [2, 4, 3, 3, 4, 1, 3, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 5, 2, 2, 2, 4, 2, 3, 3, 3, 2, 4, 4, 5, 3, 4, 2, 1, 2, 1, 5, 4, 2, 5, 2, 2, 2, 1], [2, 4, 4, 3, 4, 1, 4, 5, 1, 5, 1, 1, 1, 3, 3, 1, 2, 4, 2, 3, 4, 5, 5, 2, 2, 4, 2, 3, 3, 2, 2, 4, 4, 5, 4, 4, 2, 1, 2, 1, 5, 4, 2, 5, 2, 2, 2, 1]]

    <getting response correctness>
    IsCorrect
    [[True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, False, True, True, True, True, True, True, True, False, False, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True], [False, True, True, True, True, True, True, True, True, True, True, False, True, True, True, False, False, True, True, True, True, False, False, True, True, True, True, True, True, False, True, False, True, True, False, True, False, True, False, True, False, True, False, False, False, False, False, True], [False, True, False, False, True, False, True, True, True, True, False, False, False, False, False, True, True, False, True, True, False, False, False, False, False, True, True, True, True, False, True, False, True, True, True, False, False, False, True, True, True, True, True, True, False, False, False, True], [True, True, True, True, True, False, True, True, True, True, True, True, False, True, True, True, True, True, True, True, False, False, True, True, True, True, True, True, True, False, True, True, False, True, False, False, True, True, True, False, True, True, True, True, False, True, False, True], [True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, False, True, True, True, True, True, True, True, False, False, False, True, True, True, True, True, True, True, True, True, True, True, True, False, True, True, False], [False, False, True, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, True, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False], [True, True, True, True, True, True, True, True, True, True, True, True, True, False, True, True, True, True, True, True, True, False, True, True, True, True, True, True, True, False, False, True, True, True, False, True, True, True, True, True, True, True, True, True, True, True, True, True], [True, True, False, False, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, False, False, False, True, True, True, True, False, False, True, False, True, True, True, True, False, True, True, True], [True, True, False, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, False, True, False, True, False, True, True, True, False, True, True, True, True, True, True, True, True, True, True, False, True, True, True, True, True, True, True], [True, True, True, True, True, True, True, True, True, True, True, True, False, True, True, True, True, True, True, False, False, False, True, True, True, True, True, True, True, False, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, False, False, True], [False, True, True, True, True, False, True, True, True, True, False, True, True, True, True, True, True, True, True, True, True, False, False, True, True, True, True, True, True, False, True, True, True, True, True, False, True, True, True, False, True, True, True, False, True, False, True, False], [False, False, False, True, True, True, True, True, True, True, True, False, False, False, False, True, False, True, True, True, True, False, True, True, True, True, True, True, True, False, False, False, False, False, False, True, True, True, True, True, True, False, False, False, True, True, False, False], [True, True, True, True, True, False, True, True, True, True, True, True, True, False, True, True, True, True, True, True, True, False, True, True, True, True, True, True, True, False, False, True, True, True, True, False, True, True, True, False, True, True, True, True, True, True, False, True], [True, False, True, True, False, False, True, True, True, True, True, True, False, False, True, True, True, True, True, True, False, True, True, False, True, True, True, True, True, False, True, False, True, True, True, True, False, True, False, False, True, True, True, True, False, True, False, False], [True, True, True, True, True, False, True, True, True, True, False, False, True, False, True, False, True, True, True, False, True, False, True, False, True, True, True, True, True, False, False, True, True, True, False, True, True, True, True, False, True, True, True, True, True, True, False, True], [True, True, True, False, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, False, False, True, True, True, True, True, True, True, True, True, True, True, False, True, True, True, True, False, True, True, True, True, True, True, True, True], [True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, False, True, True, False, True, True, True, True, True, True, True, False, False, True, True, True, True, True, True, True, True, False, True, True, True, True, True, True, False, True], [True, True, True, True, True, False, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, False, True, True, True, True, True, True, True, False, False, True, True, True, True, True, False, True, True, True, True, True, True, False, True, True, False, False], [True, True, False, True, True, False, True, False, True, True, True, False, True, True, True, True, True, True, True, True, True, False, True, True, True, True, True, True, True, False, True, True, True, True, False, True, True, True, True, False, True, True, True, True, False, False, False, True], [True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, False, True, True, True, True, False, True, True, True, True, True, True, True, True, True, True, True, True, True], [True, True, True, True, True, True, False, True, True, True, True, True, True, True, True, True, True, True, True, True, True, False, True, True, True, True, True, True, True, False, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True], [True, True, False, True, True, True, True, True, True, True, True, True, True, False, True, True, True, False, True, True, True, False, False, True, True, True, True, True, True, False, True, True, True, True, False, True, True, True, True, True, True, True, True, True, True, True, True, True]]

    <getting correct answers>
    CorrectAnswers
    [2, 4, 3, 3, 4, 1, 4, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 4, 2, 2, 2, 4, 2, 3, 3, 4, 2, 4, 4, 5, 3, 4, 2, 1, 2, 1, 5, 4, 2, 5, 2, 2, 2, 1]

    <getting stated skills>
    StatedSkills
    [[True, True, True, True, False, True, True], [True, True, True, True, False, False, True], [False, False, False, True, False, True, False], [True, True, False, True, False, False, False], [True, True, False, False, False, False, True], [False, False, False, False, False, False, False], [True, True, False, False, False, False, False], [True, True, False, False, False, False, True], [True, True, False, False, True, True, True], [True, True, False, False, False, True, True], [True, True, False, False, False, False, True], [False, False, False, False, True, False, False], [True, True, False, False, False, False, False], [True, True, False, False, False, True, False], [True, False, False, False, False, False, False], [True, True, False, False, True, True, True], [True, True, False, False, False, False, True], [True, True, False, False, False, False, True], [True, True, False, False, True, False, True], [True, True, True, True, False, True, True], [True, True, False, True, True, True, True], [True, True, True, True, False, True, False]]
    [['NaN', 'NaN', 'NaN', 'NaN', 'NaN', 'NaN', 'NaN'], [True, True, True, True, False, True, True], [True, True, True, True, False, False, True], [False, False, False, True, False, True, False], [True, True, False, True, False, False, False], [True, True, False, False, False, False, True], [False, False, False, False, False, False, False], [True, True, False, False, False, False, False], [True, True, False, False, False, False, True], [True, True, False, False, True, True, True], [True, True, False, False, False, True, True], [True, True, False, False, False, False, True], [False, False, False, False, True, False, False], [True, True, False, False, False, False, False], [True, True, False, False, False, True, False], [True, False, False, False, False, False, False], [True, True, False, False, True, True, True], [True, True, False, False, False, False, True], [True, True, False, False, False, False, True], [True, True, False, False, True, False, True], [True, True, True, True, False, True, True], [True, True, False, True, True, True, True], [True, True, True, True, False, True, False]]
    [[2, 4, 3, 3, 4, 1, 4, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 4, 2, 2, 2, 4, 2, 3, 3, 4, 2, 4, 4, 5, 3, 4, 2, 1, 2, 1, 5, 4, 2, 5, 2, 2, 2, 1], [2, 4, 3, 3, 4, 3, 4, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 5, 2, 2, 2, 4, 2, 3, 3, 2, 1, 4, 4, 5, 3, 4, 2, 1, 2, 1, 5, 4, 2, 5, 2, 2, 2, 1], [1, 4, 3, 3, 4, 1, 4, 5, 1, 5, 1, 5, 1, 4, 3, 3, 5, 3, 2, 3, 4, 5, 5, 2, 2, 4, 2, 3, 3, 2, 2, 1, 4, 5, 4, 4, 3, 1, 5, 1, 1, 4, 3, 3, 4, 4, 3, 1], [3, 4, 5, 2, 4, 5, 4, 5, 1, 5, 5, 3, 2, 5, 5, 1, 2, 1, 2, 3, 1, 5, 1, 1, 4, 4, 2, 3, 3, 5, 2, 1, 4, 5, 3, 1, 3, 2, 2, 1, 5, 4, 2, 5, 5, 3, 3, 1], [2, 4, 3, 3, 4, 3, 4, 5, 1, 5, 1, 1, 2, 4, 3, 1, 2, 3, 2, 3, 2, 2, 2, 2, 2, 4, 2, 3, 3, 2, 2, 4, 2, 5, 4, 3, 2, 1, 2, 5, 5, 4, 2, 5, 4, 2, 3, 1], [2, 4, 3, 3, 4, 1, 4, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 5, 2, 2, 2, 4, 2, 3, 3, 2, 1, 1, 4, 5, 3, 4, 2, 1, 2, 1, 5, 4, 2, 5, 5, 2, 2, 5], [1, 3, 3, 5, 3, 4, 5, 2, 5, 2, 1, 4, 2, 2, 4, 4, 5, 1, 3, 2, 1, 3, 1, 2, 3, 5, 5, 1, 4, 4, 5, 5, 2, 3, 5, 3, 3, 1, 4, 4, 4, 2, 4, 3, 1, 5, 1, 3], [2, 4, 3, 3, 4, 1, 4, 5, 1, 5, 1, 1, 1, 2, 3, 1, 2, 3, 2, 3, 4, 5, 2, 2, 2, 4, 2, 3, 3, 5, 1, 4, 4, 5, 4, 4, 2, 1, 2, 1, 5, 4, 2, 5, 2, 2, 2, 1], [2, 4, 5, 2, 4, 1, 4, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 4, 2, 2, 2, 4, 2, 3, 3, 1, 1, 1, 4, 5, 3, 4, 1, 4, 2, 5, 5, 4, 2, 5, 4, 2, 2, 1], [2, 4, 1, 3, 4, 1, 4, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 2, 2, 1, 2, 5, 2, 3, 3, 2, 2, 4, 4, 5, 3, 4, 2, 1, 2, 1, 2, 4, 2, 5, 2, 2, 2, 1], [2, 4, 3, 3, 4, 1, 4, 5, 1, 5, 1, 1, 2, 4, 3, 1, 2, 3, 2, 2, 1, 5, 2, 2, 2, 4, 2, 3, 3, 2, 2, 4, 4, 5, 3, 4, 2, 1, 2, 1, 5, 4, 2, 5, 2, 1, 3, 1], [1, 4, 3, 3, 4, 3, 4, 5, 1, 5, 3, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 5, 4, 2, 2, 4, 2, 3, 3, 2, 2, 4, 4, 5, 3, 5, 2, 1, 2, 4, 5, 4, 2, 4, 2, 1, 2, 4], [1, 1, 1, 3, 4, 1, 4, 5, 1, 5, 1, 5, 5, 2, 2, 1, 5, 3, 2, 3, 4, 5, 2, 2, 2, 4, 2, 3, 3, 2, 1, 1, 2, 2, 4, 4, 2, 1, 2, 1, 5, 2, 1, 4, 2, 2, 5, 5], [2, 4, 3, 3, 4, 3, 4, 5, 1, 5, 1, 1, 1, 2, 3, 1, 2, 3, 2, 3, 4, 2, 2, 2, 2, 4, 2, 3, 3, 5, 4, 4, 4, 5, 3, 3, 2, 1, 2, 4, 5, 4, 2, 5, 2, 2, 5, 1], [2, 5, 3, 3, 5, 5, 4, 5, 1, 5, 1, 1, 5, 2, 3, 1, 2, 3, 2, 3, 2, 4, 2, 3, 2, 4, 2, 3, 3, 2, 2, 1, 4, 5, 3, 4, 3, 1, 5, 5, 5, 4, 2, 5, 4, 2, 5, 5], [2, 4, 3, 3, 4, 3, 4, 5, 1, 5, 4, 5, 1, 2, 3, 5, 2, 3, 2, 4, 4, 1, 2, 3, 2, 4, 2, 3, 3, 5, 1, 4, 4, 5, 4, 4, 2, 1, 2, 4, 5, 4, 2, 5, 2, 2, 5, 1], [2, 4, 3, 5, 4, 1, 4, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 5, 5, 2, 2, 4, 2, 3, 3, 4, 2, 4, 4, 5, 4, 4, 2, 1, 2, 5, 5, 4, 2, 5, 2, 2, 2, 1], [2, 4, 3, 3, 4, 1, 4, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 3, 3, 4, 5, 2, 2, 2, 4, 2, 3, 3, 2, 1, 4, 4, 5, 3, 4, 2, 1, 2, 5, 5, 4, 2, 5, 2, 2, 5, 1], [2, 4, 3, 3, 4, 3, 4, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 2, 2, 2, 2, 4, 2, 3, 3, 2, 5, 4, 4, 5, 3, 4, 1, 1, 2, 1, 5, 4, 2, 2, 2, 2, 3, 5], [2, 4, 5, 3, 4, 3, 4, 2, 1, 5, 1, 5, 1, 4, 3, 1, 2, 3, 2, 3, 4, 3, 2, 2, 2, 4, 2, 3, 3, 2, 2, 4, 4, 5, 4, 4, 2, 1, 2, 5, 5, 4, 2, 5, 4, 1, 5, 1], [2, 4, 3, 3, 4, 1, 4, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 4, 2, 2, 2, 4, 2, 3, 3, 5, 2, 4, 4, 5, 4, 4, 2, 1, 2, 1, 5, 4, 2, 5, 2, 2, 2, 1], [2, 4, 3, 3, 4, 1, 3, 5, 1, 5, 1, 1, 1, 4, 3, 1, 2, 3, 2, 3, 4, 5, 2, 2, 2, 4, 2, 3, 3, 3, 2, 4, 4, 5, 3, 4, 2, 1, 2, 1, 5, 4, 2, 5, 2, 2, 2, 1], [2, 4, 4, 3, 4, 1, 4, 5, 1, 5, 1, 1, 1, 3, 3, 1, 2, 4, 2, 3, 4, 5, 5, 2, 2, 4, 2, 3, 3, 2, 2, 4, 4, 5, 4, 4, 2, 1, 2, 1, 5, 4, 2, 5, 2, 2, 2, 1]]

#### Make dataframe from this data (same as table 2.6)

```python
rownames = ['ANS'] + ['P{}'.format(s) for s in range(1, 23)]
colnames = ['S{}'.format(s) for s in range(1, 8)] + ['Q{}'.format(s) for s in range(1, len(raw[0])+1)]

answers_df = pd.DataFrame(raw)
answers_df.index = rownames
answers_df.columns = colnames[7:]

skills_df = pd.DataFrame(haveskill)
skills_df.index = rownames
skills_df.columns = colnames[:7]

table26 = skills_df.join(answers_df)

table26.to_csv('./mbml/ch2/table26.tsv', sep='\t', index=True)

display(table26)
```

      S1
      S2
      S3
      S4
      S5
      S6
      S7
      Q1
      Q2
      Q3
      ...
      Q39
      Q40
      Q41
      Q42
      Q43
      Q44
      Q45
      Q46
      Q47
      Q48

      ANS
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN
      2
      4
      3
      ...
      2
      1
      5
      4
      2
      5
      2
      2
      2
      1

      P1
      True
      True
      True
      True
      False
      True
      True
      2
      4
      3
      ...
      2
      1
      5
      4
      2
      5
      2
      2
      2
      1

      P2
      True
      True
      True
      True
      False
      False
      True
      1
      4
      3
      ...
      5
      1
      1
      4
      3
      3
      4
      4
      3
      1

      P3
      False
      False
      False
      True
      False
      True
      False
      3
      4
      5
      ...
      2
      1
      5
      4
      2
      5
      5
      3
      3
      1

      P4
      True
      True
      False
      True
      False
      False
      False
      2
      4
      3
      ...
      2
      5
      5
      4
      2
      5
      4
      2
      3
      1

      P5
      True
      True
      False
      False
      False
      False
      True
      2
      4
      3
      ...
      2
      1
      5
      4
      2
      5
      5
      2
      2
      5

      P6
      False
      False
      False
      False
      False
      False
      False
      1
      3
      3
      ...
      4
      4
      4
      2
      4
      3
      1
      5
      1
      3

      P7
      True
      True
      False
      False
      False
      False
      False
      2
      4
      3
      ...
      2
      1
      5
      4
      2
      5
      2
      2
      2
      1

      P8
      True
      True
      False
      False
      False
      False
      True
      2
      4
      5
      ...
      2
      5
      5
      4
      2
      5
      4
      2
      2
      1

      P9
      True
      True
      False
      False
      True
      True
      True
      2
      4
      1
      ...
      2
      1
      2
      4
      2
      5
      2
      2
      2
      1

      P10
      True
      True
      False
      False
      False
      True
      True
      2
      4
      3
      ...
      2
      1
      5
      4
      2
      5
      2
      1
      3
      1

      P11
      True
      True
      False
      False
      False
      False
      True
      1
      4
      3
      ...
      2
      4
      5
      4
      2
      4
      2
      1
      2
      4

      P12
      False
      False
      False
      False
      True
      False
      False
      1
      1
      1
      ...
      2
      1
      5
      2
      1
      4
      2
      2
      5
      5

      P13
      True
      True
      False
      False
      False
      False
      False
      2
      4
      3
      ...
      2
      4
      5
      4
      2
      5
      2
      2
      5
      1

      P14
      True
      True
      False
      False
      False
      True
      False
      2
      5
      3
      ...
      5
      5
      5
      4
      2
      5
      4
      2
      5
      5

      P15
      True
      False
      False
      False
      False
      False
      False
      2
      4
      3
      ...
      2
      4
      5
      4
      2
      5
      2
      2
      5
      1

      P16
      True
      True
      False
      False
      True
      True
      True
      2
      4
      3
      ...
      2
      5
      5
      4
      2
      5
      2
      2
      2
      1

      P17
      True
      True
      False
      False
      False
      False
      True
      2
      4
      3
      ...
      2
      5
      5
      4
      2
      5
      2
      2
      5
      1

      P18
      True
      True
      False
      False
      False
      False
      True
      2
      4
      3
      ...
      2
      1
      5
      4
      2
      2
      2
      2
      3
      5

      P19
      True
      True
      False
      False
      True
      False
      True
      2
      4
      5
      ...
      2
      5
      5
      4
      2
      5
      4
      1
      5
      1

      P20
      True
      True
      True
      True
      False
      True
      True
      2
      4
      3
      ...
      2
      1
      5
      4
      2
      5
      2
      2
      2
      1

      P21
      True
      True
      False
      True
      True
      True
      True
      2
      4
      3
      ...
      2
      1
      5
      4
      2
      5
      2
      2
      2
      1

      P22
      True
      True
      True
      True
      False
      True
      False
      2
      4
      4
      ...
      2
      1
      5
      4
      2
      5
      2
      2
      2
      1

23 rows × 55 columns


TensorFlow

Laboratory on Neural Networks

***

***

## Introduction

Few years back, Google has open-sourced their machine learning framework: **TensorFlow**. Since then, it has become one of the most used tools in machine learning, both for research purposes and for production systems. TensorFlow is very flexible and can handle a wide range of neural architectures. With TensorFlow you can potentially deploy a *computational graph* over a cluster of machines and let them learn a very big neural model seamlessly in parallel.

In this lecture we will see how we can build a simple Neural Network capable of categorizing images from the *MNIST* dataset into digits. We will closely follow the (MNIST For ML Beginners)[https://www.tensorflow.org/versions/master/get_started/mnist/beginners] tutorial, so if you want to dig deeper you can go check that out.

This lecture assumes familiarity with the Python programming language and the libraries we have seen in the previous lecture Numpy, Matplotlib and Scikit-learn.

## Requirements

1. Python (preferably version > 3.4): https://www.python.org/downloads/
2. TensorFlow: https://www.tensorflow.org/versions/master/install/ (if you have a dedicated GPU, you may want to install TensorFlow with GPU support)

## References

- https://www.tensorflow.org
- https://www.tensorflow.org/versions/master/get_started/mnist/beginners
- https://www.tensorflow.org/versions/master/get_started/mnist/pros
- https://www.tensorflow.org/versions/master/programmers_guide/
- http://yann.lecun.com/exdb/mnist/

# TensorFlow

Let's dive into how to declare and train a Neural Network with *TensorFlow*. In order to ease the parallelization of the training process over several GPUs and or machines, TensorFlow works in two stages. First, there is a *declarative* stage, in which all the operations needed for training our machine learning model are declared. These interconnected operations form a so-called **computational graph**. The second stage consists in deploying the computaional graph over the target machine(s) and running it within a TensorFlow **session**.

Let's start with the first stage. Remember, all the operations we perform in the first stage are only **declared**, they will only be run once we execute them within a TensorFlow session.

## MNIST dataset

*MNIST* is a dataset containing labelled images of handwritten digits. This dataset is similar to the *digits* dataset we have seen in the previous lecture, but the images here are larger, 28 by 28 pixels.

We can use TensorFlow directly to download and read the MNIST data:

```python
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

mnist
```

    Extracting MNIST_data/train-images-idx3-ubyte.gz
    Extracting MNIST_data/train-labels-idx1-ubyte.gz
    Extracting MNIST_data/t10k-images-idx3-ubyte.gz
    Extracting MNIST_data/t10k-labels-idx1-ubyte.gz

    Datasets(train=<tensorflow.contrib.learn.python.learn.datasets.mnist.DataSet object at 0x7f7d7fc5de10>, validation=<tensorflow.contrib.learn.python.learn.datasets.mnist.DataSet object at 0x7f7d7fb67550>, test=<tensorflow.contrib.learn.python.learn.datasets.mnist.DataSet object at 0x7f7d7fb67470>)

The data instances in MNIST are images represented by Numpy arrays contaning 784 numbers between 0 and 1, i.e. the gray scale relative intensity of the pixels (0 means white, 1 means black).

***

MNIST already comes with a train-validation-test split. The training data `mnist.train.images` is a *tensor* of shape (55000, 784).

***

The labels are encoded into a "one-hot" representation. Each label `y` is a vector of 10 0-1 elements, in which only the position associated with the true digit is set to 1, the others are set to 0. The training labels `mnist.train.labels` is also a tensor of shape (55000, 10).

Let's visualize one of these images as we did in the previous lecture.

```python
%matplotlib inline
import matplotlib.pyplot as plt

x = mnist.train.images[0].reshape(28, 28)

plt.gray()
plt.matshow(1.0 - x) # 1.0 - x simply inverts the fading direction of the image
plt.show()

```

As said, the label is instead an array of size 10.

```python
y = mnist.train.labels[0]

y
```

    array([ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.])

## Softmax Regression

The first thing we are going to implement using TensorFlow is a simple softmax regression model to predict the digits from the images. Softmax regression is a generalization of Logistic Regression to multi-class classification.

In softmax regression we define a linear model for each possible class. These models provide evidence supporting the image `x` being of the particular class `i`:

\begin{equation}
    \theta_i = \sum_{1 \le j \le 784} W_{i,j} \cdot x_j + b_i
\end{equation}

A softmax function is then applied to each linear model in order to get the probability $y_i$ of the image being of class `i`:

\begin{equation}
    y_i = \text{softmax}(\theta_i) = \frac{e^{\theta_i}}{\sum_{j} e^{\theta_j}}
\end{equation}

This is a schematic view of the operations we are a doing:

***

Spelling out the equations of our model:

***

The vectorized form:

***

The vectorized equation is therefore the following:

\begin{equation}
    y = \text{softmax}(W \cdot x + b)
\end{equation}

Where $y$ is a $1 \times 10$ column vector, $W$ is a $10 \times 784$ matrix, $x$ is a $1 \times 784$ column vector and $b$ is a $1 \times 10$ column vector. While we used column vectors for ease of exposition, in the implementation is much more convenient to use row vectors for $x$, $b$ and $y$. Hence, the matrix `W` in the code below will be of shape (784, 10). Let's now delve into the definition of this softmax model with TensorFlow.

As said before, at this stage we need to define a model only in terms of the graph of computations. We do not need to use the actual data now, therefore we define a `placeholder` for the input vector `x`.

```python
import tensorflow as tf

x = tf.placeholder(tf.float32, [None, 784])
```

We just declared a placeholder for a tensor containing values of type `float32`. The second parameter is the shape of this tensor. Here the first dimension is the number of examples that we are going to pass to the model and `None` means that we don't know apriori how many examples we'll be passing. The second dimension is the shape of the images.
This placeholder is a value that we can use to declare the operations we want to perform in the computational graph and it will be populated with actual data as soon as we deploy the graph and we run it.

Now we need to specify the weights and the biases. These parameters are not data we give to the model but rather variables we want TensorFlow to estimate. We declare them as `Variable` and we initialize them with zeros.

```python
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))

W
```

    <tf.Variable 'Variable_10:0' shape=(784, 10) dtype=float32_ref>

Now, the final model is the following:

```python
y_hat = tf.nn.softmax(tf.matmul(x, W) + b)
```

The variable `y_hat` is going to hold the prediction of our model.

## Training

After we define the model, we want to train it using that data we have. To do so, we first need to define a **loss function**. For this task we are going to use the *cross-entropy* loss:

\begin{equation}
    H_{y}(\hat{y}) = - \sum_i y_i \log(\hat{y}_i)
\end{equation}

Where $\hat{y}$ is the distribution predicted by our model and $y$ is the true probability distribution of the classes (i.e. the one-hot vector).

Now, in order to use cross-entropy for training, we need to define a place-holder for our true labels.

```python
y = tf.placeholder(tf.float32, [None, 10])
```

And now we can define the expression that will compute the cross-entropy. Our loss is going to be the average cross-entropy over all training examples.

```python
# First y * tf.log(y_hat) computes the element-wise multiplication of the two resulting vectors
# Second, tf.reduce_sum( , reduction_indices=[1]) computes the sum along the second dimension (the first one are the examples)
# Finally, tf.reduce_mean() computes the mean over the first dimension, i.e. the examples
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y * tf.log(y_hat), reduction_indices=[1]))
```

Now we can pick a training algorithm and let it minimize our loss function; we are going to use gradient descent with a $0.5$ step-size.

```python
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
```

We are now finally ready to start the training into an `InteractiveSession`. But first let's initialize our variables:

```python
# Declares the session
sess = tf.InteractiveSession()

# Run the global variables initializer to initialize the variables we declared earlier
tf.global_variables_initializer().run()
```

Now we can train the model. We'll run `1000` epochs using a batch of `100` examples at each time.

```python
for _ in range(1000):
  batch_xs, batch_ys = mnist.train.next_batch(100)
  sess.run(train_step, feed_dict={x: batch_xs, y: batch_ys})
```

Done!

Notice that we used `feed_dict` to replace each placeholder with actual data. We now want to evaluate our model. Before we do that, one cool thing we can do with neural networks on computer vision tasks is to visualize "what" the model learned:

```python
w = W.eval()
fig = plt.figure(figsize=(8, 8), dpi=80)
for digit in range(w.shape[1]):
    ax = fig.add_subplot(4,3,digit+1)
    ax.set_aspect('equal')
    plt.imshow(w.T[digit].reshape(28,28), interpolation='nearest', cmap=plt.cm.ocean)
    plt.colorbar()
plt.show()
```

## Evaluation

Let's now evaluate our model on the test set. For the evaluation we are going to proceed in the same way as we did for the training phase: first we declare the operations we want to do, then we run them within a session. Now, remember that `y_hat` contains the probability of each class; to make an actual prediction we are going to take the `argmax` of that vector, so we get the class with highest probability.

To get the accuracy of the model, we first need to compare the prediction of the model with the true labels.

```python
# argmax( , 1) means return only the first argmax
correct_prediction = tf.equal(tf.argmax(y_hat, 1), tf.argmax(y, 1))
```

The variable `correct_prediction` will contain a array of Booleans where `True` means the prediction is equal to the label, and thus it is correct, or `False` otherwise.

Notice that we used the same variables `y_hat` and `y` we used for the training. That is fine because those variables are just placeholders, they only get assigned within a session so we can reuse them for different operations with different meanings. For the evaluation part `x` is going to get the test images, so that `y_hat` will contain the predictions over the test set, while `y` is going to get the test labels in order to calculate the accuracy over the test set.

Let's compute the accuracy and print it.

```python
# Declare the computation of the mean accuracy over the test set
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

# Actually run the accuracy computation feeding the test data
print(sess.run(accuracy, feed_dict={x: mnist.test.images, y: mnist.test.labels}))
```

    0.922

Good! Our model works (sort of). These performance on MNIST are way too low. The state-of-the-art on this dataset has an accuracy of $0.9979$. Let's try to achieve a better accuracy by refining a bit our model.

## Deep architectures

We are now going to define a deep neural network capable of classifying digits much better than the shallow softmax we used before. We are going to use two convolutional layers alternated with 2 max pool layers, followed by a ReLU layer regularized with dropout, and finally we'll get predictions using again a softmax layer. This is what it a schematic view of the architecture (bottom up).

***

### Convolutional layer

Convolutional Neural Networks are based on a method for extracting meaningfull features from images, exploiting locality and parameter sharing between "close" pixels. In particular, a convolutional layer takes as input a matrix and divides it into smaller "patches". It outputs a number of features for each patch. The resulting tensor will be similar to the one in the following picture.

***

### Max pool layer

The max pool layer is a filter that is used to reduce the number of the input dimensions between two convolutional layers. It is useful for dimensionality reduction, avoids overfitting, etc. The following is a simple example:

***

### ReLU layer

Rectified Linear Unit (ReLU) is a particular activation function of the type: $max(0, \theta)$. ReLUs have become very wide spread thanks because, differently from sigmoids, they do not saturate gradients when used with backpropagation. This has allowed neural networks to become deeper and increased their representational power. The following image compares several activation functions and their gradients.

***

### Dropout

Dropout is a regularization technique for neural networks. When using dropout, we set a probability for dropping random nodes from the networks at each gradient descent step. In this way, the network is going to learn a more robust model, not relying too much on a single node for a prediction. In some sense, using dropout we are "sampling" a lot of different networks and learning them, and finally we are left with the "average" network, which is less susceptible to outliers. Dropouts also helps in avoiding overfitting.

***

Now let's define out deep neural network, train it and then evaluate it, in the same way we did before for the softmax model.

This time we are going to initialize our weight and bias variables in a different way. For convenience, we are going to use the following initialization functions.

```python
# Initializes a weight variable by sampling from a truncated normal distribution with standard deviation of 0.1
def weight_variable(shape):
  initial = tf.truncated_normal(shape, stddev=0.1)
  return tf.Variable(initial)

# Initializes a bias variable with a constant value 0.1
def bias_variable(shape):
  initial = tf.constant(0.1, shape=shape)
  return tf.Variable(initial)
```

Another couple of convenience methods we are going to define are useful for initializing the convolutional and max pool layers. For the convolutional layers we are going to pass the input `x`, which is a 4D tensor of type [batch_size, width, height, channels]. Our images are in gray-scale, so there is only one channel; if we had RGB values the fourth dimension would instead contain three channels. The filter `W` instead contains the parameters that the network needs to learn, whose shape is [filter_height, filter_width, in_channels, out_channels], where filter_height and filter_width are the dimensions of the patches; the in_channels are the input channels, equal to the channels of the input `x`; the output channels are the number of features we want to extract. The strides are the "steps" in each direction between each patch, whereas with the padding parameter we tell the convolutional layer how to deal with the patches at the edges of the image, the `SAME` padding adds dummy values to the edges in order to keep the ouptup dimensions the same as the inputs. Visually:

***

For the max pool layer instead we are going to use a $2 \times 2$ pooling, which halves the dimension of our input `x`. We set the `ksize` to $2 \times 2$ in the dimensions of the height and width of the image. The strides are also going to be $2 \times 2$ in those dimensions in order to avoid overlappings. The `SAME` padding is going to preserve the input dimension as before.

```python
def conv2d(x, W):
  return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME', use_cudnn_on_gpu=False)

def max_pool_2x2(x):
  return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
```

For the first convolutional layer we are going to extract $32$ features out of $5 \times 5$ patches. So we need a filter $W_1$ of shape [5, 5, 1, 32], and a bias $b_1$ of size $32$.

```python
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
```

Now, rememeber that our images `x` are long vectors containing 784 values. We want to reshape these inputs into a $28 \times 28$ matrices. Remember that here we are only defining the graph of computation, so `x` is a placeholder, and this operation is only being defined here.

```python
x_image = tf.reshape(x, [-1, 28, 28, 1])
```

We are now going to convolve the `x_image` input with the weight tensor `W`, add the bias and then pass the result into a ReLU activation function. We then apply the max pool layer on top of that.

```python
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
```

We now put a second convolutional layer on top of the previous one. The previous layer has an output shape of [batch_size, 14, 14, 32], since the convolution extracted 32 features out of each patch, and the max pool halved the width and height of the images. In the second layer we are going to extract 64 features for patch.

```python
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])

h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
```

The following layer will be a densely connected ReLU layer. In order to connect each component, we first need to flatten our input, which is the output of the previous layer. The previous layer has shape [batch_size, 7, 7, 64], has we convolved the patches into 64 features and the second max pool further reduced the width and height of the images to 7. The input of the densely connected layer will therefore have $7 \cdot 7 \cdot 64$ nodes. We will output $1024$ features from this layer.

```python
W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])

h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
```

Let's now apply Dropout. As we said, we need to define the "keep" probability, i.e. the probability each node is retained when applying the dropout. We need a placeholder for that.

```python
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
```

We now define the output layer of the network, which is going to be a fully connected softmax layer with 10 outputs.

```python
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])

y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2
y_hat = tf.nn.softmax(y_conv)
```

We can again define the cross entropy loss function.

```python
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y * tf.log(y_hat), reduction_indices=[1]))
```

This time we are going to use an Adam SGD algorithm with $10^{-4}$ learning rate.

```python
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
```

As before we compute the accuracy for evaluation.

```python
correct_prediction = tf.equal(tf.argmax(y_hat, 1), tf.argmax(y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
```

We are going to train the network for $1000$ epochs, each using a $50$ examples mini-batch. The dropout keep probability is going to be $0.5$. Each $100$ epochs we are also going to evaluate our current model. Notice that in evaluation the `keep_prob` is equal to `1.0` as we want to use the full network for the prediction.

```python
sess.run(tf.global_variables_initializer())
for i in range(1000):
    batch = mnist.train.next_batch(50)
    if i % 100 == 0:
        train_accuracy = accuracy.eval(feed_dict={x: batch[0], y: batch[1], keep_prob: 1.0})
        print('step {}, training accuracy {}'.format(i, train_accuracy))
    train_step.run(feed_dict={x: batch[0], y: batch[1], keep_prob: 0.5})

# Dividing evaluation in batches for memory constraints
import numpy as np
accuracy_values = []
for j in range(100):
    batch = mnist.test.next_batch(100)
    accuracy_values.append(sess.run(accuracy, feed_dict={x: batch[0], y: batch[1], keep_prob: 1.0}))

print('test accuracy {}'.format(np.mean(accuracy_values)))
```

    step 0, training accuracy 0.10000000149011612
    step 100, training accuracy 0.8399999737739563
    step 200, training accuracy 0.8799999952316284
    step 300, training accuracy 0.8999999761581421
    step 400, training accuracy 0.8999999761581421
    step 500, training accuracy 0.9800000190734863
    step 600, training accuracy 0.8799999952316284
    step 700, training accuracy 0.8999999761581421
    step 800, training accuracy 0.9200000166893005
    step 900, training accuracy 0.9800000190734863
    test accuracy 0.9648001790046692

```python

```


```python
%load_ext autoreload
%autoreload 2
```

```python
import numpy as np
import matplotlib.pyplot as plt
```

```python
from read_data import MatrixElementParser
```

```python
matr_parse= MatrixElementParser()
matr = matr_parse.matrix
matr
matr_parse[1,2,3,4]
```

```python
import sympy as sp
import pandas as pd
Z = 1
Z_symb = sp.Symbol('Z')
m = matr_parse.sympy_data.applymap(lambda s:s.evalf(subs = {Z_symb:Z}))
col1, col2 = m.columns.str
col1, col2 = col1.astype('int'), col2.astype('int')
row1, row2 = m.index.str
row1, row2 = row1.astype('int'), row2.astype('int')

row_ind = pd.MultiIndex.from_arrays([row1,row2])
col_ind = pd.MultiIndex.from_arrays([col1,col2])
m
```

```python
m.index = row_ind
m.columns = col_ind
print(m.loc[(1,1),(1,3)])
m
```

```python
ind = np.arange(4).reshape(2,2)
ind_AS = ind.copy()
ind_AS[1] = ind[1,::-1]
print(ind//2 + 1)
print(ind_AS)
```

```python
spat_ind = (ind//2 + 1)
1 * (1 and 2)
```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```


```python
%matplotlib inline

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
import lmfit
import sklearn
from sklearn.metrics import mean_squared_error
from sklearn.metrics import r2_score
import sympy

import matplotlib.lines as mlines

colorDic = {"blue" : "#6599FF", "yellow" : "#FFAD33",  "purple": "#683b96", "green" : "#198D6D", "red" : "#FF523F"}
colors = list(colorDic.values())

raycasts = ["IFRC", "FRC","HRC", "EFRC"]
OUTPUT_PATH = "./out/"
```

```python
'''
Find intersection with infinite cylinder with center in the origin
to do that, translate the ray origin so that the center of the bottom base
is at the origin, then calculate intersection with the canonical infinite cylinder
and check if the ray intersects the lateral surface of the cylinder within our
bases, if not check if it's intersecting the bases and if not, it's not intersecting
our actual cylinder
'''
def calcCylinderIntersectValue (centerBottomBase, radius, rayOrigin, rayDirection):
    # translate the ray origin
    # Point
    p0 = np.array([rayOrigin[0]-centerBottomBase[0], rayOrigin[1]-centerBottomBase[1], rayOrigin[2]-centerBottomBase[2]])
    # coefficients for the intersection equation
    # got them mathematically intersecting the line equation with the cylinder equation
    a = rayDirection[0] * rayDirection[0] + rayDirection[2] * rayDirection[2]
    b = rayDirection[0]*p0[0] +rayDirection[2]*p0[2]
    c = p0[0]*p0[0]+p0[2]*p0[2]-radius*radius
    delta = b*b - a*c

    #use epsilon because of computation errors between doubles
    epsilon = 0.00000001

    # delta < 0 means no intersections
    if (delta < epsilon):
        return (False, np.inf, np.inf)

    # nearest intersection
    t1 = (-b - np.sqrt (delta))/a
    t2 = (-b + np.sqrt (delta))/a
    return (True, t1, t2)

def calcCylinderIntersect (centerBottomBase, radius, rayOrigin, rayDirection):
    b, t1, t2 = calcCylinderIntersectValue(centerBottomBase, radius, rayOrigin, rayDirection)
    if (b):
        i1, i2 = (rayOrigin + rayDirection* t1, rayOrigin + rayDirection* t2)
        d1 = np.sqrt(np.sum(np.square(rayDirection- i1)))
        d2 = np.sqrt(np.sum(np.square(rayDirection- i2)))
        if (d1 < d2):
            return i1
        else:
            return i2
    else:
        return np.inf

def rotate3DAroundOrigin(point, pitch, yaw):
    x = point[:,0]
    y = point[:,1]
    z = point[:,2]
    r = np.sqrt(x*x + y*y + z*z)
    phi = np.arctan2(x,z)+np.deg2rad(yaw)
    theta = np.arccos(y/r)+np.deg2rad(pitch)
    x = r * np.sin( theta ) * np.sin( phi )
    y = r * np.cos( theta )
    z = r * np.sin( theta ) * np.cos( phi )

    return np.array([x,y,z]).T

def getOrientaion(points):
    x = points[:,0]
    y = points[:,1]
    z = points[:,2]
    r = np.sqrt(x*x + y*y + z*z)
    phi = np.arctan2(x,z)
    theta = np.arccos(y/r)
    return np.array([np.rad2deg(theta), np.rad2deg(phi)]).T

    #yaw = np.arctan2(points[:,0], points[:,2])
    #pitch = np.arctan2(np.sqrt((points[:,0] * points[:,0]) + (points[:,1] * points[:,1])), points[:,2]);
    #return np.array([np.rad2deg(pitch), np.rad2deg(yaw)]).T

points = np.array([[1,1,0], [1,-1,0], [1,99,0], [1,-99,0],[1,1,1], [5, 5,5], [-5, -5,-5]])
getOrientaion(points)
```

    array([[  45.        ,   90.        ],
           [ 135.        ,   90.        ],
           [   0.57872557,   90.        ],
           [ 179.42127443,   90.        ],
           [  54.73561032,   45.        ],
           [  54.73561032,   45.        ],
           [ 125.26438968, -135.        ]])

```python
dfPointer = pd.read_pickle(OUTPUT_PATH + "data_v03_PointerOnly.pkl")
len(dfPointer)
```

    3360

```python
from mpmath import mp
def funSin2(params, pitch, yaw):

    ampPitch = params['ampPitch']
    pshiftPitch = params['phasePitch']
    freqPitch = params['frequencyPitch']
    offsetPitch = params['offsetPitch']

    ampYaw = params['ampYaw']
    pshiftYaw = params['phaseYaw']
    freqYaw = params['frequencyYaw']
    offsetYaw = params['offsetYaw']
    return ((ampYaw * np.sin(freqYaw.value * (yaw - pshiftYaw.value)) + offsetYaw.value)+1) * ((ampPitch * np.sin(freqPitch.value * (pitch - pshiftPitch.value)) + offsetPitch.value)+1)

def residualSin2(params, pitch, yaw, error):
    global iterCount
    val = funSin2(params, pitch, yaw)
    rmse = np.sqrt(sklearn.metrics.mean_squared_error(error,   val))
    #print ("Iter: %i: %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, Error: %.9f" % (iterCount, params['ampYaw'], params['phaseYaw'], params['frequencyYaw'], params['offsetYaw'],                                                                                params['ampPitch'], params['phasePitch'], params['frequencyPitch'], params['offsetPitch'], rmse))
    iterCount = iterCount+1
    return val-error

def predictSin2 (pitch, yaw, error):
    global iterCount
    iterCount = 0
    params = lmfit.Parameters()
    params.add('ampPitch', value=0)#, min=-2*np.pi/180, max = 2*np.pi/180)
    params.add('phasePitch', value=0)#,  min=0, max = 2*np.pi/180*2)
    params.add('frequencyPitch', value=1, min=0.5, max =  4*np.pi)
    params.add('offsetPitch', value=np.mean(pitch))

    params.add('ampYaw', value=0)#, min=-2*np.pi/180, max = 2*np.pi/180)
    params.add('phaseYaw', value=0)#,  min=0, max = 2*np.pi/180*2)
    params.add('frequencyYaw', value=1, vary=False)
    params.add('offsetYaw', value=np.mean(yaw))

    minfit = lmfit.Minimizer(residualSin2, params, fcn_args=(pitch, yaw, error), reduce_fcn='neglogcauchy')
    out1 = minfit.minimize(method='Nelder')
    return minfit.minimize(method='leastsq', params=out1.params)

```

```python
%%time
outPitch = predictSin2(np.deg2rad(dfPointer.IFRCPitch), np.deg2rad(dfPointer.IFRCYaw), dfPointer.IFRCPitchError)
outYaw   = predictSin2(np.deg2rad(dfPointer.IFRCPitch), np.deg2rad(dfPointer.IFRCYaw), dfPointer.IFRCYawError)
predPitch = funSin2(outPitch.params,  np.deg2rad(dfPointer.IFRCPitch), np.deg2rad(dfPointer.IFRCYaw))
predYaw   = funSin2(outYaw.params, np.deg2rad(dfPointer.IFRCPitch), np.deg2rad(dfPointer.IFRCYaw))
rsquarePitch = sklearn.metrics.r2_score(dfPointer.IFRCPitchError, predPitch)
rsquareYaw   = sklearn.metrics.r2_score(dfPointer.IFRCYawError,   predYaw)
print('   R2   Pitch: ' + str(rsquarePitch))
print('   R2   Yaw  : ' + str(rsquareYaw))
rmsePitch = np.sqrt(sklearn.metrics.mean_squared_error(dfPointer.IFRCPitchError, predPitch))
rmseYaw   = np.sqrt(sklearn.metrics.mean_squared_error(dfPointer.IFRCYawError,   predYaw))

print('   RMSE  Pitch: ' + str(rmsePitch))
print('   Error Pitch: ' + str((dfPointer.IFRCPitchError-predYaw).mean()) + '  SD: ' + str((dfPointer.IFRCPitchError-predYaw).std()))
print('   RMSE  Yaw  : ' + str(rmseYaw))
print('   Error Yaw  : ' + str((dfPointer.IFRCYawError-predYaw).mean()) + '  SD: ' + str((dfPointer.IFRCYawError-predYaw).std()))

print('   Reduction Pitch: %.2f' % ((1-((dfPointer.IFRCPitchError-predPitch).abs().mean()/dfPointer.IFRCPitchError.abs().mean()))*100))
print('   Reduction Yaw:   %.2f' % ((1-((dfPointer.IFRCYawError-predYaw).abs().mean()/dfPointer.IFRCYawError.abs().mean()))*100))
```

       R2   Pitch: 0.21131660820522535
       R2   Yaw  : 0.02340097218646009
       RMSE  Pitch: 7.419106789712207
       Error Pitch: -6.136617314607138  SD: 8.18379796099067
       RMSE  Yaw  : 10.717116712950174
       Error Yaw  : -0.001407825480383908  SD: 10.718711785575927
       Reduction Pitch: 63.24
       Reduction Yaw:   43.15
    CPU times: user 47.5 s, sys: 9.28 ms, total: 47.5 s
    Wall time: 47.5 s

```python
print(outPitch.params)
print(outYaw.params)

pitch = np.linspace(60,180,181)
yaw = np.linspace(0,360,361)

# filling the heatmap, value by value
fun_map = np.empty((pitch.size, yaw.size))
for y in range (yaw.size):
    for p in range (pitch.size):
        fun_map[p,y] = funSin2(outPitch.params, np.deg2rad(pitch[p]), np.deg2rad(yaw[y]))

f, (ax1, ax2) = plt.subplots(2, sharex=True)
ax1.set_title('Pitch correction function')
im = ax1.imshow(fun_map,extent=(yaw[0], yaw[-1], pitch[0], pitch[-1]))
f.colorbar(im, ax=ax1)

# filling the heatmap, value by value
fun_map = np.empty((pitch.size, yaw.size))
for y in range (yaw.size):
    for p in range (pitch.size):
        fun_map[p,y] = funSin2(outYaw.params, np.deg2rad(pitch[p]), np.deg2rad(yaw[y]))
#s = fig.add_subplot(1, 1, 1, xlabel='$Yaw$', ylabel='$Pitch$')
ax2.set_title('Yaw correction function')
im = ax2.imshow(fun_map,extent=(yaw[0], yaw[-1], pitch[0], pitch[-1]))
f.colorbar(im, ax=ax2)
plt.show()
plt.show()
```

```python
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

# Grab some test data.
X, Y = np.meshgrid(pitch,yaw)
Z = funSin2(outPitch.params, np.deg2rad(X), np.deg2rad(Y))

# Plot a basic wireframe.
ax.plot_wireframe(X, Y, Z, rstride=10, cstride=10)

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

# Grab some test data.
X, Y = np.meshgrid(pitch,yaw)
Z = funSin2(outYaw.params, np.deg2rad(X), np.deg2rad(Y))

# Plot a basic wireframe.
ax.plot_wireframe(X, Y, Z, rstride=10, cstride=10)
```

```python
outPitch = predictSin2(np.deg2rad(dfPointer.FRCPitch), np.deg2rad(dfPointer.FRCYaw), dfPointer.FRCPitchError)
outYaw   = predictSin2(np.deg2rad(dfPointer.FRCPitch), np.deg2rad(dfPointer.FRCYaw), dfPointer.FRCYawError)
predPitch = funSin2(outPitch.params,  np.deg2rad(dfPointer.FRCPitch), np.deg2rad(dfPointer.FRCYaw))
predYaw   = funSin2(outYaw.params, np.deg2rad(dfPointer.FRCPitch), np.deg2rad(dfPointer.FRCYaw))
rsquarePitch = sklearn.metrics.r2_score(dfPointer.FRCPitchError, predPitch)
rsquareYaw   = sklearn.metrics.r2_score(dfPointer.FRCYawError,   predYaw)
print('   R2   Pitch: ' + str(rsquarePitch))
print('   R2   Yaw  : ' + str(rsquareYaw))
rmsePitch = np.sqrt(sklearn.metrics.mean_squared_error(dfPointer.FRCPitchError, predPitch))
rmseYaw   = np.sqrt(sklearn.metrics.mean_squared_error(dfPointer.FRCYawError,   predYaw))

print('   RMSE  Pitch: ' + str(rmsePitch))
print('   Error Pitch: ' + str((dfPointer.FRCPitchError-predYaw).mean()) + '  SD: ' + str((dfPointer.FRCPitchError-predYaw).std()))
print('   RMSE  Yaw  : ' + str(rmseYaw))
print('   Error Yaw  : ' + str((dfPointer.FRCYawError-predYaw).mean()) + '  SD: ' + str((dfPointer.FRCYawError-predYaw).std()))

print('   Reduction Pitch: %.2f' % ((1-((dfPointer.FRCPitchError-predPitch).abs().mean()/dfPointer.FRCPitchError.abs().mean()))*100))
print('   Reduction Yaw:   %.2f' % ((1-((dfPointer.FRCYawError-predYaw).abs().mean()/dfPointer.FRCYawError.abs().mean()))*100))
```

       R2   Pitch: 0.09598847902043628
       R2   Yaw  : 0.030755529966377093
       RMSE  Pitch: 9.144622380620747
       Error Pitch: 15.50643174123581  SD: 9.983571998954496
       RMSE  Yaw  : 14.165197229898686
       Error Yaw  : 0.0006178655505133672  SD: 14.167305603309673
       Reduction Pitch: 52.84
       Reduction Yaw:   67.12

```python
outPitch = predictSin2(np.deg2rad(dfPointer.HRCPitch), np.deg2rad(dfPointer.HRCYaw), dfPointer.HRCPitchError)
outYaw   = predictSin2(np.deg2rad(dfPointer.HRCPitch), np.deg2rad(dfPointer.HRCYaw), dfPointer.HRCYawError)
predPitch = funSin2(outPitch.params,  np.deg2rad(dfPointer.HRCPitch), np.deg2rad(dfPointer.HRCYaw))
predYaw   = funSin2(outYaw.params, np.deg2rad(dfPointer.HRCPitch), np.deg2rad(dfPointer.HRCYaw))
rsquarePitch = sklearn.metrics.r2_score(dfPointer.HRCPitchError, predPitch)
rsquareYaw   = sklearn.metrics.r2_score(dfPointer.HRCYawError,   predYaw)
print('   R2   Pitch: ' + str(rsquarePitch))
print('   R2   Yaw  : ' + str(rsquareYaw))
rmsePitch = np.sqrt(sklearn.metrics.mean_squared_error(dfPointer.HRCPitchError, predPitch))
rmseYaw   = np.sqrt(sklearn.metrics.mean_squared_error(dfPointer.HRCYawError,   predYaw))

print('   RMSE  Pitch: ' + str(rmsePitch))
print('   Error Pitch: ' + str((dfPointer.HRCPitchError-predYaw).mean()) + '  SD: ' + str((dfPointer.HRCPitchError-predYaw).std()))
print('   RMSE  Yaw  : ' + str(rmseYaw))
print('   Error Yaw  : ' + str((dfPointer.HRCYawError-predYaw).mean()) + '  SD: ' + str((dfPointer.HRCYawError-predYaw).std()))

print('   Reduction Pitch: %.2f' % ((1-((dfPointer.HRCPitchError-predPitch).abs().mean()/dfPointer.HRCPitchError.abs().mean()))*100))
print('   Reduction Yaw:   %.2f' % ((1-((dfPointer.HRCYawError-predYaw).abs().mean()/dfPointer.HRCYawError.abs().mean()))*100))
```

       R2   Pitch: 0.046388909718275584
       R2   Yaw  : 0.0033343181715839165
       RMSE  Pitch: 10.029862635759786
       Error Pitch: 7.953671125254687  SD: 10.29177855882365
       RMSE  Yaw  : 10.96240811830408
       Error Yaw  : 0.0014271123949042488  SD: 10.964039700351892
       Reduction Pitch: 6.50
       Reduction Yaw:   18.25

```python
outPitch = predictSin2(np.deg2rad(dfPointer.EFRCPitch), np.deg2rad(dfPointer.EFRCYaw), dfPointer.EFRCPitchError)
outYaw   = predictSin2(np.deg2rad(dfPointer.EFRCPitch), np.deg2rad(dfPointer.EFRCYaw), dfPointer.EFRCYawError)
predPitch = funSin2(outPitch.params,  np.deg2rad(dfPointer.EFRCPitch), np.deg2rad(dfPointer.EFRCYaw))
predYaw   = funSin2(outYaw.params, np.deg2rad(dfPointer.EFRCPitch), np.deg2rad(dfPointer.EFRCYaw))
rsquarePitch = sklearn.metrics.r2_score(dfPointer.EFRCPitchError, predPitch)
rsquareYaw   = sklearn.metrics.r2_score(dfPointer.EFRCYawError,   predYaw)
print('   R2   Pitch: ' + str(rsquarePitch))
print('   R2   Yaw  : ' + str(rsquareYaw))
rmsePitch = np.sqrt(sklearn.metrics.mean_squared_error(dfPointer.EFRCPitchError, predPitch))
rmseYaw   = np.sqrt(sklearn.metrics.mean_squared_error(dfPointer.EFRCYawError,   predYaw))

print('   RMSE  Pitch: ' + str(rmsePitch))
print('   Error Pitch: ' + str((dfPointer.EFRCPitchError-predYaw).mean()) + '  SD: ' + str((dfPointer.EFRCPitchError-predYaw).std()))
print('   RMSE  Yaw  : ' + str(rmseYaw))
print('   Error Yaw  : ' + str((dfPointer.EFRCYawError-predYaw).mean()) + '  SD: ' + str((dfPointer.EFRCYawError-predYaw).std()))

print('   Reduction Pitch: %.2f' % ((1-((dfPointer.EFRCPitchError-predPitch).abs().mean()/dfPointer.EFRCPitchError.abs().mean()))*100))
print('   Reduction Yaw:   %.2f' % ((1-((dfPointer.EFRCYawError-predYaw).abs().mean()/dfPointer.EFRCYawError.abs().mean()))*100))
```

       R2   Pitch: 0.0010969954308157615
       R2   Yaw  : 0.017902712536573828
       RMSE  Pitch: 2.9801856080960962
       Error Pitch: -0.314207705690925  SD: 2.666925294709399
       RMSE  Yaw  : 7.862984488897352
       Error Yaw  : 0.07944886286137996  SD: 7.86375338507113
       Reduction Pitch: 2.61
       Reduction Yaw:   12.73

```python
outPitch = predictSin2(np.deg2rad(dfPointer.EFRCPitch), np.deg2rad(dfPointer.EFRCYaw), dfPointer.EFRCPitchError)
outYaw   = predictSin2(np.deg2rad(dfPointer.EFRCPitch), np.deg2rad(dfPointer.EFRCYaw), dfPointer.EFRCYawError)
predPitch = funSin2(outPitch.params,  np.deg2rad(dfPointer.EFRCPitch), np.deg2rad(dfPointer.EFRCYaw))
predYaw   = funSin2(outYaw.params, np.deg2rad(dfPointer.EFRCPitch), np.deg2rad(dfPointer.EFRCYaw))
rsquarePitch = sklearn.metrics.r2_score(dfPointer.EFRCPitchError, predPitch)
rsquareYaw   = sklearn.metrics.r2_score(dfPointer.EFRCYawError,   predYaw)
print('   R2   Pitch: ' + str(rsquarePitch))
print('   R2   Yaw  : ' + str(rsquareYaw))
rmsePitch = np.sqrt(sklearn.metrics.mean_squared_error(dfPointer.EFRCPitchError, predPitch))
rmseYaw   = np.sqrt(sklearn.metrics.mean_squared_error(dfPointer.EFRCYawError,   predYaw))

print('   RMSE  Pitch: ' + str(rmsePitch))
print('   Error Pitch: ' + str((dfPointer.EFRCPitchError-predYaw).mean()) + '  SD: ' + str((dfPointer.EFRCPitchError-predYaw).std()))
print('   RMSE  Yaw  : ' + str(rmseYaw))
print('   Error Yaw  : ' + str((dfPointer.EFRCYawError-predYaw).mean()) + '  SD: ' + str((dfPointer.EFRCYawError-predYaw).std()))

print('   Reduction Pitch: %.2f' % ((1-((dfPointer.EFRCPitchError-predPitch).abs().mean()/dfPointer.EFRCPitchError.abs().mean()))*100))
print('   Reduction Yaw:   %.2f' % ((1-((dfPointer.EFRCYawError-predYaw).abs().mean()/dfPointer.EFRCYawError.abs().mean()))*100))
```

       R2   Pitch: 0.002809217577975809
       R2   Yaw  : 0.016197803204310968
       RMSE  Pitch: 2.9776303408137177
       Error Pitch: -0.3166162639169273  SD: 2.7052007458152616
       RMSE  Yaw  : 7.869806553689896
       Error Yaw  : 0.0770403046353776  SD: 7.870600764843636
       Reduction Pitch: 3.56
       Reduction Yaw:   12.75

```python
print(outPitch.params)
print(outYaw.params)

pitch = np.linspace(60,180,181)
yaw = np.linspace(0,360,361)

# filling the heatmap, value by value
fun_map = np.empty((pitch.size, yaw.size))
for y in range (yaw.size):
    for p in range (pitch.size):
        fun_map[p,y] = funSin2(outPitch.params, np.deg2rad(pitch[p]), np.deg2rad(yaw[y]))

f, (ax1, ax2) = plt.subplots(2, sharex=True)
ax1.set_title('Pitch correction function')
im = ax1.imshow(fun_map,extent=(yaw[0], yaw[-1], pitch[0], pitch[-1]))
f.colorbar(im, ax=ax1)

# filling the heatmap, value by value
fun_map = np.empty((pitch.size, yaw.size))
for y in range (yaw.size):
    for p in range (pitch.size):
        fun_map[p,y] = funSin2(outYaw.params, np.deg2rad(pitch[p]), np.deg2rad(yaw[y]))
#s = fig.add_subplot(1, 1, 1, xlabel='$Yaw$', ylabel='$Pitch$')
ax2.set_title('Yaw correction function')
im = ax2.imshow(fun_map,extent=(yaw[0], yaw[-1], pitch[0], pitch[-1]))
f.colorbar(im, ax=ax2)
plt.show()
plt.show()
```

```python
print(outPitch.params)
print(outYaw.params)

pitch = np.linspace(60,180,181)
yaw = np.linspace(0,360,361)

# filling the heatmap, value by value
fun_map = np.empty((pitch.size, yaw.size))
for y in range (yaw.size):
    for p in range (pitch.size):
        fun_map[p,y] = funSin2(outPitch.params, np.deg2rad(pitch[p]), np.deg2rad(yaw[y]))

f, (ax1, ax2) = plt.subplots(2, sharex=True)
ax1.set_title('Pitch correction function')
im = ax1.imshow(fun_map,extent=(yaw[0], yaw[-1], pitch[0], pitch[-1]))
f.colorbar(im, ax=ax1)

# filling the heatmap, value by value
fun_map = np.empty((pitch.size, yaw.size))
for y in range (yaw.size):
    for p in range (pitch.size):
        fun_map[p,y] = funSin2(outYaw.params, np.deg2rad(pitch[p]), np.deg2rad(yaw[y]))
#s = fig.add_subplot(1, 1, 1, xlabel='$Yaw$', ylabel='$Pitch$')
ax2.set_title('Yaw correction function')
im = ax2.imshow(fun_map,extent=(yaw[0], yaw[-1], pitch[0], pitch[-1]))
f.colorbar(im, ax=ax2)
plt.show()
plt.show()
```

```python
list(outYaw.params.values())
```

    [<Parameter 'ampPitch', value=-1.1673115694525 +/- nan, bounds=[-inf:inf]>,
     <Parameter 'phasePitch', value=-17.651898597906168 +/- 2.65e+03, bounds=[-inf:inf]>,
     <Parameter 'frequencyPitch', value=0.08155604659875652 +/- 11.3, bounds=[0:6.283185307179586]>,
     <Parameter 'offsetPitch', value=0.16741989589634218 +/- nan, bounds=[-inf:inf]>,
     <Parameter 'ampYaw', value=-1710.8301816583385 +/- nan, bounds=[-inf:inf]>,
     <Parameter 'phaseYaw', value=-69.98432183581143 +/- 0.235, bounds=[-inf:inf]>,
     <Parameter 'frequencyYaw', value=1 (fixed), bounds=[-inf:inf]>,
     <Parameter 'offsetYaw', value=2920.066204937139 +/- nan, bounds=[-inf:inf]>]

```python

pitch = sympy.Symbol('pitch')
yaw = sympy.Symbol('yaw')

ampPitch = sympy.Symbol('ampPitch')
freqPitch = sympy.Symbol('freqPitch')
pshiftPitch = sympy.Symbol('pshiftPitch')
offsetPitch = sympy.Symbol('offsetPitch')
ampYaw = sympy.Symbol('ampYaw')
freqYaw = sympy.Symbol('freqYaw')
pshiftYaw = sympy.Symbol('pshiftYaw')
offsetYaw = sympy.Symbol('offsetYaw')

z = ((ampYaw * sympy.sin(freqYaw * (yaw - pshiftYaw)) + offsetYaw)+1) * ((ampPitch * sympy.sin(freqPitch * (pitch - pshiftPitch)) + offsetPitch)+1)
z = sympy.expand(z)
z
```

$\displaystyle - ampPitch ampYaw \sin{\left(freqPitch pitch - freqPitch pshiftPitch \right)} \sin{\left(freqYaw pshiftYaw - freqYaw yaw \right)} + ampPitch offsetYaw \sin{\left(freqPitch pitch - freqPitch pshiftPitch \right)} + ampPitch \sin{\left(freqPitch pitch - freqPitch pshiftPitch \right)} - ampYaw offsetPitch \sin{\left(freqYaw pshiftYaw - freqYaw yaw \right)} - ampYaw \sin{\left(freqYaw pshiftYaw - freqYaw yaw \right)} + offsetPitch offsetYaw + offsetPitch + offsetYaw + 1$