# Notebook from LondonBiofoundry/basicsynbio-BioCRNpyler
Path: scripts_nbs/Prototype Notebook.ipynb

# Installation Instructions
### To run this you will need to install:
BioCRNpyler:

    pip install biocrnpyler[all]

Bioscrape:

    pip install bioscrape

as well as numpy, matplotlib, and pandas
_____no_output_____

<code>
from biocrnpyler import *
import pylab as plt
import pandas as pd
import numpy as np

data_file = "basic-parts-biocrnpyler-translator - Parts List.tsv"

df = pd.read_csv(data_file, sep='\t')

df.loc[:, ['Part Type']].to_numpy()

#Dictionary to convert Part Types to BioCRNpyler classes
type_to_class = {
    'CDS':[CDS],
    'RBS':[RBS],
    'Terminator-ConstituitivePromoter':[Terminator, Promoter],
    'Terminator-RegulatedPromoter':[Terminator, RegulatedPromoter],
    'Linker':[],
    'Backbone':[DNA_part]
}

def backbone_concentration(s):
    if "BASIC_SEVA_36" in str(s) or "BASIC_SEVA_26" in str(s):
        return .001
    elif "BASIC_SEVA_28" in str(s):
        return .05
    else:
        return .01_____no_output_____#Constructs:
c1 = ["BASIC_SEVA_26", "LMP", "Terminator1_J23101_RiboA", "UTR1-RBS3", "sfGFP", "LMS"]
c2 = ["BASIC_SEVA_26", "LMP", "Terminator2_LacI_RiboB", "UTR1-RBS3", "mCherry", "LMS"]
c3 = ["BASIC_SEVA_36", "LMP", "Terminator1_J23101_RiboA", "UTR1-RBS2", "sfGFP", "UTR2-RBS1", "mTagBFP2", "UTR3-RBS1", "mCherry", "LMS"]
c4 = ["BASIC_SEVA_26", "LMP", "Terminator1_TetR_RiboA", "UTR1-RBS2", "LacI", "UTR2-RBS2", "sfGFP", "L1", "Terminator2_LacI_RiboB", "UTR3-RBS2", "TetR", "LMS"]
c5 = ["BASIC_SEVA_37_CmR-p15A.1", "LMP", "Terminator1_J23104_RiboA", "UTR2-RBS1", "mTagBFP2", "UTR3-RBS3", "mCherry", "UTR1-RBS3", "sfGFP", "LMS"]

basic_constructs = [c1, c2, c3, c4, c5]_____no_output_____biocrnpyler_constructs = []
for c in basic_constructs:
    print(c)
    bc_list = []
    for p in c:
        pt = df.loc[df['Part Name'] == p]['Part Type'].to_string(index = False)
        keywords = df.loc[df['Part Name'] == p]['BioCRNpyler Args'].to_string(index = False)
        try:
            class_list = type_to_class[pt]
            if keywords != "NaN":
                exec("D = {"+keywords+"}")

                name = p.replace("-", "_")
                #print(p, pt, keywords)
                for C in class_list:
                    if "Terminator" in pt and C == Terminator:
                        D['name'] = name.split("_")[0]
                    elif "Terminator" in pt and C != Terminator:
                         D['name'] = "_".join(name.split("_")[1:])
                    else:
                        D['name'] = name
                    part = C(**D)
                    bc_list.append(part)
                    #print("\t", part)

        except KeyError:
            print("Unknown Part Type", p, pt)

    print(c, bc_list)
    biocrnpyler_constructs.append(bc_list)
['BASIC_SEVA_26', 'LMP', 'Terminator1_J23101_RiboA', 'UTR1-RBS3', 'sfGFP', 'LMS']
['BASIC_SEVA_26', 'LMP', 'Terminator1_J23101_RiboA', 'UTR1-RBS3', 'sfGFP', 'LMS'] [BASIC_SEVA_26, Terminator1, J23101_RiboA, UTR1_RBS3, sfGFP]
['BASIC_SEVA_26', 'LMP', 'Terminator2_LacI_RiboB', 'UTR1-RBS3', 'mCherry', 'LMS']
['BASIC_SEVA_26', 'LMP', 'Terminator2_LacI_RiboB', 'UTR1-RBS3', 'mCherry', 'LMS'] [BASIC_SEVA_26, Terminator2, LacI_RiboB, UTR1_RBS3, mCherry]
['BASIC_SEVA_36', 'LMP', 'Terminator1_J23101_RiboA', 'UTR1-RBS2', 'sfGFP', 'UTR2-RBS1', 'mTagBFP2', 'UTR3-RBS1', 'mCherry', 'LMS']
['BASIC_SEVA_36', 'LMP', 'Terminator1_J23101_RiboA', 'UTR1-RBS2', 'sfGFP', 'UTR2-RBS1', 'mTagBFP2', 'UTR3-RBS1', 'mCherry', 'LMS'] [BASIC_SEVA_36, Terminator1, J23101_RiboA, UTR1_RBS2, sfGFP, UTR2_RBS1, mTagBFP2, UTR3_RBS1, mCherry]
['BASIC_SEVA_26', 'LMP', 'Terminator1_TetR_RiboA', 'UTR1-RBS2', 'LacI', 'UTR2-RBS2', 'sfGFP', 'L1', 'Terminator2_LacI_RiboB', 'UTR3-RBS2', 'TetR', 'LMS']
['BASIC_SEVA_26', 'LMP', 'Terminator1_TetR_RiboA', 'UTR1-RBS2', 'LacI', 'UTR2-RBS2', 'sfGFP', 'L1', 'Terminator2_LacI_RiboB', 'UTR3-RBS2', 'TetR', 'LMS'] [BASIC_SEVA_26, Terminator1, TetR_RiboA, UTR1_RBS2, LacI, UTR2_RBS2, sfGFP, Terminator2, LacI_RiboB, UTR3_RBS2, TetR]
['BASIC_SEVA_37_CmR-p15A.1', 'LMP', 'Terminator1_J23104_RiboA', 'UTR2-RBS1', 'mTagBFP2', 'UTR3-RBS3', 'mCherry', 'UTR1-RBS3', 'sfGFP', 'LMS']
Unknown Part Type BASIC_SEVA_37_CmR-p15A.1 Series([], )
['BASIC_SEVA_37_CmR-p15A.1', 'LMP', 'Terminator1_J23104_RiboA', 'UTR2-RBS1', 'mTagBFP2', 'UTR3-RBS3', 'mCherry', 'UTR1-RBS3', 'sfGFP', 'LMS'] [Terminator1, J23104_RiboA, UTR2_RBS1, mTagBFP2, UTR3_RBS3, mCherry, UTR1_RBS3, sfGFP]
timepoints = np.arange(0, 200, 10)

cl = biocrnpyler_constructs[0]
print(cl)
construct = DNA_construct(cl, circular = True)
print(construct, construct.get_species())

M = SimpleTxTlDilutionMixture("simple model", components = [construct], parameter_file = "default_parameters.txt")
CRN = M.compile_crn()

initial_concentration = {construct.get_species(): backbone_concentration(construct.get_species())}
R = CRN.simulate_with_bioscrape_via_sbml(timepoints, initial_condition_dict = initial_concentration)

