```python
import numpy as np
import sympy as sp

a = 0

class Eq(object):
    def __init__(self, symbols):

        self.symbols = symbols

        self._equations = {}
        self.equations = {}

        self._previous_globals = {}

    def __enter__(self):

        for name in self.symbols:

            # Temporarily backup variables with that name
            if name in globals().keys():
                self._previous_globals[name] = globals()[name]

            # Create a sympy symbol
            symbol = sp.Symbol(name)

            self._equations[name] = symbol

            # Put the variable on the global stack
            globals()[name] = symbol

        return self

    def __exit__(self, exc_type, exc_value, traceback):

        # Store and remove symbols
        for symbol in self.symbols:
            if self._equations[symbol] != globals()[symbol]:
                #print(symbol, "changed to", globals()[symbol])
                self.equations[symbol] = globals()[symbol].copy()

            del globals()[symbol]

        # Replace old values
        for key, val in self._previous_globals.items():
            globals()[key] = val

    def __str__(self):
        s = ""

        for var, eq in self.equations.items():
            s += var + " = " + sp.ccode(eq) + "\n"

        return s

print("Inside context")
val = 10.0
with Eq(['a', 'b', 'c']) as eq:

    a = sp.tanh(b) + val

print("Equations:")
print(eq)

print("Outside context")
print("a:", a)
#print("b:", b)
```

    Inside context
    Equations:
    a = tanh(b) + 10.0

    Outside context
    a: 0

```python
import multiprocessing

def worker(val):

    with Eq(['a', 'b', 'c']) as eq:

        a = sp.tanh(b) + val

    return str(eq)

jobs = []
vals = np.linspace(0.0, 1.0, 5)

with multiprocessing.Pool() as pool:
    res = pool.map(worker, vals)

    for w in res:
        print(w)
        print("----")

```

    ----

    ----

    ----

    ----

    ----

```python
class N(object):
    def __init__(self):
        self.a = 1

class Eq2(object):

    def __init__(self):
        self.pre = N()
        self.post = N()

    def __enter__(self):

        return self

    def __exit__(self, exc_type, exc_value, traceback):

        pass

with Eq2() as s:
    print(s.pre.a)

```

    1

```python

```


```python
import numpy as np
#import logit
from types import SimpleNamespace
%load_ext autoreload
%autoreload 2
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from copy import copy
plt.style.use('seaborn-whitegrid')

import pandas as pd
import datetime

import pandas_datareader # install with `pip install pandas-datareader`
import pydst # install with `pip install git+https://github.com/elben10/pydst`

from pandas_datareader import wb
```

# CO2 emissions

**Question 1:** Data cleaning.
* Download data sets 1-3. Exclude all entries in the data sets that are not a country listed in country_region.xlsx.
* Assign a world region to each country in data sets 1-3 using country_region.xlsx.
* If a data-point in one data set is `NaN` for country $x$ in year $y$, then delete it together with the corresponding data-points for country $x$ in year $y$ in the 2 other data sets.

Check that the resulting number of rows for GDP, population and CO2 is 4786 in each case.

```python
# Load regions
regs = pd.read_excel('country_region.xlsx', header = 0)
regs.drop(['code'], inplace = True, axis = 1)
regions = regs.region.unique()
```

```python
# Load population data
pop_name = 'SP.POP.TOTL'
pop_raw = wb.download(indicator=pop_name, country=[], start=1990, end=2016)
pop_raw = pop_raw.rename(columns = {pop_name:'POP'})
pop_raw = pop_raw.reset_index()
```

```python
# Load GDP data
gdp_name = 'NY.GDP.MKTP.PP.KD'
gdp_raw = wb.download(indicator=gdp_name, country=[], start=1990, end=2016)
gdp_raw = gdp_raw.rename(columns = {gdp_name:'GDP'})
gdp_raw = gdp_raw.reset_index()
```

```python
# Load CO2 data
co2_name = 'EN.ATM.CO2E.KT'
co2_raw = wb.download(indicator=co2_name, country=[], start=1990, end=2016)
co2_raw = co2_raw.reset_index()
co2_raw.rename(columns = {co2_name:'CO2'}, inplace=True)
```

```python
# Merge data sets to get a region for each country
data = pd.merge(co2_raw, gdp_raw, on=['year', 'country'], how='inner')
data = pd.merge(data, pop_raw, on=['year', 'country'], how='inner')
data = pd.merge(data, regs, on=['country'], how='inner')
data.sort_values(by=['region','country', 'year'], inplace = True)
data.reset_index(inplace = True, drop = True)
```

```python
# Showing the data with added regions
display(data.head(5))
# Size of data set without cleaning
display(data.shape)
```

      country
      year
      CO2
      GDP
      POP
      region

      0
      American Samoa
      1990
      NaN
      NaN
      47351.0
      East Asia & Pacific

      1
      American Samoa
      1991
      NaN
      NaN
      48682.0
      East Asia & Pacific

      2
      American Samoa
      1992
      NaN
      NaN
      49900.0
      East Asia & Pacific

      3
      American Samoa
      1993
      NaN
      NaN
      51025.0
      East Asia & Pacific

      4
      American Samoa
      1994
      NaN
      NaN
      52099.0
      East Asia & Pacific

    (5778, 6)

```python
# Clean data by dropping rows containing NaNs
is_nan = (data.CO2.isna() | data.GDP.isna() | data.POP.isna())
data = data.loc[is_nan == False,:]
data.reset_index(inplace = True, drop = True)
data.to_csv('clean_data.csv', index = False, sep='\t', float_format='%d')
```

```python
# Any NaNs left?
print('All NaNs deleted?: ' + np.str(data.isna().sum() == 0))
print('Number of rows: ' + np.str(data.shape[0]))
```

    All NaNs deleted?: country    True
    year       True
    CO2        True
    GDP        True
    POP        True
    region     True
    dtype: bool
    Number of rows: 4660

**Question 2:** Regional CO2 emissions. Calculate the regional CO2 emissions per dollar GDP on an annual basis. That is, for region $R$ in year $t$ compute
$$
\frac{\sum_{j \in R} \text{CO2}_{j,t}}{\sum_{j \in R} Y_{j,t}}
$$
where $Y_{j}$ denotes the GDP of country $j$.

Plot CO2 emissions per dollar GDP for all regions in the way you find most presentable and comment.

```python
# CO2 emissions pr mio. USD GDP (using mio. for nicer y-axis) for each region
data_reg = data.groupby(['region', 'year'])[['CO2', 'GDP', 'POP']].apply(sum)
data_reg['CO2_GDP'] = data_reg.CO2 / (data_reg.GDP/1e6)
data_reg.reset_index(inplace = True)
```

```python
# Plot development in CO2/GDP by region
reg_co2 = data_reg.loc[:,['year', 'region', 'CO2_GDP']].copy()
reg_co2 = reg_co2.pivot(index=['year'], values='CO2_GDP', columns='region')
print('Kiloton C02 emissions pr mio. $GDP')
reg_co2.plot(subplots=True, figsize=(15, 10), layout=(3,4), ylim=(0.15,0.6));
```

**Question 3:** Growth rates.
* For each region $R$, calculate population weighted average $\text{CO2}_{R,t}$  and GDP pr year. See weighted average definition below.
* Calculate the annual **growth rates** of averaged GDP and CO2 for each region.
* Finally, create **one subplot** per region containing the two associated growth rates. Make a brief comment. (*tip: better use a loop for this instead of code repetition*)

The weighted averages of of GDP and CO2 for region $R$ is obtained by first calculating the weights:
$$
w_{i,t} = \frac{POP_{i,t}}{\sum \limits_{k \in R}POP_{k,t}}
$$
for each country $i \in R$.
Then get average regional GDP by
$$
\bar{Y}_{R,t} =  \sum \limits_{i \in R}w_{i,t}Y_{i,t}
$$
and similarly for CO2.

The growth rate for regional GDP per capita is then
$$
\bar{Y}^{\Delta}_{R,t} = \frac{\bar{Y}_{R,t}}{\bar{Y}_{R,t-1}} - 1
$$
and similarly for regional CO2 pr capita

```python
# Weights
df = data.copy()
df['w'] = df.groupby(['year', 'region'])['POP'].apply(lambda x: x/x.sum())

# Weighted CO2 and GDP measures
df['wGDP'] = df.w * df.GDP
df['wCO2'] = df.w * df.CO2
df_reg = df.groupby(['region', 'year'])[['wCO2', 'wGDP']].sum()
df_reg.reset_index(inplace = True)

# Calculate weighted growth rates in CO2 and GDP
df_reg['dCO2'] = df_reg.groupby(['region'])['wCO2'].apply(lambda x: x.pct_change(fill_method='ffill'))
df_reg['dGDP'] = df_reg.groupby(['region'])['wGDP'].apply(lambda x: x.pct_change(fill_method='ffill'))
```

```python
# Plotting the growth rates
nrow = 3
ncol = int(np.ceil(len(regions)/nrow))

# Loop over each region to create plot
fig = plt.figure(figsize=(15,10));
for i,reg in enumerate(regions):
    df_r = df_reg.loc[df_reg.region == reg, ['year', 'dCO2', 'dGDP']].copy()
    ax = plt.subplot(nrow, ncol, i+1)
    plt.plot(df_r.year, df_r.dGDP)
    plt.plot(df_r.year, df_r.dCO2)
    plt.legend(['dGDP', 'dCO2'])
    ax.xaxis.set_major_locator(ticker.MultipleLocator(4))
    plt.ylim(-0.1, .2)
    plt.title(reg)
    plt.grid(False)
    plt.tight_layout()
