# **Monitoring and Optimizing Quantum Circuits**

```python
import numpy as np

# Importing standard Qiskit libraries
from qiskit import QuantumCircuit, transpile, Aer, IBMQ, execute
from qiskit.tools.jupyter import *
from qiskit.visualization import *
from ibm_quantum_widgets import *
from qiskit.providers.aer import QasmSimulator

# Loading your IBM Quantum account(s)
provider = IBMQ.load_account()
```

## **Monitoring and Tracking Jobs**

```python
# Import the Qiskit Jupyter tools
from qiskit.tools import jupyter
```

```python
# Initialize the job tracker to automatically track all jobs
%qiskit_job_watcher
```

    Accordion(children=(VBox(layout=Layout(max_width='710px', min_width='710px')),), layout=Layout(max_height='500…

    <IPython.core.display.Javascript object>

```python
# Let's run a simple circuit on the least busy quantum device
# and check the job watcher widget.
from qiskit.providers.ibmq import least_busy

backend = least_busy(provider.backends(filters=lambda x: x.configuration().n_qubits >= (2) and
                                       not x.configuration().simulator
                                       and x.status().operational==True))

#Create a simple circuit
qc = QuantumCircuit(1)
qc.h(0)
qc.measure_all()
#Execute the circuit on the backend
job = execute(qc, backend)
```

```python
#Disable the job watcher
%qiskit_disable_job_watcher
```

```python
#Display the list of all available backends and provide
#a brief overview of each
%qiskit_backend_overview
```

    VBox(children=(HTML(value="<h2 style ='color:#ffffff; background-color:#000000;padding-top: 1%; padding-bottom…

## **Transpiling a Circuit**

```python
# Import the transpiler passes object
from qiskit.transpiler import passes
# List out all the passes available
print(dir(passes))
```

    ['ALAPSchedule', 'ASAPSchedule', 'AlignMeasures', 'ApplyLayout', 'BIPMapping', 'BarrierBeforeFinalMeasurements', 'BasicSwap', 'BasisTranslator', 'CSPLayout', 'CXCancellation', 'CXDirection', 'CheckCXDirection', 'CheckGateDirection', 'CheckMap', 'Collect1qRuns', 'Collect2qBlocks', 'CollectMultiQBlocks', 'CommutationAnalysis', 'CommutativeCancellation', 'ConsolidateBlocks', 'ContainsInstruction', 'CountOps', 'CountOpsLongestPath', 'CrosstalkAdaptiveSchedule', 'DAGFixedPoint', 'DAGLongestPath', 'Decompose', 'DenseLayout', 'Depth', 'DynamicalDecoupling', 'EchoRZXWeylDecomposition', 'EnlargeWithAncilla', 'Error', 'FixedPoint', 'FullAncillaAllocation', 'GateDirection', 'GatesInBasis', 'HoareOptimizer', 'InverseCancellation', 'Layout2qDistance', 'LayoutTransformation', 'LookaheadSwap', 'MergeAdjacentBarriers', 'NoiseAdaptiveLayout', 'NumTensorFactors', 'Optimize1qGates', 'Optimize1qGatesDecomposition', 'Optimize1qGatesSimpleCommutation', 'OptimizeSwapBeforeMeasure', 'PulseGates', 'RZXCalibrationBuilder', 'RZXCalibrationBuilderNoEcho', 'RemoveBarriers', 'RemoveDiagonalGatesBeforeMeasure', 'RemoveFinalMeasurements', 'RemoveResetInZeroState', 'ResourceEstimation', 'SabreLayout', 'SabreSwap', 'SetLayout', 'Size', 'StochasticSwap', 'TemplateOptimization', 'TimeUnitConversion', 'TrivialLayout', 'UnitarySynthesis', 'Unroll3qOrMore', 'UnrollCustomDefinitions', 'Unroller', 'VF2Layout', 'ValidatePulseGates', 'Width', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'analysis', 'basis', 'calibration', 'layout', 'optimization', 'routing', 'scheduling', 'synthesis', 'unitary_synthesis_plugin_names', 'utils']

```python
#Basic Toffoli gate,
qc = QuantumCircuit(3)
qc.ccx(0,1,2)
qc.draw()
```

```python
qc_decomposed = qc.decompose()
qc_decomposed.draw()
```

```python
#Basic circuit with a single and multi-qubit gates
qc = QuantumCircuit(4)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)
qc.cx(0,3)
qc.draw()
```

```python
#Print the depth of both inital and decomposed circuit
print('Initial circuit depth: ', qc.depth())
print('Decomposed circuit depth: ', qc_decomposed.depth())
#Get the number of operators in initial circuit
print('Initial circuit operation count: ', qc.count_ops())
#Get the number of operators in decomposed circuit
print('Decomposed circuit operation count: ', qc_decomposed.count_ops())
```

    Initial circuit depth:  4
    Decomposed circuit depth:  11
    Initial circuit operation count:  OrderedDict([('cx', 3), ('h', 1)])
    Decomposed circuit operation count:  OrderedDict([('cx', 6), ('t', 4), ('tdg', 3), ('h', 2)])

## **Configuration and Optimization**

```python
# Get the backend device: ibmq_santiago
backend_santiago = provider.get_backend('ibmq_santiago')
# Launch backend viewer of ibmq_santiago
backend_santiago
```

    VBox(children=(HTML(value="<h1 style='color:#ffffff;background-color:#000000;padding-top: 1%;padding-bottom: 1…

    <IBMQBackend('ibmq_santiago') from IBMQ(hub='ibm-q', group='open', project='main')>

```python
# Get the backend device: ibmq_lima
backend_lima = provider.get_backend('ibmq_lima')
# Launch backend viewer of ibmq_lima
backend_lima
```

    VBox(children=(HTML(value="<h1 style='color:#ffffff;background-color:#000000;padding-top: 1%;padding-bottom: 1…

    <IBMQBackend('ibmq_lima') from IBMQ(hub='ibm-q', group='open', project='main')>

```python
# Visualize the coupling directional map between the qubits
plot_gate_map(backend_santiago, plot_directed=True)
```

```python
# Visualize the coupling directional map between the qubits
plot_gate_map(backend_lima, plot_directed=True)
```

```python
# Quantum circuit with a single and multi-qubit gates
qc = QuantumCircuit(4)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)
qc.cx(0,3)
qc.draw()
```

```python
# Transpile the circuit with an optimization level = 0
qc_santiago_0 = transpile(qc, backend_santiago,
seed_transpiler=10258, optimization_level=0)
# Print out the depth of the circuit
print('Depth:', qc_santiago_0.depth())
# Plot the resulting layout of the quantum circuit after Layout
plot_circuit_layout(qc_santiago_0, backend_santiago)
```

```python
# Draw the transpiled circuit pertaining to Santiago
qc_santiago_0.draw()
```

```python
# View the transpiled circuit with an optimization level = 0
qc_lima_0 = transpile(qc, backend_lima, seed_transpiler=10258, optimization_level=0)
print('Depth:', qc_lima_0.depth())
plot_circuit_layout(qc_lima_0, backend_lima)
```

```python
# Draw the transpiled circuit pertaining to Lima
qc_lima_0.draw()
```

```python
# Transpile the circuit with the optimization level = 3
qc_transpiled_santiago = transpile(qc, backend_santiago, optimization_level=3)
# Print the depth of the transpiled circuit
print('Depth:', qc_transpiled_santiago.depth())
# Print the number of operations of the transpiled circuit
print('Ops count: ', qc_transpiled_santiago.count_ops())
# Plot the layout mapping of the transpiled circuit
plot_circuit_layout(qc_transpiled_santiago, backend_santiago)
```

```python
# Redraw the transpiled circuit at new level
qc_transpiled_santiago.draw()
```

```python
# Transpile the quantum circuit with the optimization level = 3
qc_transpiled_lima = transpile(qc, backend_lima, optimization_level=3)
# Get the depth and operation count of the transpiled circuit.
print('Depth:', qc_transpiled_lima.depth())
print('Ops count: ', qc_transpiled_lima.count_ops())
# Print the circuit layout
plot_circuit_layout(qc_transpiled_lima, backend_lima)
```

```python
# View the ibmq_quito backend device configuration and properties
backend = provider.get_backend('ibmq_quito')
backend
```

    VBox(children=(HTML(value="<h1 style='color:#ffffff;background-color:#000000;padding-top: 1%;padding-bottom: 1…

    <IBMQBackend('ibmq_quito') from IBMQ(hub='ibm-q', group='open', project='main')>

```python
# View the backend coupling map, displayed as CNOTs (Control-Target)
backend = provider.get_backend('ibmq_quito')
# Extract the coupling map from the backend
ibmqquito_coupling_map = backend.configuration().coupling_map
# List out the extracted coupling map
ibmqquito_coupling_map
```

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

```python
# Transpile a custom circuit using only the coupling map.
# Set the backend to ‘None’ so it will force using the coupling map provided.
qc_custom = transpile(qc, backend=None,
coupling_map=ibmqquito_coupling_map)
# Draw the resulting custom topology circuit.
qc_custom.draw()
```

```python
# Create our own coupling map (custom topology)
custom_linear_topology = [[0,1],[1,2],[2,3],[3,4]]
# Set the coupling map to our custom linear topology
qc_custom = transpile(qc, backend=None, coupling_map=custom_linear_topology)
# Draw the resulting circuit.
qc_custom.draw()
```

```python
# Import the PassManager and a few Passes
from qiskit.transpiler import PassManager, CouplingMap
from qiskit.transpiler.passes import TrivialLayout, BasicSwap
# Create a TrivialLayout based on the ibmqx2 coupling map
trivial = TrivialLayout(CouplingMap(ibmqquito_coupling_map))

pm = PassManager()
# Append the TrivialLayout to the PassManager
pm.append(trivial)
# Run the PassManager and draw the resulting circuit
tv_qc = pm.run(qc)
tv_qc.draw()
```

```python
# Create a BasicSwap based on the ibmq_quito coupling map we used earlier
basic_swap = BasicSwap(CouplingMap(ibmqquito_coupling_map))
#Add the BasicSwap to the PassManager
pm = PassManager(basic_swap)
# Run the PassManager and draw the results
new_qc = pm.run(qc)
new_qc.draw()
```

```python
# Sample quantum circuit
qc = QuantumCircuit(4)
qc.h(0)
qc.cx(0,1)
qc.barrier()
qc.cx(0,2)
qc.cx(0,3)
qc.barrier()
qc.cz(3,0)
qc.h(0)
qc.measure_all()
# Draw the circuit using the default renderer
qc.draw()
```

```python
qc.draw('latex')
```

```python

```

QC with IBM QE/Monitoring and Optimizing Quantum Circuits.ipynb


```python
!pip install econml
```

    Collecting econml
      Downloading econml-0.12.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (3.1 MB)
         |████████████████████████████████| 3.1 MB 241 kB/s
    Collecting shap<0.40.0,>=0.38.1
      Downloading shap-0.39.0.tar.gz (356 kB)
         |████████████████████████████████| 356 kB 49.0 MB/s
      Preparing metadata (setup.py) ... - \ done
    Requirement already satisfied: numpy in /opt/conda/lib/python3.7/site-packages (from econml) (1.19.5)
    Requirement already satisfied: statsmodels>=0.10 in /opt/conda/lib/python3.7/site-packages (from econml) (0.12.2)
    Requirement already satisfied: scikit-learn>0.22.0 in /opt/conda/lib/python3.7/site-packages (from econml) (0.23.2)
    Requirement already satisfied: lightgbm in /opt/conda/lib/python3.7/site-packages (from econml) (3.3.1)
    Collecting sparse
      Downloading sparse-0.13.0-py2.py3-none-any.whl (77 kB)
         |████████████████████████████████| 77 kB 4.5 MB/s
    Requirement already satisfied: scipy>1.4.0 in /opt/conda/lib/python3.7/site-packages (from econml) (1.7.2)
    Requirement already satisfied: pandas in /opt/conda/lib/python3.7/site-packages (from econml) (1.3.4)
    Collecting dowhy
      Downloading dowhy-0.7-py3-none-any.whl (152 kB)
         |████████████████████████████████| 152 kB 47.9 MB/s
    Requirement already satisfied: numba!=0.42.1 in /opt/conda/lib/python3.7/site-packages (from econml) (0.54.1)
    Requirement already satisfied: joblib>=0.13.0 in /opt/conda/lib/python3.7/site-packages (from econml) (1.1.0)
    Requirement already satisfied: llvmlite<0.38,>=0.37.0rc1 in /opt/conda/lib/python3.7/site-packages (from numba!=0.42.1->econml) (0.37.0)
    Requirement already satisfied: setuptools in /opt/conda/lib/python3.7/site-packages (from numba!=0.42.1->econml) (59.1.1)
    Requirement already satisfied: threadpoolctl>=2.0.0 in /opt/conda/lib/python3.7/site-packages (from scikit-learn>0.22.0->econml) (3.0.0)
    Requirement already satisfied: tqdm>4.25.0 in /opt/conda/lib/python3.7/site-packages (from shap<0.40.0,>=0.38.1->econml) (4.62.3)
    Requirement already satisfied: slicer==0.0.7 in /opt/conda/lib/python3.7/site-packages (from shap<0.40.0,>=0.38.1->econml) (0.0.7)
    Requirement already satisfied: cloudpickle in /opt/conda/lib/python3.7/site-packages (from shap<0.40.0,>=0.38.1->econml) (2.0.0)
    Requirement already satisfied: patsy>=0.5 in /opt/conda/lib/python3.7/site-packages (from statsmodels>=0.10->econml) (0.5.2)
    Requirement already satisfied: python-dateutil>=2.7.3 in /opt/conda/lib/python3.7/site-packages (from pandas->econml) (2.8.0)
    Requirement already satisfied: pytz>=2017.3 in /opt/conda/lib/python3.7/site-packages (from pandas->econml) (2021.3)
    Requirement already satisfied: pydot>=1.4 in /opt/conda/lib/python3.7/site-packages (from dowhy->econml) (1.4.2)
    Requirement already satisfied: networkx>=2.0 in /opt/conda/lib/python3.7/site-packages (from dowhy->econml) (2.6.3)
    Requirement already satisfied: sympy>=1.4 in /opt/conda/lib/python3.7/site-packages (from dowhy->econml) (1.9)
    Requirement already satisfied: wheel in /opt/conda/lib/python3.7/site-packages (from lightgbm->econml) (0.37.0)
    Requirement already satisfied: six in /opt/conda/lib/python3.7/site-packages (from patsy>=0.5->statsmodels>=0.10->econml) (1.16.0)
    Requirement already satisfied: pyparsing>=2.1.4 in /opt/conda/lib/python3.7/site-packages (from pydot>=1.4->dowhy->econml) (3.0.6)
    Requirement already satisfied: mpmath>=0.19 in /opt/conda/lib/python3.7/site-packages (from sympy>=1.4->dowhy->econml) (1.2.1)
    Building wheels for collected packages: shap
      Building wheel for shap (setup.py) ... - \ | / - \ | done
      Created wheel for shap: filename=shap-0.39.0-cp37-cp37m-linux_x86_64.whl size=544687 sha256=445ef5154401c3539d7f5e36cd9a367244536c5f3536b77d40b93faca179bc5a
      Stored in directory: /root/.cache/pip/wheels/ca/25/8f/6ae5df62c32651cd719e972e738a8aaa4a87414c4d2b14c9c0
    Successfully built shap
    Installing collected packages: sparse, shap, dowhy, econml
      Attempting uninstall: shap
        Found existing installation: shap 0.40.0
        Uninstalling shap-0.40.0:
          Successfully uninstalled shap-0.40.0
    Successfully installed dowhy-0.7 econml-0.12.0 shap-0.39.0 sparse-0.13.0
    WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv

```python
# Some imports to get us started
import warnings
warnings.simplefilter('ignore')

# Utilities
import os
import urllib.request
import numpy as np
import pandas as pd
from networkx.drawing.nx_pydot import to_pydot
from IPython.display import Image, display

# Generic ML imports
from sklearn.preprocessing import PolynomialFeatures
from sklearn.ensemble import GradientBoostingRegressor

# EconML imports
from econml.dml import LinearDML, CausalForestDML
from econml.cate_interpreter import SingleTreeCateInterpreter, SingleTreePolicyInterpreter

import matplotlib.pyplot as plt

%matplotlib inline
```

```python
# Import the sample pricing data
file_url = "https://msalicedatapublic.blob.core.windows.net/datasets/Pricing/pricing_sample.csv"
train_data = pd.read_csv(file_url)
```

```python
train_data.head()
```

      account_age
      age
      avg_hours
      days_visited
      friends_count
      has_membership
      is_US
      songs_purchased
      income
      price
      demand

      0
      3
      53
      1.834234
      2
      8
      1
      1
      4.903237
      0.960863
      1.0
      3.917117

      1
      5
      54
      7.171411
      7
      9
      0
      1
      3.330161
      0.732487
      1.0
      11.585706

      2
      3
      33
      5.351920
      6
      9
      0
      1
      3.036203
      1.130937
      1.0
      24.675960

      3
      2
      34
      6.723551
      0
      8
      0
      1
      7.911926
      0.929197
      1.0
      6.361776

      4
      4
      30
      2.448247
      5
      8
      1
      0
      7.148967
      0.533527
      0.8
      12.624123

```python
#estimator inputs
train_data["log_demand"] = np.log(train_data["demand"])
train_data["log_price"] = np.log(train_data["price"])

Y = train_data["log_demand"].values
T = train_data["log_price"].values
X = train_data[["income"]].values  # features
confounder_names = ["account_age", "age", "avg_hours", "days_visited", "friends_count", "has_membership", "is_US", "songs_purchased"]
W = train_data[confounder_names].values
```

```python
# Get test data
X_test = np.linspace(0, 5, 100).reshape(-1, 1)
X_test_data = pd.DataFrame(X_test, columns=["income"])
```

## Create Causal Model

```python
# initiate an EconML cate estimator
est = LinearDML(model_y=GradientBoostingRegressor(), model_t=GradientBoostingRegressor(),
              featurizer=PolynomialFeatures(degree=2, include_bias=False))
```

```python
# fit through dowhy
est_dw = est.dowhy.fit(Y, T, X=X, W=W, outcome_names=["log_demand"], treatment_names=["log_price"], feature_names=["income"],
               confounder_names=confounder_names, inference="statsmodels")
```

```python
# Visualize causal graph
try:
    # Try pretty printing the graph. Requires pydot and pygraphviz
    display(
        Image(to_pydot(est_dw._graph._graph).create_png())
    )
except:
    # Fall back on default graph view
    est_dw.view_model()
```

```python
identified_estimand = est_dw.identified_estimand_
print(identified_estimand)
```

    Estimand type: nonparametric-ate

    ### Estimand : 1
    Estimand name: backdoor
    Estimand expression:
         d
    ────────────(Expectation(log_demand|is_US,avg_hours,age,songs_purchased,income
    d[log_price]

    ,friends_count,has_membership,account_age,days_visited))

    Estimand assumption 1, Unconfoundedness: If U→{log_price} and U→log_demand then P(log_demand|log_price,is_US,avg_hours,age,songs_purchased,income,friends_count,has_membership,account_age,days_visited,U) = P(log_demand|log_price,is_US,avg_hours,age,songs_purchased,income,friends_count,has_membership,account_age,days_visited)

    ### Estimand : 2
    Estimand name: iv
    No such variable(s) found!

    ### Estimand : 3
    Estimand name: frontdoor
    No such variable(s) found!

```python
# initiate an EconML cate estimator
est_nonparam = CausalForestDML(model_y=GradientBoostingRegressor(), model_t=GradientBoostingRegressor())
# fit through dowhy
est_nonparam_dw = est_nonparam.dowhy.fit(Y, T, X=X, W=W, outcome_names=["log_demand"], treatment_names=["log_price"],
                                         feature_names=["income"], confounder_names=confounder_names, inference="blb")
```

# Test Estimate Robustness with DoWhy

## Add Random Common Cause

How robust are our estimates to adding another confounder?

```python
res_random = est_nonparam_dw.refute_estimate(method_name="random_common_cause")
print(res_random)
```

    Refute: Add a random common cause
    Estimated effect:-0.9573995973779295
    New effect:-0.95832930881448
    p value:0.37

How robust are our estimates to unobserved confounders

```python
res_unobserved = est_nonparam_dw.refute_estimate(
    method_name="add_unobserved_common_cause",
    confounders_effect_on_treatment="linear",
    confounders_effect_on_outcome="linear",
    effect_strength_on_treatment=0.1,
    effect_strength_on_outcome=0.1,
)
print(res_unobserved)
```

    Refute: Add an Unobserved Common Cause
    Estimated effect:-0.9573995973779295
    New effect:-1.010149368734301

## Replace Treatment with a Random (Placebo) Variable

What happens our estimates if we replace the treatment variable with noise?

```python
res_placebo = est_nonparam_dw.refute_estimate(
    method_name="placebo_treatment_refuter", placebo_type="permute",
    num_simulations=3
)
print(res_placebo)
```

    Refute: Use a Placebo Treatment
    Estimated effect:-0.9573995973779295
    New effect:-0.009779517229192933
    p value:0.15574829607146912

## Remove a Random Subset of the Data

Do we recover similar estimates on subsets of the data?

```python
res_subset = est_nonparam_dw.refute_estimate(
    method_name="data_subset_refuter", subset_fraction=0.8,
    num_simulations=3)
print(res_subset)
```

    Refute: Use a subset of data
    Estimated effect:-0.9573995973779295
    New effect:-0.9517490711399156
    p value:0.31210482684964586

```python

```


```python
# %load /Users/facai/Study/book_notes/preconfig.py
%matplotlib inline

import matplotlib.pyplot as plt
import seaborn as sns
sns.set(color_codes=True)
#sns.set(font='SimHei')
plt.rcParams['axes.grid'] = False

#from IPython.display import SVG
def show_image(filename, figsize=None, res_dir=True):
    if figsize:
        plt.figure(figsize=figsize)

    if res_dir:
        filename = './res/{}'.format(filename)

    plt.imshow(plt.imread(filename))
```

Chapter 8 Optimization for Training Deep Models
=========

optimization: finding the parameters $\theta$ of a neural network that significantly reduce a cost function $J(\theta)$.

### 8.1 How Learning Differs from Pure Optimization

expectation is taken across **the data generating distribution** $p_{data}$ rather than just over the finite training set:

\begin{equation}
    J^*(\theta) = \mathcal{E}_{(x, y) \sim p_{data}} L(f(x; \theta), y)
\end{equation}

#### 8.1.1 Empirical Risk Minimization

Rather than optimizing the risk direcly, we optimize the empirical risk, and hope that the risk decreases significantly as well.

#### 8.1.2 Surrogate Loss Functions and Early Stopping

#### 8.1.3 Batch and Minibatch Algorithms

Small batches can offer a regularing effect.

gradient can handle smaller batch size like 100, while second-order methods typically require much large batch sizes like 10,000.

minibatches must be selected randomly. For very large datasets, it is usually sufficient to shuffle the order of the dataset once and then store it in shuffled fashion.

On the second pass, the estimator becomes biased because it is formed by re-sampling values used before.

### 8.2 Challenges in Neural Network Optimization

#### 8.2.1 Ill-Conditioning

To determin whether ill-conditioning, one can monitor the squared gradient norm $g^T g$ and the $g^T H g$ term. In many cases, the gradient norm does not shrink significantly throughout learning, but the $g^T H g$ term grows by more than order of magnitude.

#### 8.2.2 Local Minima

model identifiability problem: models with latent variables are often not identifiable <= weight space symmetry.

#### 8.2.3 Plateaus, Saddle Points and Other Flat Regions

+ saddle point: local minimum along one cross-section, and local maximum along another another cross-section.
  - in higher dimensional spaces, local minima are rare and saddle points are more common.
  - difficult for newton's method, while easy for gradient descent.

+ maxima
+ wide, flat regions of constant value

#### 8.2.4 Cliffs and Exploding Gradients

```python
show_image("fig8_3.png")
```

can be avoided using the *gradient clipping* heuristic

#### 8.2.5 Long-Term Dependencies

when graph becomes extremely deep => vanishing and exploding gradient problem

Vanishing gradients make it difficult to know which direction the parameters should move to improve to the cost function, while exploding gradients can make learning unstable.

#### 8.2.6 Inexact Gradients

#### 8.2.7 Poor Correspondence between Local and Global Structure

Many existing research directions are aimed at finding good initial points, rather than developing algorithms that use non-local moves.

#### 8.2.8 Theoretical Limits of Optimization

### 8.3 Basic Algorithms

#### 8.3.1 Stochastic Gradient Descent

In practice, it is necessary to gradually decrease the learning rate over time.

In practice, it is common to decay the learning rate linearly until iteration $\tau$:

\begin{equation}
    \epsilon_k = (1 - \alpha) \epsilon_0 + \alpha \epsilon_{\tau}
\end{equation}

+ $\tau$: a few hundred passes through the training set
+ $\epsilon_\tau \approx 1\% \, \epsilon_0$
+ $\epsilon_0$: monitor the first several iterations and use a learning rate that is higher than the best-performing learning rate at this time, but not so high that it causes severe instability.

To study the convergence rate of an optimization algorithm, it is common to measure the *excess error* $J(\theta) - \min_\theta J(\theta)$.
+ SGD is applied to a convex problem: $O(\frac{1}{\sqrt{k}}$
+ in the stronly convex case it is $O(\frac{1}{k})$.

#### 8.3.2 Momentum

The momentum algorithm accumulates an exponentially decaying moving average of past gradients and continues to move in their direction.

\begin{align}
    v &\gets \alpha v - \epsilon \Delta_\theta \left ( \frac1{m} \displaystyle \sum^m_{i = 1} L \left ( f(x^{(i)}; \theta), y^{(i)} \right ) \right ) \\
    \theta &\gets \theta + v
\end{align}

The larger $\alpha$ is relative to $\epsilon$, the more previous gradients affect the current direction.

```python
show_image("fig8_5.png")
```

the size of each step is $\frac{\epsilon \| g \|}{1 - \alpha} \implies$ it is thus helpful to think of the momentum hyperparameter in terms of $\frac1{1 - \alpha}$.

+ Common values of $\alpha$ used in practice include 0.5, 0.9 and 0.99.
+ Typically it begins with a small value and is later raised.
+ It is less important to adapt $\alpha$ over time than to shrink $\epsilon$ over time.

#### 8.3.3 Nesterov Momentum

Nesterov momentum: the gradient is evaluated after the current velocity is applied.

\begin{align}
    v &\gets \alpha v - \epsilon \Delta_\theta \left ( \frac1{m} \displaystyle \sum^m_{i = 1} L \left ( f(x^{(i)}; \theta + \color{blue}{\alpha v}), y^{(i)} \right ) \right ) \\
    \theta &\gets \theta + v
\end{align}

考虑了提前量

### 8.4 Parameter Initialization Strategies

Designing improved initialization strategies is a difficult task because neural network optimization is not yet well understood.

A further difficulty is that some initial points may be benefical from the viewpoint of optimization but detrimental from the viewpoint of generalization.

complete certainty: break symmetry between different units

+ initialize each unit to compute a different function from all of the other units.
+ random initialization of the parameters.
  - Typically, set biases for each unit to heuristically chosen constants, and initilize only the weights randomly.

##### weight

We can think of initializing the parameters $\theta$ to $\theta_0$ as being similar to imposing a Gaussian prior $p(\theta)$ with mean $\theta_0$.
$\implies$ choose $\theta_0$ to be near 0 = more likely that units do not interact with each other than that they do interact.

1. normalized initialization: $W_{i, j} \sim U \left ( - \frac{6}{\sqrt{m + n}}, \frac{6}{\sqrt{m + n}} \right )$
2. initializing to random orthogonal matrices
3. perserve norms
4. sparse initialization

A good rule of thumb for choosing the initial scales is to look at the range or standard deviation of activations or gradients on a single minibatch of data.

##### biase

1. Setting the biases to zero is compatible with most weight initialization schemes.
2. a few situations where we may set some biases to non-zero values:
   + for an output unit, often feneficial to initialize the bias to obtain the right marginal statistics of the output.
   + choose the bias to avoid causing too much saturation at initialization.
     eg: set the bias of ReLU hidden unit to 0.1 rather than 0
   + Sometimes a unit controls whether other units are able to participate in a function => all units have a chance to learn.

##### initialize model parameters using machine learning

eg: to initialize a supervised model with the parameters learned by an unsupervised model trained on the same inputs.

### 8.5 Algorithms with Adaptive Learning Rates

the cost is often highly sensitive to some directions in parameter space and insensitive to others. => adapt the learning rates of model parameters.

#### 8.5.1 AdaGrad

gradient accumulation

$\text{rate} =  \frac1{\sum \text{squared gradients}}$

#### 8.5.2 RMSProp

changing the gradient accumulation into an exponentially weighted moving average.

#### 8.5.3 Adam

adaptive moments: combination of RMSProp and momentum

Currently, the most popular optimization algorithms actively in use include
+ SGD,
+ SGD with momentum,
+ RMSProp,
+ RMSProp with momentum,
+ AdaDelta and Adam.

The choice of which algorithm to use, at this point, seems to depend largely on the user’s familiarity with the algorithm (for ease of hyperparameter tuning).

### 8.6 Approximate Second-Order Methods

#### 8.6.1 Newton's Method

a two-step iterative procedure:

+ update or compute the inverse Hessian
+ update the parameters: $\theta^* = \theta_0 - \mathbf{H}^{-1} \Delta_\theta J(\theta_0)$

#### 8.6.2 Conjugate Gradients

#### 8.6.3 BFGS

L-BFGS

### 8.7 Optimization Strategies and Meta-Algorithms

#### 8.7.1 Batch Normalization

adaptive reparametrization => training very deep models

#### 8.7.2 Coordinate Descent

bad: variables are dependent.

#### 8.7.3 Polyak Averaging

averaging points.

#### 8.7.4 Supervised Pretraining

training sample models on simple tasks => then make the model more complex.

#### 8.7.5 Designing Models to Aid Optimization

In practice, it is more important to choose a model family that is easy to optimize than to use a powerful optimization algorithm.

#### 8.7.6 Continuous Methods and Curriculumn Learning

```python

```


```python
# Erasmus+ ICCT project (2018-1-SI01-KA203-047081)

# Toggle cell visibility

from IPython.display import HTML
tag = HTML('''
Toggle cell visibility <a href="javascript:code_toggle()">here</a>.''')
display(tag)

# Hide the code completely

# from IPython.display import HTML
# tag = HTML('''<style>
# div.input {
#     display:none;
# }
# </style>''')
# display(tag)
```

Toggle cell visibility here.

## Diagonalne matrike: Divergentne modalne oblike

Ta interaktivni primer vključuje prednastavljeno diagonalno matriko z vsaj eno divergetno modalno obliko. Primer omogoča, da prosto spreminjate elemente te matrike in ob tem spremljate ali ima nova matrika divergentene modalne oblike (in je s tem nestabilna) ali ne.

Razišči naslednje možnosti:
* diagonalna matrika z divergentnimi (nestabilnimi) modalnimi oblikami,
* diagonalna matrika s stabilnimi modalnimi oblikami,
* diagonalna matrika s stabilnimi in divergetnimi (nestabilnimi) modalnimi oblikami.

Ali lahko enostavno ugotoviš če ima matrika divergentno modalno obliko, kadar je izražena v diagonalni obliki?

[//]: # "This example shows a diagonal matrix with at least one divergent mode. It is possible to change any element of the matrix and analyze if the new matrix has divergent modes (unstable matrix) or not.

Explore the various possibilities:
* diagonal matrices with divergent (unstable) modes,
* diagonal matrices with stable modes,
* diagonal matrices with both stable and divergent (unstable) modes.

Can you easily tell if a matrix has divergent modes when it is in diagonal form?"

```python
%matplotlib notebook
import control
import numpy
import sympy
from IPython.display import display, Markdown
import ipywidgets as widgets
import matplotlib.pyplot as plt

#print a matrix latex-like
def bmatrix(a):
     """Returns a LaTeX bmatrix - by Damir Arbula (ICCT project)

     :a: numpy array
     :returns: LaTeX bmatrix as a string
     """
     if len(a.shape) > 2:
         raise ValueError('bmatrix can at most display two dimensions')
     lines = str(a).replace('[', '').replace(']', '').splitlines()
     rv = [r'\begin{bmatrix}']
     rv += ['  ' + ' & '.join(l.split()) + r'\\' for l in lines]
     rv +=  [r'\end{bmatrix}']
     return '\n'.join(rv)

# Display formatted matrix:
def vmatrix(a):
    if len(a.shape) > 2:
         raise ValueError('bmatrix can at most display two dimensions')
    lines = str(a).replace('[', '').replace(']', '').splitlines()
    rv = [r'\begin{vmatrix}']
    rv += ['  ' + ' & '.join(l.split()) + r'\\' for l in lines]
    rv +=  [r'\end{vmatrix}']
    return '\n'.join(rv)

#matrixWidget is a matrix looking widget built with a VBox of HBox(es) that returns a numPy array as value !
class matrixWidget(widgets.VBox):
    def updateM(self,change):
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.M_[irow,icol] = self.children[irow].children[icol].value
                #print(self.M_[irow,icol])
        self.value = self.M_

    #def dummychangecallback(self,change):
        #pass

    def __init__(self,n,m):
        self.n = n
        self.m = m
        self.M_ = numpy.matrix(numpy.zeros((self.n,self.m)))
        self.value = self.M_
        widgets.VBox.__init__(self,
                             children = [
                                 widgets.HBox(children =
                                              [widgets.FloatText(value=0.0, layout=widgets.Layout(width='90px')) for i in range(m)]
                                             )
                                 for j in range(n)
                             ])

        #fill in widgets and tell interact to call updateM each time a children changes value
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].value = self.M_[irow,icol]
                self.children[irow].children[icol].observe(self.updateM, names='value')
        #value = Unicode('example@example.com', help="The email value.").tag(sync=True)
        self.observe(self.updateM, names='value', type= 'All')

    def setM(self, newM):
        #disable callbacks, change values, and reenable
        self.unobserve(self.updateM, names='value', type= 'All')
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].unobserve(self.updateM, names='value')
        self.M_ = newM
        self.value = self.M_
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].value = self.M_[irow,icol]
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].observe(self.updateM, names='value')
        self.observe(self.updateM, names='value', type= 'All')

                #self.children[irow].children[icol].observe(self.updateM, names='value')

#overlaod class for state space systems that DO NOT remove "useless" states (what "professor" of automatic control would do this?)
class sss(control.StateSpace):
    def __init__(self,*args):
        #call base class init constructor
        control.StateSpace.__init__(self,*args)
    #disable function below in base class
    def _remove_useless_states(self):
        pass
```

```python
A=matrixWidget(4,4)
A.setM(numpy.matrix('1,0,0,0;0,-2,0,0;0,0,-3,0;0,0,0,-4'))

def main_callback(matA,DW):
    (r,c) = numpy.shape(matA)
    sol = numpy.linalg.eig(matA)[0]
    print('Lastne vrednosti matrike so: %s' %str(sol))

    matAs = sympy.Matrix(matA)
    eig = matAs.eigenvals()
    eigvals = list(eig.keys())
    Amul = list(eig.values())

    diag = True
    for i in range(r):
        for j in range(c):
            if i != j:
                if matA[i,j] != 0:
                    diag = False

    if diag:
        for i in range(len(eigvals)):
            if numpy.real(eigvals[i]) > 0:
                print('Ok! Matrika je nestabilna.')
                return
            elif numpy.real(eigvals[i]) == 0:
                if Amul[i] > 1:
                    if len((matAs-eigvals[i]*sympy.eye(4)).nullspace()) < Amul[i]:
                        print('Ok! Matrika je nestabilna.')
                        return
        print('Matrika ni nestabilna.')
    else:
        for i in range(len(eigvals)):
            if numpy.real(eigvals[i]) > 0:
                print('Matrika je nestabilna, a ni diagonalna.')
                return
            elif numpy.real(eigvals[i]) == 0:
                if Amul[i] > 1:
                    if len((matAs-eigvals[i]*sympy.eye(4)).nullspace()) < Amul[i]:
                        print('Matrika je nestabilna, a ni diagonalna.')
                        return
        print('Matrika ni nestabilna in ni diagonalna.')

#create dummy widget
DW = widgets.FloatText(layout=widgets.Layout(width='0px', height='0px'))

#create button widget
START = widgets.Button(
    description='Test',
    disabled=False,
    button_style='', # 'success', 'info', 'warning', 'danger' or ''
    tooltip='Test',
    icon='check'
)

def on_start_button_clicked(b):
    #This is a workaround to have intreactive_output call the callback:
    #   force the value of the dummy widget to change
    if DW.value> 0 :
        DW.value = -1
    else:
        DW.value = 1
    pass
START.on_click(on_start_button_clicked)

out = widgets.interactive_output(main_callback,{'matA':A,'DW':DW})
display(A,START,out)
```

    matrixWidget(children=(HBox(children=(FloatText(value=1.0, layout=Layout(width='90px')), FloatText(value=0.0, …

    Button(description='Test', icon='check', style=ButtonStyle(), tooltip='Test')

    Output()

```python

```


# KVLCC2 geometry analysis

# Purpose
Analyse the geometry section by section

# Methodology
Load offset points from Rhino

# Setup

```python
# %load imports.py
"""
These is the standard setup for the notebooks.
"""

#%matplotlib inline
%matplotlib notebook
%load_ext autoreload
%autoreload 2

#from jupyterthemes import jtplot
#jtplot.style(theme='onedork', context='notebook', ticks=True, grid=False)

import pandas as pd
pd.options.display.max_rows = 999
pd.options.display.max_columns = 999
pd.set_option("display.max_columns", None)
import numpy as np
import os
import matplotlib.pyplot as plt
from collections import OrderedDict
#plt.style.use('paper')

#import data
import copy
from mdldb.run import Run

from sklearn.pipeline import Pipeline
from rolldecayestimators.transformers import CutTransformer, LowpassFilterDerivatorTransformer, ScaleFactorTransformer, OffsetTransformer
from rolldecayestimators.direct_estimator_cubic import EstimatorQuadraticB, EstimatorCubic
from rolldecayestimators.ikeda_estimator import IkedaQuadraticEstimator
import rolldecayestimators.equations as equations
import rolldecayestimators.lambdas as lambdas
from rolldecayestimators.substitute_dynamic_symbols import lambdify
import rolldecayestimators.symbols as symbols
import sympy as sp

from sympy.physics.vector.printing import vpprint, vlatex
from IPython.display import display, Math, Latex

from sklearn.metrics import r2_score
from src.data import database
from mdldb import tables
import shipflowmotionshelpers.shipflowmotionshelpers as helpers
import src.visualization.visualize as visualize

```

    Duplicate key in file WindowsPath('C:/Users/maa/.matplotlib/stylelib/paper.mplstyle'), line 461 ('figure.figsize   : 5, 3   ## figure size in inches')
    Duplicate key in file WindowsPath('C:/Users/maa/.matplotlib/stylelib/paper.mplstyle'), line 462 ('figure.dpi       : 100        ## figure dots per inch')

```python
from reports.paper_writing import save_fig
```

```python
db = database.get_db()

sql = """
SELECT * from run
INNER JOIN loading_conditions
ON (run.loading_condition_id = loading_conditions.id)
INNER JOIN models
ON (run.model_number = models.model_number)
INNER JOIN ships
ON (run.ship_name = ships.name)
WHERE run.model_number='M5057-01-A' and run.test_type='roll decay' and run.project_number=40178362;
"""
df_rolldecays = pd.read_sql(sql=sql, con=db.engine)
df_rolldecays['rho']=1000
df_rolldecays['g']=9.81
df_rolldecays=df_rolldecays.loc[:,~df_rolldecays.columns.duplicated()]
df_rolldecays.set_index('id', inplace=True)
```

```python
df_rolldecays.head()
```

      project_number
      series_number
      run_number
      test_number
      model_number
      ship_name
      loading_condition_id
      ascii_name
      ship_speed
      comment
      file_path_ascii
      file_path_ascii_temp
      file_path_log
      file_path_hdf5
      date
      test_type
      facility
      angle1
      angle2
      Körfallstyp
      name
      lcg
      kg
      gm
      CW
      TF
      TA
      BWL
      KXX
      KZZ
      BTT1
      CP
      Volume
      A0
      RH
      scale_factor
      lpp
      beam
      ABULB
      BKX
      TWIN
      DCLR
      VDES
      RHBL
      ASKEG
      PD
      ARH
      CFP
      AIX
      PDTDES
      RTYPE
      SFP
      BKL
      BKB
      PROT
      D
      LSKEG
      RR
      XSKEG
      NDES
      AR
      BR
      BRA
      IRUD
      PTYPE
      XRUD
      AI
      HSKEG
      RSKEG
      LOA
      ship_type_id
      rho
      g

      id

      21337
      40178362
      1
      94
      1
      M5057-01-A
      M5057-01-A
      166
      94.0
      NaN
      Roll decay, 0 kn
      NaN
      None
      \\sspa.local\gbg\LABmeasuredataMDL\40178362\00...
      \\sspa.local\gbg\LABmeasuredataMDL\40178362\00...
      2018-04-03
      roll decay
      MDL
      None
      None
      None
      20.8
      11.2672
      18.6
      5.73
      None
      20.8
      20.8
      None
      23.2
      80.0
      None
      None
      312653.0
      0.99538
      None
      68.0
      320.0
      58.0
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      1000
      9.81

      21338
      40178362
      1
      95
      1
      M5057-01-A
      M5057-01-A
      166
      95.0
      NaN
      Roll decay, 0 kn
      NaN
      None
      \\sspa.local\gbg\LABmeasuredataMDL\40178362\00...
      \\sspa.local\gbg\LABmeasuredataMDL\40178362\00...
      2018-04-03
      roll decay
      MDL
      None
      None
      None
      20.8
      11.2672
      18.6
      5.73
      None
      20.8
      20.8
      None
      23.2
      80.0
      None
      None
      312653.0
      0.99538
      None
      68.0
      320.0
      58.0
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      1000
      9.81

      21339
      40178362
      1
      96
      1
      M5057-01-A
      M5057-01-A
      166
      96.0
      NaN
      Roll decay, 0 kn
      NaN
      None
      \\sspa.local\gbg\LABmeasuredataMDL\40178362\00...
      \\sspa.local\gbg\LABmeasuredataMDL\40178362\00...
      2018-11-28
      roll decay
      MDL
      None
      None
      None
      20.8
      11.2672
      18.6
      5.73
      None
      20.8
      20.8
      None
      23.2
      80.0
      None
      None
      312653.0
      0.99538
      None
      68.0
      320.0
      58.0
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      1000
      9.81

      21340
      40178362
      1
      97
      1
      M5057-01-A
      M5057-01-A
      166
      97.0
      15.5
      Roll decay, 15.5 kn
      NaN
      None
      \\sspa.local\gbg\LABmeasuredataMDL\40178362\00...
      \\sspa.local\gbg\LABmeasuredataMDL\40178362\00...
      2018-04-04
      roll decay
      MDL
      None
      None
      None
      20.8
      11.2672
      18.6
      5.73
      None
      20.8
      20.8
      None
      23.2
      80.0
      None
      None
      312653.0
      0.99538
      None
      68.0
      320.0
      58.0
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      None
      1000
      9.81

```python
loading_condition = df_rolldecays.loc[21340]
#print(loading_condition.project_path)
```

```python
loading_condition.TA
```

    20.8

```python
loading_condition.TF
```

    20.8

```python
loading_condition.lpp
```

    320.0

```python
draught = (loading_condition.TA + loading_condition.TF)/2
```

```python
file_path = r'S:/2020/40209514-DEMOPS/03_Project/020_PROJECT_MANAGEMENT/arbetsmapp/ISOPE/KVLCC2_points.txt'
points = pd.read_csv(file_path, sep=';', header=None)
points.columns = ['x','y','z']
points.describe()
```

      x
      y
      z

      count
      7140.000000
      7140.000000
      7.140000e+03

      mean
      157.454893
      17.750299
      7.740190e+00

      std
      110.633263
      10.884987
      9.114148e+00

      min
      -5.495000
      -0.000173
      -2.220446e-16

      25%
      48.179054
      6.451813
      8.789816e-01

      50%
      153.290740
      22.257320
      3.160564e+00

      75%
      264.429320
      27.889503
      1.273352e+01

      max
      327.995000
      29.000004
      4.000111e+01

```python
ns = np.arange(10)
n_sections = []
for n in ns:
    n_sections.append( len(points['x'].round(decimals=n).unique()))

fig,ax=plt.subplots()
ax.plot(ns,n_sections)
```

    <IPython.core.display.Javascript object>

    [<matplotlib.lines.Line2D at 0x26026c47f28>]

```python
points['x'] = points['x'].round(decimals=6)
points['z'] = points['z'].round(decimals=2)
x = points['x'].unique()
x_groups = points.groupby(by='x')
points2 = x_groups.filter(lambda x:x['x'].count() > 2)
x = points2['x'].unique()
x_groups = points2.groupby(by='x')
```

```python
from mpl_toolkits.mplot3d import Axes3D

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

ax.plot(points['x'], points['y'], points['z'], '.')
```

    <IPython.core.display.Javascript object>

    [<mpl_toolkits.mplot3d.art3d.Line3D at 0x2602ed15748>]

```python

N = 100
z_ = np.linspace(0,1,N)**-1.1
z_ = z_/z_[1]
z = draught*z_[1:]
z = np.concatenate((z,[0]))
z = np.flipud(z)

section = x_groups.get_group(x[int(len(x)/2)])
section.sort_values(by='z', inplace=True)
y = np.interp(z, section['z'], section['y'])

fig,ax=plt.subplots()
section.plot(x='y', y='z', ax=ax)
ax.plot(y,z,'.:')
```

    c:\dev\prediction-of-roll-damping-using-fully-nonlinear-potential-flow-and-ikedas-method\venv\lib\site-packages\ipykernel_launcher.py:2: RuntimeWarning: divide by zero encountered in power

    c:\dev\prediction-of-roll-damping-using-fully-nonlinear-potential-flow-and-ikedas-method\venv\lib\site-packages\ipykernel_launcher.py:10: SettingWithCopyWarning:
    A value is trying to be set on a copy of a slice from a DataFrame

    See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
      # Remove the CWD from sys.path while we load stuff.

    <IPython.core.display.Javascript object>

    [<matplotlib.lines.Line2D at 0x2602ed49668>]

```python
data = {
    'y':[3,2,1],
    'z':[5,3,2],
}
section = pd.DataFrame(data=data)

fig,ax=plt.subplots()
section.plot(x='y', y='z', style = 'ko-', label='original', ax=ax)

xy = section[['y','z']].values.tolist()

if xy[-1][0] > 0:
    centre_end_point = [0,xy[-1][1]]
    xy.append(centre_end_point)  # Adding missing centre end point

if xy[0][1] < draught:
    water_line_point_1 = [xy[0][0],draught]
    xy.insert(0,water_line_point_1)  # Adding missing water line point

top_centre_point = [0,xy[0][1]]
xy.insert(0,top_centre_point)

close_point = xy[-1]
xy.insert(0,close_point)
xy = np.array(xy)

ax.plot(xy[:,0], xy[:,1],'--')
ax.plot(*centre_end_point, 'ro', label='centre end point')
ax.plot(*water_line_point_1, 'bo', label='water_line_point_1')
ax.plot(*top_centre_point, 'yo', label='top_centre_point')
ax.plot(*close_point, 'ro', label='close_point')

ax.legend()

```

    <IPython.core.display.Javascript object>

    <matplotlib.legend.Legend at 0x2602ee4c940>

```python
def close_section(section):

    """
    The sections are defined as:
    (y,z):
            0
            1
            2
            3
           4
    5
    """
    xy = section[['y','z']].values.tolist()

    if xy[-1][0] > 0:
        centre_end_point = [0,xy[-1][1]]
        xy.append(centre_end_point)  # Adding missing centre end point

    if xy[0][1] < draught:
        water_line_point_1 = [xy[0][0],draught]
        xy.insert(0,water_line_point_1)  # Adding missing water line point

    top_centre_point = [0,xy[0][1]]
    xy.insert(0,top_centre_point)

    close_point = xy[-1]
    xy.insert(0,close_point)
    xy = np.array(xy)

    closed_section = pd.DataFrame()
    closed_section['y'] = xy[:,0]
    closed_section['z'] = xy[:,1]
    closed_section['x'] = section.iloc[0].x

    return closed_section

```

```python
x_groups = points.groupby(by='x')
N = 100
z_ = np.linspace(0,1,N)**-1.1
z_ = z_/z_[1]
z = draught*z_[1:]
z = np.concatenate((z,[0]))
z = np.flipud(z)

df_sections = pd.DataFrame()

for i,(x_, section) in enumerate(x_groups):
    #section.sort_values(by='z', inplace=True)
    mask = section['z'] <= draught
    section = section.loc[mask]
    if len(section)==0:
        continue

    new_section = pd.DataFrame()
    new_section['y']=section['y']
    new_section['x']=x_
    new_section['z']=section['z']
    new_closed_section = close_section(new_section)
    new_closed_section['no']=i
    df_sections = df_sections.append(new_closed_section)

```

    c:\dev\prediction-of-roll-damping-using-fully-nonlinear-potential-flow-and-ikedas-method\venv\lib\site-packages\ipykernel_launcher.py:3: RuntimeWarning: divide by zero encountered in power
      This is separate from the ipykernel package so we can avoid doing imports until

```python
df_sections.head()
```

      y
      z
      x
      no

      0
      -9.001914e-15
      18.80
      -5.495
      0

      1
      0.000000e+00
      20.80
      -5.495
      0

      2
      5.819288e+00
      20.80
      -5.495
      0

      3
      5.819288e+00
      20.75
      -5.495
      0

      4
      5.198539e+00
      20.43
      -5.495
      0

```python
def poly_area(xy):
    """
        Calculates polygon area using Greens formula.
        x = xy[:,0], y = xy[:,1]
    """
    xy1 = np.roll(xy,-1,axis = 0) # shift by -1
    return -0.5*np.inner(xy1[:,0] - xy[:,0],xy1[:,1] + xy[:,1])

def section_area(section):
    xy = section[['y','z']].values
    return np.abs(poly_area(xy=xy))
```

```python
df_sections['x'].unique()
```

    array([ -5.495   ,  -3.258581,  -1.022162,   1.214257,   3.450676,
             5.687095,   7.923513,  10.159932,  12.396351,  14.63277 ,
            16.869189,  19.105608,  21.342027,  23.578446,  25.814865,
            28.051284,  30.287703,  32.524122,  34.760541,  36.996959,
            39.233378,  41.469797,  43.706216,  45.942635,  48.179054,
            50.415473,  52.651892,  54.888311,  57.12473 ,  59.361149,
            61.597568,  63.833986,  66.070405,  68.306824,  70.543243,
            72.779662,  75.016081,  77.2525  ,  79.488919,  81.725338,
            83.961757,  86.198176,  88.434595,  90.671014,  92.907432,
            95.143851,  97.38027 ,  99.616689, 101.85311 , 104.08953 ,
           106.32595 , 108.56236 , 110.79878 , 113.0352  , 115.27162 ,
           117.50804 , 119.74446 , 121.98088 , 124.2173  , 126.45372 ,
           128.69014 , 130.92655 , 133.16297 , 135.39939 , 137.63581 ,
           139.87223 , 142.10865 , 144.34507 , 146.58149 , 148.81791 ,
           151.05432 , 153.29074 , 155.52716 , 157.76358 , 160.      ,
           162.2702  , 164.54041 , 166.81061 , 169.08081 , 171.35101 ,
           173.62122 , 175.89142 , 178.16162 , 180.43182 , 182.70203 ,
           184.97223 , 187.24243 , 189.51264 , 191.78284 , 194.05304 ,
           196.32324 , 198.59345 , 200.86365 , 203.13385 , 205.40405 ,
           207.67426 , 209.94446 , 212.21466 , 214.48486 , 216.75507 ,
           219.02527 , 221.29547 , 223.56568 , 225.83588 , 228.10608 ,
           230.37628 , 232.64649 , 234.91669 , 237.18689 , 239.45709 ,
           241.7273  , 243.9975  , 246.2677  , 248.53791 , 250.80811 ,
           253.07831 , 255.34851 , 257.61872 , 259.88892 , 262.15912 ,
           264.42932 , 266.69953 , 268.96973 , 271.23993 , 273.51014 ,
           275.78034 , 278.05054 , 280.32074 , 282.59095 , 284.86115 ,
           287.13135 , 289.40155 , 291.67176 , 293.94196 , 296.21216 ,
           298.48236 , 300.75257 , 303.02277 , 305.29297 , 307.56318 ,
           309.83338 , 312.10358 , 314.37378 , 316.64399 , 318.91419 ,
           321.18439 , 323.45459 , 325.7248  , 327.995   ])

```python
sections = df_sections.groupby(by='x')
areas = sections.apply(func=section_area)
fig,ax=plt.subplots()
areas.plot(ax=ax)
```

    <IPython.core.display.Javascript object>

    <AxesSubplot:xlabel='x'>

```python
min_area = 1
df_sections2 = sections.filter(func=lambda x : section_area(x) > min_area)
sections = df_sections2.groupby(by='x')
```

```python
from mpl_toolkits.mplot3d import Axes3D

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

for no,section, in sections:

    ax.plot(section['x'], section['y'], section['z'])
```

    <IPython.core.display.Javascript object>

```python
x_interps = np.linspace(df_sections2['x'].min(), df_sections2['x'].max(),21)
xs = df_sections2['x'].unique()
x_ = []
for x_interp in x_interps:
    i = np.argmin(np.abs(x_interp - xs))
    x_.append(xs[i])

df_sections3 = pd.DataFrame()
for i,x in enumerate(x_):
    section = sections.get_group(x)
    section['no'] = i
    df_sections3 = df_sections3.append(section)
```

    c:\dev\prediction-of-roll-damping-using-fully-nonlinear-potential-flow-and-ikedas-method\venv\lib\site-packages\ipykernel_launcher.py:11: SettingWithCopyWarning:
    A value is trying to be set on a copy of a slice from a DataFrame.
    Try using .loc[row_indexer,col_indexer] = value instead

    See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
      # This is added back by InteractiveShellApp.init_path()

```python
from mpl_toolkits.mplot3d import Axes3D

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

sections2 = df_sections3.groupby(by='no')
for no, section in sections2:

    ax.plot(section['x'], section['y'], section['z'], label=no)

ax.legend()
```

    <IPython.core.display.Javascript object>

    <matplotlib.legend.Legend at 0x2602f11d748>

```python
areas2 = sections2.apply(section_area)
fig,ax=plt.subplots()
areas2.plot(ax=ax)
for no,a in areas2.items():
    ax.annotate(no,xy=(no,a))
```

    <IPython.core.display.Javascript object>

```python
def estimate_bilge_radius(section):
    b = section['b']
    t = section['t']
    A = section['area']

    r = np.sqrt((b*t-A)/(1-np.pi/4))

    return r

def section_data(section):

    s = pd.Series()
    s.name=section.name
    s['area'] = 2*section_area(section) # two sides
    s['x'] = section.iloc[0].x
    s['t'] = section.z.max() - section.z.min()
    s['b'] = 2*section.y.max()  # two sides
    s['r_b'] = estimate_bilge_radius(s)

    return s

```

```python
df_section_properties = sections2.apply(func=section_data)
```

    c:\dev\prediction-of-roll-damping-using-fully-nonlinear-potential-flow-and-ikedas-method\venv\lib\site-packages\ipykernel_launcher.py:12: DeprecationWarning: The default dtype for empty Series will be 'object' instead of 'float64' in a future version. Specify a dtype explicitly to silence this warning.
      if sys.path[0] == '':

```python
df_section_properties.to_csv('../data/interim/kvlcc_areas.csv', sep=';')
```

```python
df_section_properties
```

      area
      x
      t
      b
      r_b

      no

      0
      13.826612
      -5.495000
      2.00
      11.638577
      6.636080

      1
      123.851306
      10.159932
      18.25
      27.893522
      42.367175

      2
      428.211409
      28.051284
      20.80
      41.824284
      45.369454

      3
      683.709165
      43.706216
      20.80
      50.282514
      41.080696

      4
      917.895066
      61.597568
      20.80
      56.159232
      34.146143

      5
      1056.860933
      77.252500
      20.80
      57.870498
      26.158540

      6
      1139.503552
      92.907432
      20.80
      58.000008
      17.655717

      7
      1186.852794
      110.798780
      20.80
      58.000008
      9.543935

      8
      1203.217372
      126.453720
      20.80
      58.000008
      3.851125

      9
      1203.929931
      144.345070
      20.80
      58.000008
      3.392755

      10
      1203.929195
      160.000000
      20.80
      58.000008
      3.393260

      11
      1203.928607
      175.891420
      20.80
      58.000008
      3.393664

      12
      1203.929195
      194.053040
      20.80
      58.000008
      3.393260

      13
      1203.929195
      209.944460
      20.80
      58.000008
      3.393260

      14
      1203.906161
      225.835880
      20.80
      58.000008
      3.409039

      15
      1195.569763
      243.997500
      20.80
      57.987376
      7.017342

      16
      1159.988460
      259.888920
      20.80
      57.487792
      12.908255

      17
      1077.577243
      275.780340
      20.80
      55.361310
      18.561674

      18
      899.155097
      291.671760
      20.80
      48.130896
      21.797880

      19
      502.526691
      309.833380
      20.80
      28.203708
      19.797403

      20
      43.489245
      325.724800
      16.18
      5.709535
      15.093775

```python
fig,ax = plt.subplots()

zmax = sections2['z'].max().max()
for no,section, in sections2:

    section = section.iloc[2:]  # Removing close

    y = section['z']

    if no < 10:
        n = no
        x = -section['y']
        y_text = zmax - n*(zmax/9)
    else:
        n = 20 - no
        x = section['y']
        y_text = zmax - n*(zmax/10)

    x_text = np.interp(y_text,np.flipud(y),np.flipud(x))

    ax.plot(x, y)
    ax.annotate(no,xy=(x_text,y_text))

ymax = sections2['y'].max().max()
ax.plot(1.03*np.array([-ymax,ymax]),[zmax,zmax],'b-', lw=2)
ax.set_xlabel('y [m]')
ax.set_ylabel('z [m]')
ax.grid(True)
save_fig(fig, name='KVLCC2_body_plan')
```

    <IPython.core.display.Javascript object>

```python
section = sections2.get_group(4)
section = section.iloc[2:]
y = section['z']
n = 20 - no
x = section['y']

fig,ax=plt.subplots()
ax.plot(y,x)

np.interp(y_text,np.flipud(y),np.flipud(x))
```

    <IPython.core.display.Javascript object>

    28.079615999999998

```python

```

```python

```

```python

```


# **Turma de Pós-Graduação de Ciência de Dados**

**Disciplina: Linguagem de Programação Python**

**prof: Sérgio Assunção Monteiro, DSc**

**Aula 09**

# **Introdução à Computação Quântica**

**Fontes:**

> https://qiskit.org/documentation/intro_tutorial1.html

> https://qiskit.org/textbook/ch-states/introduction.html

```python
pip install qiskit
```

    Collecting qiskit
      Downloading qiskit-0.34.1.tar.gz (13 kB)
    Collecting qiskit-terra==0.19.1
      Downloading qiskit_terra-0.19.1-cp37-cp37m-manylinux2010_x86_64.whl (6.4 MB)
         |████████████████████████████████| 6.4 MB 4.3 MB/s
    Collecting qiskit-aer==0.10.2
      Downloading qiskit_aer-0.10.2-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (18.0 MB)
         |████████████████████████████████| 18.0 MB 350 kB/s
    Collecting qiskit-ibmq-provider==0.18.3
      Downloading qiskit_ibmq_provider-0.18.3-py3-none-any.whl (238 kB)
         |████████████████████████████████| 238 kB 70.1 MB/s
    Collecting qiskit-ignis==0.7.0
      Downloading qiskit_ignis-0.7.0-py3-none-any.whl (200 kB)
         |████████████████████████████████| 200 kB 72.1 MB/s
    Requirement already satisfied: scipy>=1.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-aer==0.10.2->qiskit) (1.4.1)
    Requirement already satisfied: numpy>=1.16.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-aer==0.10.2->qiskit) (1.19.5)
    Requirement already satisfied: requests>=2.19 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.18.3->qiskit) (2.23.0)
    Requirement already satisfied: python-dateutil>=2.8.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.18.3->qiskit) (2.8.2)
    Collecting requests-ntlm>=1.1.0
      Downloading requests_ntlm-1.1.0-py2.py3-none-any.whl (5.7 kB)
    Collecting websocket-client>=1.0.1
      Downloading websocket_client-1.2.3-py3-none-any.whl (53 kB)
         |████████████████████████████████| 53 kB 1.6 MB/s
    Requirement already satisfied: urllib3>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.18.3->qiskit) (1.24.3)
    Collecting retworkx>=0.8.0
      Downloading retworkx-0.11.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (1.6 MB)
         |████████████████████████████████| 1.6 MB 58.7 MB/s
    Requirement already satisfied: setuptools>=40.1.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-ignis==0.7.0->qiskit) (57.4.0)
    Collecting scipy>=1.0
      Downloading scipy-1.7.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (38.1 MB)
         |████████████████████████████████| 38.1 MB 1.3 MB/s
    Collecting symengine>=0.8
      Downloading symengine-0.8.1-cp37-cp37m-manylinux2010_x86_64.whl (38.2 MB)
         |████████████████████████████████| 38.2 MB 1.1 MB/s
    Collecting ply>=3.10
      Downloading ply-3.11-py2.py3-none-any.whl (49 kB)
         |████████████████████████████████| 49 kB 4.1 MB/s
    Collecting stevedore>=3.0.0
      Downloading stevedore-3.5.0-py3-none-any.whl (49 kB)
         |████████████████████████████████| 49 kB 219 kB/s
    Collecting python-constraint>=1.4
      Downloading python-constraint-1.4.0.tar.bz2 (18 kB)
    Requirement already satisfied: psutil>=5 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.19.1->qiskit) (5.4.8)
    Requirement already satisfied: sympy>=1.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.19.1->qiskit) (1.7.1)
    Requirement already satisfied: dill>=0.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.19.1->qiskit) (0.3.4)
    Collecting tweedledum<2.0,>=1.1
      Downloading tweedledum-1.1.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (943 kB)
         |████████████████████████████████| 943 kB 65.2 MB/s
    Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from python-dateutil>=2.8.0->qiskit-ibmq-provider==0.18.3->qiskit) (1.15.0)
    Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.18.3->qiskit) (2.10)
    Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.18.3->qiskit) (3.0.4)
    Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.18.3->qiskit) (2021.10.8)
    Collecting cryptography>=1.3
      Downloading cryptography-36.0.1-cp36-abi3-manylinux_2_24_x86_64.whl (3.6 MB)
         |████████████████████████████████| 3.6 MB 58.1 MB/s
    Collecting ntlm-auth>=1.0.2
      Downloading ntlm_auth-1.5.0-py2.py3-none-any.whl (29 kB)
    Requirement already satisfied: cffi>=1.12 in /usr/local/lib/python3.7/dist-packages (from cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.18.3->qiskit) (1.15.0)
    Requirement already satisfied: pycparser in /usr/local/lib/python3.7/dist-packages (from cffi>=1.12->cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.18.3->qiskit) (2.21)
    Collecting pbr!=2.1.0,>=2.0.0
      Downloading pbr-5.8.0-py2.py3-none-any.whl (112 kB)
         |████████████████████████████████| 112 kB 58.0 MB/s
    Requirement already satisfied: importlib-metadata>=1.7.0 in /usr/local/lib/python3.7/dist-packages (from stevedore>=3.0.0->qiskit-terra==0.19.1->qiskit) (4.10.0)
    Requirement already satisfied: typing-extensions>=3.6.4 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata>=1.7.0->stevedore>=3.0.0->qiskit-terra==0.19.1->qiskit) (3.10.0.2)
    Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata>=1.7.0->stevedore>=3.0.0->qiskit-terra==0.19.1->qiskit) (3.7.0)
    Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.7/dist-packages (from sympy>=1.3->qiskit-terra==0.19.1->qiskit) (1.2.1)
    Building wheels for collected packages: qiskit, python-constraint
      Building wheel for qiskit (setup.py) ... done
      Created wheel for qiskit: filename=qiskit-0.34.1-py3-none-any.whl size=11771 sha256=86db6f0858d82a18d20e81db5bfa58720eb18dbf6d6d27471a63e5ba617490c2
      Stored in directory: /root/.cache/pip/wheels/79/b1/3f/8cdfd5543a84705e4bd16e081f2362b9b3bfd9898d2e2d4150
      Building wheel for python-constraint (setup.py) ... done
      Created wheel for python-constraint: filename=python_constraint-1.4.0-py2.py3-none-any.whl size=24081 sha256=b209c85fb81681840b73f43a3a02a35b1db5b502382c51a8acbef98ce8ed72e8
      Stored in directory: /root/.cache/pip/wheels/07/27/db/1222c80eb1e431f3d2199c12569cb1cac60f562a451fe30479
    Successfully built qiskit python-constraint
    Installing collected packages: pbr, tweedledum, symengine, stevedore, scipy, retworkx, python-constraint, ply, ntlm-auth, cryptography, websocket-client, requests-ntlm, qiskit-terra, qiskit-ignis, qiskit-ibmq-provider, qiskit-aer, qiskit
      Attempting uninstall: scipy
        Found existing installation: scipy 1.4.1
        Uninstalling scipy-1.4.1:
          Successfully uninstalled scipy-1.4.1
    ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
    albumentations 0.1.12 requires imgaug<0.2.7,>=0.2.5, but you have imgaug 0.2.9 which is incompatible.
    Successfully installed cryptography-36.0.1 ntlm-auth-1.5.0 pbr-5.8.0 ply-3.11 python-constraint-1.4.0 qiskit-0.34.1 qiskit-aer-0.10.2 qiskit-ibmq-provider-0.18.3 qiskit-ignis-0.7.0 qiskit-terra-0.19.1 requests-ntlm-1.1.0 retworkx-0.11.0 scipy-1.7.3 stevedore-3.5.0 symengine-0.8.1 tweedledum-1.1.1 websocket-client-1.2.3

```python
import numpy as np
from qiskit import QuantumCircuit, transpile
from qiskit.providers.aer import QasmSimulator
from qiskit.visualization import plot_histogram
```

```python
# Usar o Aer's qasm_simulator
simulator = QasmSimulator()
```

```python
# Criar um circuito quântico para atuar no registro q
circuit = QuantumCircuit(2, 2)
```

```python
# Adiciona uma porta H no qubit 0
circuit.h(0)
```

    <qiskit.circuit.instructionset.InstructionSet at 0x7f12742e66e0>

```python
# Adiciona uma porta CX (CNOT) o qubit 0 de controle e o qubit 1 alvo
circuit.cx(0, 1)
```

    <qiskit.circuit.instructionset.InstructionSet at 0x7f12742f00f0>

```python
# Mapear a medição quântica para os bits clássicos
circuit.measure([0,1], [0,1])
```

```python
# compilar o circuito para instruções QASM
# suportado pelo back-end
compiled_circuit = transpile(circuit, simulator)
```

```python
# Execute o circuito no simulador qasm
job = simulator.run(compiled_circuit, shots=1000)
```

```python
# Obtenha os resultados do trabalho
result = job.result()
```

```python
# Returna o contador
counts = result.get_counts(compiled_circuit)
print("\nTotal de contagens para 00 e 11 é:",counts)
```

```python
# Desenha o circuito
circuit.draw()
```

```python
# Denhar o histograma
plot_histogram(counts)
```


Unit 6

Assessing model accuracy

(Notebook: regression_model_comparison.ipynb)

IST 718 – Big Data Analytics

Daniel E. Acuna

http://acuna.io

# From previous unit
- We took a **statistical approach to learning** which acknowledges our uncertainty and noise in the data science process.

- We defined a **model** of the data.

- We estimated the parameters using **training data**.

- We used the model to **predict** and **interpret** the results.

- We could use **supervised** or **unsupervised** learning to find relationships between variables.

- If variables are not quantitative, we used classification models.

# In this unit
- Generalization performance
- Estimating generalization performance: cross-validation
- Bias-variance decomposition
- Performance of classifiers: confusion matrix, ROC curve, AUC
- Bayes rule

# Generalization performance
- Generalization is the performance of a learning method on independent **test data**.

- Generalization performance guides the choice of learning method or model.
- **Why don't we teach the *best method*?**
  - *Because there is no free lunch in statistics*: David Wolpert, "The Lack of A Priori Distinctions Between Learning Algorithms", 1996.

    No one method dominates all others over all possible data sets.

# Generalization performance (2)
- The no free lunch theorem implies that we need to:

  1. Learn about the **particular dataset** we are working on (data science!)

  2. **Select the best method** using generalization performance (data science!)

# Measuring generalization performance: theory
- We need to define a loss function:

$$l(Y,\hat{f}(X))$$

- For example, the Squared Error for regression:

$$l(Y,\hat{f}(X)) = (Y - \hat{f}(X))^2$$

(which is the same as the negative likelihood with Gaussian noise.)

- Or zero-one loss for classification:

$$l(Y,\hat{f}(X)) = \text{I}(Y \neq \hat{f}(X))$$

where $\text{I}(a,b)$ is 1 if $a = b$, and 0 otherwise.

# Measuring generalization performance: test error and expected prediction error
- Test error is the prediction error over an *independent* test sample:

$$Err_T = E[l(Y,\hat{f}(X)) \mid T]$$

where both $Y$ and $X$ are randomly sampled with a fixed training set $T$.

- A related quantity is the expected prediction error:

$$Err = E[l(Y,\hat{f}(X))] = E[Err_T]$$

where everything is random including the training dataset.

- Most methods effectively estimate $Err$ instead of $Err_T$.

# Measuring generalization performance: model comparison

- Typically, we must compare several models
- We split the data into **three datasets** and compute the following over a **testing dataset**
$$Err_{V,T}=E[l(Y,\hat{f}(X))\mid V,T ]$$
where $V$ is a **validation dataset**, and $T$ is a **training dataset**.
- We further restrict the previous quantity to the best model on **validation** performance, and therefore we end up estimating
$$Err = E[E_T[Err_{V^*,T}]]$$

# Estimating test error in practice: cross validation

Often, models have differing degrees of complexity controlled by a parameter $\alpha$ (e.g., $\;\hat{f}_\alpha(X)$)

Training split is used to **fit** one model.

Validation split is used to select **complexity**.

Test split is used to estimate **expected test error**.

# Estimating test error in practice: cross validation (2)
- The previous approach is known as **training, validation, and testing split**
- If no alternative models are compared, there are only two splits and the method is known as training and testing
- Typical data splits are 60%-30%-10% for training, validation, and testing
- Or 80%-20% for training and testing splits

# Estimating the expected test error: $k$-fold cross validation

The problem with the previous procedure is that we "throw away" the validation and test splits during training.

$k$-fold cross validation (partially) fixes this by running cross validation multiple times.

# Estimating the expected test error: training data splits
- The **test split** should **only** be used at the end of the data science.
- (Demo) We will compare two models in diabetes dataset: linear regression using BMI (M1) and linear regression using BMI and age (M2).

  1. Model fit:
  M1 MSE (training) = 3723, **M2 MSE  (training) = 3680**

  2. Model selection:
  **M1 MSE (validation) = 4582**, M2 MSE (validation) = 4618

  3. Model assessment:
  **M1 MSE (test) = 3925**

# Estimating the expected test error: variable selection
- This procedure can be generalized to answer the question:
  - Which variables should be included in the model?

- One of the simplest such procedures is the null-to-full model variable selection:

For $k$ variables from 0 to $p$:

Fit $p – k$ models which add one variable to the current model.

If none of the models has lower validation error than current model, then break.

Set the current model to the one with lowest validation error.

Return current model.

- This procedure selects age, bmi, map, tc, ltg, and glu as the most important variables with estimated test error of **3324**.

# More on expected test error
- The loss function used to cross validation **does not** necessarily match the loss function used during model fitting.
- For example, you can fit models with gradient descent to minimize MSE (because it is easy and fast) but you might choose models based on the Mean Absolute Deviation (MAD)
$$\text{MAD} = \frac{1}{n} \sum_{i=1}^n | \hat{y}_i - y |$$

# The Bias-Variance decomposition: Math
- Mathematically:

$$\begin{align}
Err(x_0) &= E[(Y-\hat{f}(x_0))^2\mid x_0] \\
Err(x_0) &= \sigma_{\epsilon}^2 + (E[\hat{f}(x_0)]-f(x_0))^2 + E[\hat{f}(x_0)-E[\hat{f}(x_0)]]^2 \\
Err(x_0) &= \text{Irreducible error} + \text{Bias}^2 + \text{Variance}
\end{align}$$

# The Bias-Variance decomposition of test error
- In general, more complex models have low bias and high variance.

- Vice versa, simple models have high bias and low variance.

- This is a **fundamental tradeoff**.

- **High variance** means that the estimation has high "error bars."

- **High bias** means that the estimation will not change much even if more data is seen.

# The Bias-Variance decomposition: Nearest neighbor regression
- Take the $k$ closest points and compute the average $y$ of those points
$$\hat{f}(x_0) = \frac{1}{k}\sum_{i=1}^k f(x_{\text{nn}_i(x_0)})$$
where $\text{nn}_i(x)$ is the index of $i$-th closest neighbor to $x$
- The Bias-variance decomposition then becomes
$$Err(x_0) = \sigma_\epsilon^2 + [f(x_0) - \frac{1}{k}\sum_{i=1}^k f(x_{\text{nn}_i(x_0)})]^2 + \frac{\sigma_\epsilon^2}{k}$$
where $\frac{\sigma_\epsilon^2}{k}$ is the variance of $\hat{f}$
- Bias increases with $k$ and variance decreases with $k$

# The Bias-Variance decomposition: Linear regression
- The argument is more complex: $\hat{f}(x_0) = x_0^T b$ with vector $b$ having $p$ components
- On average
$$Err(x_0) \approx \sigma_\epsilon^2 + \text{Bias}^2 + p \sigma_\epsilon^2$$
- Variance is proportional to the number of parameters and therefore Bias decreases with number of parameters

# The Bias-Variance decomposition: in practice
- It is many times impossible to know the irreducible error, bias, and variance decomposition
- There are several heuristics for trying to understand whether we need to increase the bias or the variance, or whether we have simply hit the irreducible error
- We will want to **search over many models to find the one which minimizes bias and variance**

# The Bias-Variance decomposition (2)
An example with the diabetes dataset:
- We add a polynomial expansion to the variables:
  - bmi, age, bmi*age, bmi$^2$, age$^2$, etc.

- We will have a new set of features of size:

$$p_\text{new} = 2p + p(p-1)/2$$

- We run the following procedure:
  Repeat many times:
  1. Randomly split the data into training and testing
  2. For $k=1$ to $p_\text{new}$
    1. Fit model with $k$ randomly selected features and predict.
    2. Estimate training and testing MSEs.

# The Bias-Variance decomposition (3)

# The Bias-Variance decomposition: the learning curve

Simple model doesn’t learn much after 50 examples.

Complex model keeps learning.

Simple model has better performance with small datasets.

Complex model has better performance with big datasets.

# Measuring generalization performance: classification
- A typical method for measuring classification performance is *accuracy*:

    $$\text{Accuracy} = \frac{1}{n} \sum_{i=1}^{n} \text{I}(y_i,\hat{f}(x_i))$$
where $\text{I}(a, b)$ is 1 if $a = b$, and 0 otherwise.

- What might be the problem with using accuracy? (hint: What is the accuracy of an algorithm that predicts that every email received is not a spam?)

# Measuring generalization performance: confusion matrix
- **Activity**: Predict spam. Given one normal email (True Normal: TN) and one spam email (True Spam: TS), Gmail can classify as spam (Predict Spam: PS) or not spam (Predict Normal: PN). Where would you put all these cases in a confusion matrix?

# Measuring generalization performance: confusion matrix (2)
- **Activity**: Predict spam. Given one normal email (True Normal: TN) and one spam email (True Spam: TS), Gmail can classify as spam (Predict Spam: PS) or not spam (Predict Normal: PN). Where would you put all these cases in a confusion matrix?

# Measuring generalization performance: confusion matrix (3)
- This matrix is called a **confusion matrix**: summary of true vs predicted cases

# Measuring generalization performance: Common statistics from confusion matrix
- **Prevalence**: (TP+FN) / everything
- **Precision**: TP / (TP + FP)
- **Sensitivity**, **Recall**, or **True positive rate**: TP / true condition positive
- **Specificity**: TN / true condition negative
- **F1**: $\frac{2*precision*recall}{precision + recall}$

# Measuring generalization performance: ROC curve
- In general, no single algorithm has the best sensitivity and specificity simultaneously.
- Some algorithms offer a range of sensitivity/specificity points.  ROC curve displays this

# Measuring generalization performance: ROC curve (2)
- We will classify as spam if classifier thinks with more than 50% probability.

# Measuring generalization performance: ROC curve (3)
- **Less stringent, we will classify as spam if P(spam) > 0.1**

# Measuring generalization performance: ROC curve (4)
- We can put the two thresholds $\theta = 0.5$ and $\theta = 0.1$ in a plot.

# Measuring generalization performance: ROC curve  (5)
- We can do this for every threshold value and plot the result as a curve.

**Activity**: can you guess the curve of a random classifier?

# Measuring generalization performance: ROC curve (6)
- We can do this for every threshold value and plot the result as follows:

A measure that combines all thresholds is the **Area Under the ROC Curve (AUC)**

# Area under the ROC curve (AUC)

- It is hard to interpret but:
    - It can be thought as how good is the model to rank the probabilities with the real labels
- The **AUC** is 1/2 if the classifier is *random* or *constant*
- **Activity: show that the AUC is in fact 1/2 for a random predictor**

# AUC for random prediction

- AP, AN, PP, PN mean actual positive, actual negative, predicted positive, and predictived negative, respectively
- There are $n$ cases in total

| .  | PP|PN |
|---|---|---|
| AP| TP|FN |
| AN| FP|TN |

- Cases are gonna randomly fall on PP and PN.
- For a given threshold $\theta$, FP = $(1-\theta)$AN and TN=$\theta$AN, and similarly TP=$(1-\theta)$AP and FN=$\theta$AP
- FPR = $(1-\theta)$AN/($(1-\theta)$AN + $\theta$AN) = $1-\theta$
- Therefore, AUC = $\frac{1}{2}$

# AUC for constant prediction
- As we move the threshold there will be a change between all points being in PP to PN, or viceverse.
- There two points will have FPR=TPR=0 and FPR=TPR=1 respectively
- By interpolation, AUC = $\frac{1}{2}$

# The Bayesian classifier
- Many values from confusion matrix are misleading because we are not using *prevalence rates*.

- Maximum likelihood estimators estimate the most likely classification of the data given a hypothesis (actual class) without consideration of the *prevalance rate*

- The optimal decision however is achieved by Bayes' theorem

$$p(\text{hypothesis} \mid \text{data}) = \frac{p(\text{data} \mid \text{hypothesis})\;p(\text{hypothesis})}{p(\text{data})}$$

# The Bayesian classifier (2)
- Sometimes classification performance in terms of metrics that do not consider prevalance.
- For example, a classifier for cancer (C) given positive results (R) or might be reported with:
  - Specificity: 95%
  - Sensitivity: 60%
- But cancer prevalance is low (0.2%)
$$p(\text{C}\mid \text{R}) = \frac{p(\text{R}\mid\text{C})p(\text{C})}{p(\text{R})}$$
with
$$p(\text{R}) = p(\text{R}\mid\text{C})p(\text{C}) + p(\text{R}\mid\neg\text{C})p(\neg\text{C})$$

# Take home message
- Always keep the testing dataset in vault until the end of your analysis.

- Use model selection to choose from models of different complexity.

- Stick to a loss function throughout your analysis.

- For classification problems, think carefully about the requirements of your problem.

- Be careful about the prevalence values.


```python
from IPython.display import Image
Image('../../../python_for_probability_statistics_and_machine_learning.jpg')
```

[Python for Probability, Statistics, and Machine Learning](https://www.springer.com/fr/book/9783319307152)

# Moment Generating Functions

Generating moments usually involves integrals that are extremely
difficult to compute. Moment generating functions make this much, much
easier. The moment generating function is defined as,

$$
M(t) = \mathbb{E}(\exp(t X))
$$

 The first moment is the mean, which we can easily compute from
$M(t)$ as,

$$
\begin{align*}
\frac{dM(t)}{dt} &= \frac{d}{dt}\mathbb{E}(\exp(t X)) = \mathbb{E}\frac{d}{dt}(\exp(t X))\\\
                 &= \mathbb{E}(X \exp(t X))  \\\
\end{align*}
$$

 Now, we have to set $t=0$ and we have the mean,

$$
M^{(1)}(0) = \mathbb{E}(X)
$$

 continuing this derivative process again, we obtain the second moment as,

$$
\begin{align*}
M^{(2)}(t) &= \mathbb{E}(X^2\exp(t X)) \\\
M^{(2)}(0) &= \mathbb{E}(X^2)
\end{align*}
$$

 With this in hand, we can easily compute the variance as,

$$
\mathbb{V}(X) = \mathbb{E}(X^2) -\mathbb{E}(X)^2=M^{(2)}(0)-M^{(1)}(0)^2
$$

**Example.** Returning to our favorite binomial distribution, let's compute
some moments using Sympy.

```python
import sympy as S
from sympy import stats
p,t = S.symbols('p t',positive=True)
x=stats.Binomial('x',10,p)
mgf = stats.E(S.exp(t*x))
```

 Now, let's compute the first moment (aka, mean) using
the usual integration method and using moment generating functions,

```python
print S.simplify(stats.E(x))
print S.simplify(S.diff(mgf,t).subs(t,0))
```

    10*p
    10*p

 Otherwise, we can compute this directly as follows,

```python
print S.simplify(stats.moment(x,1)) # mean
print S.simplify(stats.moment(x,2)) # 2nd moment
```

    10*p
    10*p*(9*p + 1)

 In general, the moment generating function for the binomial
distribution is the following,

$$
M_X(t) = \left(p\left(e^t-1\right)+1\right) ^n
$$

A key aspect of moment generating functions is that they are unique identifiers
of probability distributions.  By the uniqueness theorem, given two random
variables $X$ and $Y$, if their respective moment generating functions are
equal, then the corresponding probability distribution functions are equal.

**Example.** Let's use the uniqueness theorem to consider the following
problem. Suppose we know that the probability distribution of $X$ given $U=p$
is binomial with parameters $n$ and $p$. For example, suppose $X$ represents the
number of heads in $n$ coin flips, given the probability of heads is $p$. We
want to find the unconditional distribution of $X$. Writing out the
moment generating function as the following,

$$
\mathbb{E}(e^{t X}\vert U=p) = (p e^t + 1-p)^n
$$

 Because $U$ is uniform over the unit interval, we can
integrate this part out

$$
\begin{align*}
\mathbb{E}(e^{t X}) &=\int_0^1 (p e^t + 1-p)^n dp \\\
                    &= \frac{1}{n+1} \frac{e^{t(n+1)-1}}{e^t-1} \\\
                    &= \frac{1}{n+1} (1+e^t+e^{2t}+e^{3t}+\ldots+e^{n t}) \\\
\end{align*}
$$

 Thus, the moment generating function of $X$ corresponds to that of a
random variable that is equally likely to be any of the values $0,1,\ldots,n$.
This is another way of saying that the distribution of $X$ is discrete uniform
over $\lbrace 0,1,\ldots,n \rbrace$. Concretely, suppose we have a box of coins
whose individual probability of heads is unknown and  that we  dump the box on
the floor, spilling all of the coins. If we then count the number of coins facing
heads-up, that distribution is uniform.

Moment generating functions are useful for deriving distributions of
sums of independent random variables. Suppose $X_1$ and $X_2$ are independent
and $Y=X_1+X_2$. Then, the moment generating function of $Y$ follows
from the properties of the expectation,

$$
\begin{align*}
M_Y(t) &= \mathbb{E}(e^{t Y}) =  \mathbb{E}(e^{t X_1 + t X_2}) \\\
       &= \mathbb{E}(e^{t X_1} e^{ t X_2 }) =\mathbb{E}(e^{t X_1})\mathbb{E}(e^{t X_2}) \\\
       &= M_{X_1}(t)M_{X_2}(t)
\end{align*}
$$

**Example.** Suppose we have two normally distributed random variables,
$X_1\sim \mathcal{N}(\mu_1,\sigma_1)$ and $ X_2\sim \mathcal{N}(\mu_2,\sigma_2)$.
We can save some tedium by exploring this in Sympy,

```python
S.var('x:2',real=True)
S.var('mu:2',real=True)
S.var('sigma:2',positive=True)
S.var('t',positive=True)
x0=stats.Normal(x0,mu0,sigma0)
x1=stats.Normal(x1,mu1,sigma1)
```

**Programming Tip.**

The `S.var` function defines the variable and injects it into the global
namespace. This is sheer laziness. It is more expressive to define variables
explicitly as in `x = S.symbols('x')`. Also notice that we used the Greek names
for the `mu` and `sigma` variables.  This will come in handy later when we want
to render the equations in the Jupyter/IPython notebook which understands
how to typeset these symbols in \LaTeX{}. The `var('x:2')` creates two
symbols, `x0` and `x1`. Using the colon this way makes it easy to generate
array-like sequences of symbols.

 In the next block we compute the moment generating functions

```python
mgf0=S.simplify(stats.E(S.exp(t*x0)))
mgf1=S.simplify(stats.E(S.exp(t*x1)))
mgfY=S.simplify(mgf0*mgf1)
```

 The moment generating functions an individual normally distributed
random variable is the following,

$$
e^{\mu_{0} t + \frac{\sigma_{0}^{2} t^{2}}{2}}
$$

 Note the coefficients of $t$. To show that $Y$ is normally
distributed, we want to match the moment generating function of $Y$ to this
format. The following is the form of the moment generating function of $Y$,

$$
M_Y(t)=e^{\frac{t}{2} \left(2 \mu_{0} + 2 \mu_{1} + \sigma_{0}^{2} t + \sigma_{1}^{2} t\right)}
$$

 We can extract the exponent using Sympy and collect on the $t$
variable using the following code,

```python
S.collect(S.expand(S.log(mgfY)),t)
```

    t**2*(sigma0**2/2 + sigma1**2/2) + t*(mu0 + mu1)

 Thus, by the uniqueness theorem, $Y$ is normally distributed with
$\mu_Y=\mu_0+\mu_1$ and $\sigma_Y^2=\sigma_0^2+\sigma_1^2$.

**Programming Tip.**

When using the Jupyter/IPython notebook, you can do `S.init_printing` to get
the mathematical typesetting to work in the browser. Otherwise, if you want to
keep the raw expression and to selectively render to \LaTeX{}, then you can
`from IPython.display import Math`, and then use `Math(S.latex(expr))` to see
the typeset version of the expression.