print(CRN.pretty_print(show_rates = False))
plt.figure()
plt.title(str(construct))
for s in CRN.species:
    if s == construct.get_species():
        label = "construct"
        ls = "-"
    elif "rna" in str(s):
        label = "transcript"
        ls = ":"
    elif "ordered_polymer" in str(s) and "dna" in str(s):
        label = "construct - complex"
        ls = "-"
    else:
        label = str(s)
        ls = "-."
    plt.plot(timepoints, R[str(s)], ls, label = label)
plt.legend()[BASIC_SEVA_26, Terminator1, J23101_RiboA, UTR1_RBS3, sfGFP]
DNA_construct = BASIC_SEVA_26_Terminator1_J23101_RiboA_UTR1_RBS3_sfGFP_o dna_dna_BASIC_SEVA_26_forward_dna_Terminator1_forward_dna_J23101_RiboA_forward_dna_UTR1_RBS3_forward_dna_sfGFP_forward_circular_
Species(N = 3) = {
protein[sfGFP] (@ 0),  rna[rna[UTR1_RBS3-forward]:rna[sfGFP-forward]:rna[BASIC_SEVA_26-forward]:rna[Terminator1-forward]] (@ 0),  dna[dna[BASIC_SEVA_26-forward]:dna[Terminator1-forward]:dna[J23101_RiboA-forward]:dna[UTR1_RBS3-forward]:dna[sfGFP-forward](circular)] (@ 0),
}

Reactions (5) = [
0. dna[dna[BASIC_SEVA_26-forward]:dna[Terminator1-forward]:dna[J23101_RiboA-forward]:dna[UTR1_RBS3-forward]:dna[sfGFP-forward](circular)] --> dna[dna[BASIC_SEVA_26-forward]:dna[Terminator1-forward]:dna[J23101_RiboA-forward]:dna[UTR1_RBS3-forward]:dna[sfGFP-forward](circular)]+rna[rna[UTR1_RBS3-forward]:rna[sfGFP-forward]:rna[BASIC_SEVA_26-forward]:rna[Terminator1-forward]]
1. rna[rna[UTR1_RBS3-forward]:rna[sfGFP-forward]:rna[BASIC_SEVA_26-forward]:rna[Terminator1-forward]] --> rna[rna[UTR1_RBS3-forward]:rna[sfGFP-forward]:rna[BASIC_SEVA_26-forward]:rna[Terminator1-forward]]+protein[sfGFP]
2. rna[rna[UTR1_RBS3-forward]:rna[sfGFP-forward]:rna[BASIC_SEVA_26-forward]:rna[Terminator1-forward]] -->
3. protein[sfGFP] -->
4. rna[rna[UTR1_RBS3-forward]:rna[sfGFP-forward]:rna[BASIC_SEVA_26-forward]:rna[Terminator1-forward]] -->
]
timepoints = np.arange(0, 200, 10)

cl = biocrnpyler_constructs[1]
print(cl)
construct = DNA_construct(cl, circular = True)
print(construct)

M = SimpleTxTlDilutionMixture("simple model", components = [construct], parameter_file = "default_parameters.txt")
CRN = M.compile_crn()

initial_concentration = {construct.get_species(): backbone_concentration(construct.get_species())}

print(CRN.pretty_print(show_rates = False))
plt.figure(figsize = (10, 6))
plt.suptitle(str(construct))
for ind, lacR0 in enumerate([0, .1, 1]):
    initial_concentration["protein_lacR"] = lacR0
    R = CRN.simulate_with_bioscrape_via_sbml(timepoints, initial_condition_dict = initial_concentration)

    plt.subplot(130+ind+1)
    plt.title("lacR = "+str(lacR0))

    for s in CRN.species:
        if s == construct.get_species():
            label = "construct"
            ls = "-"
        elif "rna" in str(s):
            label = "transcript"
            ls = ":"
        elif "ordered_polymer" in str(s) and "dna" in str(s):
            label = "construct - complex"
            ls = "-"
        else:
            label = str(s)
            ls = "-."
        plt.plot(timepoints, R[str(s)], ls, label = label)
    plt.legend()C:\Users\wp_ix\miniconda3\lib\site-packages\ipykernel\ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
  and should_run_async(code)
timepoints = np.arange(0, 200, 10)

cl = biocrnpyler_constructs[2]
print(cl)
construct = DNA_construct(cl, circular = True)
print(construct, construct.get_species())

M = SimpleTxTlDilutionMixture("simple model", components = [construct], parameter_file = "default_parameters.txt")
CRN = M.compile_crn()

initial_concentration = {construct.get_species(): backbone_concentration(construct.get_species())}
R = CRN.simulate_with_bioscrape_via_sbml(timepoints, initial_condition_dict = initial_concentration)

print(CRN.pretty_print(show_rates = False))
plt.figure()
plt.title(str(construct))
for s in CRN.species:
    if s == construct.get_species():
        label = "construct"
        ls = "-"
    elif "rna" in str(s):
        label = "transcript"
        ls = ":"
    elif "ordered_polymer" in str(s) and "dna" in str(s):
        label = "construct - complex"
        ls = "-"
    else:
        label = str(s)
        ls = "-."
    plt.plot(timepoints, R[str(s)],ls, label = label)
plt.legend()C:\Users\wp_ix\miniconda3\lib\site-packages\ipykernel\ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
  and should_run_async(code)
timepoints = np.arange(0, 20000, 100)

cl = biocrnpyler_constructs[3]
print(cl)
construct = DNA_construct(cl, circular = True)
print(construct)

M = SimpleTxTlDilutionMixture("simple model", components = [construct], parameter_file = "default_parameters.txt")
CRN = M.compile_crn()

initial_concentration = {construct.get_species(): backbone_concentration(construct.get_species())}
R = CRN.simulate_with_bioscrape_via_sbml(timepoints, initial_condition_dict = initial_concentration)

print(CRN.pretty_print(show_rates = False))
plt.figure(figsize = (14, 6))
plt.suptitle(str(construct))

conc_list = [0, .1, .5, 1]
for ind in range(len(conc_list)):
    lacR0 = conc_list[ind]
    tetR0 = conc_list[-(1+ind)]

    initial_concentration["protein_lacR"] = lacR0
    initial_concentration["protein_tetR"] = tetR0
    R = CRN.simulate_with_bioscrape_via_sbml(timepoints, initial_condition_dict = initial_concentration)

    plt.subplot(140+ind+1)
    plt.title("lacR = "+str(lacR0)+" tetR = "+str(tetR0))

    for s in CRN.species:
        if s == construct.get_species():
            label = "construct"
            ls = "-"
        elif "rna" in str(s):
            label = "transcript"
            ls = ":"
        elif "ordered_polymer" in str(s) and "dna" in str(s):
            label = "construct - complex"
            ls = "-"
        else:
            label = str(s)
            ls = "-."
        plt.plot(timepoints, R[str(s)], ls, label = label)
        plt.ylim(0, 7)
    plt.legend()C:\Users\wp_ix\miniconda3\lib\site-packages\ipykernel\ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
  and should_run_async(code)
timepoints = np.arange(0, 200, 10)

cl = biocrnpyler_constructs[4]
print(cl)
construct = DNA_construct(cl, circular = True)
print(construct)

M = SimpleTxTlDilutionMixture("simple model", components = [construct], parameter_file = "default_parameters.txt")
CRN = M.compile_crn()

initial_concentration = {construct.get_species(): backbone_concentration(construct.get_species())}
R = CRN.simulate_with_bioscrape_via_sbml(timepoints, initial_condition_dict = initial_concentration)