```

One observation is that severe economic downturns are reflected in CO2 emissions. For instance, the crisis of 2007-2009 shows up very clearly in several regions. In general, year-to-year changes in emissions track economic activity, though on varying scales. In the Western hemisphere, the two variables move together quite closely, whereas CO2 emissions fluctuate much more than GDP in Sub-Saharan Africa.

# Risky assets

The consumption savings model with uncertainty in both income and interest rate.

A **household** lives two periods.

In the **second period** it gets utility from **consuming** and **leaving a bequest**,

$$
\begin{aligned}
v_{2}(m_{2})&= \max_{c_{2}}\frac{c_{2}^{1-\rho}}{1-\rho}+\nu\frac{(a_2+\kappa)^{1-\rho}}{1-\rho}\\
\text{s.t.} \\
a_2 &= m_2-c_2 \\
a_2 &\geq 0
\end{aligned}
$$

The **value function** $v_t(m_t)$ measures the household's value of having $m_t$ at the beginning of period $t$.

In the **first period**, the household gets utility from consuming and takes into account that it will also live in the next-period, where it receives a stochastic income.

We assume that the consumption decision is made at the **beginning** of the period.

We denote the interest rate on savings from period 1 to 2 by $r_{1,2}$. It is assumed to be **unknown** until the end of the period 1; ie. *after* the consumption decision is made.

$$
\begin{aligned}
	v_1(m_1) &= \max_{c_1}\frac{c_{1}^{1-\rho}}{1-\rho}+\beta\mathbb{E}_{1}\left[v_2(m_2)\right]\\&\text{s.t.}&\\
	a_1 & = m_1-c_1\\
	m_2 &= (1+r_{1,2})a_1+y_2 \\
	r_{1,2} &= \begin{cases}
	r_0 - \Delta^r & \text{with prob. }\frac{1}{2}\\
	r_0 + \Delta^r & \text{with prob. }\frac{1}{2} \\
	\end{cases}\\
	y_{2} &= \begin{cases}
	1-\Delta^y & \text{with prob. }\frac{1}{2}\\
	1+\Delta^y & \text{with prob. }\frac{1}{2}
	\end{cases}\\
	a_1 & \geq0
\end{aligned}
$$

```python
# Parameters
rho = 8
kappa = 0.5
nu = 0.2
r0 = 0.3
Delta_r = 0.29
Delta_y = 0.5
beta = 0.98
```

**Question 1:** Solve the model for both periods and obtain value functions $v_1(m_1), v_2(m_2)$ together with the optimal consumption functions $c^*_1(m_1), c^*_2(m_2)$.
Plot $v_1(m_1), v_2(m_2)$ in one graph and  $c^*_1(m_1), c^*_2(m_2)$ in another.

```python
import ConsumptionSavingReexam as CS
```

```python
mp = SimpleNamespace()
mp.rho = rho
mp.kappa = kappa
mp.nu = nu
mp.r0 = r0
mp.beta = beta
mp.Delta_y = Delta_y
mp.y_prb_low = 0.5
mp.Delta_r = Delta_r
mp.n_r = 1
```

```python
# a. Solving the model
model = CS.ConsumptionSavingModel(mp)
m1, c1, v1, m2, c2, v2 = model.solve()

# b. Plotting solution
fig = plt.figure(figsize=(10,5))
ax = fig.add_subplot(1,2,1)
ax.plot(m1,v1, label=f'Period {1}')
ax.plot(m2,v2, label=f'Period {2}')
ax.legend(loc='lower right',facecolor='white',frameon=True)
ax.set_xlabel('$m_t$')
ax.set_ylabel('$v_t$')
ax.set_title('value functions');
ax.set_xlim([0.1,4.04])
ax.set_ylim([-10.5,1.04]);

ax = fig.add_subplot(1,2,2)
ax.plot(m1,c1, label=f'Period {1}')
ax.plot(m2,c2, label=f'Period {2}')
ax.legend(loc='lower right',facecolor='white',frameon=True)
ax.set_xlabel('$m_t$')
ax.set_ylabel('$c_t$')
ax.set_title('consumption functions');
ax.set_xlim([0,4.0])
ax.set_ylim([0,3.0]);
```

**Question 2:** Now set $\Delta^r = 0$ and solve the model once again. Plot the associated $c^*_1(m_1), c^*_2(m_2)$ and compare them with the consumption functions from the solution in Question 1.

```python
# a. Resolve model with no risk in r
mp_norisk = copy(mp)
mp_norisk.Delta_r = 0

model_norisk = CS.ConsumptionSavingModel(mp_norisk)
m1_norisk, c1_norisk, m2_norisk, _, c2_norisk, _ = model_norisk.solve()

# b. Plot comparison
fig = plt.figure(figsize=(10,5))
ax = fig.add_subplot(1,2,1)
ax.plot(m1,c1, label=f'$\Delta^r$ = {mp.Delta_r}')
ax.plot(m1,c1_norisk, label=f'$\Delta^r$ = {mp_norisk.Delta_r}')
ax.legend(loc='lower right',facecolor='white',frameon=True)
ax.set_xlabel('$m_1$')
ax.set_ylabel('$c_1$')
ax.set_title(f'consumption functions, $t = 1$');
ax.set_xlim([0,4.0])
ax.set_ylim([0,3.0]);

ax = fig.add_subplot(1,2,2)
ax.plot(m2,c2, label=f'$\Delta^r$ = {mp.Delta_r}')
ax.plot(m2,c2_norisk, label=f'$\Delta^r$ = {mp_norisk.Delta_r}')
ax.legend(loc='lower right',facecolor='white',frameon=True)
ax.set_xlabel('$m_2$')
ax.set_ylabel('$c_2$')
ax.set_title(f'consumption functions, $t = 2$');
ax.set_xlim([0,4.0])
ax.set_ylim([0,3.0]);
```

**Question 3 - option 1:** Simulate the period 2 choices of $N$ households both for the case where $\Delta^r=0.02$ and where $\Delta^r=0$.
You can use the same distribution of $m_1$ for both cases as specified below.
Plot the two distributions of $c_2$ and comment.

```python
# a. Simulate from the two different models and plot
np.random.seed(2021)
simN = 50000
sim_m1 = np.fmax(np.random.normal(1, 0.05, size = simN), 0)

model.sim_m1 = copy(sim_m1)
c1_sim_risk, c2_sim_risk, m2_sim_risk  = model.simulate()

model_norisk.sim_m1 = copy(sim_m1)
c1_sim_norisk, c2_sim_norisk, m2_sim_norisk = model_norisk.simulate()

# b. Plot distribution of period 1 consumption
fig = plt.figure()
ax = fig.add_subplot(1,1,1)

ax.hist(c1_sim_risk,bins=100,label=f'$c_1$: $\Delta_r = {model.Delta_r}$', alpha = 0.6)
ax.hist(c1_sim_norisk,bins=100,label=f'$c_1$: $\Delta_r = {model_norisk.Delta_r}$', alpha = 0.6)
fig.tight_layout()

ax.legend(loc='lower right',facecolor='white',frameon=True)
ax.set_xlabel('$c_1$')
ax.set_ylabel('freq.')
ax.set_title('consumption in period 1');
```

```python
# c. Plot distribution of period 2 consumption
fig = plt.figure()
ax = fig.add_subplot(1,1,1)

ax.hist(c2_sim_risk,bins=100,label=f'$c_2$: $\Delta_r = {model.Delta_r}$', alpha = 0.6)
ax.hist(c2_sim_norisk,bins=100,label=f'$c_2$: $\Delta_r = {model_norisk.Delta_r}$', alpha = 0.6)
fig.tight_layout()
ax.legend(loc='lower right',facecolor='white',frameon=True)
ax.set_xlabel('$c_2$')
ax.set_ylabel('freq.')
ax.set_title('consumption in period 2');
```

**Question 3 - option 2:** Generalizing the set of possible interest rate outcomes.
We now consider the case where the interest rate has $N = 2n$ different possible realizations.
Specifically, $r$ has $n$ possible outcomes higher than $r_0$, and $n$ outcomes lower than $r_0$, which are **uniformly** distributed:
$$
\begin{aligned}
	r &= \begin{cases}
	r_0 - n\Delta^r & \text{with prob. }\frac{1}{N}\\
	r_0 - (n-1)\Delta^r & \text{with prob. }\frac{1}{N}\\
	r_0 - (n-2)\Delta^r & \text{with prob. }\frac{1}{N}\\
    \vdots & \\
	r_0 - \Delta^r & \text{with prob. }\frac{1}{N}\\
	r_0 + \Delta^r & \text{with prob. }\frac{1}{N}\\
    \vdots & \\
	r_0 + (n-1)\Delta^r & \text{with prob. }\frac{1}{N} \\
	r_0 + n\Delta^r & \text{with prob. }\frac{1}{N} \\
	\end{cases}\\
\end{aligned}
$$

Implement this generalized specification in code.
Test the model on the parameterization below and plot $c^*_1(m_1), c^*_2(m_2)$.

```python
# Initialize model with larger set of possible r outcomes.
mp_r = copy(mp)
mp_r.n_r = 5
mp_r.Delta_r = 0.05
model_r = CS.ConsumptionSavingModel(mp_r)
```

Note: you can inspect `ConsumptionSavingModel.v1()` for the implementation together with the function `ConsumptionSavingModel.r_outcomes` for the generation of interest rate realizations

```python
display(model_r.r_outcomes())
```

    (array([0.05, 0.1 , 0.15, 0.2 , 0.25, 0.35, 0.4 , 0.45, 0.5 , 0.55]),
     array([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]))

```python
# a. Solve model with N outcomes of r
_, c1_r, _, _, c2_r, _ = model_r.solve()

# b. Plot comparison
fig = plt.figure(figsize=(10,5))

ax = fig.add_subplot(1,2,2)
ax.plot(m1,c1, label=f'Period {1}')
ax.plot(m2,c2, label=f'Period {2}')
ax.legend(loc='lower right',facecolor='white',frameon=True)
ax.set_xlabel('$m_t$')
ax.set_ylabel('$c_t$')
ax.set_title('consumption functions');
ax.set_xlim([0,4.0])
ax.set_ylim([0,3.0]);
```

# Interpolation

**Polynomial interpolation**
Assume we have a set of data points $D = \{(y_i,x_i)\}_{i=1}^{n}$.

We now want to create a polynomial $p$ of degree $n-1$ such that $y_i = p(x_i)$ *exactly* for all $x_i,y_i \in D$.

We can obtain such a polynomial $p$ by the following steps:
$$
\begin{align}
	p(x) & = \sum \limits_{i = 1}^{n} y_i \times l_i(x) \\
	l_i(x) & = \prod \limits_{j \ne i} \frac{x - x_j}{x_i - x_j}
\end{align}
$$

**Example**
Say we have the function $f(x) = x^2 - 1$. We can then construct our set of data points $D$.
To do so, we evaluate $f$ in the points $\{1, 3, 5\}$:
* $f(1) = 0$
* $f(3) = 8$
* $f(5) = 24$

Thus we have $D = \{(0,1), (8,3), (24,5)\}$

Interpolating our function $f(x) = x^2 - 1$ at some $\hat{x} \in [1,5]$ is then given by

$$
p(\hat{x})  =  0 \times l_1(\hat{x}) + 3 \times l_2(\hat{x}) + 5 \times l_3 (\hat{x})
$$

$$
\begin{align}
	l_1(\hat{x}) = \frac{(\hat{x} - 3)}{(1 - 3)} \frac{(\hat{x} - 5)}{(1 - 5)}, \:\:
    l_2(\hat{x}) = \frac{(\hat{x} - 1)}{(3 - 1)} \frac{(\hat{x} - 5)}{(3 - 5)}, \:\:
    l_3(\hat{x}) = \frac{(\hat{x} - 1)}{(5 - 1)} \frac{(\hat{x} - 3)}{(5 - 3)}
\end{align}
$$

**Algorithm**
First evaluate the function $f$ on the set of points $X$ so as to obtain the associated function values $Y$. Then follow the pseudo-code below.

**Question 1:** Implement the algorithm to interpolate function values of $f$ to the point $\hat{x}$

```python
def polynomial_interpol(x0, xs, ys):
    ''' Interpolate between the points (xs,ys) using a polynomial.

    Args:

    x0 (float): the point at which to interpolate.
    xs (ndarray): the set of points (x-coordinates) for which we interpolate exactly.
    ys (ndarray): the set of y-coordinates that correspond to the x-coordinates xs.

    Returns
    px (float): the y-coordinate that corresponds to x0 given the interpolating polynomial.
    '''
    px = 0
    n = len(xs)
    for i in range(n):
        # Create basis function i
        l_i = 1
        for j in range(n):
            if j == i:
                continue
            else:
                l_i *= (x0 - xs[j])/(xs[i] - xs[j])
        # Add basis function i in sum
        px += ys[i]*l_i

    return px
