# Causal Impact

Will Fuks

https://github.com/WillianFuks/tfcausalimpact

[LinkedIn](https://www.linkedin.com/in/willian-fuks-62622217/)

https://github.com/WillianFuks/pyDataSP-tfcausalimpact

```sh
git clone git@github.com:WillianFuks/pyDataSP-tfcausalimpact.git
cd pyDataSP-tfcausalimpact/
python3.9 -m venv .env
source .env/bin/activate
pip install -r requirements.txt

.env/bin/jupyter notebook
```

```python
import daft
import os
import collections

os. environ['TF_CPP_MIN_LOG_LEVEL'] = '3'

import matplotlib.pyplot as plt
from matplotlib import rc
from IPython.display import HTML
import tensorflow as tf
import tensorflow_probability as tfp
import seaborn as sns
import pandas as pd
import numpy as np

# Attempts to disable TF warnings
tf.get_logger().setLevel('ERROR')
tf.autograph.set_verbosity(tf.compat.v1.logging.ERROR)
import logging
tf.get_logger().setLevel(logging.ERROR)

# TFP namespaces
tfd = tfp.distributions
tfb = tfp.bijectors

# Remove prompt from notebook css
HTML(open('styles/custom.css').read())
```

## Here's our final destination:

## Our Journey:

1. Causality

2. Bayesian Time Series

3. Causal Impact

## Causality Is Simple!

```python
rc("font", family="serif", size=12)
rc("text", usetex=False)

pgm = daft.PGM(grid_unit=4.0, node_unit=2.5)
rect_params = {"lw": 2}
edge_params = {
    'linewidth': 1,
    'head_width': .8
}

pgm.add_node("rain", r"$Rain$", 0.5, 1.5, scale=1.5, fontsize=24)
pgm.add_node("wet", r"$Wet$", 2.5 + 0.2, 1.5, scale=1.5, fontsize=24)
pgm.add_edge("rain", "wet", plot_params=edge_params)
pgm.render();
```

## Until It's Not...

```python
pgm = daft.PGM(grid_unit=4.0, node_unit=4.5)
pgm.add_node("fatigue", r"$Fatigue Train$", 0.5, 1.5, scale=1.5, fontsize=24)
pgm.add_node("perf", r"$Performance$", 2.5 + 0.2, 1.5, scale=1.5, fontsize=24)
pgm.add_edge("fatigue", "perf", plot_params=edge_params)
pgm.render();
```

```python
pgm = daft.PGM(grid_unit=4.0, node_unit=2.5)
pgm.add_node("diet", r"$Diet$", 0.5, 3, scale=1.5, fontsize=24)
pgm.add_node("rest", r"$Rest$", 0.5, 1.5, scale=1.5, fontsize=24)
pgm.add_node("volume", r"$Volume$", 0.5, 0, scale=1.5, fontsize=24)
pgm.add_node("fatigue", r"$Fatigue$", 0.5, -1.5, scale=1.5, fontsize=24)
pgm.add_node("perf", r"$Performance$", 2.5 + 0.2, 1.5, scale=2.5, fontsize=24)
pgm.add_edge("diet", "perf", plot_params=edge_params)
pgm.add_edge("volume", "perf", plot_params=edge_params)
pgm.add_edge("rest", "perf", plot_params=edge_params)
pgm.add_edge("fatigue", "perf", plot_params=edge_params)

# pgm.add_edge("diet", "rest", plot_params=edge_params)
# pgm.add_edge("rest", "diet", plot_params=edge_params)
# pgm.add_edge("diet", "volume", plot_params=edge_params)
# pgm.add_edge("volume", "diet", plot_params=edge_params)
# pgm.add_edge("diet", "fatigue", plot_params=edge_params)
# pgm.add_edge("fatigue", "diet", plot_params=edge_params)

pgm.render();
```

## So How To Compute Causality?!

### Correlations?

## Let's Explore The Idea

### Tensorflow Probability

(Random Variables)

```python
import os
import tensorflow as tf
import tensorflow_probability as tfp

# tf.get_logger().setLevel('INFO')
# os. environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
# tf.autograph.set_verbosity(1)

tfd = tfp.distributions
tfb = tfp.bijectors
```

```python
X = tfd.Normal(loc=2, scale=1)
sns.displot(X.sample(1000), kde=True);
```

```python
b_X = tfd.TransformedDistribution(
    tfd.Normal(loc=2, scale=1),
    bijector=tfb.Exp()
)
sns.displot(b_X.sample(1000), kde=True);
```

```python

```

## Suppose This (Simplified) Structure

```python
pgm = daft.PGM(grid_unit=4.0, node_unit=2.5)
pgm.add_node("diet", r"$Diet$", 0., 0., scale=1.5, fontsize=24)
pgm.add_node("volume", r"$Volume$", 1.25, 0, scale=1.5, fontsize=24)
pgm.add_node("perf", r"$Performance$", 3., 0., scale=2.5, fontsize=24)
pgm.add_edge("diet", "volume", plot_params=edge_params)
pgm.add_edge("volume", "perf", plot_params=edge_params)

pgm.render();
```

## And Respective Data

```python
dist = tfd.JointDistributionNamed(
    {
        'diet': tfd.Normal(loc=3, scale=1),
        'volume': lambda diet: tfd.Normal(diet * 2, scale=0.5),
        'performance': lambda volume: tfd.Normal(volume * 1.3, scale=0.3)
    }
)
data = dist.sample(3000)
data = pd.DataFrame(data, columns=['performance', 'diet', 'volume'])
# data.set_index(pd.date_range(start='20200101', periods=len(data)), inplace=True)
# data
```

## Linear Regression Keras

$$ performance = W\cdot[x_{diet}, x_{volume}]^T$$

```python
import tensorflow as tf

linear_model = tf.keras.Sequential([
    tf.keras.layers.Dense(units=1, use_bias=False)
])

linear_model.compile(
    optimizer=tf.optimizers.Adam(learning_rate=0.1),
    loss=tf.keras.losses.MeanSquaredError()
)

linear_model.fit(
    data[['diet']],
    data['performance'],
    epochs=100,
    verbose=0,
)

w = linear_model.get_weights()
print(f"Linear Relationship is: {w[0][0][0]:.2f}")
```

```python
linear_model = tf.keras.Sequential([
    tf.keras.layers.Dense(units=1, use_bias=False)
])

linear_model.compile(
    optimizer=tf.optimizers.Adam(learning_rate=0.1),
    loss=tf.keras.losses.MeanSquaredError()
)

linear_model.fit(
    data[['volume']],
    data['performance'],
    epochs=100,
    verbose=0,
)

w = linear_model.get_weights()
print(f"Linear Relationship is: {w[0][0][0]:.2f}")
```

```python
linear_model = tf.keras.Sequential([
    tf.keras.layers.Dense(units=1, use_bias=False)
])

linear_model.compile(
    optimizer=tf.optimizers.Adam(learning_rate=0.1),
    loss=tf.keras.losses.MeanSquaredError()
)

linear_model.fit(
    data[['diet', 'volume']],
    data['performance'],
    epochs=100,
    verbose=0,
)

w = linear_model.get_weights()
print(f"Linear Relationship is: {w[0]}")
```

## Bayesian Linear Regression

### Recipe!

$$\begin{equation} \label{eq1}
\begin{split}
P(A|B) & = \frac{P(B|A)P(A)}{P(B)} = \frac{P(A, B)}{P(B)}
\end{split}
\end{equation}
$$

$$\begin{equation} \label{eq1}
\begin{split}
P(\theta|D) & = \frac{P(\theta, D)}{P(D)}
\end{split}
\end{equation}
$$

## Step 1: Priors

```python
pgm = daft.PGM(grid_unit=4.0, node_unit=2.5)
pgm.add_node("diet", r"$w_{diet}$", 0., 0., scale=1.5, fontsize=24)
pgm.add_node("volume", r"$w_{volume}$", 1.25, 0, scale=1.5, fontsize=24)
pgm.add_node("sigma", r"$\sigma^2$", 2.5, 0, scale=1.5, fontsize=24)
pgm.add_node("perf", r"$Performance$", 1.25, -2, scale=2.25, fontsize=24)

pgm.add_edge("diet", "perf", plot_params=edge_params)
pgm.add_edge("volume", "perf", plot_params=edge_params)
pgm.add_edge("sigma", "perf", plot_params=edge_params)
pgm.render();
```

\begin{equation} \label{eq1}
\begin{split}
w_{diet} & \sim N(3, 5)   \\
w_{volume} & \sim N(3, 5) \\
\sigma^2 & \sim Exp(2) \\
performance & \sim N(w_{diet} \cdot x_{diet} + w_{volume} \cdot x_{volume}, \sigma^2)
\end{split}
\end{equation}

```python
joint_dist = tfd.JointDistributionNamedAutoBatched(dict(
    sigma=tfd.Exponential(2),

    w_diet=tfd.Normal(loc=3, scale=5),

    w_volume=tfd.Normal(loc=3, scale=5),

    performance=lambda sigma, w_diet, w_volume: tfd.Normal(loc=data['diet'].values * w_diet + data['volume'].values * w_volume, scale=sigma)

))
```

```python
prior_samples = joint_dist.sample(500)
nrows = 3
labels = ['sigma', 'w_diet', 'w_volume']
fig, axes = plt.subplots(nrows=nrows, ncols=1, figsize=(10, 8))

for i in range(nrows):
    sns.histplot(prior_samples[labels[i]], kde=True, ax=axes[i], label=labels[i]);
    axes[i].legend();
```

# Step 2: Joint Distribution

$$P(\theta, X)$$

```python
def target_log_prob_fn(sigma, w_diet, w_volume):
    return joint_dist.log_prob(sigma=sigma, w_diet=w_diet, w_volume=w_volume, performance=data['performance'].values)
```

# Step 3: MCMC

```python
num_results = int(1e4)
num_burnin_steps = int(1e3)

kernel = tfp.mcmc.HamiltonianMonteCarlo(
    target_log_prob_fn=target_log_prob_fn,
    step_size=0.3,
    num_leapfrog_steps=3
)

kernel = tfp.mcmc.TransformedTransitionKernel(
    inner_kernel=kernel,
    bijector=[tfb.Exp(), tfb.Identity(), tfb.Identity()]
)

kernel = tfp.mcmc.DualAveragingStepSizeAdaptation(
    inner_kernel=kernel,
    num_adaptation_steps=int(num_burnin_steps * 0.8)
)
```

```python
@tf.function(autograph=False)
def sample_chain():
    return tfp.mcmc.sample_chain(
        num_results=num_results,
        num_burnin_steps=num_burnin_steps,
        kernel=kernel,
        current_state=[
            tf.constant(0.5, dtype=tf.float32),
            tf.constant(0.3, dtype=tf.float32),
            tf.constant(0.2, dtype=tf.float32)
        ],
        trace_fn=lambda _, pkr: [pkr.inner_results.inner_results.accepted_results.step_size,
                                 pkr.inner_results.inner_results.log_accept_ratio]
    )
```

```python
samples, [step_size, log_accept_ratio] = sample_chain()
```

```python
p_accept = tf.reduce_mean(tf.exp(tf.minimum(log_accept_ratio, 0.)))
p_accept
```

```python
nrows = 3
labels = ['sigma', 'w_diet', 'w_volume']
fig, axes = plt.subplots(nrows=nrows, ncols=2, figsize=(10, 8))

axes[0][0].set_title('Prior')
axes[0][1].set_title('Posterior')

for i in range(nrows):
    sns.histplot(prior_samples[labels[i]], kde=True, ax=axes[i][0], label=labels[i]);
    sns.histplot(samples[i], kde=True, ax=axes[i][1], label=labels[i]);
    axes[i][0].legend();
    axes[i][1].legend();
plt.tight_layout()
```

```python
sigmas, w_diets, w_volumes = samples
```

```python
performance_estimated = (
    tf.linalg.matmul(data['diet'].values[..., tf.newaxis], w_diets[..., tf.newaxis], transpose_b=True) +
    tf.linalg.matmul(data['volume'].values[..., tf.newaxis], w_volumes[..., tf.newaxis], transpose_b=True)
)
```

```python
quanties_performance = tf.transpose(tfp.stats.percentile(
    performance_estimated, [2.5, 97.5], axis=1, interpolation=None, keepdims=False,
))
```

```python
mean_y = tf.math.reduce_mean(performance_estimated, axis=1)
mean_y
```

```python
std_y = tf.math.reduce_std(performance_estimated, axis=1)
std_y
```

```python
fig, ax = plt.subplots(figsize=(9, 8))
ax.errorbar(
    x=data['performance'],
    y=mean_y,
    yerr=2*std_y,
    fmt='o',
    capsize=2,
    label='predictions +/- CI'
)

sns.regplot(
    x=data['performance'],
    y=mean_y,
    scatter=False,
    line_kws=dict(alpha=0.5),
    label='performance / predicted performance',
    truncate=False,
    ax=ax
);
ax.set(ylabel='predicted performance');
plt.legend();
```

When we fit `diet` and `volume` together

`diet` seems to lose causality!!

## Interesting Problem

```python
pgm = daft.PGM(grid_unit=4.0, node_unit=2.5)
pgm.add_node("diet", r"$Diet$", 0.5, 3, scale=1.5, fontsize=24)
pgm.add_node("rest", r"$Rest$", 0.5, 1.5, scale=1.5, fontsize=24)
pgm.add_node("volume", r"$Volume$", 0.5, 0, scale=1.5, fontsize=24)
pgm.add_node("fatigue", r"$Fatigue$", 0.5, -1.5, scale=1.5, fontsize=24)
pgm.add_node("question", r"$?$", 2.5 + 0.2, 1.5, scale=1.5, fontsize=24)
pgm.add_node("perf", r"$Performance$", 5, 1.5, scale=2.5, fontsize=24)

pgm.add_edge("diet", "question", plot_params=edge_params)
pgm.add_edge("volume", "question", plot_params=edge_params)
pgm.add_edge("rest", "question", plot_params=edge_params)
pgm.add_edge("fatigue", "question", plot_params=edge_params)
pgm.add_edge("question", "perf", plot_params=edge_params)

pgm.render();
```

```python

```

## Correlations won't work

## Better Solution?

## A/B Test!

## Not so fast...

## Control Group Fail...

## Solution: Quasi Experiments

## Structural Time Series

## Important thing is: Structures

- [AutoRegressive](https://github.com/tensorflow/probability/blob/v0.11.1/tensorflow_probability/python/sts/autoregressive.py#L258)
- [DynamicRegression](https://github.com/tensorflow/probability/blob/v0.11.1/tensorflow_probability/python/sts/dynamic_regression.py#L230)
- [LocalLevel](https://github.com/tensorflow/probability/blob/v0.11.1/tensorflow_probability/python/sts/local_level.py#L254)
- [Seasonal](https://github.com/tensorflow/probability/blob/v0.11.1/tensorflow_probability/python/sts/seasonal.py#L688)
- [LocalLinearTrend](https://github.com/tensorflow/probability/blob/v0.11.1/tensorflow_probability/python/sts/local_linear_trend.py#L222)
- [SemiLocalLinearTrend](https://github.com/tensorflow/probability/blob/v0.11.1/tensorflow_probability/python/sts/semilocal_linear_trend.py#L294)
- [SmoothSeasonal](https://github.com/tensorflow/probability/blob/v0.11.1/tensorflow_probability/python/sts/smooth_seasonal.py#L321)
- [Regression](https://github.com/tensorflow/probability/blob/v0.11.1/tensorflow_probability/python/sts/regression.py#L51)
- [SparseLinearRegression](https://github.com/tensorflow/probability/blob/v0.11.1/tensorflow_probability/python/sts/regression.py#L264)

## Local Level

$$\mu_t = \mu_{t-1} + Normal(0, \sigma^2_{\mu})$$

```python
local_level_model = tfp.sts.LocalLevelStateSpaceModel(
    num_timesteps=20,
    level_scale=.1,
    initial_state_prior=tfd.MultivariateNormalDiag(scale_diag=[1.])
)

s = local_level_model.sample(1)
plt.plot(tf.squeeze(s));
```

```python
local_level_model.log_prob(s)
```

## Local And Regression (Model Fit)

```python
data = pd.read_csv('https://raw.githubusercontent.com/WillianFuks/tfcausalimpact/master/tests/fixtures/arma_data.csv', dtype=np.float32)[['y', 'X']]
data.iloc[70:, 0] += 5
data.plot()
plt.axvline(70, 0, 130, linestyle='--', color='r', linewidth=0.85);

y = tf.cast(data['y'].values[:70], tf.float32)
```

```python
local_level = tfp.sts.LocalLevel(
    observed_time_series=y
)
```

```python
regression = tfp.sts.LinearRegression(
    design_matrix=tf.cast(data['X'].values[..., tf.newaxis], tf.float32)
)
```

```python
model = tfp.sts.Sum([local_level, regression], observed_time_series=y)
```

```python
samples, _ = tfp.sts.fit_with_hmc(model, y)
```

```python
one_step_predictive_dist = tfp.sts.one_step_predictive(model, observed_time_series=y, parameter_samples=samples)
```

```python
predictive_means = one_step_predictive_dist.mean()
predictive_means
```

```python
predictive_scales = one_step_predictive_dist.stddev()
predictive_scales
```

```python
plt.figure(figsize=(10, 9))
color = (1.0, 0.4981, 0.0549)
plt.plot(y, label='y', color='k')
# plt.plot(predictive_means[1:], color=color, label='predictive mean')
plt.fill_between(
    np.arange(1, 70),
    predictive_means[1:70] - predictive_scales[1:70],
    predictive_means[1:70] + predictive_scales[1:70],
    alpha=0.4,
    color=color,
    label='predictive std'
)
plt.legend();
```

```python
forecast_dist = tfp.sts.forecast(model, observed_time_series=y, parameter_samples=samples, num_steps_forecast=30)
```

```python
forecast_means = tf.squeeze(forecast_dist.mean())
forecast_scales = tf.squeeze(forecast_dist.stddev())
```

```python
plt.figure(figsize=(10, 9))
plt.plot(data['y'], label='y', color='k')
plt.fill_between(
    np.arange(1, 70),
    predictive_means[1:] - predictive_scales[1:],
    predictive_means[1:] + predictive_scales[1:],
    alpha=0.4,
    color=color,
    label='predictive std'
)
plt.plot(np.arange(70, 100), forecast_means, color='r', label='mean forecast')
plt.fill_between(
    np.arange(70, 100),
    forecast_means - forecast_scales,
    forecast_means + forecast_scales,
    alpha=0.4,
    color='red',
    label='forecast std'
)
plt.legend();
```

## How to obtain causal impact?

### tfcausalimpact

```python
!pip install tfcausalimpact > /dev/null
```

```python
from causalimpact import CausalImpact

data = pd.read_csv('https://raw.githubusercontent.com/WillianFuks/tfcausalimpact/master/tests/fixtures/arma_data.csv')[['y', 'X']]
data.iloc[70:, 0] += 5

pre_period = [0, 69]
post_period = [70, 99]

ci = CausalImpact(data, pre_period, post_period)
```

```python
print(ci.summary())
```

```python
print(ci.summary(output='report'))
```

```python
ci.plot(figsize=(15, 15))
```

```python
ci.model.components_by_name
```

```python
ci.model_samples
```

```python
# https://www.tensorflow.org/probability/examples/Structural_Time_Series_Modeling_Case_Studies_Atmospheric_CO2_and_Electricity_Demand

component_dists = tfp.sts.decompose_by_component(
    ci.model,
    observed_time_series=y,
    parameter_samples=ci.model_samples
)

component_means, component_stddevs = (
    {k.name: c.mean() for k, c in component_dists.items()},
    {k.name: c.stddev() for k, c in component_dists.items()}
)

def plot_components(dates,
                    component_means_dict,
                    component_stddevs_dict):
  x_locator, x_formatter = None, None
  colors = sns.color_palette()
  c1, c2 = colors[0], colors[1]

  axes_dict = collections.OrderedDict()
  num_components = len(component_means_dict)
  fig = plt.figure(figsize=(12, 2.5 * num_components))
  for i, component_name in enumerate(component_means_dict.keys()):
    component_mean = component_means_dict[component_name]
    component_stddev = component_stddevs_dict[component_name]

    ax = fig.add_subplot(num_components, 1, 1 + i)
    ax.plot(dates, component_mean, lw=2)
    ax.fill_between(dates, component_mean-2*component_stddev,
                    component_mean+2*component_stddev,
                    color=c2, alpha=0.5)
    ax.set_title(component_name)
    if x_locator is not None:
      ax.xaxis.set_major_locator(x_locator)
      ax.xaxis.set_major_formatter(x_formatter)
    axes_dict[component_name] = ax
#   fig.autofmt_xdate()
  fig.tight_layout()

plot_components(np.arange(0, 70), component_means, component_stddevs);
```

## Real Example: Bitcoin

```python
! pip install pandas-datareader > /dev/null
```

```python
import datetime
import pandas_datareader as pdr

btc_data = pdr.get_data_yahoo(['BTC-USD'],
                              start=datetime.datetime(2018, 1, 1),
                              end=datetime.datetime(2020, 12, 3))['Close']
btc_data = btc_data.reset_index().drop_duplicates(subset='Date', keep='last').set_index('Date').sort_index()
btc_data = btc_data.resample('D').fillna('nearest')
X_data = pdr.get_data_yahoo(['TWTR', 'GOOGL', 'AAPL', 'MSFT', 'AMZN', 'FB', 'GOLD'],
                            start=datetime.datetime(2018, 1, 1),
                            end=datetime.datetime(2020, 12, 2))['Close']
X_data = X_data.reset_index().drop_duplicates(subset='Date', keep='last').set_index('Date').sort_index()
X_data = X_data.resample('D').fillna('nearest')
data = pd.concat([btc_data, X_data], axis=1)
data.dropna(inplace=True)
data = data.resample('W-Wed').last()  # Weekly is easier to process. We select Wednesday so 2020-10-21 is available.
data = data.astype(np.float32)

np.log(data).plot(figsize=(15, 12))
plt.axvline('2020-10-14', 0, np.max(data['BTC-USD']), lw=2, ls='--', c='red', label='PayPal Impact')
plt.legend(loc='upper left');
```

```python
pre_period=['20180103', '20201014']
post_period=['20201021', '20201125']
ci = CausalImpact(data, pre_period, post_period, model_args={'fit_method': 'vi'})
```

```python
print(ci.summary())
```

```python
ci.plot(figsize=(15, 15))
```

## Tips

## Q - How select covariates?

## A - Yes!

tfp.sts.SparseLinearRegression

## Decompose by [`statsmodels`](https://github.com/statsmodels/statsmodels)

```python
!pip install statsmodels > /dev/null
```

```python
from statsmodels.tsa.seasonal import seasonal_decompose

fig, axes = plt.subplots(4, 1, figsize=(15, 15))
fig.tight_layout()

res = seasonal_decompose(data['BTC-USD'])

axes[0].plot(res.observed)
axes[0].set_title('Observed')

axes[1].plot(res.trend)
axes[1].set_title('Trend')

axes[2].plot(res.seasonal)
axes[2].set_title('Seasonal')

axes[3].plot(res.resid)
axes[3].set_title('Residuals');
```

# Fourier FTW

```python
# https://colab.research.google.com/drive/10VADEg8F5t_FuryEf_ObFfeIFwX-CxII?usp=sharing#scrollTo=UyA6K6GTyJqF

from numpy.fft import rfft, irfft, rfftfreq

def annot_max(x, y, ax=None):
    xmax = x[np.argmax(y)]
    ymax = y.max()
    text= "x={:.3f}, y={:.3f}".format(xmax, ymax)
    #text = f"{xmax=}, {ymax=}, (period: {1./xmax} days)" #Eh, Colab has Python 3.6 ...
    text = f"x={xmax:.3f}, y={ymax:.3f}, (period: {(1./xmax):.2f} weeks)"
    if not ax:
        ax=plt.gca()
    bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="k", lw=0.72)
    arrowprops=dict(arrowstyle="->",connectionstyle="angle,angleA=0,angleB=60")
    kw = dict(xycoords='data',textcoords="axes fraction",
              arrowprops=arrowprops, bbox=bbox_props, ha="right", va="top")
    ax.annotate(text, xy=(xmax, ymax),  xytext=(0.94, 0.96), **kw)

y = data['BTC-USD']
nobs = len(y)
btc_ft = np.abs(rfft(y))
btc_freq = rfftfreq(nobs)
plt.plot(btc_freq[2:], btc_ft[2:])
annot_max(btc_freq[2:], btc_ft[2: ]);
```

# Cross Validation

```python
plt.figure(figsize=(15, 10))
plt.plot(y)
plt.axvline(pd.to_datetime('2018-09-01'), 0, 19000, c='r')
plt.axvline(pd.to_datetime('2019-09-01'), 0, 19000, c='g')

plt.text(pd.to_datetime('2018-03-01'), 18000, 'train', bbox=dict(fill=False, edgecolor='k', linewidth=0.5), fontdict={'fontsize': 20})
plt.text(pd.to_datetime('2019-01-01'), 18000, 'cross-validate', bbox=dict(fill=False, edgecolor='k', linewidth=0.5), fontdict={'fontsize': 20})
plt.text(pd.to_datetime('2020-02-01'), 18000, 'causal impact', bbox=dict(fill=False, edgecolor='k', linewidth=0.5), fontdict={'fontsize': 20});
```

## And that's pretty much it ;)!

## Thanks!


# Question 1

```python
import sympy

def checkPrimeNumber(num):
    if sympy.isprime(num):
        return num
    else:
        return("No Prime")
```

```python
checkPrimeNumber(3)
```

    3

```python
%%writefile check_pri.py
import sympy
'''
Module to check prime Number
'''
def checkPrimeNumber(num):
    '''
    Function to Check Prime takes a Input
    '''
    if sympy.isprime(num):
        '''
        if statement
        '''
        return num
    else:
        return "No Prime"

```

    Overwriting check_pri.py

```python
!pylint check_pri.py
```

    ************* Module check_pri
    check_pri.py:1:0: C0114: Missing module docstring (missing-module-docstring)
    check_pri.py:4:-1: W0105: String statement has no effect (pointless-string-statement)
    check_pri.py:5:0: C0103: Function name "checkPrimeNumber" doesn't conform to snake_case naming style (invalid-name)
    check_pri.py:9:4: R1705: Unnecessary "else" after "return" (no-else-return)
    check_pri.py:12:-1: W0105: String statement has no effect (pointless-string-statement)

    ------------------------------------------------------------------

    Your code has been rated at 2.86/10 (previous run: 2.86/10, +0.00)

```python
%%writefile check_prime.py
import unittest
import check_pri

class TestPrime(unittest.TestCase):
    def checkprimeNumber(self):
        num = 7
        result = chPrime.checkPrimeNumber(num)
        self.assertEqual(result,num)

    def checkNotPrimenumber(self):
        num= 8
        result = chPrime.checkPrimeNumber(num)
        self.assertEqual(result,"No Prime")

if __name__ == "__main__":
    unittest.main()
```

    Writing check_prime.py

```python
!python check_prime.py
```

    ----------------------------------------------------------------------
    Ran 0 tests in 0.000s

    OK

# Question 2

```python
def armstrong_number(k):
	j=k
	a=0
	while k>0:
		b=k%10
		a=a+b**3
		k//=10
	if j==a:
		return j

print("Armstrong numbers between 1 and 1000 are: ",end="")
for i in range(1,1001):
	x=armstrong_number(i)
	if x!=None:
		print("%d"%x,end=", ")
```

    Armstrong numbers between 1 and 1000 are: 1, 153, 370, 371, 407,

```python

```


# Policy-Gradients with the REINFORCE algorithm

**Background**:
In this practical we will train an agent using the REINFORCE algorithm to learn to balance a pole in the OpenAI gym [Cartpole environment](https://gym.openai.com/envs/CartPole-v1).

**Learning objectives**:
* Understand the policy-gradient approach to directly training a parameterised policy to maximise expected future rewards.
* Understand how the policy-gradient theorem allows us to improve the policy using on-policy state = env.reset().

**What is expected of you**:
 * Go through the explanation, keeping the above learning objectives in mind.
 * Fill in the missing code ("#IMPLEMENT-ME") and train a model to solve the Cartpole-v1 environment in OpenAI gym (you solve it when reward=500).

# A Simple Policy-Gradient Cartpole Example

## Introduction

We have seen in your course that there are many different approaches to training RL agents. In this practical we will take a look at REINFORCE - a simple policy-based method. REINFORCE (and policy-based methods in general) directly optimise a parametrised policy in order to maximise future rewards.

We will try to learn a policy $\pi_\theta(a | s)$ which outputs a distribution over the possible actions $a$, given the current state $s$ of the environment. The goal is find a set of parameters $\theta$ to maximise the expected discounted return:
\begin{align}
J(\theta) = \mathbb{E}_{\tau \sim p_\theta} \left[\sum_{t=0}^T \gamma^t r(s_t, a_t)\right],
\end{align}
where $\tau$ is a trajectory sampled from $p_\theta$. The **policy-gradient** theorem gives us the derivative of this objective function:
\begin{align}
    \nabla_\theta J(\theta) = \mathbb{E}_{\tau \sim p_\theta} \left[\left(\sum_{t=0}^{T} \nabla_\theta \log \pi_\theta(a_t|s_t)\right) \left(\sum_{t=0}^T \gamma^t r(s_t, a_t) \right) \right].
\end{align}

**NOTE**:
* We have a policy $\pi_\theta(a|s)$ which tells the agent which action $a$ to take, given the state $s$, and it is parameterised in terms of parameters $\theta$.
* Our goal is to maximise $J(\theta)$ by **choosing actions from this policy** that lead to high future rewards.
* We'll use gradient-based optimisation to update the policy parameters $\theta$. We therefore want the gradient of our objective wrt the policy parameters.
* We use the policy-gradient theorem to find a expression for the gradient. This is an expectation over trajectories from our policy and the environment.
* Since we can now sample trajectories $(s_0, a_0, R_0, s_1, a_1, R_1, \ldots)$ using our policy $\pi_\theta$, we can approximate this gradient using **[Monte-Carlo](https://en.wikipedia.org/wiki/Monte_Carlo_integration)** methods.

This algorithm is called **Monte-Carlo REINFORCE**, and is one type of policy-gradient algorithm. Let's use this to solve the Cartpole environment!

**Monte-Carlo REINFORCE**:

for each episode:
1. sample a trajectory $\tau$ using the policy $\pi_\theta$.
2. compute $\nabla_\theta J(\theta) \approx \left(\sum_{t=0}^{T} \nabla_\theta \log \pi_\theta(a_t|s_t)\right) \left(\sum_{t=0}^T \gamma^t r(s_t, a_t) \right)$.
3. update policy parameters $\theta \leftarrow \theta + \alpha \nabla_\theta J(\theta)$

```python
# import various packages
from collections import deque

import numpy as np
import matplotlib.pyplot as plt
import gym

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.distributions import Categorical
```

```python
# use gpu if available
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# configure matplotlib
%matplotlib inline
plt.rcParams['figure.figsize'] = (15.0, 10.0) # set default size of plots
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'
```

## The Environment

Cartpole is a standard benchmark in reinforcement learning and is a good sandbox for trying things out. The goal is to balance a pendulum on top of a moving cart. We have 2 actions - either push the cart to the left or push to the right. The state space consists of the cart's position and velocity and the pendulum's angle and angular velocity. Let's create the environment and take a look at the state and action spaces:

```python
env = gym.make('CartPole-v1')
print('action space:', env.action_space)
print('observation space:', env.observation_space)
```

    action space: Discrete(2)
    observation space: Box(4,)

    /Users/shahil/miniconda3/envs/rl/lib/python3.6/site-packages/gym/envs/registration.py:14: PkgResourcesDeprecationWarning: Parameters to load are deprecated.  Call .resolve and .require separately.
      result = entry_point.load(False)

Here we can see that there are 2 discrete actions and a continuous state space.

### Taking a few steps

To get a better feel for the environment, we will use a random policy to genrate a short trajectory.

```python
SUB = str.maketrans("t0123456789+", "ₜ₀₁₂₃₄₅₆₇₈₉₊")
print('-'*115)

state = env.reset()
for i in range (5):
    # sample random action
    action = env.action_space.sample()
    # take action in the environment
    new_state, reward, done, _ = env.step(action)

    print('Step t=', i+1, ': (', 'st, at , rt, st+1'.translate(SUB),')')
    print('(', state, ',', action, ',', reward, ',', new_state, ')')
    print('-'*115)

    state = new_state
```

    -------------------------------------------------------------------------------------------------------------------
    Step t= 1 : ( sₜ, aₜ , rₜ, sₜ₊₁ )
    ( [ 0.03687934  0.02502932 -0.00176111  0.0380932 ] , 0 , 1.0 , [ 0.03737992 -0.17006733 -0.00099925  0.33021996] )
    -------------------------------------------------------------------------------------------------------------------
    Step t= 2 : ( sₜ, aₜ , rₜ, sₜ₊₁ )
    ( [ 0.03737992 -0.17006733 -0.00099925  0.33021996] , 1 , 1.0 , [0.03397858 0.02506883 0.00560515 0.03722208] )
    -------------------------------------------------------------------------------------------------------------------
    Step t= 3 : ( sₜ, aₜ , rₜ, sₜ₊₁ )
    ( [0.03397858 0.02506883 0.00560515 0.03722208] , 1 , 1.0 , [ 0.03447995  0.22010995  0.00634959 -0.25368711] )
    -------------------------------------------------------------------------------------------------------------------
    Step t= 4 : ( sₜ, aₜ , rₜ, sₜ₊₁ )
    ( [ 0.03447995  0.22010995  0.00634959 -0.25368711] , 0 , 1.0 , [0.03888215 0.02489792 0.00127585 0.04099181] )
    -------------------------------------------------------------------------------------------------------------------
    Step t= 5 : ( sₜ, aₜ , rₜ, sₜ₊₁ )
    ( [0.03888215 0.02489792 0.00127585 0.04099181] , 1 , 1.0 , [ 0.03938011  0.22000155  0.00209568 -0.2512883 ] )
    -------------------------------------------------------------------------------------------------------------------

### Watching a random policy agent play

Let's also see how a random policy performs in this enviroment:

```python
env_1 = gym.make('CartPole-v1')
state = env_1.reset()
for t in range(200):
    # sample a random action
    action = env_1.action_space.sample()
    env_1.render()
    state, reward, done, _ = env_1.step(action)
env_1.close()
```

    WARN: You are calling 'step()' even though this environment has already returned done = True. You should always call 'reset()' once you receive 'done = True' -- any further steps are undefined behavior.

Not very good! The pole only stayed up for a few time steps... Now let's improve things using REINFORCE.

## The Policy

We begin by parameterising the policy $\pi_\theta(a | s)$ as a simple neural network which takes the state (a vector of 4 elements provided by `gym`) as input, and produces a Categorical distribution over the possible actions as output. Simple enough. Refer to [torch.nn](https://pytorch.org/docs/stable/nn.html)

```python
class Policy(nn.Module):
    def __init__(self, s_size=4, h_size=16, a_size=2):
        super(Policy, self).__init__()
        self.fc1 = nn.Linear(s_size, h_size)
        self.fc2 = nn.Linear(h_size, a_size)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return Categorical(logits=x)
```

### Selecting actions with our policy

For a given state our policy returns a pytorch `Categorial` object. We can sample from this distribution by calling it's `sample` method and we can find the log probability of an action using `log_prob`:

```python
policy = Policy().to(device)
state = env.reset()
state = torch.from_numpy(state).float().to(device)
dist = policy(state)
action = dist.sample()
print("Sampled action: ", action.item())
print("Log probability of action: ", dist.log_prob(action).item())
```

    Sampled action:  0
    Log probability of action:  -0.6158570647239685

### Computing the return

Given a sequence of rewards $(r_0, \ldots, r_T)$ we want to calculate the return $\sum_{t=0}^T \gamma^t r(s_t, a_t)$.

```python
def compute_returns(rewards, gamma):
    returns = 0
    for step in range(len(rewards)):
        returns += gamma**step * rewards[step]
    return returns
```

## REINFORCE

Now its time to implement the algorithm

**Monte-Carlo REINFORCE**:

for each episode:
1. sample a trajectory $\tau$ using the policy $\pi_\theta$.
2. compute $\nabla_\theta J(\theta) \approx \left(\sum_{t=0}^{T} \nabla_\theta \log \pi_\theta(a_t|s_t)\right) \left(\sum_{t=0}^T \gamma^t r(s_t, a_t) \right)$.
3. update policy parameters $\theta \leftarrow \theta + \alpha \nabla_\theta J(\theta)$

### Hyperameters

```python
learning_rate = 1e-2
number_episodes = 1500
max_episode_length = 1000
gamma = 1.0
```

```python
def reinforce(seed, verbose=True):
    # set random seeds (for reproducibility)
    torch.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    env.seed(seed)

    # instantiate the policy and optimizer
    policy = Policy().to(device)
    optimizer = optim.Adam(policy.parameters(), lr=learning_rate)

    scores = []
    scores_deque = deque(maxlen=100)
    for episode in range(1, number_episodes+1):
        #################################################################
        # 1. Collect trajectories using our policy and save the rewards #
        # and the log probability of each action taken.                 #
        #################################################################
        log_probs = []
        rewards = []
        state = env.reset()
        for t in range(max_episode_length):
            # get the distribution over actions for state
            dist = policy(torch.from_numpy(state).float().to(device))

            # sample an action from the distribution
            action = dist.sample()

            # compute the log probability
            log_prob = dist.log_prob(action).unsqueeze(0)

            # take a step in the environment
            state, reward, done, _ = env.step(action.item())

            # save the reward and log probability
            rewards.append(reward)
            log_probs.append(log_prob)

            if done:
                break

        # for reporting save the score
        scores.append(sum(rewards))
        scores_deque.append(sum(rewards))

        #################################################################
        # 2. evaluate the policy gradient                               #
        #################################################################

        # calculate the discounted return of the trajectory
        returns = compute_returns(rewards, gamma)

        log_probs = torch.cat(log_probs)
        #  multiply the log probabilities by the returns and sum
        # (remember to multiply the result by -1 because we want to maximise the returns)
        policy_loss = -torch.sum(log_probs*returns)

        #################################################################
        # 3. update the policy parameters (gradient descent)            #
        #################################################################
        optimizer.zero_grad()
        policy_loss.backward()
        optimizer.step()

        # report the score to check that we're making progress
        if episode % 50 == 0 and verbose:
            print('Episode {}\tAverage Score: {:.2f}'.format(episode, np.mean(scores_deque)))

        if np.mean(scores_deque) >= 495.0 and verbose:
            print('Environment solved in {:d} episodes!\tAverage Score: {:.2f}'.format(episode, np.mean(scores_deque)))
            break

    return policy, scores
```

```python
policy, scores = reinforce(42)
```

    Episode 50	Average Score: 27.78
    Episode 100	Average Score: 32.39
    Episode 150	Average Score: 42.82
    Episode 200	Average Score: 46.35
    Episode 250	Average Score: 51.75
    Episode 300	Average Score: 58.77
    Episode 350	Average Score: 67.28
    Episode 400	Average Score: 78.60
    Episode 450	Average Score: 79.20
    Episode 500	Average Score: 82.28
    Episode 550	Average Score: 101.07
    Episode 600	Average Score: 111.77
    Episode 650	Average Score: 115.17
    Episode 700	Average Score: 253.49
    Episode 750	Average Score: 371.92
    Episode 800	Average Score: 324.80
    Episode 850	Average Score: 233.88
    Episode 900	Average Score: 103.90
    Episode 950	Average Score: 95.75
    Episode 1000	Average Score: 324.37
    Episode 1050	Average Score: 489.01
    Episode 1100	Average Score: 463.15
    Episode 1150	Average Score: 468.13
    Environment solved in 1197 episodes!	Average Score: 495.11

## Seeing our learned policy in action

Let's watch our learned policy balance the pole!

```python
env = gym.make('CartPole-v1')
state = env.reset()
for t in range(2000):
    dist = policy(torch.from_numpy(state).float().to(device))
    action = dist.sample()
    env.render()
    state, reward, done, _ = env.step(action.item())
    if done:
        break
env.close()
```

### Plotting the results

Finally, let's plot the learning curve.

```python
def moving_average(a, n) :
    ret = np.cumsum(a, dtype=float)
    ret[n:] = ret[n:] - ret[:-n]
    return ret / n
```

```python
fig = plt.figure()
ax = fig.add_subplot(111)
x = np.arange(1, len(scores)+1)
ax.plot(x, scores, label='Score')
m_average = moving_average(scores, 50)
ax.plot(x, m_average, label='Moving Average (w=100)', linestyle='--')
plt.legend()
plt.ylabel('Score')
plt.xlabel('Episode #')
plt.title('REINFORCE learning curve - CartPole-v1')
plt.show()
```

We can see that at the end of training our policy consistantly (more or less) recieves returns of 500.

## Investigating the variance of REINFORCE

We noted in class that REINFORCE is a high variance algorithm. We can investigate the variance by running multiple trials and averaging the results.

```python
np.random.seed(53)
seeds = np.random.randint(1000, size=5)
all_scores = []
for seed in seeds:
    print("started training with seed: ", seed)
    _, scores = reinforce(int(seed), verbose=False)
    print("completed training with seed: ", seed)
    all_scores.append(scores)
```

    started training with seed:  537
    completed training with seed:  537
    started training with seed:  797
    completed training with seed:  797
    started training with seed:  885
    completed training with seed:  885
    started training with seed:  421
    completed training with seed:  421
    started training with seed:  763
    completed training with seed:  763

```python
smoothed_scores = [moving_average(s, 50) for s in all_scores]
smoothed_scores = np.array(smoothed_scores)
mean = smoothed_scores.mean(axis=0)
std = smoothed_scores.std(axis=0)
```

```python
fig = plt.figure()
ax = fig.add_subplot(111)
x = np.arange(1, len(mean)+1)
ax.plot(x, mean, '-', color='blue')
ax.fill_between(x, mean - std, mean + std, color='blue', alpha=0.2)
plt.ylabel('Score')
plt.xlabel('Episode #')
plt.title('REINFORCE averaged over 5 seeds')
plt.show()
```

## Reducing the variance of REINFORCE

In class we saw a couple of tricks to reduce the variance of REINFORCE and improve its performance. Firstly, future actions should not change past decision. Present actions only impact the future. Therefore, we can change our objective function to reflect this:
\begin{align}
    \nabla_\theta J(\theta) = \mathbb{E}_{\tau \sim p_\theta} \left[\sum_{t=0}^{T} \nabla_\theta \log \pi_\theta(a_t|s_t) \sum_{t'= t}^T \gamma^{t'- t} r(s_{t'}, a_{t'})\right].
\end{align}

We can also reduce variance by subtracing a state dependent baseline to get:
\begin{align}
    \nabla_\theta J(\theta) = \mathbb{E}_{\tau \sim p_\theta} \left[\sum_{t=0}^{T} \nabla_\theta \log \pi_\theta(a_t|s_t) \sum_{t'= t}^T \left( \gamma^{t'- t} r(s_{t'}, a_{t'}) - b(s_{t'}) \right)\right].
\end{align}

For our baseline we'll use the average of the returns over the trajectory. As a final trick we normalise the returns by dividing by the standard deviation.

```python
def compute_returns_baseline(rewards, gamma):
    r = 0
    returns = []
    for step in reversed(range(len(rewards))):
        r = rewards[step] + gamma * r
        returns.insert(0, r)
    returns = np.array(returns)
    returns -= returns.mean()
    returns /= returns.std()
    return returns
```

```python
def reinforce_baseline(seed, verbose=True):
    # set random seeds (for reproducibility)
    torch.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    env.seed(seed)

    # instantiate the policy and optimizer
    policy = Policy().to(device)
    optimizer = optim.Adam(policy.parameters(), lr=learning_rate)

    scores = []
    scores_deque = deque(maxlen=100)
    for episode in range(1, number_episodes+1):
        #################################################################
        # 1. Collect trajectories using our policy and save the rewards #
        # and the log probability of each action taken.                 #
        #################################################################
        log_probs = []
        rewards = []
        state = env.reset()
        for t in range(max_episode_length):
            # get the distribution over actions for state
            dist = policy(torch.from_numpy(state).float().to(device))

            # sample an action from the distribution
            action = dist.sample()

            # compute the log probability
            log_prob = dist.log_prob(action).unsqueeze(0)

            # take a step in the environment
            state, reward, done, _ = env.step(action.item())

            # save the reward and log probability
            rewards.append(reward)
            log_probs.append(log_prob)

            if done:
                break

        # for reporting save the score
        scores.append(sum(rewards))
        scores_deque.append(sum(rewards))

        #################################################################
        # 2. evaluate the policy gradient                               #
        #################################################################

        # calculate the discounted return of the trajectory
        returns = compute_returns_baseline(rewards, gamma)
        returns = torch.from_numpy(returns).float().to(device)

        log_probs = torch.cat(log_probs)
        #  multiply the log probabilities by the returns and sum
        # (remember to multiply the result by -1 because we want to maximise the returns)
        policy_loss = -torch.sum(log_probs*returns)

        #################################################################
        # 3. update the policy parameters (gradient descent)            #
        #################################################################
        optimizer.zero_grad()
        policy_loss.backward()
        optimizer.step()

        # report the score to check that we're making progress
        if episode % 50 == 0 and verbose:
            print('Episode {}\tAverage Score: {:.2f}'.format(episode, np.mean(scores_deque)))

        if np.mean(scores_deque) >= 495.0 and verbose:
            print('Environment solved in {:d} episodes!\tAverage Score: {:.2f}'.format(episode, np.mean(scores_deque)))
            break

    return policy, scores
```

Let's see if these changes give us any improvement.

```python
np.random.seed(53)
seeds = np.random.randint(1000, size=5)
all_scores_baseline = []
for seed in seeds:
    print("started training with seed: ", seed)
    _, scores = reinforce_baseline(int(seed), verbose=False)
    print("completed training with seed: ", seed)
    all_scores_baseline.append(scores)
```

    started training with seed:  537
    completed training with seed:  537
    started training with seed:  797
    completed training with seed:  797
    started training with seed:  885
    completed training with seed:  885
    started training with seed:  421
    completed training with seed:  421
    started training with seed:  763
    completed training with seed:  763

## Comparing the methods

Finally we'll compare the performance of the two methods.

```python
smoothed_scores_baseline = [moving_average(s, 50) for s in all_scores_baseline]
smoothed_scores_baseline = np.array(smoothed_scores_baseline)
mean_baseline = smoothed_scores_baseline.mean(axis=0)
std_baseline = smoothed_scores_baseline.std(axis=0)
```

```python
fig = plt.figure()
ax = fig.add_subplot(111)
x = np.arange(1, len(mean_baseline)+1)
ax.plot(x, mean_baseline, '-', color='green', label='Variance reduced REINFORCE')
ax.plot(x, mean, '-', color='blue', label='REINFORCE')
ax.fill_between(x, mean_baseline - std_baseline, mean_baseline + std_baseline, color='green', alpha=0.2)
ax.fill_between(x, mean - std, mean + std, color='blue', alpha=0.2)
plt.ylabel('Score')
plt.xlabel('Episode #')
plt.legend()
plt.title('Comparison of REINFORCE and Variance reduced REINFORCE (averaged over 5 seeds)')
plt.show()
```


\title{Digital Arithmetic Cells with myHDL}
\author{Steven K Armour}
\maketitle

# Table of Contents

1  Refs

2  Python Libraries Used

3  Compression of Number System Values

4  Computer Number Systems

4.1  Decimal to Binary

4.2  Decimal to hex

4.3  Decmial to oct

5  Addition

5.1  Half Adder

5.2  Full Adder

5.3  Ripple Adder

5.4  Ripple Adder using bitvectors

5.5  Adder Carry Look Ahead

5.6  Adder Behavioral

6  Suptraction

6.1  Two Bit Suptraction (Need to cheeck)

6.2  Suptraction Behavioral

7  Multiplication

7.1  Multiplication Beahavioral

8  Division

9  comparator

10  Intergration

# Refs
@book{brown_vranesic_2014,
place={New York, NY},
edition={3},
title={Fundamentals of digital logic with Verilog design},
publisher={McGraw-Hill},
author={Brown, Stephen and Vranesic, Zvonko G},
year={2014}
},

@book{lameres_2017,
title={Introduction to logic circuits & logic design with Verilog},
publisher={springer},
author={LaMeres, Brock J},
year={2017}
}

@misc{four bit adder - rosetta code-myhdl_2017,
url={https://rosettacode.org/wiki/Four_bit_adder#MyHDL},
journal={Rosettacode.org},
year={2017}
},

@book{cavanagh_2010,
place={Boca Raton, FL},
title={Computer arithmetic and Verilog HDL fundamentals},
publisher={CRC Press},
author={Cavanagh, Joseph},
year={2010}
},

@misc{peeker_hier_add_2017,
url={http://www.xess.com/static/media/pages/peeker_hier_add.html},
journal={Xess.com},
year={2017}
},
Christopher Felton's CIC Exsample from myHDL old site (https://github.com/jandecaluwe/site-myhdl-retired/blob/master/_ori/pages/projects/gciccomplete.txt)

# Python Libraries Used

```python
import numpy as np
import pandas as pd
from sympy import *
init_printing()

from myhdl import *
from myhdlpeek import *
import random

from sympy_myhdl_tools import *

import  scipy.signal as sig
import matplotlib.pyplot as plt
%matplotlib inline
```

# Compression of Number System Values

```python
ConversionTable=pd.DataFrame(columns=['Decimal'])
```

```python
ConversionTable['Decimal']=np.arange(0, 21)
ConversionTable['Binary']=[bin(i, 3) for i in np.arange(0, 21)]
ConversionTable['hex']=[hex(i) for i in np.arange(0, 21)]
ConversionTable['oct']=[oct(i) for i in np.arange(0, 21)]
ConversionTable
```

      Decimal
      Binary
      hex
      oct

      0
      0
      000
      0x0
      0o0

      1
      1
      001
      0x1
      0o1

      2
      2
      010
      0x2
      0o2

      3
      3
      011
      0x3
      0o3

      4
      4
      100
      0x4
      0o4

      5
      5
      101
      0x5
      0o5

      6
      6
      110
      0x6
      0o6

      7
      7
      111
      0x7
      0o7

      8
      8
      1000
      0x8
      0o10

      9
      9
      1001
      0x9
      0o11

      10
      10
      1010
      0xa
      0o12

      11
      11
      1011
      0xb
      0o13

      12
      12
      1100
      0xc
      0o14

      13
      13
      1101
      0xd
      0o15

      14
      14
      1110
      0xe
      0o16

      15
      15
      1111
      0xf
      0o17

      16
      16
      10000
      0x10
      0o20

      17
      17
      10001
      0x11
      0o21

      18
      18
      10010
      0x12
      0o22

      19
      19
      10011
      0x13
      0o23

      20
      20
      10100
      0x14
      0o24

# Computer Number Systems

## Decimal to Binary

```python
def DecimialtoBaseN(dec, base):
    quotant=[dec]; remander=[]
    while True:
        q, r=np.divmod(quotant[-1], base)
        quotant.append(q); remander.append(r)
        if q==0: break

    TwosProd=[]
    for i in range(len(remander)):
        TwosProd.append(remander[i]*base**i)
    return quotant, remander, TwosProd[::-1], np.sum(TwosProd)
```

## Decimal to hex

## Decmial to oct

# Addition

```python
binarySum=lambda a, b, bits=2: np.binary_repr(a+b, bits)
```

```python
for i in [[0,0], [0,1], [1,0], [1,1]]:
    print(f'{i[0]} + {i[1]} yield {binarySum(*i)}')
```

    0 + 0 yield 00
    0 + 1 yield 01
    1 + 0 yield 01
    1 + 1 yield 10

if we split the yileded output up we find that the right most place is the 'Sum' and the next place to the left if the 'carry'

therfore we can posttulate the following

```python
S_out, C_out=symbols('S_out, C_out')
```

```python
x_1in, x_2in, y_out=symbols('x_1in, x_2in, y_out')
SEq=Eq(S_out, x_1in^x_2in)
print(SEq)
SEq_TT=TruthTabelGenrator(SEq)
SEq_TT
```

    Eq(S_out, Xor(x_1in, x_2in))

      x_1in
      x_2in
      S_out

      0
      0
      0
      0

      1
      0
      1
      1

      2
      1
      0
      1

      3
      1
      1
      0

```python
CEq=Eq(C_out, x_1in & x_2in)
CEq_TT=TruthTabelGenrator(CEq)
print(CEq)
CEq_TT
```

    Eq(C_out, x_1in & x_2in)

      x_1in
      x_2in
      C_out

      0
      0
      0
      0

      1
      0
      1
      0

      2
      1
      0
      0

      3
      1
      1
      1

```python
pd.merge(SEq_TT, CEq_TT[CEq_TT.columns.difference(SEq_TT.columns)], left_index=True, right_index=True)
```

      x_1in
      x_2in
      S_out
      C_out

      0
      0
      0
      0
      0

      1
      0
      1
      1
      0

      2
      1
      0
      1
      0

      3
      1
      1
      0
      1

Passing the carry

```python

```

```python
binarySum=lambda a, b, bits=3: np.binary_repr(a+b, bits)
```

```python
for i in [[0,0], [0,1], [1,0], [1,1], [0, 2], [1, 2], [2,2],
          [0, 3], [1, 3], [2, 3], [3,3]]:
    print(f'{i[0]} + {i[1]} yield {binarySum(*i)}')
```

    0 + 0 yield 000
    0 + 1 yield 001
    1 + 0 yield 001
    1 + 1 yield 010
    0 + 2 yield 010
    1 + 2 yield 011
    2 + 2 yield 100
    0 + 3 yield 011
    1 + 3 yield 100
    2 + 3 yield 101
    3 + 3 yield 110

We can see that once exceded the binary repersentation of 2 we have a carry to the next bit

```python
C_in=symbols('C_in')
Sum3Eq=Eq(S_out, x_1in ^x_2in^C_in)
print(Sum3Eq)
SEq_TT=TruthTabelGenrator(Sum3Eq)
SEq_TT
```

    Eq(S_out, Xor(C_in, x_1in, x_2in))

      C_in
      x_1in
      x_2in
      S_out

      0
      0
      0
      0
      0

      1
      0
      0
      1
      1

      2
      0
      1
      0
      1

      3
      0
      1
      1
      0

      4
      1
      0
      0
      1

      5
      1
      0
      1
      0

      6
      1
      1
      0
      0

      7
      1
      1
      1
      1

```python
C3Eq=Eq(C_out, (x_1in & x_2in) | ((x_1in & x_2in)& C_in))
CEq_TT=TruthTabelGenrator(C3Eq)
print(CEq)
CEq_TT
```

    Eq(C_out, x_1in & x_2in)

      C_in
      x_1in
      x_2in
      C_out

      0
      0
      0
      0
      0

      1
      0
      0
      1
      0

      2
      0
      1
      0
      0

      3
      0
      1
      1
      1

      4
      1
      0
      0
      0

      5
      1
      0
      1
      0

      6
      1
      1
      0
      0

      7
      1
      1
      1
      1

```python
pd.merge(SEq_TT, CEq_TT[CEq_TT.columns.difference(SEq_TT.columns)], left_index=True, right_index=True)
```

      C_in
      x_1in
      x_2in
      S_out
      C_out

      0
      0
      0
      0
      0
      0

      1
      0
      0
      1
      1
      0

      2
      0
      1
      0
      1
      0

      3
      0
      1
      1
      0
      1

      4
      1
      0
      0
      1
      0

      5
      1
      0
      1
      0
      0

      6
      1
      1
      0
      0
      0

      7
      1
      1
      1
      1
      1

## Half Adder

```python
def HalfAdder(x_1in, x_2in, S_out, C_out):
    @always_comb
    def logic():
        S_out.next=x_1in ^ x_2in
        C_out.next=x_1in & x_2in

    return logic
```

```python
#create the test signals and intilize value to both be false(0)
x_1in, x_2in, S_out, C_out=[Signal(bool(0)) for _ in range(4)]

#Peeker is helper libary for viewing wavefrom outputs and run the sim inside
#jupyter notebooks
#clear any previeese data loaded into peeker
Peeker.clear()

#load and name the signals to watch into peeker
Peeker(x_1in, 'x_1in'); Peeker(x_2in, 'x_2in')
Peeker(S_out, 'y_out'); Peeker(C_out, 'C_out')

#make an instatince of the NotGate as the DUT
DUT=HalfAdder(x_1in=x_1in, x_2in=x_2in, S_out=S_out, C_out=C_out)

inputs=[x_1in, x_2in]

sim=Simulation(DUT, Combo_TB(inputs), *Peeker.instances()).run()
Peeker.to_wavedrom(start_time=0, stop_time=2*2**len(inputs), tock=True,
                  title='HalfAdder simulation',
                  caption=f'after clock cycle {2**len(inputs)-1} ->random input')

```

    <class 'myhdl.StopSimulation'>: No more events

```python
MakeDFfromPeeker(Peeker.to_wavejson(start_time=0, stop_time=2**len(inputs) -1))
```

      C_out
      x_1in
      x_2in
      y_out

      0
      0
      0
      0
      0

      1
      0
      0
      1
      1

      2
      0
      1
      0
      1

      3
      1
      1
      1
      0

```python
toVerilog(HalfAdder, x_1in, x_2in, S_out, C_out)
#toVHDL(And3Gate, x_1in, x_2in, x_3in y_out)
_=VerilogTextReader('HalfAdder')
```

    ***Verilog modual from HalfAdder.v***

     // File: HalfAdder.v
    // Generated by MyHDL 0.9.0
    // Date: Mon Oct  9 19:20:28 2017

    `timescale 1ns/10ps

    module HalfAdder (
        x_1in,
        x_2in,
        S_out,
        C_out
    );

    input x_1in;
    input x_2in;
    output S_out;
    wire S_out;
    output C_out;
    wire C_out;

    assign S_out = (x_1in ^ x_2in);
    assign C_out = (x_1in & x_2in);

    endmodule

## Full Adder

```python
def FullAdder21(x_1in, x_2in, C_in, S_out, C_out):
    @always_comb
    def logic():
        S_out.next=x_1in ^ x_2in ^C_in
        C_out.next=(x_1in and x_2in) or (x_1in and C_in) or (x_2in and C_in)

    return logic
```

! need to change the order of the inputs in the simulator so that Cin is last

```python
#create the test signals and intilize value to both be false(0)
x_1in, x_2in, C_in, S_out, C_out=[Signal(bool(0)) for _ in range(5)]

#Peeker is helper libary for viewing wavefrom outputs and run the sim inside
#jupyter notebooks
#clear any previeese data loaded into peeker
Peeker.clear()

#load and name the signals to watch into peeker
Peeker(x_1in, 'x_1in'); Peeker(x_2in, 'x_2in'); Peeker(C_in, 'C_in')
Peeker(S_out, 'y_out'); Peeker(C_out, 'C_out')

#make an instatince of the NotGate as the DUT
DUT=FullAdder21(x_1in=x_1in, x_2in=x_2in, C_in=C_in, S_out=S_out, C_out=C_out)

inputs=[x_1in, x_2in, C_in]

sim=Simulation(DUT, Combo_TB(inputs), *Peeker.instances()).run()
Peeker.to_wavedrom(start_time=0, stop_time=2*2**len(inputs), tock=True,
                  title='FullAdder21 simulation',
                  caption=f'after clock cycle {2**len(inputs)-1} ->random input')

```

    <class 'myhdl.StopSimulation'>: No more events

```python
FA21DF=MakeDFfromPeeker(Peeker.to_wavejson(start_time=0, stop_time=2**len(inputs) -1))
FA21DF[['x_1in', 'x_2in','C_in',  'y_out','C_out' ]]
```

      x_1in
      x_2in
      C_in
      y_out
      C_out

      0
      0
      0
      0
      0
      0

      1
      0
      0
      1
      1
      0

      2
      0
      1
      0
      1
      0

      3
      0
      1
      1
      0
      1

      4
      1
      0
      0
      1
      0

      5
      1
      0
      1
      0
      1

      6
      1
      1
      0
      0
      1

      7
      1
      1
      1
      1
      1

## Ripple Adder

Ripple adders are a exsample of composistion  built by using previoues instaninces

```python
def FullAdder(x_1in, x_2in, C_in, S_out, C_out):

    S_1out=Signal(bool()); C_1out=Signal(bool())
    HalfAdder1=HalfAdder(x_1in=x_1in, x_2in=C_in,
                         S_out=S_1out, C_out=C_1out)

    C_2out=Signal(bool())
    HalfAdder2=HalfAdder(x_1in=S_1out, x_2in=x_2in,
                         S_out=S_out, C_out=C_2out)

    @always_comb
    def C_outLogic():
        C_out.next= C_1out or C_2out

    return  HalfAdder1, HalfAdder2, C_outLogic
```

```python
#create the test signals and intilize value to both be false(0)
x_1in, x_2in, C_in, S_out, C_out=[Signal(bool(0)) for _ in range(5)]

#Peeker is helper libary for viewing wavefrom outputs and run the sim inside
#jupyter notebooks
#clear any previeese data loaded into peeker
Peeker.clear()

#load and name the signals to watch into peeker
Peeker(x_1in, 'x_1in'); Peeker(x_2in, 'x_2in'); Peeker(C_in, 'C_in')
Peeker(S_out, 'y_out'); Peeker(C_out, 'C_out')

#make an instatince of the NotGate as the DUT
DUT=FullAdder(x_1in=x_1in, x_2in=x_2in, C_in=C_in, S_out=S_out, C_out=C_out)

inputs=[x_1in, x_2in, C_in]

sim=Simulation(DUT, Combo_TB(inputs), *Peeker.instances()).run()
Peeker.to_wavedrom(start_time=0, stop_time=2*2**len(inputs), tock=True,
                  title='FullAdder simulation',
                  caption=f'after clock cycle {2**len(inputs)-1} ->random input')

```

    <class 'myhdl.StopSimulation'>: No more events

```python
FARDF=MakeDFfromPeeker(Peeker.to_wavejson(start_time=0, stop_time=2**len(inputs) -1))
FARDF[['x_1in', 'x_2in','C_in',  'y_out','C_out' ]]
```

      x_1in
      x_2in
      C_in
      y_out
      C_out

      0
      0
      0
      0
      0
      0

      1
      0
      0
      1
      1
      0

      2
      0
      1
      0
      1
      0

      3
      0
      1
      1
      0
      1

      4
      1
      0
      0
      1
      0

      5
      1
      0
      1
      0
      1

      6
      1
      1
      0
      0
      1

      7
      1
      1
      1
      1
      1

## Ripple Adder using bitvectors

## Adder Carry Look Ahead

## Adder Behavioral

```python
def AdderBehavioral(x_1in, x_2in, S_out):
    @always_comb
    def logic():
        S_out.next=x_1in+x_2in
    return logic
```

```python
Peeker.clear()
BITWidth=3
x_1in=Signal(intbv(0)[BITWidth:]); Peeker(x_1in, 'x_1in')
x_2in=Signal(intbv(0)[BITWidth:]); Peeker(x_2in, 'x_2in')
S_out=Signal(intbv(0)[BITWidth+1:]); Peeker(S_out, 'S_out')

DUT=AdderBehavioral(x_1in, x_2in, S_out)

def AdderBehavioral_TB():
    for i in ((x,y) for x in range(2**BITWidth) for y in range(2**BITWidth)):
        x_1in.next, x_2in.next=i

        yield delay(1)

    raise StopSimulation
sim=Simulation(DUT, AdderBehavioral_TB(), *Peeker.instances()).run()
Peeker.to_wavedrom(tock=True,start_time=0, stop_time=3*2**BITWidth -1,
                  title='AdderBehavioral simulation',
                  caption=f'bitwidth inputs:{BITWidth}, outputs:{BITWidth+1}')

```

```python
#got to improve MakeDFfromPeeker to deal with =
MakeDFfromPeeker(Peeker.to_wavejson())

```

      S_out
      x_1in
      x_2in

      0
      =
      =
      =

      1
      =
      =
      =

      2
      =
      =
      =

      3
      =
      =
      =

      4
      =
      =
      =

      5
      =
      =
      =

      6
      =
      =
      =

      7
      =
      =
      =

      8
      =
      =
      =

      9
      =
      =
      =

      10
      =
      =
      =

      11
      =
      =
      =

      12
      =
      =
      =

      13
      =
      =
      =

      14
      =
      =
      =

      15
      =
      =
      =

      16
      =
      =
      =

      17
      =
      =
      =

      18
      =
      =
      =

      19
      =
      =
      =

      20
      =
      =
      =

      21
      =
      =
      =

      22
      =
      =
      =

      23
      =
      =
      =

      24
      =
      =
      =

      25
      =
      =
      =

      26
      =
      =
      =

      27
      =
      =
      =

      28
      =
      =
      =

      29
      =
      =
      =

      ...
      ...
      ...
      ...

      34
      =
      =
      =

      35
      =
      =
      =

      36
      =
      =
      =

      37
      =
      =
      =

      38
      =
      =
      =

      39
      =
      =
      =

      40
      =
      =
      =

      41
      =
      =
      =

      42
      =
      =
      =

      43
      =
      =
      =

      44
      =
      =
      =

      45
      =
      =
      =

      46
      =
      =
      =

      47
      =
      =
      =

      48
      =
      =
      =

      49
      =
      =
      =

      50
      =
      =
      =

      51
      =
      =
      =

      52
      =
      =
      =

      53
      =
      =
      =

      54
      =
      =
      =

      55
      =
      =
      =

      56
      =
      =
      =

      57
      =
      =
      =

      58
      =
      =
      =

      59
      =
      =
      =

      60
      =
      =
      =

      61
      =
      =
      =

      62
      =
      =
      =

      63
      =
      =
      =

64 rows × 3 columns

```python

x_1in, x_2in=[Signal(intbv(0)[BITWidth:]) for _ in range(2)]
S_out=Signal(intbv(0)[BITWidth+1:])
toVerilog(AdderBehavioral, x_1in, x_2in, S_out)
#toVHDL(AdderBehavioral, x_1in, x_2in, x_3in y_out)
_=VerilogTextReader('AdderBehavioral')
```

    ***Verilog modual from AdderBehavioral.v***

     // File: AdderBehavioral.v
    // Generated by MyHDL 0.9.0
    // Date: Mon Oct  9 19:20:31 2017

    `timescale 1ns/10ps

    module AdderBehavioral (
        x_1in,
        x_2in,
        S_out
    );

    input [2:0] x_1in;
    input [2:0] x_2in;
    output [3:0] S_out;
    wire [3:0] S_out;

    assign S_out = (x_1in + x_2in);

    endmodule

# Suptraction

## Two Bit Suptraction (Need to cheeck)

```python
def FullSup21(x_1in, x_2in, B_in, S_out, B_out):
    @always_comb
    def logic():
        S_out.next=x_1in ^ (not x_2in) ^B_in
        B_out.next=(x_1in and  (not x_2in)) or (x_1in and B_in) or ((not x_2in) and B_in)

    return logic
```

```python
#create the test signals and intilize value to both be false(0)
x_1in, x_2in, S_out, B_out=[Signal(bool(0)) for _ in range(4)]
B_in=Signal(bool(1))
#Peeker is helper libary for viewing wavefrom outputs and run the sim inside
#jupyter notebooks
#clear any previeese data loaded into peeker
Peeker.clear()

#load and name the signals to watch into peeker
Peeker(x_1in, 'x_1in'); Peeker(x_2in, 'x_2in'); Peeker(B_in, 'B_in')
Peeker(S_out, 'y_out'); Peeker(B_out, 'B_out')

#make an instatince of the NotGate as the DUT
DUT=FullSup21(x_1in=x_1in, x_2in=x_2in, B_in=B_in, S_out=S_out, B_out=B_out)

inputs=[x_1in, x_2in]

sim=Simulation(DUT, Combo_TB(inputs), *Peeker.instances()).run()
Peeker.to_wavedrom(start_time=0, stop_time=2*2**len(inputs), tock=True,
                  title='FullSup21 simulation',
                  caption=f'after clock cycle {2**len(inputs)-1} ->random input')

```

    <class 'myhdl.StopSimulation'>: No more events

## Suptraction Behavioral

```python
def SuptractionBehavioral(x_1in, x_2in, S_out):
    @always_comb
    def logic():
        S_out.next=x_1in-x_2in
    return logic
```

```python
Peeker.clear()
BITWidth=3
x_1in=Signal(intbv(0)[BITWidth:]); Peeker(x_1in, 'x_1in')
x_2in=Signal(intbv(0)[BITWidth:]); Peeker(x_2in, 'x_2in')
S_out=Signal(intbv(0, min=-2**BITWidth, max=2**BITWidth)); Peeker(S_out, 'S_out')

DUT=SuptractionBehavioral(x_1in, x_2in, S_out)

def SuptractionBehavioral_TB():
    for i in ((x,y) for x in range(2**BITWidth) for y in range(2**BITWidth)):
        x_1in.next, x_2in.next=i

        yield delay(1)

    raise StopSimulation
sim=Simulation(DUT, AdderBehavioral_TB(), *Peeker.instances()).run()
Peeker.to_wavedrom(tock=True,start_time=0, stop_time=3*2**BITWidth -1,
                  title='SuptractionBehavioral simulation',
                  caption=f'bitwidth inputs:{BITWidth}, outputs:{BITWidth+1}')

```

```python
#got to improve MakeDFfromPeeker to deal with =
MakeDFfromPeeker(Peeker.to_wavejson())

```

      S_out
      x_1in
      x_2in

      0
      =
      =
      =

      1
      =
      =
      =

      2
      =
      =
      =

      3
      =
      =
      =

      4
      =
      =
      =

      5
      =
      =
      =

      6
      =
      =
      =

      7
      =
      =
      =

      8
      =
      =
      =

      9
      =
      =
      =

      10
      =
      =
      =

      11
      =
      =
      =

      12
      =
      =
      =

      13
      =
      =
      =

      14
      =
      =
      =

      15
      =
      =
      =

      16
      =
      =
      =

      17
      =
      =
      =

      18
      =
      =
      =

      19
      =
      =
      =

      20
      =
      =
      =

      21
      =
      =
      =

      22
      =
      =
      =

      23
      =
      =
      =

      24
      =
      =
      =

      25
      =
      =
      =

      26
      =
      =
      =

      27
      =
      =
      =

      28
      =
      =
      =

      29
      =
      =
      =

      ...
      ...
      ...
      ...

      34
      =
      =
      =

      35
      =
      =
      =

      36
      =
      =
      =

      37
      =
      =
      =

      38
      =
      =
      =

      39
      =
      =
      =

      40
      =
      =
      =

      41
      =
      =
      =

      42
      =
      =
      =

      43
      =
      =
      =

      44
      =
      =
      =

      45
      =
      =
      =

      46
      =
      =
      =

      47
      =
      =
      =

      48
      =
      =
      =

      49
      =
      =
      =

      50
      =
      =
      =

      51
      =
      =
      =

      52
      =
      =
      =

      53
      =
      =
      =

      54
      =
      =
      =

      55
      =
      =
      =

      56
      =
      =
      =

      57
      =
      =
      =

      58
      =
      =
      =

      59
      =
      =
      =

      60
      =
      =
      =

      61
      =
      =
      =

      62
      =
      =
      =

      63
      =
      =
      =

64 rows × 3 columns

```python
x_1in, x_2in=[Signal(intbv(0)[BITWidth:]) for _ in range(2)]
S_out=S_out=Signal(intbv(0, min=-2**BITWidth, max=2**BITWidth))
toVerilog(SuptractionBehavioral, x_1in, x_2in, S_out)
#toVHDL(SuptractionBehavioral, x_1in, x_2in, x_3in y_out)
_=VerilogTextReader('SuptractionBehavioral')
```

    ***Verilog modual from SuptractionBehavioral.v***

     // File: SuptractionBehavioral.v
    // Generated by MyHDL 0.9.0
    // Date: Mon Oct  9 19:20:32 2017

    `timescale 1ns/10ps

    module SuptractionBehavioral (
        x_1in,
        x_2in,
        S_out
    );

    input [2:0] x_1in;
    input [2:0] x_2in;
    output signed [3:0] S_out;
    wire signed [3:0] S_out;

    assign S_out = (x_1in - x_2in);

    endmodule

# Multiplication

## Multiplication Beahavioral

```python
def MultiplyBehavioral(x_1in, x_2in, P_out):
    @always_comb
    def logic():
        P_out.next=x_1in*x_2in
    return logic
```

```python
Peeker.clear()
BITWidth=3
x_1in=Signal(intbv(0)[BITWidth:]); Peeker(x_1in, 'x_1in')
x_2in=Signal(intbv(0)[BITWidth:]); Peeker(x_2in, 'x_2in')
P_out=Signal(intbv(0)[BITWidth**2:]); Peeker(P_out, 'P_out')

DUT=MultiplyBehavioral(x_1in, x_2in, P_out)

def MultiplyBehavioral_TB():
    for i in ((x,y) for x in range(2**BITWidth) for y in range(2**BITWidth)):
        x_1in.next, x_2in.next=i

        yield delay(1)

    raise StopSimulation
sim=Simulation(DUT, MultiplyBehavioral_TB(), *Peeker.instances()).run()
Peeker.to_wavedrom(tock=True,start_time=0, stop_time=3*2**BITWidth -1,
                  title='MultiplyBehavioral simulation',
                  caption=f'bitwidth inputs:{BITWidth}, outputs:{BITWidth+1}')

```

```python
#got to improve MakeDFfromPeeker to deal with =
MakeDFfromPeeker(Peeker.to_wavejson())
```

      P_out
      x_1in
      x_2in

      0
      =
      =
      =

      1
      =
      =
      =

      2
      =
      =
      =

      3
      =
      =
      =

      4
      =
      =
      =

      5
      =
      =
      =

      6
      =
      =
      =

      7
      =
      =
      =

      8
      =
      =
      =

      9
      =
      =
      =

      10
      =
      =
      =

      11
      =
      =
      =

      12
      =
      =
      =

      13
      =
      =
      =

      14
      =
      =
      =

      15
      =
      =
      =

      16
      =
      =
      =

      17
      =
      =
      =

      18
      =
      =
      =

      19
      =
      =
      =

      20
      =
      =
      =

      21
      =
      =
      =

      22
      =
      =
      =

      23
      =
      =
      =

      24
      =
      =
      =

      25
      =
      =
      =

      26
      =
      =
      =

      27
      =
      =
      =

      28
      =
      =
      =

      29
      =
      =
      =

      ...
      ...
      ...
      ...

      34
      =
      =
      =

      35
      =
      =
      =

      36
      =
      =
      =

      37
      =
      =
      =

      38
      =
      =
      =

      39
      =
      =
      =

      40
      =
      =
      =

      41
      =
      =
      =

      42
      =
      =
      =

      43
      =
      =
      =

      44
      =
      =
      =

      45
      =
      =
      =

      46
      =
      =
      =

      47
      =
      =
      =

      48
      =
      =
      =

      49
      =
      =
      =

      50
      =
      =
      =

      51
      =
      =
      =

      52
      =
      =
      =

      53
      =
      =
      =

      54
      =
      =
      =

      55
      =
      =
      =

      56
      =
      =
      =

      57
      =
      =
      =

      58
      =
      =
      =

      59
      =
      =
      =

      60
      =
      =
      =

      61
      =
      =
      =

      62
      =
      =
      =

      63
      =
      =
      =

64 rows × 3 columns

```python
x_1in, x_2in=[Signal(intbv(0)[BITWidth:]) for _ in range(2)]
P_out=Signal(intbv(0)[BITWidth**2:])
toVerilog(MultiplyBehavioral, x_1in, x_2in, P_out)
#toVHDL(MultiplyBehavioral, x_1in, x_2in, x_3in y_out)
_=VerilogTextReader('MultiplyBehavioral')
```

    ***Verilog modual from MultiplyBehavioral.v***

     // File: MultiplyBehavioral.v
    // Generated by MyHDL 0.9.0
    // Date: Mon Oct  9 19:20:33 2017

    `timescale 1ns/10ps

    module MultiplyBehavioral (
        x_1in,
        x_2in,
        P_out
    );

    input [2:0] x_1in;
    input [2:0] x_2in;
    output [8:0] P_out;
    wire [8:0] P_out;

    assign P_out = (x_1in * x_2in);

    endmodule

# Division

```python

```

# comparator

# Intergration

$$\int f(x)dx \approx \sum_{i=1}^{\infty} w_i f(x_i) +r_i$$

ignoring the residual $r_i$ and setting the weight $w_i=1$ for all $i$ we can make a very very crude approximation of the integral to be
$$y=\int f(x)dx  \approx f(x_i)+f(x_{i-1})$$

wich is better written as

```python
z, n=symbols('z, n')
yn=Function('y')(n)
yn_1=Function('y')(n-1)
xn=Function('x')(n)
IntEq=Eq(yn, yn_1+xn); IntEq
```

Perform a primitive Z transform on the above equation
(note at this time sympy has no Z transform, so I am hacking one here) we get

```python
Y=Function('Y')(z); X=Function('X')(z)
Equat=IntEq
for j in [yn, yn_1]:
    zsups=expand(z**-(list(j.args))[0])
    zsups
    Equat=Equat.subs(j, z**(list(j.args))[0])
Equat=expand(Equat).subs(z**n, Y)
for j in [xn]:
    zsups=expand(z**-(list(j.args))[0])
    zsups
    Equat=Equat.subs(j, z**(list(j.args))[0])
Equat=expand(Equat).subs(z**n, X)
Equat
```

 that then yields the Transfer function $H(z)=\dfrac{Y(z)}{X(z)}$

```python
TF=solve(Equat, Y)[0]/X; TF
```

the resulting Transfer Function is analyzed by splitting apart the numurator and denomantior to find zeros and poles respectively as well as the coefficient

```python
TFnum, TFden=fraction(TF); TFnum, TFden
```

```python
TFCoefN=lambda NumDen: [float(i) for i in Poly(NumDen, z).coeffs()]
TFnumC=TFCoefN(TFnum); TFdenC=TFCoefN(TFden)
TFnumC, TFdenC
```

these coefinents for the poles and zeros can be pulled into scipy's signals modul for further anylsis via

```python
TFN=sig.TransferFunction(TFnumC, TFdenC, dt=1)
w, mag, phase = sig.dbode(TFN)
```

    ** RuntimeWarning: divide by zero encountered in true_divide
    ** RuntimeWarning: invalid value encountered in true_divide
    ** RuntimeWarning: invalid value encountered in remainder
    ** RuntimeWarning: invalid value encountered in greater
    ** RuntimeWarning: invalid value encountered in less

```python
fig, [ax1, ax2]=plt.subplots(nrows=2, ncols=1)
ax1.semilogx(w, mag, label='Mag')
ax1.set_ylabel('mag')
ax2.semilogx(w, phase)
ax2.set_ylabel('phase')
ax2.set_xlabel('ang freq')

```

```python
#The following will not synth but will run in peeker ?
ysim, xsim=symbols('y,x')
sups={xn:xsim, yn_1:ysim}
IntEq.rhs.subs(sups)
toMyHDL=lambdify((xsim, ysim), IntEq.rhs.subs(sups), dummify=False)
```

```python
def IntCurde(x_in, y_out, clk, rst):
    @always(clk.posedge)
    def logic():
        if rst:
            y_out.next=0
        else:
            y_out.next=x_in+y_out
            #!!! Houston we have a problom here
            #this will not synthesis to HDL but will run in Peeker
            #y_out.next=toMyHDL(x=x_in, y=y_out)
    return logic
```

```python
BITWidth=3

Peeker.clear()
x_in=Signal(intbv(int(BITWidth*random.uniform(-1, 1)), min=-10, max=10)); Peeker(x_in, 'x_in')
y_out=Signal(intbv(0, min=-40, max=40)); Peeker(y_out, 'y_out')

clk=Signal(bool(0)); Peeker(clk, 'clk')
rst=Signal(bool(0)); Peeker(rst, 'rst')

DUT=IntCurde(x_in, y_out, clk, rst)
def Int_TB():

    @always(delay(1))
    def clkgen():
        clk.next = not clk

    @always(clk.posedge)
    def ith():
        x_in.next=int(BITWidth*random.uniform(0, 2))

    return ith, clkgen
```

```python
sim=Simulation(DUT, Int_TB(), *Peeker.instances()).run(15)
Peeker.to_wavedrom(tock=True)
```

    <class 'myhdl._SuspendSimulation'>: Simulated 15 timesteps

```python
x_in=Signal(intbv(0, min=-10, max=10))
y_out=Signal(intbv(0, min=-40, max=40))
clk=Signal(bool(0)); rst=Signal(bool(0))
toVerilog(IntCurde, x_in, y_out, clk, rst)
_=VerilogTextReader('IntCurde')
```

    ***Verilog modual from IntCurde.v***

     // File: IntCurde.v
    // Generated by MyHDL 0.9.0
    // Date: Mon Oct  9 19:20:40 2017

    `timescale 1ns/10ps

    module IntCurde (
        x_in,
        y_out,
        clk,
        rst
    );

    input signed [4:0] x_in;
    output signed [6:0] y_out;
    reg signed [6:0] y_out;
    input clk;
    input rst;

    always @(posedge clk) begin: INTCURDE_LOGIC
        if (rst) begin
            y_out <= 0;
        end
        else begin
            y_out <= (x_in + y_out);
        end
    end

    endmodule

    ** ToVerilogWarning: Output port is read internally: y_out


# Hauptkomponentenanalyse
# (Principal Component Analysis, PCA)
# vs.
# Denoising Variational Autoencoders

### _an Hand von Beispielen_

jupyter nbconvert PCAvsDVAEde.ipynb --to slides --post serve

# Eine intuitive Perspektive ...

#### "... realistische, hochdimensionale Daten konzentrieren sich in der Nähe einer nichtlinearen, niedrigdimensionalen Mannigfaltigkeit ..." [Lei et al., 2018]

# Eine intuitive Perspektive ...

#### Aber wie lernt man die Mannigfaltigkeit und die Wahrscheinlichkeitsverteilung darauf?

# PCA vs. DVAE an Hand von Beispielen

# PCA vs. DVAE an Hand von Beispielen

Der __MNIST (Modified National Institute of Standards and Technology) Datensatz__ von handgeschriebenen Zahlen besteht aus __60,000 Trainings- und 10,000 Test-Beispielen__. Die Zahlen wurden hinsichtlich Ihrer Größe __normalisiert und in einem Bild fester Größe zentriert__.

# Vorstellung der Wettbewerber

# PCA

# PCA

* __Unüberwachtes__ Lernen
* __Lineare Transformation__

* __"Transformiere"__ eine Menge von Beobachtungen in ein __anderes Koordinatensystem__, in dem die Werte der ersten Koordinate (Komponente) die __größtmögliche Varianz__ aufweisen [Friedman et al., 2017]
* Die __resultierenden Koordinaten (Komponenten)__ sind __nicht__ mit den ursprünglichen Koordinaten __korreliert__

# PCA

* Wird zur __Dimensions-Reduzierung__ genutzt (Komprimierung)
* Die __Rekonstruktion der Beobachtungen__("decoding") aus den führenden __Hauptkomponenten__ hat den __niedrigsten quadratischen Fehler__

# Autoencoders

# Autoencoders

* unüberwachtes __neuronales Netz__
* __minimiert__ den Fehler zwischen Rekonstruktionen und Beobachtungen [Goodfellow et al., 2016]
* lernt die __Identitätsfunktion__
* wird mit Hilfe von __Fehlerrückführung (Backpropagation) trainiert__
* aufgetrennt um __Kodierung und Dekodierung__

# Autoencoders

# PCA vs. Autoencoders
## Implementierung

```python
# import all necessary libs
from __future__ import print_function
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm
import tensorflow as tf
# we use Keras to implement, layer-by-layer the DVAE and PCA
from keras.layers import Input, Dense, Lambda
from keras.models import Model
from keras import backend as K
from keras import metrics
from keras.datasets import mnist
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Conv2DTranspose,Reshape
from sklearn.decomposition import PCA
import os
%matplotlib inline
os.environ["PATH"] += os.pathsep + 'C:/Program Files (x86)/Graphviz2.38/bin/'
```

```python
# we use Keras to implement, layer-by-layer the DVAE and PCA
from keras.layers import Input, Dense, Lambda
from keras.models import Model
from keras import backend as K
from keras import metrics
from keras.datasets import mnist
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Conv2DTranspose,Reshape
from sklearn.decomposition import PCA
```

## Experiment Parametrierung

```python
# training params for PCA vs. DVAE
num_train = 50000
n_images = 6
batch_size = 205
original_dim = 784
latent_dim = 8
epochs = 1000
epsilon_std = 1.0
noise_factor = 0.5
```

```python
# get the MNIST digits
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# prepare data for DVAE
x_train = x_train.astype('float32') / 255.
x_test = x_test.astype('float32') / 255.
x_train = x_train.reshape((len(x_train), 28,28,1))
x_test = x_test.reshape((len(x_test), 28,28,1))
noise_train = x_train + noise_factor * np.random.randn(*x_train.shape)
noise_test = x_test + noise_factor * np.random.randn(*x_test.shape)
# clip the images to be between 0 and 1
noise_train = np.clip(noise_train, 0., 1.)
noise_test = np.clip(noise_test, 0., 1.)
# prepare data for PCA
# training
x_test_pca = x_test
shape_x_test = x_test_pca.shape
pcaInputTest = np.reshape(x_test,[shape_x_test[0],shape_x_test[1]*shape_x_test[2]]).astype('float32')/255
```

```python
# display the images (28x28 px)
digit_size = 28
figure = np.zeros((digit_size * 2, digit_size * n_images))
showidx=np.random.randint(0,num_train,n_images)
# display input, noisy input
for i,idx in enumerate (showidx):
    figure[0: 28,i *28: (i + 1) * 28] = np.reshape(x_train[idx], [28, 28])
    figure[28: 56,i *28: (i + 1) * 28] = np.reshape(noise_train[idx], [28, 28])
plt.figure(figsize=(28*2, 28*n_images))
plt.imshow(figure, cmap='Greys_r')
plt.show()
```

## Grundlegende Mathematik der PCA

### Lineare Transformation

Es sei $\{y_i\}^N_{i=1}$ eine Menge von $N$ Beobachtungs-Vektoren der Dimension $n$ mit $n\leq N$.

Eine __lineare Transformation__ eines __endlich-dimensionalen__ Vektors kann als __Matrix Multiplikation__ ausgedrückt werden:

$$ \begin{align} x_i = W y_i \end{align} $$

mit $y_i \in R^{n}, x_i \in R^{m}$ und $W \in R^{nxm}$.

### Lineare Transformation

* Das $j-te$ Element in $x_i$ ist das __Innere Produkt__ von $y_i$ und der $j-ten$ Spalte der Matrix $W$, welche wir durch $w_j$ bezeichen. Es sei $Y \in R^{nxN}$ die Matrix, welche wir durch horizontale Aneinanderreihung der Vektoren $\{y_i\}^N_{i=1}$ erhalten,

$$ Y = \begin{bmatrix} | ... | \\ y_1 ... y_N \\ | ... | \end{bmatrix} $$

* Aus der __linearen Transformation__ folgt:

$$ X = W^TY,  X_0 = W^TY_0, $$

wobei $Y_0$ die __Matrix der zentrierten Elemente__ (d.h. wir subtrahieren den Mittelwert von jeder Beobachtung) bezeichnet, und __Kovarianzmatrix__ $Y_0Y_0^T$.

### Dimensionsreduzierung, Komprimierung

PCA wird zur __Dimensions-Reduktion__ verwendet, da sie durch die durch eine lineare Transformation die __Anzahl der Variablen reduziert__.

Da nur die ersten __$m$ Hauptkomponenten erhalten__ werden, __verliert__ PCA __information__ (d.h. __verlustreiche Komprimierung__).

Der __Verlust__ ( _Summe des quadratischen Rekonstruktions-Fehlers_ ) wird jedoch durch die __Maximierung der Komponenten-Varianzen minimiert__

$$ \min_{W \in R^{nxm}} \| Y_0 - WW^TY_0 \|_F^2, W^TW = I_{mxm}$$

wobei $F$ die Frobenius-Norm bezeichnet.

### Minimaler quadratischer Gesamtrekonstruktionsfehler

Die Transformations-Matrix $P_m$ kann ebenfalls durch Lösung der folgenden Gleichung berechnet werden:

$$ \min_{W \in R^{nxm}} \| Y_0 - WW^TY_0 \|_F^2, W^TW = I_{mxm}$$

wobei $F$ die Frobenius-Norm bezeichnet.

Daraus folgt, dass $P_m$ __jeden zentrierten Vektor__ der Länge $n$ in einen Vektor der Länge $m$ mit ($ m < n$) derart __komprimiert__, dass die __Summe des quadratischen Rekonstruktions-Fehlers minimiert wird__.

### Skalierung

Zur Berechung der PCA können viele verschiedene __iterative Algorithmen__ eingesetzt werden
* QR Algorithmen
* Jacobi Algorithmus
* Power methode
* Singulärwert-Zerlegung (Singular Value Decomposition, SVD)

Für __sehr große Datenmengen__ eignen sich diese Algorithmen __nicht__!

```python
# analytical PCA of the training set
def analytical_pca(y):
    # variance to explain
    pca = PCA()
    # apply PCA
    pca.fit(y)
    # extract the components
    loadings = pca.components_
    # apply the transformation
    components = pca.transform(y)
    # reconstruct from components for visualization
    filtered = pca.inverse_transform(components)
    return filtered
```

## Grundlegende Mathematik der Autoencoder

Für jeden Eingangsvektor $x$ der Dimension $d$ des kompletten Datensaztes der Länge $n$ generiert das neuronale Netz eine Rekonstruktion $x'$ durch:

* __Kodierung der Eingangsdaten__ (d.h. verwende die lineare / nicht-lineare Transformation $g_\phi(.)$)
* dies liefert eine __komprimierte Kodierung__ in der dünnsten Netzwerk-Ebene, $z$
* __Dekodierung der komprimierten Eingangsdaten__ durch Anwendung der linearen / nicht-linearen Transformation $f_\theta(.)$

Die __Parameter $(\theta, \phi)$ werden im Verlauf des Training derart optimiert__, dass ein den Eingangsdaten möglichst ähnliches Ergebnis , $x \approx f_\theta(g_\phi(x))$, produziert wird. In anderen Worten: __die Indentitäts-Funktion wird erlernt__ mit __Cross-Entropy (bei sigmoid Aktivierungsfuntionen)__, __mittlere quadratische Fehler (MSE)__ etc.:

# Denoising Variational Autoencoders (DVAE)

Das Funktionsprinzip __unterscheidet sich__ vom grundlegenden Autoencoder dahingehend, dass ein gewisses Maß an __Störrauschen__  (einer __gewissen Wahrscheinlichkeitsverteilung__ folgend) den __Eingangsdaten hinzugefügt wird__ und dass die __verborgenen Ebenen__ dieses Rauschen __ausgleichen muss__ um die Eingangsdaten zu __rekonstruieren__ [Im, Bengio et al., 2017, Kingma et al., 2017].

Für jeden gestörten Eingangsvektor $\tilde x$ eines originalen Vektors $x$ der Dimension $d$, generiert das neuronale Netz eine Rekonstruktion $x'$ durch:
* __Kodierung der Eingangsdaten__, welche die Abbildung als Wahrscheinlichkeit der Schätzung von $z$ unter Verwendung der Eingangsdaten darstellt
* dies liefert eine __komprimierte Kodierung in der dünnsten Netzwerk-Ebene__ $z$, welche der Verteilung $q_\phi(z|x)$ folgt
* __Dekodierung der komprimierten Eingangsdaten__ an der Ausgangsebene unter Einhaltung des __Beobachtungs-Modells__ $p_\theta(x|z)$

```python
# Implement the DVAE
# encoder part
x_noise = Input(shape=(28,28,1))
conv_1 = Conv2D(64,(3, 3), padding='valid',activation='relu')(x_noise)
conv_2 = Conv2D(64,(3, 3), padding='valid',activation='relu')(conv_1)
pool_1 = MaxPooling2D((2, 2))(conv_2)
conv_3 = Conv2D(32,(3, 3), padding='valid',activation='relu')(pool_1)
pool_2 = MaxPooling2D((2, 2))(conv_3)
h=Flatten()(pool_2)
z_mean = Dense(latent_dim)(h)
z_log_var = Dense(latent_dim)(h)
```

```python
# Implement the DVAE
# reparameterization trick
def sampling(args):
    z_mean, z_log_var = args
    epsilon = K.random_normal(shape=(K.shape(z_mean)[0], latent_dim), mean=0.,
                              stddev=epsilon_std)
    return z_mean + K.exp(z_log_var / 2) * epsilon

z = Lambda(sampling, output_shape=(latent_dim,))([z_mean, z_log_var])
```

```python
# Implement the DVAE
# decoder part
# we instantiate these layers separately so as to reuse them later
z=Reshape([1,1,latent_dim])(z)
conv_0T = Conv2DTranspose(128,(1, 1), padding='valid',activation='relu')(z)#1*1
conv_1T = Conv2DTranspose(64,(3, 3), padding='valid',activation='relu')(conv_0T)#3*3
conv_2T = Conv2DTranspose(64,(3, 3), padding='valid',activation='relu')(conv_1T)#5*5
conv_3T = Conv2DTranspose(48,(3, 3), strides=(2, 2),padding='same',activation='relu')(conv_2T)#10*10
conv_4T = Conv2DTranspose(48,(3, 3), padding='valid',activation='relu')(conv_3T)#12*12
conv_5T = Conv2DTranspose(32,(3, 3), strides=(2, 2),padding='same',activation='relu')(conv_4T)#24*24
conv_6T = Conv2DTranspose(16,(3, 3), padding='valid',activation='relu')(conv_5T)#26*26
x_out = Conv2DTranspose(1,(3, 3), padding='valid',activation='sigmoid')(conv_6T)#28*28
```

## DVAE

* DVAE __Verlustfunktion__ beinhaltet die Erstellung von Beispielen aus $z \backsim q_\phi(z|x)$. Dies ist ein __stochastischer Prozess__ und eignet sich daher __nicht zur Fehlerrückführung__.

* Die __geschätzte Posteriori-Verteilung $q_\phi(z|x)$__ approximiert die tatsächliche Verteilung $p_\theta(z|x)$.

* Wir können die __Kullback-Leibler Abweichung__, $D_{KL}$  benutzen um die __Differenz der beiden Verteilungen__ zu quantifizieren.

```python
# Implement the DVAE
# reparameterization trick
def sampling(args):
    z_mean, z_log_var = args
    epsilon = K.random_normal(shape=(K.shape(z_mean)[0], latent_dim), mean=0.,
                              stddev=epsilon_std)
    return z_mean + K.exp(z_log_var / 2) * epsilon

z = Lambda(sampling, output_shape=(latent_dim,))([z_mean, z_log_var])
```

## DVAE

Durch __Minimierung des Verlusts__, __maximieren__ wir daher die __untere Schranke der Wahrscheinlichkeit__ (__evidence lower bound (ELBO)__) zur Generierung echter Daten-Beispiele.

```python
# Implement the DVAE
# instantiate model
dvae = Model(x_noise, x_out)
dvae.summary()

# Compute loss
def DVAE_loss(x_origin,x_out):
    x_origin=K.flatten(x_origin)
    x_out=K.flatten(x_out)
    xent_loss = original_dim * metrics.binary_crossentropy(x_origin, x_out)
    kl_loss = - 0.5 * K.sum(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var), axis=-1)
    dvae_loss = K.mean(xent_loss + kl_loss)
    return dvae_loss

# compile the model
dvae.compile(optimizer='adam', loss=DVAE_loss)
```

```python
# Train the DVAE
dvae.fit(noise_train,x_train,  shuffle=True, epochs=epochs, batch_size=batch_size, validation_data=(noise_test, x_test))

# Comparison PCA vs. DVAE
# testing the DVAE
num_test=10000
showidx=np.random.randint(0,num_test,n_images)
x_out=dvae.predict(x_test[showidx])

# prepare data for testing PCA
pcaInputTest = np.reshape(x_test,[shape_x_test[0],shape_x_test[1]*shape_x_test[2]]).astype('float32')/255
pcaOutput = analytical_pca(pcaInputTest)
```

```python
# Display results
digit_size = 28
figure = np.zeros((digit_size * 4, digit_size * n_images))
for i,idx in enumerate (showidx):
    figure[0: 28,i *28: (i + 1) * 28] = np.reshape(x_test[idx], [28, 28]) # input data
    figure[28: 28 * 2,i *28: (i + 1) * 28] = np.reshape(noise_test[idx], [28, 28]) # noisy input data
    figure[28 * 2: 28 * 3,i *28: (i + 1) * 28] = np.reshape(x_out[i], [28, 28]) # DVAE output
    figure[28 * 3: 28 * 4,i *28: (i + 1) * 28] = np.reshape(pcaOutput[idx], [28, 28]) # PCA output
plt.figure(figsize=(28 * 4, 28*n_images))
plt.imshow(figure, cmap='Greys_r')
# plt.savefig('inference_output.png')
plt.show()
```

```python
%%HTML
<style>
td {
  font-size: 15px
}
</style>
```

# Vergleich von PCA und DVAE

### Inferenz

# Vergleich von PCA und DVAE

### Kostenfunktion

# Vergleich zwischen PCA und DVAE

# Vergleich zwischen PCA und DVAE

# PCA vs. Autoencoders
## "Zwei identische Fremde"

# PCA vs. Autoencoders

*  Ein __Autoencoder__ mit einer einzelnen __voll verbundenen (fully-connected) versteckten Ebene__, einer __linearen Aktivierungsfunktion__ und dem __quadratischen Fehler als Kostenfunktion__ ist __eng mit der PCA verwandt__ - seine __Gewichten__ spannen den __Untervektorraum der Hauptkomponenten__ auf [Plaut, 2018]

* Bei __Autoencodern__ sorgt die __diagonale Approximation beim Kodiervorgang__ zusammen mit der __inhärenten Stochastizität__ für lokale __Orthogonalität beim Dekodieren__  [Rolinek et al, 2019]

# Lieraturverzeichnis

[Goodfellow et al., 2016] Ian Goodfellow, Yoshua Bengio and Aaron Courville, Deep Learning, MIT Press, 2016.

[Friedman et al., 2017] Jerome H. Friedman, Robert Tibshirani, and Trevor Hastie, The Elements of Statistical Learning: Data Mining, Inference, and Prediction, Springer, 2017.

[Plaut, 2018] Plaut, E., 2018. From principal subspaces to principal components with linear autoencoders. arXiv preprint arXiv:1804.10253.

[Im, Bengio et al., 2017] Im, D.I.J., Ahn, S., Memisevic, R. and Bengio, Y., 2017, February. Denoising criterion for variational auto-encoding framework. In Thirty-First AAAI Conference on Artificial Intelligence.

[Rolinek et al, 2019] Rolinek, M., Zietlow, D. and Martius, G., 2019. Variational Autoencoders Pursue PCA Directions (by Accident). In Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (pp. 12406-12415).

[Lei et al., 2018] Lei, N., Luo, Z., Yau, S.T. and Gu, D.X., 2018. Geometric understanding of deep learning. arXiv preprint arXiv:1805.10451.

[Kingma et al., 2013] Kingma, D.P. and Welling, M., 2013. Auto-encoding variational bayes. arXiv preprint arXiv:1312.6114.

### Maximale Varianzkomponenten, Kovarianz und Dekorrelation

* Der erste Ladungs-Vektor ist derjenige Einheitsvektor mit dem das innere Produkt der Beobachtungs-Vektoren die __größte Varianz__ aufweisen:

$$ \max w_1^T Y_0Y_0^Tw_1, w_1^Tw_1 = 1$$

* Die Lösung der vorherigen leichung ist der erste Eigenvektor der __Kovarianz-Matrix__ $Y_0Y_0^T$, welcher zum größten Eigenwert gehört.

* Die Matrix $P$ kann durch __Diagonalisierung der Kovarianz-Matrix__ berechnet werden:

$$ Y_0Y_0^T = P \Lambda P^{-1} = P \Lambda P^T $$

$\Lambda = Y_0Y_0^T $ ist eine Diagonal-Matrix, deren Diagonal-Elemente $\{\lambda_i\}^N_{i=1}$ der Größe nach absteigend sortiert sind. $ Y = PX $ liefert die inverse Tranformation. Da die Kovarianz-Matrix von $X$ diagonal ist, ist die PCA eine __dekorrelierende Transformation__.

### Singulärwert-Zerlegung
### (Singular Value Decomposition, SVD)

Ein Vektor $v$ der Dimension $N$ ist ein __Eigenvektor__ einer quadratischen N × N Matrix $A$, wenn diese die folgende __lineare Gleichung__ erfüllt

$$Av =\lambda v$$

wobei $λ$ ein skalarer Wert ist, welcher als der __zum Eigenvektor v gehörende Eigenwert__ bezeichnet wird.

### Singulärwert-Zerlegung
### (Singular Value Decomposition, SVD)

Die Matrix $Y_0 \in R^{nxN}$ kann __faktorisert__ werden als $Y_0 = U \Sigma V^T$, wobei $U \in R^{nxn}$ und $V \in R^{NxN}$ __orthogonale Matrizen__ sind und $\Sigma \in R^{nxN}$ abgesehen von der Diagonalwerten (den sogenannten __Singulär-Werten__) nur aus Nullen besteht.

Die Singulärwertzerlegung von $Y_0$ ist äquivalent zur __Eigenwertzerlegung__ von $Y_0T_0^T$.

<style>
td {
  font-size: 15px
}
</style>

# Vergleich von PCA und DVAE

### Lernen der Mannigfaltigkeit

|__PCA__|__DVAE__|
|:-----|:---|
| Kodierung/Dekodierung, keine Robustheit gegen Rauschen | nicht-linear, probabilistische Kodierung/Dekodierung mit Robustheit gegen Rauschen und nicht-linearen Aktivierungsfunktionen|
| unkorrelierte Koordinaten | korrelierte Ausgansdaten an der dünnsten Netzwerkebene |
| Koordinaten sind in absteigener Reihenfolge der Varianz geordnet | Koordinaten sind ungeordnet |
| die Spalten der Transformations-Matrix sind orthonormal | die Spalten der Transformations-Matrix sind nicht notwendigerweise orthonormal |
| Robustheit gegenüber moderatem Rauschen mit bekannten Verteilungen | Robustheit gegen eine Vielzahl verschiedener Arten und Größenordnungen an injeziertem Rauschen (masking noise, Gaussian noise, salt-and-pepper noise), da das Entrauschen entscheidung für die Generalisierung ist |
| einfacher Algorithmus (ohne Regularisierung), geringe Robustheit | die Punkte in niedrig-dimensionalen Mannifaltigkeiten sind robust gegen Rauschen im hoch-dimensionalen Beobachtungs-Raum |

<style>
td {
  font-size: 15px
}
</style>
# Vergleich zwischen PCA und DVAE

### Training

|__PCA__|__DVAE__|
|:-----|:---|
| Abbildung der Eingangsdaten auf einen festen Vektor | Abbildung der Eingangsdaten auf eine Wahrscheinlichkeitsverteilung |
| iterative Methoden: QR Zerlegung, Jacobi Algorithmus, Singulärwertzerlegung | Fehlerrückführung (Backpropagation)  |
| aufgrund der Kovarianz-Berechnung ineffizient bei großen Datenmengen | effizient bei großen Datenmengen aufgrund der starken Fähigkeit des Erlernens der Mannigfaltigkeit |
| basiert auf der Korrelations-/Kovarianz-Matrix, welche - zumindest in der Theorie - sehr empfindlich gegenüber Ausreißern sein kann | kann Beispiele direkt aus dem Eingangsraum generieren und daher die Eigenschfaten des Eingangsrauschens beschreiben ("reparametrization trick") |