print(CRN.pretty_print(show_rates = False))
plt.figure()
plt.title(str(construct))
for s in CRN.species:
    if s == construct.get_species():
        label = "construct"
        ls = "-"
    elif "rna" in str(s):
        label = "transcript"
        ls = ":"
    elif "ordered_polymer" in str(s) and "dna" in str(s):
        label = "construct - complex"
        ls = "-"
    else:
        label = str(s)
        ls = "-."
    plt.plot(timepoints, R[str(s)], ls, label = label)
plt.legend()[Terminator1, J23104_RiboA, UTR2_RBS1, mTagBFP2, UTR3_RBS3, mCherry, UTR1_RBS3, sfGFP]
DNA_construct = Terminator1_J23104_RiboA_UTR2_RBS1_mTagBFP2_UTR3_RBS3_mCherry_UTR1_RBS3_sfGFP_o
Species(N = 5) = {
protein[sfGFP] (@ 0),  rna[rna[UTR2_RBS1-forward]:rna[mTagBFP2-forward]:rna[UTR3_RBS3-forward]:rna[mCherry-forward]:rna[UTR1_RBS3-forward]:rna[sfGFP-forward]:rna[Terminator1-forward]] (@ 0),  protein[mCherry] (@ 0),  dna[dna[Terminator1-forward]:dna[J23104_RiboA-forward]:dna[UTR2_RBS1-forward]:dna[mTagBFP2-forward]:dna[UTR3_RBS3-forward]:dna[mCherry-forward]:dna[UTR1_RBS3-forward]:dna[sfGFP-forward](circular)] (@ 0),  protein[BFP2] (@ 0),
}

Reactions (9) = [
0. dna[dna[Terminator1-forward]:dna[J23104_RiboA-forward]:dna[UTR2_RBS1-forward]:dna[mTagBFP2-forward]:dna[UTR3_RBS3-forward]:dna[mCherry-forward]:dna[UTR1_RBS3-forward]:dna[sfGFP-forward](circular)] --> dna[dna[Terminator1-forward]:dna[J23104_RiboA-forward]:dna[UTR2_RBS1-forward]:dna[mTagBFP2-forward]:dna[UTR3_RBS3-forward]:dna[mCherry-forward]:dna[UTR1_RBS3-forward]:dna[sfGFP-forward](circular)]+rna[rna[UTR2_RBS1-forward]:rna[mTagBFP2-forward]:rna[UTR3_RBS3-forward]:rna[mCherry-forward]:rna[UTR1_RBS3-forward]:rna[sfGFP-forward]:rna[Terminator1-forward]]
1. rna[rna[UTR2_RBS1-forward]:rna[mTagBFP2-forward]:rna[UTR3_RBS3-forward]:rna[mCherry-forward]:rna[UTR1_RBS3-forward]:rna[sfGFP-forward]:rna[Terminator1-forward]] --> rna[rna[UTR2_RBS1-forward]:rna[mTagBFP2-forward]:rna[UTR3_RBS3-forward]:rna[mCherry-forward]:rna[UTR1_RBS3-forward]:rna[sfGFP-forward]:rna[Terminator1-forward]]+protein[BFP2]
2. rna[rna[UTR2_RBS1-forward]:rna[mTagBFP2-forward]:rna[UTR3_RBS3-forward]:rna[mCherry-forward]:rna[UTR1_RBS3-forward]:rna[sfGFP-forward]:rna[Terminator1-forward]] --> rna[rna[UTR2_RBS1-forward]:rna[mTagBFP2-forward]:rna[UTR3_RBS3-forward]:rna[mCherry-forward]:rna[UTR1_RBS3-forward]:rna[sfGFP-forward]:rna[Terminator1-forward]]+protein[mCherry]
3. rna[rna[UTR2_RBS1-forward]:rna[mTagBFP2-forward]:rna[UTR3_RBS3-forward]:rna[mCherry-forward]:rna[UTR1_RBS3-forward]:rna[sfGFP-forward]:rna[Terminator1-forward]] --> rna[rna[UTR2_RBS1-forward]:rna[mTagBFP2-forward]:rna[UTR3_RBS3-forward]:rna[mCherry-forward]:rna[UTR1_RBS3-forward]:rna[sfGFP-forward]:rna[Terminator1-forward]]+protein[sfGFP]
4. rna[rna[UTR2_RBS1-forward]:rna[mTagBFP2-forward]:rna[UTR3_RBS3-forward]:rna[mCherry-forward]:rna[UTR1_RBS3-forward]:rna[sfGFP-forward]:rna[Terminator1-forward]] -->
5. protein[BFP2] -->
6. protein[mCherry] -->
7. protein[sfGFP] -->
8. rna[rna[UTR2_RBS1-forward]:rna[mTagBFP2-forward]:rna[UTR3_RBS3-forward]:rna[mCherry-forward]:rna[UTR1_RBS3-forward]:rna[sfGFP-forward]:rna[Terminator1-forward]] -->
]

</code>


# Notebook from derekchen-2/physics-math-tutorials
Path: colabs/Multivariate Calculus for ML, 1 of 2.ipynb

Copyright 2021 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License._____no_output_____**Make a copy of this notebook!**_____no_output_____

#@title Python imports
import collections
import datetime
from functools import partial
import math

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from scipy import stats
import seaborn as sns

from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split_____no_output_____# Make colab plots larger
plt.rcParams['figure.figsize'] = [8, 6]
plt.rcParams['figure.dpi'] = 100_____no_output_____

# Section 1

In this section we'll cover:

* Using Jax
* Computing derivatives with Jax
* Gradient Descent (single variable)
* Newton's Method (single variable)
_____no_output_____## Jax