```

Testing the interpolation function:

```python
f = lambda x: x**3

X1 = np.array([1, 2, 3, 4])
X2 = np.array([0.5, 3.2, 7, 12.9])

Y1 = f(X1)
Y2 = f(X2)

x = 3.22345

y1_interpol = polynomial_interpol(x, X1, Y1)
print(f'f(x) = {f(x): .7f}  interpolated y = {y1_interpol: .7f}')

y2_interpol = polynomial_interpol(x, X2, Y2)
print(f'f(x) = {f(x): .7f}  interpolated y = {y2_interpol: .7f}')
```

    f(x) =  33.4936760  interpolated y =  33.4936760
    f(x) =  33.4936760  interpolated y =  33.4936760


# Computer vision and deep learning - Laboratory 5

In this laboratory we'll work with a semantic segmentation model. The task of semantic segmentation implies the labeling/classification of __all__ the pixels in the input image.

You'll build and train a fully convolutional neural network inspired by U-Net.
Also, you will learn about how you can use various callbacks during the training of your model.

Finally, you'll implement several metrics suitable for evaluating segmentation models.

```python
import os
import cv2
import glob
import shutil
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
```

## Data loading

As in the previous laboratory, we'll work with the OxfordPets dataset. Each image has a segmentation assigned; three classes are defined on each segmentation mask
- Label 1: pet;
- Label 3: border of the pet;
- Label 2: background.

Let's first write an offline processing step that will split our dataset into train/test sets, and will pre-process the input images.

```python
!wget https://www.robots.ox.ac.uk/~vgg/data/pets/data/images.tar.gz
!wget https://www.robots.ox.ac.uk/~vgg/data/pets/data/annotations.tar.gz
```

    --2022-01-12 15:11:54--  https://www.robots.ox.ac.uk/~vgg/data/pets/data/images.tar.gz
    Resolving www.robots.ox.ac.uk (www.robots.ox.ac.uk)... 129.67.94.2
    Connecting to www.robots.ox.ac.uk (www.robots.ox.ac.uk)|129.67.94.2|:443... connected.
    HTTP request sent, awaiting response... 200 OK
    Length: 791918971 (755M) [application/x-gzip]
    Saving to: ‘images.tar.gz’

    images.tar.gz       100%[===================>] 755.23M   110MB/s    in 6.9s

    2022-01-12 15:12:01 (110 MB/s) - ‘images.tar.gz’ saved [791918971/791918971]

    --2022-01-12 15:12:01--  https://www.robots.ox.ac.uk/~vgg/data/pets/data/annotations.tar.gz
    Resolving www.robots.ox.ac.uk (www.robots.ox.ac.uk)... 129.67.94.2
    Connecting to www.robots.ox.ac.uk (www.robots.ox.ac.uk)|129.67.94.2|:443... connected.
    HTTP request sent, awaiting response... 200 OK
    Length: 19173078 (18M) [application/x-gzip]
    Saving to: ‘annotations.tar.gz’

    annotations.tar.gz  100%[===================>]  18.28M  81.7MB/s    in 0.2s

    2022-01-12 15:12:01 (81.7 MB/s) - ‘annotations.tar.gz’ saved [19173078/19173078]

```python
!tar -xf images.tar.gz
!tar -xf annotations.tar.gz
```

```python
!ls images/*.jpg | head -4
!ls annotations/trimaps/*.png | head -4
```

    images/Abyssinian_100.jpg
    images/Abyssinian_101.jpg
    images/Abyssinian_102.jpg
    images/Abyssinian_103.jpg
    annotations/trimaps/Abyssinian_100.png
    annotations/trimaps/Abyssinian_101.png
    annotations/trimaps/Abyssinian_102.png
    annotations/trimaps/Abyssinian_103.png

```python
img = cv2.imread('images/Abyssinian_101.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

mask = cv2.imread('annotations/trimaps/Abyssinian_101.png', cv2.IMREAD_GRAYSCALE)
plt.subplot(1, 3, 1)
plt.imshow(img)
plt.subplot(1, 3, 2)
plt.imshow(mask)
plt.subplot(1, 3, 3)
plt.imshow(mask, cmap="gray")

```

```python
mask = cv2.resize(mask, ( 45//2, 31//2), interpolation=cv2.INTER_NEAREST)
print(mask)
```

    [[2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2]
     [2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2]
     [2 2 2 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2]
     [2 2 2 3 1 3 3 2 2 2 2 2 2 2 3 3 2 2 2 2 2 2]
     [2 2 2 3 1 1 3 3 3 3 3 3 3 3 1 3 2 2 2 2 2 2]
     [2 2 2 3 1 1 1 1 1 1 1 1 1 1 1 3 2 2 2 2 2 2]
     [2 2 2 3 1 3 3 3 1 3 3 3 3 1 1 3 2 2 2 2 2 2]
     [2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2]
     [2 2 2 2 3 3 3 3 1 1 1 1 1 3 3 2 2 2 2 2 2 2]
     [2 2 2 2 3 1 1 1 1 1 1 1 1 3 2 2 2 2 2 2 2 2]
     [2 2 2 2 2 3 1 1 1 1 1 1 3 2 2 2 2 2 2 2 2 2]
     [2 2 2 2 2 2 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2]
     [2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2]
     [2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2]
     [2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2]]

```python
def make_image_square(img, padding_mode='edge', padding_value=0):
    height, width = img.shape[0], img.shape[1]
    if height > width:
        padding = ((0, 0), ((height - width) // 2, (height - width) // 2), (0, 0))
    else:
        padding = (((width - height) // 2, (width - height) // 2), (0, 0), (0, 0))

    if padding_mode == 'edge':
        return np.pad(img, padding, mode=padding_mode)
    return np.pad(img, padding, mode=padding_mode, constant_values=padding_value)

def get_splits(root_dir):
    image_paths = glob.glob(root_dir + "/*.jpg")
    labels = ["_".join(os.path.basename(path).split("_")[:-1]) for path in image_paths]
    class_names = sorted(list(set(labels)))
    assert (len(class_names) == 37)

    X = image_paths
    y = np.array([class_names.index(label) for label in labels])

    X_train, y_train, X_test, y_test = train_test_split(X, y, test_size=len(class_names)*20,
                                                        random_state=13,
                                                        shuffle=True)

    return X_train, X_test, y_train, y_test

def save_splits(images_paths, anno_dir, out_dir, img_shape = (128, 128)):
    for img_path in images_paths:
        filename = os.path.basename(img_path)
        anno_path = os.path.join(anno_dir, filename.replace('.jpg', '.png'))
        img = cv2.imread(img_path)
        if img is None:
            print('Error while loading image: ', img_path)
            continue
        mask = cv2.imread(anno_path)

        img = make_image_square(img, 'edge')
        mask = make_image_square(mask, 'constant', 2) # 2 - color of the background pixels
        img = cv2.resize(img, img_shape)
        mask = cv2.resize(mask, img_shape, interpolation=cv2.INTER_NEAREST)
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
        np.save(os.path.join(out_dir, filename.replace('.jpg', '.img.npy')), img)
        np.save(os.path.join(out_dir, filename.replace('.jpg', '.mask.npy')), mask)

def save_db(images_dir, anno_dir, outdir, img_shape = (128, 128)):
    X_train, _, X_test, _ = get_splits(images_dir)
    train_dir = os.path.join(outdir, 'train')
    test_dir = os.path.join(outdir, 'test')
    if os.path.exists(outdir):
        shutil.rmtree(outdir)
    os.mkdir(outdir)
    os.mkdir(train_dir)
    os.mkdir(test_dir)

    save_splits(X_train, anno_dir, train_dir)
    save_splits(X_test, anno_dir, test_dir)

def show_pair(img, mask):
    plt.subplot(1, 2, 1)
    plt.imshow(img)
    plt.title('Image')
    plt.subplot(1, 2, 2)
    plt.imshow(mask)
    plt.title('Mask')

    plt.show()

save_db('./images', './annotations/trimaps', 'oxford_pets_seg')
```

    Error while loading image:  ./images/Egyptian_Mau_145.jpg
    Error while loading image:  ./images/Egyptian_Mau_177.jpg
    Error while loading image:  ./images/Egyptian_Mau_191.jpg
    Error while loading image:  ./images/Abyssinian_34.jpg
    Error while loading image:  ./images/Egyptian_Mau_139.jpg
    Error while loading image:  ./images/Egyptian_Mau_167.jpg

You will use a slightly modified version of the data generator that you've written in the previous laboratory.

```python
class DataGenerator(tf.keras.utils.Sequence):
    def __init__(self, db_dir, batch_size,
                 shuffle=True):
        self.batch_size = batch_size
        self.shuffle = shuffle
        self.image_paths, self.mask_paths = None, None
        self.get_data(db_dir)
        self.indices = np.arange(len(self.image_paths))
        self.on_epoch_end()

    def get_data(self, root_dir):
        """"
        Loads the paths to the images and their corresponding labels from the database directory
        """
        self.image_paths = np.asarray(glob.glob(root_dir + "/*.img.npy"))
        self.mask_paths = np.asarray([path.replace('.img.npy', '.mask.npy') for path in self.image_paths])

    def __len__(self):
        """
        Returns the number of batches per epoch: the total size of the dataset divided by the batch size
        """
        return int(np.floor(len(self.image_paths) / self.batch_size))

    def __getitem__(self, index):
        """"
        Generates a batch of data
        """
        batch_indices = self.indices[index*self.batch_size : (index+1)*self.batch_size]

        batch_x = np.asarray([np.load(img_path).astype(np.float32)/255.0 for img_path in self.image_paths[batch_indices]])
        batch_y = np.asarray([np.expand_dims(np.load(mask_path) - 1, axis=-1) for mask_path in self.mask_paths[batch_indices]])

        return batch_x, batch_y

    def on_epoch_end(self):
        """"
        Called at the end of each epoch
        """
        # if required, shuffle your data after each epoch
        self.indices = np.arange(len(self.image_paths))
        if self.shuffle:
            # you might find np.random.shuffle useful here
            np.random.shuffle(self.indices)

train_generator = DataGenerator("./oxford_pets_seg/train", 32)

batch_x, batch_y = train_generator[0]
print(len(batch_x))
fig, axes = plt.subplots(nrows=1, ncols=6, figsize=[16, 9])
for i in range(len(axes)//2):
    axes[2*i].set_title('Image')
    axes[2*i].imshow(batch_x[i])

    axes[2*i + 1].set_title('Mask')
    axes[2*i + 1].imshow(batch_y[i][:,:,0]*64)
plt.show()

```

## Building the model

The model that will be used in this laboratory is inspired by the [U-Net](https://arxiv.org/abs/1505.04597) architecture.
U-Net is a fully convolutional neural network comprising two symmetric paths: a contracting path (to capture context) and an expanding path  (which enables precise localization).
The network also uses skip connections between the corresponding layers in the downsampling path to the layer in the upsampling path, and thus directly fast-forwards high-resolution feature maps from the encoder to the decoder network.

An overview of the U-Net architecture is depicted in the figure below.

## The downsampling path

For the downsampling path we'll use a convolutional neural network from the tensorflow.keras.applications module. We'll first load the pre-trained weights on ImageNet, and we'll "freeze" these weights during the training process.
In this example, we'll use the Mobile-Net architecture, but you can feel free to experiment with any other network.

The problem is that to create the skip connections required by the U-Net architecture we need access to the feature maps of some intermediate layers in the network and these are not accessible by default.

The first step is to determine the names for the layers that will be included in the skip connections. So, let's load the MobileNetV2 architecture and use the _summary()_ method to identify these layers.
[MobileNetV2](https://keras.io/api/applications/) has a size of 16MB and achieves a top-1 accuracy of 71.3% on ImageNet.

```python
input_shape = (128, 128, 3)
feature_extractor = tf.keras.applications.MobileNetV2(input_shape=input_shape, include_top=False)
feature_extractor.summary()

# TODO select the names of the layers that we'll be included in the skip connections
downsample_skip_layers = ["block_1_expand_relu", "block_2_expand_relu", "block_5_expand_relu", "block_8_expand_relu"]
```

    Model: "mobilenetv2_1.00_128"
    __________________________________________________________________________________________________
     Layer (type)                   Output Shape         Param #     Connected to
    ==================================================================================================
     input_12 (InputLayer)          [(None, 128, 128, 3  0           []
                                    )]

     Conv1 (Conv2D)                 (None, 64, 64, 32)   864         ['input_12[0][0]']

     bn_Conv1 (BatchNormalization)  (None, 64, 64, 32)   128         ['Conv1[0][0]']

     Conv1_relu (ReLU)              (None, 64, 64, 32)   0           ['bn_Conv1[0][0]']

     expanded_conv_depthwise (Depth  (None, 64, 64, 32)  288         ['Conv1_relu[0][0]']
     wiseConv2D)

     expanded_conv_depthwise_BN (Ba  (None, 64, 64, 32)  128         ['expanded_conv_depthwise[0][0]']
     tchNormalization)

     expanded_conv_depthwise_relu (  (None, 64, 64, 32)  0           ['expanded_conv_depthwise_BN[0][0
     ReLU)                                                           ]']

     expanded_conv_project (Conv2D)  (None, 64, 64, 16)  512         ['expanded_conv_depthwise_relu[0]
                                                                     [0]']

     expanded_conv_project_BN (Batc  (None, 64, 64, 16)  64          ['expanded_conv_project[0][0]']
     hNormalization)

     block_1_expand (Conv2D)        (None, 64, 64, 96)   1536        ['expanded_conv_project_BN[0][0]'
                                                                     ]

     block_1_expand_BN (BatchNormal  (None, 64, 64, 96)  384         ['block_1_expand[0][0]']
     ization)

     block_1_expand_relu (ReLU)     (None, 64, 64, 96)   0           ['block_1_expand_BN[0][0]']

     block_1_pad (ZeroPadding2D)    (None, 65, 65, 96)   0           ['block_1_expand_relu[0][0]']

     block_1_depthwise (DepthwiseCo  (None, 32, 32, 96)  864         ['block_1_pad[0][0]']
     nv2D)

     block_1_depthwise_BN (BatchNor  (None, 32, 32, 96)  384         ['block_1_depthwise[0][0]']
     malization)

     block_1_depthwise_relu (ReLU)  (None, 32, 32, 96)   0           ['block_1_depthwise_BN[0][0]']

     block_1_project (Conv2D)       (None, 32, 32, 24)   2304        ['block_1_depthwise_relu[0][0]']

     block_1_project_BN (BatchNorma  (None, 32, 32, 24)  96          ['block_1_project[0][0]']
     lization)

     block_2_expand (Conv2D)        (None, 32, 32, 144)  3456        ['block_1_project_BN[0][0]']

     block_2_expand_BN (BatchNormal  (None, 32, 32, 144)  576        ['block_2_expand[0][0]']
     ization)

     block_2_expand_relu (ReLU)     (None, 32, 32, 144)  0           ['block_2_expand_BN[0][0]']

     block_2_depthwise (DepthwiseCo  (None, 32, 32, 144)  1296       ['block_2_expand_relu[0][0]']
     nv2D)

     block_2_depthwise_BN (BatchNor  (None, 32, 32, 144)  576        ['block_2_depthwise[0][0]']
     malization)

     block_2_depthwise_relu (ReLU)  (None, 32, 32, 144)  0           ['block_2_depthwise_BN[0][0]']

     block_2_project (Conv2D)       (None, 32, 32, 24)   3456        ['block_2_depthwise_relu[0][0]']

     block_2_project_BN (BatchNorma  (None, 32, 32, 24)  96          ['block_2_project[0][0]']
     lization)

     block_2_add (Add)              (None, 32, 32, 24)   0           ['block_1_project_BN[0][0]',
                                                                      'block_2_project_BN[0][0]']

     block_3_expand (Conv2D)        (None, 32, 32, 144)  3456        ['block_2_add[0][0]']

     block_3_expand_BN (BatchNormal  (None, 32, 32, 144)  576        ['block_3_expand[0][0]']
     ization)

     block_3_expand_relu (ReLU)     (None, 32, 32, 144)  0           ['block_3_expand_BN[0][0]']

     block_3_pad (ZeroPadding2D)    (None, 33, 33, 144)  0           ['block_3_expand_relu[0][0]']

     block_3_depthwise (DepthwiseCo  (None, 16, 16, 144)  1296       ['block_3_pad[0][0]']
     nv2D)

     block_3_depthwise_BN (BatchNor  (None, 16, 16, 144)  576        ['block_3_depthwise[0][0]']
     malization)

     block_3_depthwise_relu (ReLU)  (None, 16, 16, 144)  0           ['block_3_depthwise_BN[0][0]']

     block_3_project (Conv2D)       (None, 16, 16, 32)   4608        ['block_3_depthwise_relu[0][0]']

     block_3_project_BN (BatchNorma  (None, 16, 16, 32)  128         ['block_3_project[0][0]']
     lization)

     block_4_expand (Conv2D)        (None, 16, 16, 192)  6144        ['block_3_project_BN[0][0]']

     block_4_expand_BN (BatchNormal  (None, 16, 16, 192)  768        ['block_4_expand[0][0]']
     ization)

     block_4_expand_relu (ReLU)     (None, 16, 16, 192)  0           ['block_4_expand_BN[0][0]']

     block_4_depthwise (DepthwiseCo  (None, 16, 16, 192)  1728       ['block_4_expand_relu[0][0]']
     nv2D)

     block_4_depthwise_BN (BatchNor  (None, 16, 16, 192)  768        ['block_4_depthwise[0][0]']
     malization)

     block_4_depthwise_relu (ReLU)  (None, 16, 16, 192)  0           ['block_4_depthwise_BN[0][0]']

     block_4_project (Conv2D)       (None, 16, 16, 32)   6144        ['block_4_depthwise_relu[0][0]']

     block_4_project_BN (BatchNorma  (None, 16, 16, 32)  128         ['block_4_project[0][0]']
     lization)

     block_4_add (Add)              (None, 16, 16, 32)   0           ['block_3_project_BN[0][0]',
                                                                      'block_4_project_BN[0][0]']

     block_5_expand (Conv2D)        (None, 16, 16, 192)  6144        ['block_4_add[0][0]']

     block_5_expand_BN (BatchNormal  (None, 16, 16, 192)  768        ['block_5_expand[0][0]']
     ization)

     block_5_expand_relu (ReLU)     (None, 16, 16, 192)  0           ['block_5_expand_BN[0][0]']

     block_5_depthwise (DepthwiseCo  (None, 16, 16, 192)  1728       ['block_5_expand_relu[0][0]']
     nv2D)

     block_5_depthwise_BN (BatchNor  (None, 16, 16, 192)  768        ['block_5_depthwise[0][0]']
     malization)

     block_5_depthwise_relu (ReLU)  (None, 16, 16, 192)  0           ['block_5_depthwise_BN[0][0]']

     block_5_project (Conv2D)       (None, 16, 16, 32)   6144        ['block_5_depthwise_relu[0][0]']

     block_5_project_BN (BatchNorma  (None, 16, 16, 32)  128         ['block_5_project[0][0]']
     lization)

     block_5_add (Add)              (None, 16, 16, 32)   0           ['block_4_add[0][0]',
                                                                      'block_5_project_BN[0][0]']

     block_6_expand (Conv2D)        (None, 16, 16, 192)  6144        ['block_5_add[0][0]']

     block_6_expand_BN (BatchNormal  (None, 16, 16, 192)  768        ['block_6_expand[0][0]']
     ization)

     block_6_expand_relu (ReLU)     (None, 16, 16, 192)  0           ['block_6_expand_BN[0][0]']

     block_6_pad (ZeroPadding2D)    (None, 17, 17, 192)  0           ['block_6_expand_relu[0][0]']

     block_6_depthwise (DepthwiseCo  (None, 8, 8, 192)   1728        ['block_6_pad[0][0]']
     nv2D)

     block_6_depthwise_BN (BatchNor  (None, 8, 8, 192)   768         ['block_6_depthwise[0][0]']
     malization)

     block_6_depthwise_relu (ReLU)  (None, 8, 8, 192)    0           ['block_6_depthwise_BN[0][0]']

     block_6_project (Conv2D)       (None, 8, 8, 64)     12288       ['block_6_depthwise_relu[0][0]']

     block_6_project_BN (BatchNorma  (None, 8, 8, 64)    256         ['block_6_project[0][0]']
     lization)

     block_7_expand (Conv2D)        (None, 8, 8, 384)    24576       ['block_6_project_BN[0][0]']

     block_7_expand_BN (BatchNormal  (None, 8, 8, 384)   1536        ['block_7_expand[0][0]']
     ization)

     block_7_expand_relu (ReLU)     (None, 8, 8, 384)    0           ['block_7_expand_BN[0][0]']

     block_7_depthwise (DepthwiseCo  (None, 8, 8, 384)   3456        ['block_7_expand_relu[0][0]']
     nv2D)

     block_7_depthwise_BN (BatchNor  (None, 8, 8, 384)   1536        ['block_7_depthwise[0][0]']
     malization)

     block_7_depthwise_relu (ReLU)  (None, 8, 8, 384)    0           ['block_7_depthwise_BN[0][0]']

     block_7_project (Conv2D)       (None, 8, 8, 64)     24576       ['block_7_depthwise_relu[0][0]']

     block_7_project_BN (BatchNorma  (None, 8, 8, 64)    256         ['block_7_project[0][0]']
     lization)

     block_7_add (Add)              (None, 8, 8, 64)     0           ['block_6_project_BN[0][0]',
                                                                      'block_7_project_BN[0][0]']

     block_8_expand (Conv2D)        (None, 8, 8, 384)    24576       ['block_7_add[0][0]']

     block_8_expand_BN (BatchNormal  (None, 8, 8, 384)   1536        ['block_8_expand[0][0]']
     ization)

     block_8_expand_relu (ReLU)     (None, 8, 8, 384)    0           ['block_8_expand_BN[0][0]']

     block_8_depthwise (DepthwiseCo  (None, 8, 8, 384)   3456        ['block_8_expand_relu[0][0]']
     nv2D)

     block_8_depthwise_BN (BatchNor  (None, 8, 8, 384)   1536        ['block_8_depthwise[0][0]']
     malization)

     block_8_depthwise_relu (ReLU)  (None, 8, 8, 384)    0           ['block_8_depthwise_BN[0][0]']

     block_8_project (Conv2D)       (None, 8, 8, 64)     24576       ['block_8_depthwise_relu[0][0]']

     block_8_project_BN (BatchNorma  (None, 8, 8, 64)    256         ['block_8_project[0][0]']
     lization)

     block_8_add (Add)              (None, 8, 8, 64)     0           ['block_7_add[0][0]',
                                                                      'block_8_project_BN[0][0]']

     block_9_expand (Conv2D)        (None, 8, 8, 384)    24576       ['block_8_add[0][0]']

     block_9_expand_BN (BatchNormal  (None, 8, 8, 384)   1536        ['block_9_expand[0][0]']
     ization)

     block_9_expand_relu (ReLU)     (None, 8, 8, 384)    0           ['block_9_expand_BN[0][0]']

     block_9_depthwise (DepthwiseCo  (None, 8, 8, 384)   3456        ['block_9_expand_relu[0][0]']
     nv2D)

     block_9_depthwise_BN (BatchNor  (None, 8, 8, 384)   1536        ['block_9_depthwise[0][0]']
     malization)

     block_9_depthwise_relu (ReLU)  (None, 8, 8, 384)    0           ['block_9_depthwise_BN[0][0]']

     block_9_project (Conv2D)       (None, 8, 8, 64)     24576       ['block_9_depthwise_relu[0][0]']

     block_9_project_BN (BatchNorma  (None, 8, 8, 64)    256         ['block_9_project[0][0]']
     lization)

     block_9_add (Add)              (None, 8, 8, 64)     0           ['block_8_add[0][0]',
                                                                      'block_9_project_BN[0][0]']

     block_10_expand (Conv2D)       (None, 8, 8, 384)    24576       ['block_9_add[0][0]']

     block_10_expand_BN (BatchNorma  (None, 8, 8, 384)   1536        ['block_10_expand[0][0]']
     lization)

     block_10_expand_relu (ReLU)    (None, 8, 8, 384)    0           ['block_10_expand_BN[0][0]']

     block_10_depthwise (DepthwiseC  (None, 8, 8, 384)   3456        ['block_10_expand_relu[0][0]']
     onv2D)

     block_10_depthwise_BN (BatchNo  (None, 8, 8, 384)   1536        ['block_10_depthwise[0][0]']
     rmalization)

     block_10_depthwise_relu (ReLU)  (None, 8, 8, 384)   0           ['block_10_depthwise_BN[0][0]']

     block_10_project (Conv2D)      (None, 8, 8, 96)     36864       ['block_10_depthwise_relu[0][0]']

     block_10_project_BN (BatchNorm  (None, 8, 8, 96)    384         ['block_10_project[0][0]']
     alization)

     block_11_expand (Conv2D)       (None, 8, 8, 576)    55296       ['block_10_project_BN[0][0]']

     block_11_expand_BN (BatchNorma  (None, 8, 8, 576)   2304        ['block_11_expand[0][0]']
     lization)

     block_11_expand_relu (ReLU)    (None, 8, 8, 576)    0           ['block_11_expand_BN[0][0]']

     block_11_depthwise (DepthwiseC  (None, 8, 8, 576)   5184        ['block_11_expand_relu[0][0]']
     onv2D)

     block_11_depthwise_BN (BatchNo  (None, 8, 8, 576)   2304        ['block_11_depthwise[0][0]']
     rmalization)

     block_11_depthwise_relu (ReLU)  (None, 8, 8, 576)   0           ['block_11_depthwise_BN[0][0]']

     block_11_project (Conv2D)      (None, 8, 8, 96)     55296       ['block_11_depthwise_relu[0][0]']

     block_11_project_BN (BatchNorm  (None, 8, 8, 96)    384         ['block_11_project[0][0]']
     alization)

     block_11_add (Add)             (None, 8, 8, 96)     0           ['block_10_project_BN[0][0]',
                                                                      'block_11_project_BN[0][0]']

     block_12_expand (Conv2D)       (None, 8, 8, 576)    55296       ['block_11_add[0][0]']

     block_12_expand_BN (BatchNorma  (None, 8, 8, 576)   2304        ['block_12_expand[0][0]']
     lization)

     block_12_expand_relu (ReLU)    (None, 8, 8, 576)    0           ['block_12_expand_BN[0][0]']

     block_12_depthwise (DepthwiseC  (None, 8, 8, 576)   5184        ['block_12_expand_relu[0][0]']
     onv2D)

     block_12_depthwise_BN (BatchNo  (None, 8, 8, 576)   2304        ['block_12_depthwise[0][0]']
     rmalization)

     block_12_depthwise_relu (ReLU)  (None, 8, 8, 576)   0           ['block_12_depthwise_BN[0][0]']

     block_12_project (Conv2D)      (None, 8, 8, 96)     55296       ['block_12_depthwise_relu[0][0]']

     block_12_project_BN (BatchNorm  (None, 8, 8, 96)    384         ['block_12_project[0][0]']
     alization)

     block_12_add (Add)             (None, 8, 8, 96)     0           ['block_11_add[0][0]',
                                                                      'block_12_project_BN[0][0]']

     block_13_expand (Conv2D)       (None, 8, 8, 576)    55296       ['block_12_add[0][0]']

     block_13_expand_BN (BatchNorma  (None, 8, 8, 576)   2304        ['block_13_expand[0][0]']
     lization)

     block_13_expand_relu (ReLU)    (None, 8, 8, 576)    0           ['block_13_expand_BN[0][0]']

     block_13_pad (ZeroPadding2D)   (None, 9, 9, 576)    0           ['block_13_expand_relu[0][0]']

     block_13_depthwise (DepthwiseC  (None, 4, 4, 576)   5184        ['block_13_pad[0][0]']
     onv2D)

     block_13_depthwise_BN (BatchNo  (None, 4, 4, 576)   2304        ['block_13_depthwise[0][0]']
     rmalization)

     block_13_depthwise_relu (ReLU)  (None, 4, 4, 576)   0           ['block_13_depthwise_BN[0][0]']

     block_13_project (Conv2D)      (None, 4, 4, 160)    92160       ['block_13_depthwise_relu[0][0]']

     block_13_project_BN (BatchNorm  (None, 4, 4, 160)   640         ['block_13_project[0][0]']
     alization)

     block_14_expand (Conv2D)       (None, 4, 4, 960)    153600      ['block_13_project_BN[0][0]']

     block_14_expand_BN (BatchNorma  (None, 4, 4, 960)   3840        ['block_14_expand[0][0]']
     lization)

     block_14_expand_relu (ReLU)    (None, 4, 4, 960)    0           ['block_14_expand_BN[0][0]']

     block_14_depthwise (DepthwiseC  (None, 4, 4, 960)   8640        ['block_14_expand_relu[0][0]']
     onv2D)

     block_14_depthwise_BN (BatchNo  (None, 4, 4, 960)   3840        ['block_14_depthwise[0][0]']
     rmalization)

     block_14_depthwise_relu (ReLU)  (None, 4, 4, 960)   0           ['block_14_depthwise_BN[0][0]']

     block_14_project (Conv2D)      (None, 4, 4, 160)    153600      ['block_14_depthwise_relu[0][0]']

     block_14_project_BN (BatchNorm  (None, 4, 4, 160)   640         ['block_14_project[0][0]']
     alization)

     block_14_add (Add)             (None, 4, 4, 160)    0           ['block_13_project_BN[0][0]',
                                                                      'block_14_project_BN[0][0]']

     block_15_expand (Conv2D)       (None, 4, 4, 960)    153600      ['block_14_add[0][0]']

     block_15_expand_BN (BatchNorma  (None, 4, 4, 960)   3840        ['block_15_expand[0][0]']
     lization)

     block_15_expand_relu (ReLU)    (None, 4, 4, 960)    0           ['block_15_expand_BN[0][0]']

     block_15_depthwise (DepthwiseC  (None, 4, 4, 960)   8640        ['block_15_expand_relu[0][0]']
     onv2D)

     block_15_depthwise_BN (BatchNo  (None, 4, 4, 960)   3840        ['block_15_depthwise[0][0]']
     rmalization)

     block_15_depthwise_relu (ReLU)  (None, 4, 4, 960)   0           ['block_15_depthwise_BN[0][0]']

     block_15_project (Conv2D)      (None, 4, 4, 160)    153600      ['block_15_depthwise_relu[0][0]']

     block_15_project_BN (BatchNorm  (None, 4, 4, 160)   640         ['block_15_project[0][0]']
     alization)

     block_15_add (Add)             (None, 4, 4, 160)    0           ['block_14_add[0][0]',
                                                                      'block_15_project_BN[0][0]']

     block_16_expand (Conv2D)       (None, 4, 4, 960)    153600      ['block_15_add[0][0]']

     block_16_expand_BN (BatchNorma  (None, 4, 4, 960)   3840        ['block_16_expand[0][0]']
     lization)

     block_16_expand_relu (ReLU)    (None, 4, 4, 960)    0           ['block_16_expand_BN[0][0]']

     block_16_depthwise (DepthwiseC  (None, 4, 4, 960)   8640        ['block_16_expand_relu[0][0]']
     onv2D)

     block_16_depthwise_BN (BatchNo  (None, 4, 4, 960)   3840        ['block_16_depthwise[0][0]']
     rmalization)

     block_16_depthwise_relu (ReLU)  (None, 4, 4, 960)   0           ['block_16_depthwise_BN[0][0]']

     block_16_project (Conv2D)      (None, 4, 4, 320)    307200      ['block_16_depthwise_relu[0][0]']

     block_16_project_BN (BatchNorm  (None, 4, 4, 320)   1280        ['block_16_project[0][0]']
     alization)

     Conv_1 (Conv2D)                (None, 4, 4, 1280)   409600      ['block_16_project_BN[0][0]']

     Conv_1_bn (BatchNormalization)  (None, 4, 4, 1280)  5120        ['Conv_1[0][0]']

     out_relu (ReLU)                (None, 4, 4, 1280)   0           ['Conv_1_bn[0][0]']

    ==================================================================================================
    Total params: 2,257,984
    Trainable params: 2,223,872
    Non-trainable params: 34,112
    __________________________________________________________________________________________________

After identifying the layers that you want to use in the skip connections, you can get access to these layers by using the method _get\_layer()_ defined from the _tensorflow.keras.Model_ class.

```python
layer_act_map = feature_extractor.get_layer("block_1_expand_relu").output
print(layer_act_map)

for layer_name in downsample_skip_layers:
  print(layer_name, '->', feature_extractor.get_layer(layer_name).output.shape)
```

    KerasTensor(type_spec=TensorSpec(shape=(None, 64, 64, 96), dtype=tf.float32, name=None), name='block_1_expand_relu/Relu6:0', description="created by layer 'block_1_expand_relu'")
    block_1_expand_relu -> (None, 64, 64, 96)
    block_3_expand_relu -> (None, 32, 32, 144)
    block_6_expand_relu -> (None, 16, 16, 192)
    block_12_add -> (None, 8, 8, 96)

## The upsampling path

In the upsampling path, we'll use transposed convolutions to progressively increase the resolution of the activation maps. The layers for the transposed convolution is [Conv2DTranspose](https://keras.io/api/layers/convolution_layers/convolution2d_transpose/).

Let's write a function to implement an upsampling block, consisting of a transposed convolution, a batch normalization block and a ReLu activation.

Remember, the output size $W_o$ of a transposed convolutional layer is:
\begin{equation}
W_o = (W_i - 1) \cdot S - 2P + F
\end{equation},

where $W_i$ is the size of the input, $S$ is the stride, $P$ is the amount of padding and $F$ is the filter size.

```python
def upsample_block(x, filters, size, stride = 2):
  """
  x - the input of the upsample block
  filters - the number of filters to be applied
  size - the size of the filters
  """
  # TODO your code here
  # transposed convolution
  # BN
  # relu activation
  x = tf.keras.layers.Convolution2DTranspose(kernel_size=size, filters=filters, strides=stride, padding="same", kernel_initializer='he_normal')(x)
  x = tf.keras.layers.BatchNormalization()(x)
  output = tf.keras.layers.ReLU()(x)
  return output

```

Now let's test this upsampling block

```python
in_layer = feature_extractor.get_layer(downsample_skip_layers[0]).output

filter_sz = 3
num_filters = 16

for stride in [2, 4, 8]:
  x = upsample_block(in_layer, num_filters, filter_sz, stride)
  print('in shape: ', in_layer.shape, ' upsample with filter size ', filter_sz, '; stride ', stride, ' -> out shape ', x.shape)
```

    in shape:  (None, 64, 64, 96)  upsample with filter size  3 ; stride  2  -> out shape  (None, 128, 128, 16)
    in shape:  (None, 64, 64, 96)  upsample with filter size  3 ; stride  4  -> out shape  (None, 256, 256, 16)
    in shape:  (None, 64, 64, 96)  upsample with filter size  3 ; stride  8  -> out shape  (None, 512, 512, 16)

## Putting it all together

Now we understand all the parts required to build the U-Net architecture.

Let's write the function _build\_unet()_ which will build our architecture. Of course, we'll use the function API when writing the model.

```python
def build_unet(input_shape, num_classes, trainable=False):
    # define the input layer
    inputs = tf.keras.layers.Input(shape=input_shape)

    # TODO define the downsampling path as a MobilenetV2 architecture, pre-loaded with imagenet weights
    mobilenet_v2 = tf.keras.applications.MobileNetV2(input_shape=input_shape, include_top=False)

    downsample_skip_layer_name = ["block_1_expand_relu", "block_2_expand_relu", "block_5_expand_relu", "block_8_expand_relu"]

    # get access to the skip layers in the downsample path
    down_stack = tf.keras.Model(inputs=mobilenet_v2.input, outputs=[mobilenet_v2.get_layer(name).output for name in downsample_skip_layer_name])
    # TODO freeze the downsampling path
    down_stack.trainable = trainable

    skips = down_stack(inputs)
    x = skips[-1]

    filter_sz = 3
    for skip_layer in  reversed(skips[:-1]):
        x = upsample_block(x, skip_layer.shape[-1], 3)
        x = tf.keras.layers.Concatenate()([x, skip_layer])
        # determine the number of filters
        # add an upsampling block
        # add the skip connection - tf.keras.layers.Concatenate

    # add the last conv2d transpose layer with the number of filters equal to the number of segmentation classes
    output = tf.keras.layers.Conv2DTranspose(filters=num_classes, kernel_size=3, strides=(2, 2), padding='same')(x)

    # return the model
    return tf.keras.Model(inputs=inputs, outputs=output)

model = build_unet((128, 128, 3), 3)

tf.keras.utils.plot_model(model, show_shapes=True)
```

##  Training the model. Defining callbacks.

Now that we've built the model, we can proceed to the training step.
We are dealing with a multi-class segmentation problem (pet pixels, pet border pixels and background pixels), so we should use the  [tf.keras.losses.CategoricalCrossentropy](https://www.tensorflow.org/api_docs/python/tf/keras/losses/CategoricalCrossentropy) loss function.

When training the model you can use various [callbacks](https://www.tensorflow.org/api_docs/python/tf/keras/callbacks/Callback).
Define the following callbacks:
- [ModelCheckpoint](https://keras.io/api/callbacks/model_checkpoint/) to save the best model so far every 5 to 5 epochs;
- [TerminateOnNaN](https://keras.io/api/callbacks/terminate_on_nan/) to stop the training process is a NaN loss is encountered;
- [EarlyStopping](https://keras.io/api/callbacks/early_stopping/) for when the loss has stopped improving.

```python
train_generator = DataGenerator("./oxford_pets_seg/train", 32)
val_generator = DataGenerator("./oxford_pets_seg/test", 32)

def train(input_shape, num_classes, train_datagen, val_datagen, num_epochs=5):
  model = build_unet(input_shape, num_classes)
  # TODO configure the traning process model.compile(...)
  model.compile(
    optimizer="adam",
    loss=tf.keras.losses.SparseCategoricalCrossentropy(
    from_logits=True, reduction="auto", name="sparse_categorical_crossentropy"
    ),
    metrics=["accuracy"]
  )

  # train the model
  history = model.fit(train_datagen, validation_data=val_datagen, epochs=num_epochs)
  model.save('model_final.h5')

  loss = history.history['loss']
  val_loss = history.history['val_loss']

  plt.figure()
  plt.plot(history.epoch, loss, 'r', label='Training loss')
  plt.plot(history.epoch, val_loss, 'b', label='Validation loss')
  plt.title('Loss evolution')
  plt.xlabel('Epoch')
  plt.ylabel('Loss')
  plt.ylim([0, 1])
  plt.legend()
  plt.show()

  return model

model = train((128, 128, 3), 3, train_generator, val_generator, 5)
model = build_unet((128, 128, 3), 3)

# now let's see some predictions
batch_x, batch_y = val_generator[0]
predictions = model.predict(batch_x)
predictions = tf.argmax(predictions, axis=-1)
fig, axes = plt.subplots(nrows=6, ncols=3, figsize=[16, 9])

for i in range(len(axes)):
    axes[i][0].imshow(batch_x[i])
    axes[i][0].set_title('Image')

    axes[i][1].imshow(batch_y[i][:, :, 0])
    axes[i][1].set_title('GT Mask')

    # pred_mask = model.predict(image)
    axes[i][2].imshow(predictions[i] )
    axes[i][2].set_title('Pred Mask')

plt.show()

```

## Evaluation metrics

Finally, you will implement several segmentation metrics to evaluate the model you've just trained. As usual, try to implement these metrics without using any for loops.

In the remainder of this section we'll use the following notation:
- $n_{ij}$ - the total number of pixels classified to class
j but actually belonging to class i; $i, j \in 1, .., C$;
- $t_i = \sum_{j = 1}^{C} n_{ij}$ - the total number of pixels belonging to class $i$ (in the ground truth segmentation mask);
- $C$ - the total number of classes in the segmentation problem.

### Mean pixel accuracy

Pixel accuracy is the simplest image segmentation metric; it is defined as the percentage of pixels that were correctly classified by the model.

\begin{equation}
p_a = \frac{1}{C} \frac{\sum_{i}^{C} n_{ii}}{\sum_{i}^{C} t_i}
\end{equation}

This metric is not that relevant for class imbalanced problems (which occurs for most segmentation problems).

### Intersection over Union (IoU)

the intersection over union metric is defined as the ratio between the area of intersection and the area of union (between the predicted segmentation mask and the ground truth segmentation mask of a single class).
In case of a multi-class segmentation problem, we simple average the IoUs over all the classes. This metric is called mean Intersection over Union (mIou).

\begin{equation}
mIoU = \frac{1}{C} \sum_{i = 1}^{C} \frac{p_{ii}}{t_i - n_{ii} + \sum_{j = 1}^{C} n_{ji}}
\end{equation}

The ideal value for this metric is 1; usually values lower than 0.6 indicate a very bad performance.

### Frequency Weighted Intersection over Union

The frequency weighted over union metric is similar to mean IoU, but the values are weighted with the adequate frequencies of the pixels.

\begin{equation}
fIou = (\sum_{i = 1}^{k} t_i)^{-1}   \sum_{i = 1}^{C} t_i \cdot \frac{p_{ii}}{t_i - n_{ii} + \sum_{j = 1}^{C} n_{ji}}
\end{equation}

The values of this metric lie in the interval [0, 1], and the ideal value for this metric is 1.

Compute and report these metrics for your trained model(s).

```python
def mean_pixel_acc(y_true, y_pred):
    """
    y_true - array of shape (batch_size, height, width) with the ground truth labels
    y_pred - array of shape (bacth_size, height, width) with the predicted labels
    """
    y_true = y_true.flatten().astype(int)
    y_pred = y_pred.flatten().astype(int)
    conf_mat = confusion_matrix(y_true, y_pred)
    C = conf_mat.shape[0]

    return (1/C) * (np.trace(conf_mat) / np.sum(np.sum(conf_mat, axis=1)))

def iou(y_true, y_pred):
    """
    y_true - array of shape (batch_size, height, width) with the ground truth labels
    y_pred - array of shape (bacth_size, height, width) with the predicted labels
    """
    y_true = y_true.flatten().astype(int)
    y_pred = y_pred.flatten().astype(int)
    conf_mat = confusion_matrix(y_true, y_pred)
    C = conf_mat.shape[0]
    diag = np.diagonal(conf_mat)

    return (1/C) * (np.sum(diag / (np.sum(conf_mat, axis=1) -   diag + np.sum(conf_mat, axis=0)) ))

def fw_iou(y_true, y_pred):
    """
    y_true - array of shape (batch_size, height, width) with the ground truth labels
    y_pred - array of shape (bacth_size, height, width) with the predicted labels
    """
    y_true = y_true.flatten().astype(int)
    y_pred = y_pred.flatten().astype(int)
    conf_mat = confusion_matrix(y_true, y_pred)
    C = conf_mat.shape[0]

    diag = np.diagonal(conf_mat)
    ti = np.sum(conf_mat, axis=1)

    return 1/np.sum(ti) * np.sum(ti * diag / (ti - diag + np.sum(conf_mat, axis=0)))
```

```python
true = batch_y[2]
plt.imshow(true[:,:,0])
```

```python
plt.imshow(batch_x[2])
print(batch_x[2].shape)
```

```python
pred = predictions[2].numpy()
plt.imshow(pred)
```

```python
# printing scores
print(mean_pixel_acc(true, pred))
print(iou(true, pred))
print(fw_iou(true, pred))
```

    0.0927734375
    0.157893454915853
    0.16999516540543902

Computer Vision and Deep Learning/Labs/Computer_Vision_and_Deep_Learning_Laboratory_5_Mos_Daniele.ipynb


# The Specific Factors or Ricardo-Viner Model

### Background

The SF model is a workhorse model in trade, growth, political economy and development.  We will see variants of the model used to describe rural to urban migration, the Lewis model and other dual sector models of sectoral misallocation, models such as the Harris-Todaro model that explain migration and the urban informal sector. The specific factors model predicts that, in the absence of political redistribution mechanisms, specific factors in declining sectors will organize strong opposition to policies that might otherwise raise growth. The initial distribution of factor endowments may therefore make a big difference in terms of what types of political coalitions mobilize for and against different policies.  This is the basic driving force in Moav-Galor's (2006) growth model on why some regions made public investments in human capital which sped the transition from agriculture to manufacturing and enhanced growth, whereas similar policies were delayed in other regions where political/economic resistance was stronger, for instance where landlords had stronger voice in political decisions.

These are just a few of the applications.  The model is relatively easy to analyze -- it can be described compactly in terms of diagrams and yet is very rich in predictions.

The Specific Factors (SF) or Ricardo-Viner model is a close relative of the Hecksher-Ohlin-Samuelson (HOS) neoclassical trade model. The 2x2 HOS model assumes production in each of two sectors takes place by firms using constant returns to scale technologies with capital and labor as inputs and that both capital and labor are mobile across sectors. In the SF model only labor is inter-sectorally mobile and the fixed amounts of capital become 'specific' to the sector they are trapped within.

In effect the SF model therefore consists of three factors of production: mobile labor and two types of capital, one specific to each sector.  Let's label the two sectors are Agriculture and Manufacturing. In agriculture competitive firms bid to hire land and labor. In manufacturing competitive firms bid to hire capital and labor. Each factor of production will be competitively priced in equilibrium but only labor is priced on a national labor market.

The SF model is often described as a short-run version of the HOS model.  For example suppose we start with a HOS model equilibrium where labor wage and rental rate of capital have equalized across sectors (which also implies marginal products are equalized across sectors -- no productivity differences across sectors).  Now suppose that the relative price of manufacturing products suddenly rises (due to a change of world price, or government trade protection or other policies that favor manufacturing).  The higher relative product price should lead firms in the booming manufacturing sector to demand both more capital and labor. Correspondingly, demand for land and labor decline in the agricultural sector.  In the short run however only labor can be move from agriculture to manufacturing.  Agricultural workers can become factory workers but agricultural capital (say 'land' or 'tractors') cannot be easily converted to manufacturing capital (say 'weaving machines'). So labor moves from manufacturing to the agriculture, lowering the capital labor ratio in manufacturing and raising the land to labor ratio in agriculture.  The model thus predicts a surge in the real return to capital in the expanding sector and a real decline in the real return to capital in agriculture (land).  Hence the measured average and marginal product of capital will now diverge across sectors.  What happens to the real wage is more ambiguous: it rises measured in terms of purchasing power over agricultural goods but falls in terms of purchasing power over manufacturing goods.   Whether workers are better off or worse off following this price/policy change therefore comes down to how important agricultural and manufacturing goods are in their consumption basket. This result is labeled the neo-classical ambiguity.

Over the longer-term weaving machines cannot be transformed into tractors but over time new capital accumulation will build tractors and old weaving machines can be sold overseas or as scrap metal. Hence over time more capital will arrive into the manufacturing sector and leave the agricultural sector, whcih in turn will lead to even more movement of labor to manufacturing.  If this process continues capital has, in effect become mobile over time, and we end up getting closer to the predictions of the HOS model.

### Technology and Endowments
There are two sectors Agriculture and Manufacturing.  Production in each sector takes place with a linear homogenous (constant returns to scale) production functions. Agricultural production requires land which is non-mobile or specific to the sector and mobile labor.

$$Q_a = F(\bar T, L_a)$$

Manufacturing production requires specific capital $K$ and mobile labor.

$$Q_m = G(\bar K, L_m)$$

The quantity of land in the agricultural sector and the quantity of capital in the manufacturing sector are in fixed supply  during the period of analysis.  That means that firms within the agricultural (manufacturing) sector may compete with one another for the limited supply of the factor, but no new land (capital) can be supplied in the short-run period of analysis.  This of course means that the price of the factor will rise (or fall) quickly in response to swings in factor demand compared to the wage of labor whose supply is more elastic.

The market for mobile labor is competitive and the market clears at a wage where the sum of labor demands from each sector equals total labor supply. While the total labor supply in the economy is inelastic, the supply of labor to each sector will be elastic, since a rise in the wage in one sector will attract workers from the other sector.

$$L_a + L_m = \bar{L}$$

Notice that we can invert the two production function to get minimum labor requirement functions $L_a(Q_a)$ and $L_m(Q_m)$ which tell us the minimum amount of labor $L_i$ required in sector $i$ to produce quantity $Q_i$.  If we take these expressions and substitute them into the labor resource constraint we get an expression for the **production possibility frontier (PPF)** which summarizes the tradeoffs between sectors.

### Assumptions and parameters for visualizations

Let's get concrete and assume each sector employs a CRS Cobb-Douglas production function:

$$F(\bar T, L_a)=\bar T^{1-\alpha} \cdot  L_a^\alpha$$

$$G(\bar K, L_m)=\bar K^{1-\beta} \cdot  L_m^\beta$$

If $\alpha = \beta = \frac{1}{2}$ and $\bar T= \bar K = 100$.  Then

$$Q_a =  \sqrt{\bar T} \sqrt{L_a}  $$

$$Q_m = \sqrt{\bar K} \sqrt{L_m}$$

Substituting these into the labor resource constraint yields:

$$\frac{Q_a^2}{\bar T}+\frac{Q_m^2}{\bar K} = \bar L$$

or

$$Q_m = \sqrt{\bar K \bar L - \frac{\bar K Q_a^2}{\bar T} }   $$

If we make the further assumption that $\bar K = \bar L  = 100$ and $\bar L = 400$ then the PPF would look like this:

**NOTE:** If you are running this as a live jupyter notebook please first go to the [code section](#codesection) below and execute all the code cells there. Then return and run the code cells that follow sequentially.

```python
ppf(Tbar=100, Kbar=100, Lbar=400)
```

## Labor market equilibrium

Profit maximizing firms in each sector will hire labor up to the point where the marginal value product of labor (MVPL) equals the market wage. Since labor is mobile across sectors in equilibrium workers must be paid the same nominal wage $w$ in either sector:

$$ P_a \cdot MPL_a(\bar T, L_a) = w = P_m \cdot MPL_m(\bar K, L_m) $$

It will be useful to express the wage in real terms.  Divide each expression above by $P_m$ to get

$$ p \cdot MPL_a = \frac{w}{p_m} = MPL_m $$

$$\text{    where   } p = \frac{P_a}{P_m}$$

In the plots below we will place the real wage measured in terms of manufactured goods on the vertical axis of the labor demand and supply diagrams.

### Labor allocation across sectors

In a competitive market firms take the market prices of goods $p$ and wages $w$ as given. Since in equilibrium the labor market clears we can write $L_m = \bar L - L_a$ and substitute into the equilibrium condition above to get:

$$p \cdot MPL_a(L_A) = w = MPL_m(\bar L-L_A) $$

The left hand side $p \cdot MPL_a(L_A)=w$ can be solved to give us demand for labor in the agricultural sector as a function of the wage $L_a(w/p)$ and is plotted below.  The right hand side $w = MPL_m(\bar L-L_a)$ gives us demand for labor in the manufacturing sector $L_m$ as a function of the real wage but this in turn also gives us the supply of labor to the agricultural sector $L_a^s = \bar L - L_m(w/p)$ since firms in the agricultural sector can only attract workers to their sector by paying those workers just a bit above what they would be paid for the jobs they have to leave in the manufacturing sector.

The diagram below can therefore be interpreted as showing labor demand and supply in the agricultural sector and their intersection gives us the equilibrium real wage (measured on the vertical in terms of purchasing power over manufactured goods).

```python
sfmplot(p=1)
```

We can solve for the equilibrium labor allocation $L_a$ as a function of the relative price $p$, and this in turn also gives us the equilibrium real wage. When $\alpha=\beta = \frac{1}{2}$ the equilibrium condition $p \cdot MPL_a(L_a) = MPL_m(\bar L-L_a)$ becomes:

$$p \cdot \frac{\sqrt{\bar T}}{\sqrt{L_a}} = \frac{\sqrt{\bar K}}{\sqrt{\bar L - L_a}}$$

which we solve to find:

$$L_a^e = \frac{p^2 \bar T \bar L}{\bar K - p^2 \bar T}$$

For cases where $\alpha \ne \beta$ we find the optimal $L_a$ that solves this equilibrium condition numerically.

## Autarky prices

Thus far we have a pretty complete model of how production allocations and real wages would be determined in a small open economy where producers face world price ratio $p$.  we explore comparative statics in this economy in more detail below.

If however the economy is inititally in autarky or closed to the world then we must also consider the role of domestic consumer preferences in the determination of domestic equilibrium product prices.

With Cobb-Douglas preferences $u(x,y) = \gamma \ln(x) + (1-\gamma) \ln(y)$ consumers demands can be written as a function of relative price $p=\frac{P_a}{P_m}$. Setting $P_m=1$ to make manufacturing the numeraire good, this can be written:

$$\begin{align}
C_a(p) =& \gamma \cdot \frac{I(p)}{p} \\
C_m(p) =& (1-\gamma) \cdot I(p)
\end{align}$$

Income $I$ in the expressions is given by the value of national production or GDP at these prices. Measured in manufactured goods:

$$I(p) = p \cdot F(\bar T, L_a(p)) + G(\bar K, \bar L - L_a(p)$$

By Walras' law we only need to find the relative price at which output equals demand in one of the two product markets so in the the code below we solve for equilibrium domestic prices from the condition $Q_a(p)=C_a(p)$.

For parameters $\alpha=\beta=\gamma = \frac{1}{2}$, $\bar T = \bar K =100$ and $\bar L=400$ the domestic equilibrium prices are unitary:

```python
p_autarky(Lbar, Tbar, Kbar)
```

    1.0

And this in turn leads to an equilibrium autarky allocation with $L_a = L_m = 200$ and a real wage $\frac{w}{p}=0.35$.

```python
eqn(p_autarky())
```

    (200.0, 0.35355339059327373)

The plot below shows the autarky production and consumption point (marked by an 'X') as well as the new production point (marked by a circle) and consumption point (marked by a square) if the country opened to trade with a world relative price $p=\frac{7}{4}$.

Opening to trade leads the country to expand agricultural production by re-allocating labor from manufacturing to agriculture. We confirm the expected **neoclassical ambiguity** result which is that an increase in the relative price of agricultural goods will lead to an *increase* in the real wage measured in terms of manufactured goods  $\frac{w}{p_m}$ ($=w$ since we have $P_m=1$) but a *decrease* in real wage measured in terms of agricultural goods or $\frac{w}{P_a} = \frac{w}{p}$ in our notation (since $\frac{w}{P_a} = \frac{w}{p_m} \cdot \frac{P_m}{P_a}$).

Diagrammatically

$$p \uparrow \rightarrow \frac{w}{p_m} \uparrow , \frac{w}{p_a} \downarrow$$

```python
pw = 7/4
Lao, wo = eqn(p=pw)
Lao, wo, wo/pw
```

    (301.53846153846149, 0.50389110926865943, 0.28793777672494825)

```python
sfmtrade(p=7/4)
```

## Effects of a price change on the income distribution

More explanations to be placed here...

```python
sfmplot2(2)
```

## Code Section

Make sure you run the cells below FIRST.  Then run the cells above

## Python simulation and plots

```python
import numpy as np
from scipy.optimize import fsolve
np.seterr(divide='ignore', invalid='ignore')
import matplotlib.pyplot as plt
from ipywidgets import interact, fixed
import seaborn
%matplotlib inline
```

```python
plt.style.use('seaborn-colorblind')
plt.rcParams["figure.figsize"] = [7,7]
plt.rcParams["axes.spines.right"] = True
plt.rcParams["axes.spines.top"] = False
plt.rcParams["font.size"] = 18
plt.rcParams['figure.figsize'] = (10, 6)
plt.rcParams['axes.grid']=True
```

```python
Tbar = 100       # Fixed specific land in ag.
Kbar = 100       # Fixed specific capital in manuf
Lbar = 400       # Total number of mobile workers
LbarMax = 400    # Lbar will be on slider, max value.

p    = 1.00      # initial rel price of ag goods, p = Pa/Pm
alpha, beta = 0.5, 0.5  # labor share in ag, manuf
```

For the plots we want to plot over $L_a$ and $L_m = \bar L -l_a$:

```python
La = np.linspace(1, LbarMax-1,LbarMax)
Lm = Lbar - La
```

The production functions in each sector:

```python
def F(La, Tbar = Tbar):
    return (Tbar**(1-alpha) * La**alpha)

def G(Lm, Kbar =Kbar):
    return (Kbar**(1-beta) * Lm**beta)
```

```python
def MPLa(La):
    return alpha*Tbar**(1-alpha) * La**(alpha-1)

def MPLm(Lm):
    return beta*Kbar**(1-beta) * Lm**(beta-1)

def MPT(La, Tbar=Tbar):
    return (1-alpha)*Tbar**(-alpha) * La**alpha

def MPK(Lm, Kbar=Kbar):
    return (1-beta)*Kbar**(-beta) * Lm**beta
```

We have enough to plot a production possibility Frontier (and how it varies with factor supplies):

```python
def ppf(Tbar=Tbar, Kbar=Kbar, Lbar=Lbar):
    Qa = F(La, Tbar) * (La<Lbar)
    Qm = G(Lm, Kbar)
    plt.title('Production Possibility Frontier')
    plt.xlabel(r'$Q_a$')
    plt.ylabel(r'$Q_m$')
    plt.plot(Qa, Qm)
    plt.gca().set_aspect('equal');
```

```python
ppf(Tbar=100, Kbar=100, Lbar=400)
```

Labor demand in each sector as a function of $p=\frac{P_a}{P_m}$:

```python
LDa = p * MPLa(La) *(La<Lbar)         # for Cobb-Douglas MPL can be written this way
LDm = MPLm(Lbar-La)
```

The following function returns the equilibrium allocation of labor to agriculture and equilibrium nominal wage for any initial relative price of agricultural goods $p=\frac{P_a}{P_m}$

```python
def eqn(p, Lbar=Lbar):
    '''returns numerically found equilibrium labor allocation and wage'''
    def func(La):
        return p*MPLa(La) - MPLm(Lbar-La)
    Laeq = fsolve(func, 1)[0]
    return Laeq, p*MPLa(Laeq)
```

```python
def u(x,y):
    '''Utility function'''
    return x*y

def XD(p, Lbar = Lbar):
    '''Cobb-Douglas demand for goods given world prices (national income computed)'''
    LAe, we = eqn(p)
    # gdp at world prices measured in manuf goods
    gdp = p*F(LAe, Tbar = Tbar) + G(Lbar -LAe, Kbar=Kbar)
    return (1/2)*gdp/p, (1/2)*gdp

def indif(x, ubar):
    return ubar/x
```

```python
def p_autarky(Lbar=Lbar, Tbar=Tbar, Kbar=Kbar):
    '''Find autarky product prices. By Walras' law enough to find price that
    sets excess demand in just one market'''
    def excessdemandA(p):
        LAe, _ = eqn(p)
        QA = F(LAe, Tbar = Tbar)
        CA, CM = XD(p, Lbar=Lbar)
        return QA-CA
    peq = fsolve(excessdemandA, 1)[0]
    return peq

```

```python
p_autarky()
```

    1.0

```python
def sfmtrade(p):
    Ca = np.linspace(0,200,200)
    LAe, we = eqn(p)
    X, Y = F(LAe, Tbar = Tbar), G(Lbar -LAe)
    gdp = p*X + Y
    plt.scatter(F(LAe, Tbar), G(Lbar-LAe, Tbar), label='Trade produce')
    plt.scatter(*XD(p),label='Trade consume', marker='s')
    plt.scatter(*XD(p_autarky()), marker='x', label='Autarky')
    plt.plot([0,gdp/p],[gdp, 0])
    ppf(100)
    ub = u(*XD(p))
    plt.ylim(0,gdp)
    plt.xlim(0,gdp)
    plt.plot(Ca, indif(Ca, ub))
    plt.grid(False)
    plt.legend()
    plt.gca().spines['bottom'].set_position('zero')
    plt.gca().spines['left'].set_position('zero')
```

```python
sfmtrade(p=7/4)
```

Simple example with $\bar L$ at $\bar L^{max}$

```python
La = np.arange(0, LbarMax)   # this is always over the LbarMax range

def sfmplot(p, Lbar=LbarMax, show=True):
    Lm = Lbar - La
    Qa = (Tbar**(1-alpha) * La**alpha) * (La<Lbar)
    Qm = Kbar**(1-beta) * (Lbar - La)**beta
    pMPLa = (p * alpha * Qa/La)*(La<Lbar)         # for Cobb-Douglass MPL can be written this way
    MPLm = beta * Qm/(Lbar-La)
    LA, weq = eqn(p)
    ymax = 1.0
    plt.ylim(0,ymax)
    plt.xlim(0,LbarMax)
    plt.title('Specific Factors Model')
    plt.plot(La, pMPLa, linewidth = 3, label='AG labor demand')
    plt.plot(La, MPLm, linewidth=3, label ='MF labor demand')
    plt.scatter(LA, weq, s=100, color='black')
    plt.axhline(weq, linestyle='dashed')
    plt.axvline(Lbar, linewidth = 3)
    plt.plot([LA, LA],[0, weq], linestyle='dashed')
    plt.xlabel('Labor')
    plt.ylabel('Real wage --' + r'$\frac{w}{p_M}$')
    plt.grid()
    if show:
        plt.legend(loc=(1.04,0.9))
        print("(La, Lm) = ({0:3.0f}, {1:3.0f})   (w/Pm, w/Pa) =({2:3.2f}, {3:3.2f})"
              .format(LA, Lbar-LA, weq, weq/p))
        print("(v/Pa, v/Pm) = ({0:3.1f}, {1:3.1f})   (r/Pa, r/Pm) = ({2:3.1f}, {3:3.1f})"
              .format(MPT(LA), p*MPT(LA), MPK(Lbar-LA)/p, MPK(Lbar-LA)    )   )
        plt.show()

```

If you are wondering about why we skip the plt.show() when show=True, it's because for figures that use the ipywidgets interact function to allow parameters to change with sliders we will need to have a plt.show() to redraw the plot in every function call (otherwise we would end up with a stack of plots rather than a changing plot).

```python
sfmplot(p=2)
```

Suppose that by opening to trade the relative price of agricultural goods $p$ doubles from $p=1$ to $p=2$.  This greatly shifts the demand for agricultural workers:

```python
def sfmplot2(p):
    sfmplot(1, show=False)
    sfmplot(p, show=False)
    plt.grid(False)
    if p == 1:
        plt.title('SF Model');
    else:
        La0, w0 = eqn(1)
        plt.scatter(La0,w0*p, s=100, color='black')  #where wage would rise to without labor movement
        if p>1:
            plt.title(r'$\frac{P_a}{P_m} \uparrow  \rightarrow  \frac{w}{P_m} \uparrow, \frac{w}{P_a} \downarrow $'  );
        elif p<1:
            plt.title(r'$\frac{P_a}{P_m} \downarrow  \rightarrow  \frac{w}{P_m} \downarrow, \frac{w}{P_a} \uparrow $'  );
    plt.show();

```

```python
sfmplot2(2)
```

```python
interact(sfmplot2, p=(0.1, 2,0.1));
```

Failed to display Jupyter Widget of type interactive.

  If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean
  that the widgets JavaScript is still loading. If this message persists, it
  likely means that the widgets JavaScript library is either not installed or
  not enabled. See the Jupyter
  Widgets Documentation for setup instructions.

  If you're reading this message in another frontend (for example, a static
  rendering on GitHub or NBViewer),
  it may mean that your frontend doesn't currently support widgets.

## Before and after plots

The trick here is to define a new plotting function that first plots a static plot and then allows an interaction.

```python
interact(sfmplot2, p=(0,2,0.2), Lbar=fixed(400), show=fixed(True))
```

Failed to display Jupyter Widget of type interactive.

  If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean
  that the widgets JavaScript is still loading. If this message persists, it
  likely means that the widgets JavaScript library is either not installed or
  not enabled. See the Jupyter
  Widgets Documentation for setup instructions.

  If you're reading this message in another frontend (for example, a static
  rendering on GitHub or NBViewer),
  it may mean that your frontend doesn't currently support widgets.

    <function __main__.sfmplot2>