[Autodiff cookbook](https://jax.readthedocs.io/en/latest/notebooks/autodiff_cookbook.html)

Jax is a python library that provides the ability to differentiate many python
functions.
_____no_output_____

import jax
import jax.numpy as jnp
from jax import jit, grad, vmap, api, random, jacfwd, jacrev
from jax.experimental import optimizers, stax_____no_output_____

## Basic autodiff example

Let's take the derivative of $y = f(x) = x^2$, which we know to be $f'(x) = 2x$.

_____no_output_____

def square(x):
  return x * x

# Compute the derivative with grad from Jax
dsquare = grad(square)

# Plot the function and the derivative
domain = np.arange(0, 2.5, 0.1)
plt.plot(domain, square(domain), label="$y=x^2$")
plt.plot(domain, list(map(dsquare, domain)), label="$y'=\\frac{dy}{dx} = 2x$")
# Note can also use JAX's vmap: vmap(dsquare)(domain) instead of list(map(dsquare, domain))
plt.legend()
plt.show()_____no_output_____

### Exercise

Write a function to compute the sigmoid

$$f(x) = \frac{1}{1 + e^{-x}}$$

using `jnp.exp` to compute $e^x$ -- need to use the Jax version of
numpy for autodiff to work.

Then verify that

$$ f'(x) = f(x) (1 - f(x))$$

For example, compare some explicit values and plot the differences between the derivative and $f(x) (1 - f(x))$._____no_output_____

## Your code here

# Compute the sigmoid
def sigmoid(x):
  pass

# Compute the derivative (using grad)

# Compare derivative values to f(x) * (1 - f(x))
_____no_output_____# @title Solution (double-click to show)

# Compute the sigmoid
def sigmoid(x):
  return 1. / (1. + jnp.exp(-x))

# Compute the derivative (using grad)
deriv = grad(sigmoid)

# Compare derivative values to f(x) * (1 - f(x))
xs = np.arange(-3, 3.1, 0.1)
ys = []
for x in xs:
  ys.append(deriv(x) - sigmoid(x) * (1. - sigmoid(x)))
plt.scatter(xs, ys)
plt.title("Differences between Jax derivative and $f(x)(1-f(x))$\nNote the scale modifier in the upper left")
plt.show()_____no_output_____

# Single variable gradient descent examples

In this example we solve $x^2=x$, which we know has two solutions. Different
initial starting points yield convergence to different solutions, or
non-convergence to either solution at $x_0 = 1/2$.

We need to turn the problem into one of finding local extrema. So we consider
the function $f(x) = \left( x^2 - x\right)^2$, which is differentiable and
has local minimum at $x=0$ and $x=1$. We square so that the points where
$x^2=x$ are minima instead of zeros of a function like $g(x) = x^2 - x$.

Notice in the plot below that the function also has a local maximum at $x=1/2$,
which is centered between the two solutions. Intuitively, gradient descent
starting at $x_0=1/2$ will not move because there's no reason to favor either
local minumum.

Let's plot the function first:_____no_output_____

def f(x):
  return (x**2 - x)**2

xs = np.arange(-1, 2.05, 0.05)
ys = f(xs)
plt.plot(xs, ys)
plt.show()_____no_output_____

Let's define a function to compute iterations of gradient descent.

$$\begin{eqnarray}
x_{n+1} &=& x_n - \alpha f'(x_n) \\
\end{eqnarray}$$
_____no_output_____

def gradient_descent(dfunc, x0, iterations=100, alpha=0.1):
  """dfunc is the derivative of the function on which we
  perform descent."""
  xs = [x0]
  for i in range(iterations):
    x = xs[-1]
    x = x - alpha * dfunc(float(x))
    xs.append(x)
  return xs_____no_output_____# Let's try it on our function f now.

# Compute the derivative
df = grad(f)

# Try some different starting points.
for x0 in [0.25, 0.5, 0.50001, 0.85]:
  xs = gradient_descent(df, x0, iterations=30, alpha=1.)
  plt.plot(range(len(xs)), xs, label=x0)
plt.xlabel("iterations")
plt.legend()
plt.show()_____no_output_____

### Check your understanding: Explain what happened with each of the four curves in the plot._____no_output_____### Exercise: What happens if we decrease the learning rate $\alpha$?
Recreate the plot above using $\alpha=0.1$ instead._____no_output_____

## Your code here
_____no_output_____

## Example 2

In this example we use gradient descent to approximate $\sqrt{3}$. We use
the function $f(x) = \left(x^2 - 3\right)^2$ and construct a sequence converging
to the positive solution. In this case notice the impact of the learning rate
both on the time to convergence and whether the convergence is monotonic or
oscillitory. Larger learning rates such as $\alpha=1$ can cause divergence._____no_output_____

def f2(x):
  return (x*x - 3)**2

df = grad(f2)

x0 = 2
for alpha in [0.08, 0.01]:
  xs = gradient_descent(df, x0, iterations=40, alpha=alpha)
  plt.plot(range(len(xs)), xs, label="$\\alpha = {}$".format(alpha))
plt.xlabel("iterations")

# Plot the correct value
sqrt3 = math.pow(3, 0.5)
n = len(xs)
plt.plot(range(n), [sqrt3]*n, label="$\\sqrt{3}$", linestyle="--")

plt.legend()
plt.show()
print("Sqrt(3) =", sqrt3)_____no_output_____

## Exercise

Solve the equation $x = e^{-x}$, which does not have an easily obtainable solution algebraically.

The solution is approximately $x = 0.567$. Again note the impact of
the learning rate.

Use `jnp.exp` for the exponential function.
_____no_output_____

def f3(x):
  """Define the function f(x) = (x - e^(-x))^2."""
  ## Your code here
  pass

# Compute the gradient

# Initial guess
x0 = 0.4

## Add code here for gradient descent using the functions above

## Plot the gradient descent values
_____no_output_____#@title Solution (double click to expand)

def f3(x):
  """Define the function f(x) = (x - e^(-x))^2."""
  return (x - jnp.exp(-x))**2

# Compute the gradient
df = grad(f3)

# Initial guess
x0 = 0.4
for alpha in [0.01, 0.1]:
  xs = gradient_descent(df, x0, iterations=50, alpha=alpha)
  plt.plot(range(len(xs)), xs, label="$\\alpha = {}$".format(alpha))
plt.xlabel("iterations")

plt.legend()
plt.show()

print("Final iteration:", xs[-1])_____no_output_____

# Newton's method, single variable

We can use Newton's
method to find zeros of functions. Since local extrema occur at zeros of
the derivative, we can apply Newton's method to the first derivative to obtain
a second-order alternative to gradient descent.

$$\begin{eqnarray}
x_{n+1} &=& x_n - \alpha \frac{f'(x_n)}{f''(x_n)} \\
\end{eqnarray}$$
_____no_output_____

def newtons_method(func, x0, iterations=100, alpha=1.):
  dfunc = grad(func)
  xs = [x0]
  for i in range(iterations):
    x = xs[-1]
    x = x - alpha * func(x) / dfunc(float(x))
    xs.append(x)
  return xs_____no_output_____

Let's repeat the example of finding the value of $\sqrt{3}$ with Newton's method and compare to gradient descent.

For small $\alpha$, gradient descent seems to perform better:_____no_output_____

def f(x):
  return (x**2 - 3)**2

# Let's make a function we can reuse
def compare_gradient_newton(func, x0, alpha=0.01, iterations=50):
  # Compute the first and second derivatives
  df = grad(func)

  # Compute Newton's method iterations
  xs = newtons_method(df, x0, alpha=alpha, iterations=iterations)

  # Compute gradient descent with same alpha
  xs2 = gradient_descent(df, x0, alpha=alpha, iterations=iterations)

  # Plot it all
  plt.plot(range(len(xs2)), xs2, label="Gradient Descent")
  plt.plot(range(len(xs)), xs, label="Newton's method")
  plt.xlabel("iterations")
  plt.legend()
  plt.title("$\\alpha = {}$".format(alpha))

  # Plot the solution
  sqrt3 = math.pow(3, 0.5)
  n = len(xs)
  plt.plot(range(n), [sqrt3]*n, label="$\\sqrt{3}$", linestyle="--")

  plt.show()

compare_gradient_newton(f, 2., alpha=0.01, iterations=50)_____no_output_____

But for larger $\alpha$, Newton's method is better behaved and gradient descent fails to converge._____no_output_____

compare_gradient_newton(f, 2., alpha=0.1, iterations=50)_____no_output_____

In this case we can also apply Newton's method with just the first derivative to find a zero of $x^2 - 3$, i.e. we don't have to look for a minimum of
$(x^2 - 3)^2$ since Newton's method can also find zeros of functions._____no_output_____

def f(x):
  return x**2 - 3

xs = newtons_method(f, 2., alpha=0.5, iterations=10)
plt.plot(range(len(xs)), xs, label="$\\alpha = {}$".format(alpha))
plt.xlabel("iterations")

# Plot the solution
sqrt3 = math.pow(3, 0.5)
n = len(xs)
plt.plot(range(n), [sqrt3]*n, label="$\\sqrt{3}$", linestyle="--")

print(xs[-1])_____no_output_____

# Section 2

Now we'll look at multivariate derivatives and gradient descent,
again using Jax._____no_output_____## Multivariate derivatives

$$f(x, y) = x y^2$$

$$ \nabla f = [y^2, 2 x y]$$_____no_output_____

def f(x, y):
  return x * y * y

# Compute the partial derivatives with grad from Jax
# Use float as arguments, else Jax will complain
print("f(3, 1)=", f(3., 1.))

# argnums allows us to specify which variable to take the derivative of, positionally
print("Partial x derivative at (3, 1):", grad(f, argnums=0)(3., 1.))
print("Partial y derivative at (3, 1):", grad(f, argnums=1)(3., 1.))

# We can get both partials at the same time
print("Gradient vector at (3, 1):", grad(f, (0, 1))(3., 1.))
g = [float(z) for z in grad(f, (0, 1))(3., 1.)]
print("Gradient vector at (3, 1):", g)_____no_output_____

We can plot some of the vectors of a gradient. Let's consider
$$f(x, y) = x^2 + y^2$$
The partial derivatives are
$$\frac{\partial f}{\partial x} = 2x$$

$$\frac{\partial f}{\partial y} = 2y$$

So the gradient is $$\nabla f = [2x, 2y]^T$$_____no_output_____

def f(x, y):
  return x * x + y * y

partial_x = grad(f, argnums=0)
partial_y = grad(f, argnums=1)

xs = np.arange(-1, 1.25, 0.25)
ys = np.arange(-1, 1.25, 0.25)

plt.clf()

# Compute and plot the gradient vectors
for x in xs:
  for y in ys:
    u = partial_x(x, y)
    v = partial_y(x, y)
    plt.arrow(x, y, u, v, length_includes_head=True,
              head_width=0.1)
plt.xlim(-3, 3)
plt.ylim(-3, 3)
plt.show()_____no_output_____

## Jacobians and Hessians with Jax

Let's verify some of the example from the slides.

Let $f(x) = \sum_i{x_i} = x_1 + \cdots + x_n$. Then the gradient is $\nabla f (x) = [1, \ldots, 1]^T.$_____no_output_____

n = 4
def f(x):
  return sum(x)

test_point = [1., 2., 3., 4.]

print("x = ", test_point)
print("Gradient(x):", [float(x) for x in grad(f)(test_point)])

## Try other test points, even random ones:
test_point = np.random.rand(4)
print()
print("x = ", test_point)
print("Gradient(x):", [float(x) for x in grad(f)(test_point)])
_____no_output_____

### Exercise

Compute the gradient of the function that sums the squares of the elements of a vector._____no_output_____

# @title Solution (double click to show)
def sum_squares(x):
  return jnp.dot(x, x)

test_point = np.array([1., 2., 3., 4.])

print("x = ", test_point)
print("Gradient(x):", [float(x) for x in grad(sum_squares)(test_point)])_____no_output_____

Let's try a Jacobian now. In the slides we saw that Jacobian of $f(\mathbf{x}) = Ax$ is $A$. Let's verify with Jax.

_____no_output_____

A = np.array([[1., 2.], [3., 4.]])

def f(x):
  return jnp.dot(A, x)

x = np.array([1., 1.])
jacfwd(f)(x)_____no_output_____# Try some other matrices A and a 3x3 matrix
# Note that Jax handles larger matrices with the same code
# But you'll need a length 3 vector for x
_____no_output_____

We can compute the Hessian by taking the Jacobian twice. In this case, $f(\mathbf{x}) = Ax$ is a linear function, so the second derivatives should all be zero._____no_output_____

A = np.array([[1., 2.], [3., 4.]])

def f(x):
  return jnp.dot(A, x)

def hessian(f):
  return jacfwd(jacrev(f))

x = np.array([1., 1.])
hessian(f)(x)_____no_output_____# Try some other matrices A and a 3x3 matrix

_____no_output_____

### Exercise

Now try to take derivatives of the function $f(\mathbf{x}) = x \cdot A x$. Hints:
* Is it scalar or vector-valued?
* Does it matter if $A$ is symmetric $(A = A^T)$ or anti-symmetric $(A = -A^T)$?_____no_output_____

# Your code here

def f(x):
  """Compute x . A x"""
  pass

# Compute the first and second derivatives at a test point x

_____no_output_____#@title Solution (double-click to show)

A = np.array([[1., 0.], [0., 3.]])

def f(x):
  return jnp.dot(x, jnp.dot(A, x))

x = np.array([2., -1.])
print(grad(f)(x))
print(hessian(f)(x))_____no_output_____

### Exercise: Entropy

Compute the Gradient and Hessian of the Shannon entropy:
$$S = -\sum_{i}{x_i \log x_i}$$

Note that for a test point you'll need to have all the elements positive and summing to 1, so a good choice is $$[1 / n, \ldots, 1 / n]$$
_____no_output_____

## Your code here

def entropy(x):
  pass_____no_output_____#@title Solution (double-click to show)

def entropy(x):
  return - sum(a * jnp.log(a) for a in x)

x = np.array([1./2, 1./2])
print(entropy(x))
print(grad(entropy)(x))
print(hessian(entropy)(x))_____no_output_____

## Example: Linear Regression with Jax

Given some data of the form $(x_i, y_i)$, let's find a best fit line $$ y = m x + b $$ by minimizing the sum of squared errors.

$$ S = \sum_{i}{\left(y_i - (m x_i + b) \right)^2}$$
_____no_output_____

## Adapted from JAX docs: https://coax.readthedocs.io/en/latest/examples/linear_regression/jax.html

# Generate some data using sklearn
X, y = make_regression(n_features=1, noise=10)
X, X_test, y, y_test = train_test_split(X, y)

# Plot the data
plt.scatter([x[0] for x in X], y)
plt.title("Randomly generated dataset")
plt.show()
_____no_output_____

Read through the following code, which minimizes the sum of squared errors for a linear model._____no_output_____

# In JAX, we can specify our parameters as various kinds of Python objects,
# including dictionaries.

# Initial model parameters
params = {
    'w': jnp.zeros(X.shape[1:]),
    'b': 0.
}

# The model function itself, a linear function.
def forward(params, X):
  """y = w x + b"""
  return jnp.dot(X, params['w']) + params['b']

# The loss function we want to minimize, the sum of squared errors
# of the model prediction versus the true values
def sse(params, X, y):
  """Sum of squared errors (mean)"""
  err = forward(params, X) - y
  return jnp.mean(jnp.square(err))

# Function to update our parameters in each step of gradient descent
def update(params, grads, alpha=0.1):
    return jax.tree_multimap(lambda p, g: p - alpha * g, params, grads)

# We'll define a gradient descent function similarly to as before,
# and we'll track the loss function values for plotting
# Note also that we compute our gradients on the training data X and y
# but our loss function on the test data X_test and y_test
def gradient_descent(f, params, X, X_test, y, y_test, alpha=0.1, iterations=30):
  """
  Apply gradient descent to the function f with starting point x_0
  and learning rate \alpha.

  x_{n+1} = x_n - \alpha d_f(x_n)

  """
  grad_fn = grad(f)
  params_ = []
  losses = []

  for _ in range(iterations):
    grads = grad_fn(params, X, y)
    params = update(params, grads, alpha)
    params_.append(params)

    loss = f(params, X_test, y_test)
    losses.append(loss)

  return params_, losses

# Function to plot our residuals to see how the loss function progresses
def plot_residuals(params, model_fn, X, y, color='blue'):
  res = y - model_fn(params, X)
  plt.hist(res, bins=10, color=color, alpha=0.5)

# Find the best fit line
fit_params, losses = gradient_descent(sse, params, X, X_test, y, y_test)

# Plot the decrease in the loss function over iterations.
plt.plot(range(len(losses)), losses)
plt.ylabel("SSE")
plt.xlabel("Iteration")
plt.title("Loss evolution\nMinimizing sum of squared errors")
plt.show()

# Compare the errors of our initial guess model with the final model
# Note the differences in scales
plot_residuals(params, forward, X, y)
plt.title("Histogram of initial residuals (errors for each point)")
plt.show()

plot_residuals(fit_params[-1], forward, X, y, color='green')
plt.title("Histogram of final residuals (errors for each point)")
plt.show()
_____no_output_____# Let's plot the best fit line

# Plot the data
xs = [x[0] for x in X]
plt.scatter(xs, y)
plt.title("Best fit line")

# Plot the best fit line
params = fit_params[-1]
xs = np.arange(min(xs), max(xs), 0.1)
m = float(params['w'])
b = float(params['b'])
ys = [m * x + b for x in xs]

plt.plot(xs, ys, color='black')
plt.show()
_____no_output_____

We can easily use another loss function, like the mean absolute error, where use the absolute value of residuals instead of the square, which reduces the
impact of outliers.

$$ S = \sum_{i}{\left|y_i - (m x_i + b) \right|}$$

This will give us a different best fit line for some data sets._____no_output_____

## Minimize MAE instead of SSE

# MAE is the p=1 case of this function.
def lp_norm(p=2):
  def norm(params, X, y):
    err = forward(params, X) - y
    return jnp.linalg.norm(err, ord=p)
  return norm

# Generate some noisier data
X, y = make_regression(n_features=1, noise=100, bias=5)
X, X_test, y, y_test = train_test_split(X, y)

fit_params, losses = gradient_descent(lp_norm(p=1.), params, X, X_test, y, y_test)

# Plot the data
xs = [x[0] for x in X]
plt.scatter(xs, y)
plt.title("Best fit line")

# Plot the best fit line
params = fit_params[-1]
xs = np.arange(min(xs), max(xs), 0.1)
m = float(params['w'])
b = float(params['b'])
ys = [m*x+b for x in xs]
plt.plot(xs, ys, color='black', label="MAE")

# Compare to SEE best fit line

# Find the best fit line
fit_params, losses = gradient_descent(sse, params, X, X_test, y, y_test)

# Plot the best fit line
params = fit_params[-1]
xs = np.arange(min(xs), max(xs), 0.1)
m = float(params['w'])
b = float(params['b'])
ys = [m*x+b for x in xs]
plt.plot(xs, ys, color='green', label="SSE")
plt.legend()
plt.show()
_____no_output__________no_output_____


# Notebook from udapy/MadeWithML
Path: notebooks/05_PyTorch.ipynb

 Made With ML

Applied ML · MLOps · Production

Join 30K+ developers in learning how to responsibly deliver value with ML.

    🔥  Among the top ML repositories on GitHub

_____no_output_____# PyTorch

In this notebook, we'll learn the basics of [PyTorch](https://pytorch.org), which is a machine learning library used to build dynamic neural networks. We'll learn about the basics, like creating and using Tensors. _____no_output_____

_____no_output_____# Set up_____no_output_____

import numpy as np
import torch_____no_output_____SEED = 1234_____no_output_____# Set seed for reproducibility
np.random.seed(seed=SEED)
torch.manual_seed(SEED)_____no_output_____

# Basics_____no_output_____

# Creating a random tensor
x = torch.randn(2, 3) # normal distribution (rand(2,3) -> uniform distribution)
print(f"Type: {x.type()}")
print(f"Size: {x.shape}")
print(f"Values: \n{x}")Type: torch.FloatTensor
Size: torch.Size([2, 3])
Values:
tensor([[ 0.0461,  0.4024, -1.0115],
        [ 0.2167, -0.6123,  0.5036]])
# Zero and Ones tensor
x = torch.zeros(2, 3)
print (x)
x = torch.ones(2, 3)
print (x)tensor([[0., 0., 0.],
        [0., 0., 0.]])
tensor([[1., 1., 1.],
        [1., 1., 1.]])
# List → Tensor
x = torch.Tensor([[1, 2, 3],[4, 5, 6]])
print(f"Size: {x.shape}")
print(f"Values: \n{x}")Size: torch.Size([2, 3])
Values:
tensor([[1., 2., 3.],
        [4., 5., 6.]])
# NumPy array → Tensor
x = torch.Tensor(np.random.rand(2, 3))
print(f"Size: {x.shape}")
print(f"Values: \n{x}")Size: torch.Size([2, 3])
Values:
tensor([[0.1915, 0.6221, 0.4377],
        [0.7854, 0.7800, 0.2726]])
# Changing tensor type
x = torch.Tensor(3, 4)
print(f"Type: {x.type()}")
x = x.long()
print(f"Type: {x.type()}")Type: torch.FloatTensor
Type: torch.LongTensor

# Operations_____no_output_____

# Addition
x = torch.randn(2, 3)
y = torch.randn(2, 3)
z = x + y
print(f"Size: {z.shape}")
print(f"Values: \n{z}")Size: torch.Size([2, 3])
Values:
tensor([[ 0.0761, -0.6775, -0.3988],
        [ 3.0633, -0.1589,  0.3514]])
# Dot product
x = torch.randn(2, 3)
y = torch.randn(3, 2)
z = torch.mm(x, y)
print(f"Size: {z.shape}")
print(f"Values: \n{z}")Size: torch.Size([2, 2])
Values:
tensor([[ 1.0796, -0.0759],
        [ 1.2746, -0.5134]])
# Transpose
x = torch.randn(2, 3)
print(f"Size: {x.shape}")
print(f"Values: \n{x}")
y = torch.t(x)
print(f"Size: {y.shape}")
print(f"Values: \n{y}")Size: torch.Size([2, 3])
Values:
tensor([[ 0.8042, -0.1383,  0.3196],
        [-1.0187, -1.3147,  2.5228]])
Size: torch.Size([3, 2])
Values:
tensor([[ 0.8042, -1.0187],
        [-0.1383, -1.3147],
        [ 0.3196,  2.5228]])
# Reshape
x = torch.randn(2, 3)
z = x.view(3, 2)
print(f"Size: {z.shape}")
print(f"Values: \n{z}")Size: torch.Size([3, 2])
Values:
tensor([[ 0.4501,  0.2709],
        [-0.8087, -0.0217],
        [-1.0413,  0.0702]])
# Dangers of reshaping (unintended consequences)
x = torch.tensor([
    [[1,1,1,1], [2,2,2,2], [3,3,3,3]],
    [[10,10,10,10], [20,20,20,20], [30,30,30,30]]
])
print(f"Size: {x.shape}")
print(f"x: \n{x}\n")

a = x.view(x.size(1), -1)
print(f"\nSize: {a.shape}")
print(f"a: \n{a}\n")

b = x.transpose(0,1).contiguous()
print(f"\nSize: {b.shape}")
print(f"b: \n{b}\n")

c = b.view(b.size(0), -1)
print(f"\nSize: {c.shape}")
print(f"c: \n{c}")Size: torch.Size([2, 3, 4])
x:
tensor([[[ 1,  1,  1,  1],
         [ 2,  2,  2,  2],
         [ 3,  3,  3,  3]],

        [[10, 10, 10, 10],
         [20, 20, 20, 20],
         [30, 30, 30, 30]]])

Size: torch.Size([3, 8])
a:
tensor([[ 1,  1,  1,  1,  2,  2,  2,  2],
        [ 3,  3,  3,  3, 10, 10, 10, 10],
        [20, 20, 20, 20, 30, 30, 30, 30]])

Size: torch.Size([3, 2, 4])
b:
tensor([[[ 1,  1,  1,  1],
         [10, 10, 10, 10]],

        [[ 2,  2,  2,  2],
         [20, 20, 20, 20]],

        [[ 3,  3,  3,  3],
         [30, 30, 30, 30]]])

Size: torch.Size([3, 8])
c:
tensor([[ 1,  1,  1,  1, 10, 10, 10, 10],
        [ 2,  2,  2,  2, 20, 20, 20, 20],
        [ 3,  3,  3,  3, 30, 30, 30, 30]])
# Dimensional operations
x = torch.randn(2, 3)
print(f"Values: \n{x}")
y = torch.sum(x, dim=0) # add each row's value for every column
print(f"Values: \n{y}")
z = torch.sum(x, dim=1) # add each columns's value for every row
print(f"Values: \n{z}")Values:
tensor([[ 0.5797, -0.0599,  0.1816],
        [-0.6797, -0.2567, -1.8189]])
Values:
tensor([-0.1000, -0.3166, -1.6373])
Values:
tensor([ 0.7013, -2.7553])

# Indexing, Splicing and Joining_____no_output_____

x = torch.randn(3, 4)
print (f"x: \n{x}")
print (f"x[:1]: \n{x[:1]}")
print (f"x[:1, 1:3]: \n{x[:1, 1:3]}")x:
tensor([[ 0.2111,  0.3372,  0.6638,  1.0397],
        [ 1.8434,  0.6588, -0.2349, -0.0306],
        [ 1.7462, -0.0722, -1.6794, -1.7010]])
x[:1]:
tensor([[0.2111, 0.3372, 0.6638, 1.0397]])
x[:1, 1:3]:
tensor([[0.3372, 0.6638]])
# Select with dimensional indicies
x = torch.randn(2, 3)
print(f"Values: \n{x}")

col_indices = torch.LongTensor([0, 2])
chosen = torch.index_select(x, dim=1, index=col_indices) # values from column 0 & 2
print(f"Values: \n{chosen}")

row_indices = torch.LongTensor([0, 1])
col_indices = torch.LongTensor([0, 2])
chosen = x[row_indices, col_indices] # values from (0, 0) & (2, 1)
print(f"Values: \n{chosen}") Values:
tensor([[ 0.6486,  1.7653,  1.0812],
        [ 1.2436,  0.8971, -0.0784]])
Values:
tensor([[ 0.6486,  1.0812],
        [ 1.2436, -0.0784]])
Values:
tensor([ 0.6486, -0.0784])
# Concatenation
x = torch.randn(2, 3)
print(f"Values: \n{x}")
y = torch.cat([x, x], dim=0) # stack by rows (dim=1 to stack by columns)
print(f"Values: \n{y}")Values:
tensor([[ 0.5548, -0.0845,  0.5903],
        [-1.0032, -1.7873,  0.0538]])
Values:
tensor([[ 0.5548, -0.0845,  0.5903],
        [-1.0032, -1.7873,  0.0538],
        [ 0.5548, -0.0845,  0.5903],
        [-1.0032, -1.7873,  0.0538]])

# Gradients_____no_output_____* $ y = 3x + 2 $
* $ z = \sum{y}/N $
* $ \frac{\partial(z)}{\partial(x)} = \frac{\partial(z)}{\partial(y)} \frac{\partial(y)}{\partial(x)} = \frac{1}{N} * 3 = \frac{1}{12} * 3 = 0.25 $_____no_output_____

# Tensors with gradient bookkeeping
x = torch.rand(3, 4, requires_grad=True)
y = 3*x + 2
z = y.mean()
z.backward() # z has to be scalar
print(f"x: \n{x}")
print(f"x.grad: \n{x.grad}")x:
tensor([[0.7379, 0.0846, 0.4245, 0.9778],
        [0.6800, 0.3151, 0.3911, 0.8943],
        [0.6889, 0.8389, 0.1780, 0.6442]], requires_grad=True)
x.grad:
tensor([[0.2500, 0.2500, 0.2500, 0.2500],
        [0.2500, 0.2500, 0.2500, 0.2500],
        [0.2500, 0.2500, 0.2500, 0.2500]])

# CUDA tensors_____no_output_____

# Is CUDA available?
print (torch.cuda.is_available())False

If False (CUDA is not available), let's change that by following these steps: Go to *Runtime* > *Change runtime type* > Change *Hardware accelertor* to *GPU* > Click *Save*_____no_output_____

import torch_____no_output_____# Is CUDA available now?
print (torch.cuda.is_available())True
# Set device
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print (device)cuda
x = torch.rand(2,3)
print (x.is_cuda)
x = torch.rand(2,3).to(device) # sTensor is stored on the GPU
print (x.is_cuda)False
True


# Notebook from lfunderburk/Interactive_Jupyter
Path: Notebooks/Introduction.ipynb

# !pip install IPython --user
# !pip install matplotlib --user
# !pip install ipywidgets --user_____no_output_____from IPython.display import HTML
HTML('''
The raw code for this IPython notebook is by default hidden for easier reading.
To toggle on/off the raw code, click here.''')_____no_output_____

Introducting Interactivity into Jupyter Notebooks

Laura Gutierrez Funderburk$\mid$ SciProg $\mid$ Simon Fraser University
_____no_output_____
Abstract

In this workshop we will explore the use of Jupyter Widgets and how they allow developers to add an interactive element to Jupyter notebooks._____no_output_____
Introduction to Jupyter Widgets

Jupyter Widgets are eventful python objects that have a representation in the browser, often as a control like a slider, textbox, buttons, among others.

Widgets can be used to incorporate interactive GUIs within a notebook. Widgets can also be used to "sincronize stateful and stateless information between Python and JavaScript" $^{[1]}$.

In my work as a developer, I have found widgets useful when sharing technical information with audiences who do not have a technical background, for example, when building learning material for K12 students._____no_output_____
Example I: Rolling a Die

Let us run a simple probability experiment. Suppose we want to roll a die multiple times, and we wish to track and plot the number of times we obtain each face. We can use basic python coding along with Matplotlib to plot what we wish.

We can also use Jupyter widgets to create a nice interface that allows us to perform several probability experiments and observe the distribution.

Using the slider below, change the number of trials and press the Run Interact button multiple times to run the experiment. _____no_output_____

#Code written by Laura Gutierrez Funderburk

# Import libraries
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import random
from ipywidgets import widgets, interact_manual,interact

# This function takes as input an integer number_turns indicating the number of times we wish to repeat
#   experiment
# This function plots two figures: the left hand side figure is a scatter plot where each point is determined
#  by the pair (n,y) where n is the nth trial and y is the outcome obtained after rolling a die
#  The right hand side is a bar plot that displays the frequency of each face after all trials.
def plot_random(number_turns):

    # Initialize figure
    fig = plt.figure(figsize=(15,10))

    # Initialize left hand side figure
    ax = fig.add_subplot(221)

    # Set x and y limits
    ax.set_xlim([0,number_turns + 1])
    ax.set_ylim([0,7])

    # Draw grid
    ax.grid(True)

    # Compute die outcome as a random choice between integers 1 through 6, repeat for number_turns times
    die_outcome = [random.choice([1,2,3,4,5,6]) for i in range(number_turns)]

    # Compute y values
    y = [i for i in range(1,number_turns+1)]

    # Plot outcome and label plot
    ax.scatter(y,die_outcome,color='#000000',label="Experiment Outcome")
    ax.set_ylabel("Outcome",fontsize=15)
    ax.set_xlabel("Number of trials",fontsize=15)
    ax.set_title("Rolling a Die",fontsize=25)

    # Initialize right hand side figure
    ax3 = fig.add_subplot(222)

    # Compute frequency of each outcome
    freq = [die_outcome.count(i) for i in range(1,7)]

    # Draw grid
    ax3.grid(True)

    # Plot outcome and label plot
    ax3.bar([1,2,3,4,5,6],freq)
    ax3.set_ylabel("Frequency",fontsize=15)
    ax3.set_xlabel("Face",fontsize=15)
    ax3.set_title("Distribution",fontsize=25)

    # Show plot
    plt.show()

# Initalize and configure widget: connect function and manual widget
style = {'description_width': 'initial'}
interact_manual(plot_random,number_turns=widgets.IntSlider(
            value=2,
            min=1,
            max=1000,
            step=1,
            description='Number of trials',
            disabled=False,
            continuous_update=False,
            orientation='horizontal',
            readout=True,
            readout_format='d',
            style =style
));_____no_output_____

Example II: Modelling a Disease Outbreak

Widgets can also be useful when sharing technical information among scientists.

For instance, suppose we wish to simulate a disease outbreak using a discrete version of the S.I.R. model. Suppose we are given the following data set:

| Date  |Day Number |Susceptible | Infected |
|-------||-------------|----------|
|June 19 1665|0|254|7|
|July 3 1665|14|235|14|
|July 19 1665|28|201|22|
|Aug 3 1665|42|153|29|
|Aug 19 1665|56|121| 21|
|Sept 3 1665|70|108|8|
|Sept 19 1665|84|121|21|
|Oct 3 1665| 98|NA | NA|
|Oct 19 1665|112| 83 | 0|

We can code the discrete version of the SIR model. Suppose we wish to share our implementation of this model with a colleague so that they can perform analysis of the outbreak.

What if the colleague we wish to share information with does not have a mathematical nor coding background?

This is where the power of Jupyter Widgets comes in handy. We can provide a visual interface for them to interact with the model.

Using the sliders and the boxes, change the values of initial conditions. Press the Run Interact button once you are ready and simulate an outbreak. _____no_output_____

# Code written and revised by Laura Gutierrez Funderburk and Dr. Cedric Chauve

import matplotlib.pyplot as plt
import numpy as np
from math import ceil

# This function takes as input a vector y holding all initial values,
#    n the number of time points
#    beta: beta parameter of the SIR
#    gamma: gamma parameter of the SIR
#    S1,I1,R1 = initial values

def discrete_SIR(S1,I1,R1,n,beta,gamma):
    # Empy arrays for each class
    S = []
    I = []
    R = []
    N = S1+I1+R1

    # Append initial values
    S.append(S1)
    I.append(I1)
    R.append(R1)

    # apply SIR model: iterate over the total number of days - 1
    for i in range(n-1):
        S_next = S[i] - (beta/N)*((S[i]*I[i]))
        S.append(S_next)

        I_next = I[i] + (beta/N)*((S[i]*I[i])) - gamma*I[i]
        I.append(I_next)

        R_next = R[i] + gamma * I[i]
        R.append(R_next)

    # return arrays S,I,R whose entries are various values for susceptible, infected, removed
    return((S,I,R))

# This function takes as input initial values of susceptible, infected and removed, number of days, beta and gamma
# it plots the SIR model with the above conditions
def plot_SIR(S1,I1,R1,n,beta,gamma):

    # Initialize figure
    fig = plt.figure(facecolor='w',figsize=(17,5))
    ax  = fig.add_subplot(111,facecolor = '#ffffff')

    # Compute SIR values for our initial data and parameters
    (S_f,I_f,R_f) = discrete_SIR(S1,I1,R1,n,beta,gamma)

    # Set x axis
    x = [i for i in range(n)]

    # Scatter plot of evolution of susceptible over the course of x days
    plt.scatter(x,S_f,c= 'b',label='Susceptible')

    # Scatter plot of evolution of infected over the course of x days
    plt.scatter(x,I_f,c='r',label='Infected')

    # Scatter plot of evolution of removed over the course of x days
    plt.scatter(x,R_f,c='g',label='Removed')

    # Make the plot pretty
    plt.xlabel('Time (days)')
    plt.ylabel('Number of individuals')
    plt.title('Simulation of a disease outbreak using the SIR model')
    legend = ax.legend()
    plt.show()

    # Print messages to aid student understand and interpret what is happening in the plot
    print("SIMULATION DATA\n")
    print("Beta: " + str(beta))
    print("Gamma: " + str(gamma))
    print("\n")
    print("Initial Conditions:")
    print("Total number of Susceptible: "  + str(ceil(S_f[0])))
    print("Total number of Infected: "  + str(ceil(I_f[0])))
    print("Total number of Removed: "  + str(ceil(R_f[0])))
    print("\n")
    print("After " + str(n) + " days:")
    print("Total number of Susceptible: "  + str(ceil(S_f[n-1])))
    print("Total number of Infected: "  + str(ceil(I_f[n-1])) )
    print("Total number of Removed: "  + str(ceil(R_f[n-1])))
# Tweaking initial Values
from ipywidgets import widgets, interact, interact_manual

# Set function above so that the user can set all parameters and manually start simulation
s = {'description_width': 'initial'}
interact_manual(plot_SIR,
        S1  =widgets.IntSlider(value = 254,
                               min = 200,
                               max = 1000,
                               step = 1,
                               style=s,
                               description="Susceptible Initial",
                               disabled=False,
                               orientation='horizontal',
                               readout=True),
        I1 = widgets.IntSlider(value = 7,
                               min = 0,
                               max = 500,
                               step = 1,
                               style=s,
                               description="Infected Initial",
                               disabled=False,
                               orientation='horizontal',
                               readout=True),
         R1 = widgets.IntSlider(value = 0,
                               min = 0,
                               max = 500,
                               step = 1,
                               style=s,
                               description="Removed Initial",
                               disabled=False,
                               orientation='horizontal',
                               readout=True),
         n = widgets.IntSlider(value = 112,
                               min = 0,
                               max = 500,
                               step = 1,
                               style=s,
                               description="Time (days)",
                               disabled=False,
                               orientation='horizontal',
                               readout=True),
         beta = widgets.FloatText(
    value=1.50,
    description=r'$ \beta$ parameter',
    disabled=False,
    style = s,
    step=0.01
),
        gamma = widgets.FloatText(
    value=1.50,
    description= r'$ \gamma$ parameter',
    disabled=False,
    style = s,
    step=0.01
)
        );
_____no_output_____

Summary

In this notebooks we learned how Jupyter Widgets can help up introduce interactivity into our notebooks and explored two examples in different scenarios.

In the next notebook, you will have an opportunity to learn about different Jupyter Widgets and to implement your own widget into a simple Python function._____no_output_____
References

$[1]$ https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20Basics.html_____no_output_____