```python
from resources.workspace import *
```

$
% START OF MACRO DEF
% DO NOT EDIT IN INDIVIDUAL NOTEBOOKS, BUT IN macros.py
%
\newcommand{\Reals}{\mathbb{R}}
\newcommand{\Expect}[0]{\mathbb{E}}
\newcommand{\NormDist}{\mathcal{N}}
%
\newcommand{\DynMod}[0]{\mathscr{M}}
\newcommand{\ObsMod}[0]{\mathscr{H}}
%
\newcommand{\mat}[1]{{\mathbf{{#1}}}}
%\newcommand{\mat}[1]{{\pmb{\mathsf{#1}}}}
\newcommand{\bvec}[1]{{\mathbf{#1}}}
%
\newcommand{\trsign}{{\mathsf{T}}}
\newcommand{\tr}{^{\trsign}}
\newcommand{\tn}[1]{#1}
\newcommand{\ceq}[0]{\mathrel{≔}}
%
\newcommand{\I}[0]{\mat{I}}
\newcommand{\K}[0]{\mat{K}}
\newcommand{\bP}[0]{\mat{P}}
\newcommand{\bH}[0]{\mat{H}}
\newcommand{\bF}[0]{\mat{F}}
\newcommand{\R}[0]{\mat{R}}
\newcommand{\Q}[0]{\mat{Q}}
\newcommand{\B}[0]{\mat{B}}
\newcommand{\C}[0]{\mat{C}}
\newcommand{\Ri}[0]{\R^{-1}}
\newcommand{\Bi}[0]{\B^{-1}}
\newcommand{\X}[0]{\mat{X}}
\newcommand{\A}[0]{\mat{A}}
\newcommand{\Y}[0]{\mat{Y}}
\newcommand{\E}[0]{\mat{E}}
\newcommand{\U}[0]{\mat{U}}
\newcommand{\V}[0]{\mat{V}}
%
\newcommand{\x}[0]{\bvec{x}}
\newcommand{\y}[0]{\bvec{y}}
\newcommand{\z}[0]{\bvec{z}}
\newcommand{\q}[0]{\bvec{q}}
\newcommand{\br}[0]{\bvec{r}}
\newcommand{\bb}[0]{\bvec{b}}
%
\newcommand{\bx}[0]{\bvec{\bar{x}}}
\newcommand{\by}[0]{\bvec{\bar{y}}}
\newcommand{\barB}[0]{\mat{\bar{B}}}
\newcommand{\barP}[0]{\mat{\bar{P}}}
\newcommand{\barC}[0]{\mat{\bar{C}}}
\newcommand{\barK}[0]{\mat{\bar{K}}}
%
\newcommand{\D}[0]{\mat{D}}
\newcommand{\Dobs}[0]{\mat{D}_{\text{obs}}}
\newcommand{\Dmod}[0]{\mat{D}_{\text{obs}}}
%
\newcommand{\ones}[0]{\bvec{1}}
\newcommand{\AN}[0]{\big( \I_N - \ones \ones\tr / N \big)}
%
% END OF MACRO DEF
$
Many mathematical methods are tagged as "least squares". They typically have one thing in common: some sum of squared terms is being minimized. Both (least-squares) linear regression and Kalman filtering (KF) may be derived from "least squares".
Do they yield the same estimate (when applied to the linear regression problem)? That's what we'll investigate...

# A straight-line example

Consider the straight line
$$\begin{align}
x_k = a k \, , \tag{1}
\end{align}$$
and suppose we have observations ($y$) of the line, but corrupted by noise ($r$):
$$\begin{align}
y_k &= x_k + r_k \, , \tag{2}
\end{align}$$
where $r_k \sim \mathcal{N}(0, R)$ for some $R>0$.

The code below sets up an experiment based on eqns. (1) and (2).

Parameters:

```python
a = 0.4  # Slope (xx[k] = a*k) paramterer. Unknown to be estimated.
K = 10   # Length of experiment (final time index)
R = 1    # Observation noise strength
```

Simulate a sequence of truth ($x$) and observation ($y$).

```python
# Recall the naming convention: xx and yy hold time series of x and y.
xx = np.zeros(K+1) # truth states
yy = np.zeros(K+1) # obs

for k in 1+arange(K):
    xx[k] = a*k
    yy[k] = xx[k] + sqrt(R)*randn()

# The obs at k==0 should not be used (since we know xx[0]==0, it is worthless).
yy[0] = np.nan
```

Let's visualize the experiment:

```python
@interact(k=IntSlider(min=1, max=K))
def plot_experiment(k):
    plt.figure(figsize=(10,6))
    kk = arange(k+1)
    plt.plot(kk,xx[kk]        ,'k' ,label='true state ($x$)')
    plt.plot(kk,yy[kk]        ,'k*',label='noisy obs ($y$)')

    ### Uncomment this block AFTER doing the Exc 3.4 ###
    # plt.plot(kk,kk*lin_reg(k) ,'r' ,label='Linear regress.')

    ### Uncomment this block AFTER doing the Exc 3.8 ###
    # pw_bb, pw_xxhat = weave_fa(bb,xxhat)
    # pw_kf, pw_ka    = weave_fa(arange(K+1))
    # plt.plot(pw_kf[:3*k],pw_bb[:3*k]     ,'c'  ,label='KF forecasts')
    # plt.plot(pw_ka[:3*k],pw_xxhat[:3*k]  ,'b'  ,label='KF analyses')
    # #plt.plot(kk,kk*xxhat[k]/k           ,'g--',label='KF extrapolated')

    plt.xlim([0,1.01*K])
    plt.ylim([-1,1.2*a*K])
    plt.xlabel('time index (k)')
    plt.ylabel('$x$, $y$, and $\hat{x}$')
    plt.legend(loc='upper left')
    plt.show()
```

### Esitmation by linear regression
The observations eqn. (2)
yields the likelihood
$$\begin{align}
p(y_k|x_k) = \mathcal{N}(y_k \mid x_k,R) \, . \tag{3}
\end{align}$$
Hopefully this is intuitive; otherwise, a derivation is provided in T4.

(Least-squares) linear regression minimizes the cost/objective function
$$\begin{align}
J_K(a) = \sum_{k=1}^K (y_k - a k)^2 \, ,  \tag{4}
\end{align}$$

**Exc 3.2:** Use eqns. (1) and (2) to show that minimizing $J_K$ yields the same optimum location, $\hat{a}$, as maximizing the likelihood $p\, (y_1, \ldots, y_K \;|\; a)$.

```python
#show_answer('LinReg deriv a')
```

The minimization yields the estimator
$$\begin{align}
\hat{a} = \frac{\sum_{k=1}^K {k} y_{k}}{\sum_{k=1}^K {k}^2} \, . \tag{6}
\end{align}$$

**Exc 3.3:** Derive (6) from (4).

```python
#show_answer('LinReg deriv b')
```

**Exc 3.4:** Code up the linear regresson estimator (6).
Then, go back to the animation above and uncomment the block that plots the its estimates.
If you did it right, then the estimated line should look reasonable.

```python
def lin_reg(k):
    "Liner regression estimator based on observations y_1, ..., y_k."
    ### INSERT ANSWER HERE ###
    return a
```

```python
#show_answer('LinReg_k')
```

In the following we tackle the same problem, but using the KF.

# Estimation by the (univariate) Kalman filter (KF)
The KF assumes that the truth (or "true state", or "nature"), $x_k$, "evolves in time (indexed by $k$)" according to some linear "dynamical" model:
$$\begin{align}
x_{k} = \DynMod_{k-1} x_{k-1} + q_{k-1} \, , \tag{Dyn} \, .
\end{align}$$
where $q_k$ is a random noise (process) that accounts for model errors.

For now, $\DynMod_{k-1}$ is just a number. In later tutorials it will be generalized to matrices, and eventually nonlinear operators.

**Exc 3.6 (a):** For the KF we need to reformulate the problem of estimating the parameter $a$
as the problem of estimating $x_k$.
Derive the "forecast/dynamical model" $\DynMod_k$ (a function of $k>0$ only) such that
the recursion $x_{k+1} = \DynMod_k x_k$ is equivalent to eqn (1).
Then implement it below:

```python
def Mod(k):
    return ### INSERT ANSWER HERE ###
```

```python
#show_answer('Sequential 2 Recusive')
```

**Exc 3.6 (b):** Explain in which sense the KF is optimal for a larger class of problems than linear regression.

```python
#show_answer('LinReg ⊂ KF')
```

So although the KF (and its implementation below) may seem like "overkill" for this problem,
this "heavy machinery" can do a lot more, and will pay off later.

The KF recursively estimates $x_k$ in successive cycles which consist of two steps :

####  Exc 3.7: The forecast step:
It is supposed that $\;\;\;\quad x_{k-1} \sim \mathcal{N}(\hat{x}_{k-1}, P_{k-1})$,
and that (independently) $q_{k-1} \sim \mathcal{N}(0, Q)$.

Show that the mean and variance of $x_k$, respectively, are given by:
$b_k = \DynMod_{k-1} \hat{x}_{k-1}$,
$B_k = \DynMod_{k-1}^2 P_{k-1} + Q$.

```python
#show_answer('RV sums')
```

It can be shown that [the sum of two Gaussian random variables](https://en.wikipedia.org/wiki/Sum_of_normally_distributed_random_variables#Proof_using_convolutions)  is again a Gaussian.
Thus, the forecast step just requires computing the new moments (mean and variance).

#### The analysis step
"updates" the prior (forecast), $\mathcal{N}(x_k \mid \; b_k,\; B_k)$,
based on the likelihood, $\quad\;\;\;\, \mathcal{N}(y_k \mid \, x_k, \; R)$,
into the posterior (analysis), $\; \; \, \mathcal{N}(x_k \mid \; \hat{x}_{k}, \, P_{k})$.
The update formulae was derived as the Gaussian-Gaussian Bayes' rule in [the previous tutorial](T2%20-%20Bayesian%20inference.ipynb#Gaussian-Gaussian-Bayes).

This completes the cycle, which can then restart with the forecast from $k$ to $k+1$.

**Exc 3.8:** Implement the KF (described at the top of this notebook) in the below code block to estimate $x_k$ for $k=1,\ldots, K$.

NB: for this example, do not use the "Kalman gain" form of the analysis update.
This problem involves the peculiar, unrealistic situation of infinities
(related to "improper priors") at `k==1`, yielding platform-dependent behaviour.
These peculariaties are of mainly of academic interest.

```python
Q = 0 # Dynamical model noise strength

# Allocation
bb    = np.zeros(K+1) # mean estimates -- prior/forecast values
xxhat = np.zeros(K+1) # mean estimates -- post./analysis values
BB    = np.zeros(K+1) # var  estimates -- prior/forecast values
PP    = np.zeros(K+1) # var  estimates -- post./analysis values

def KF(k):
    "Cycle k of the Kalman filter"
    # Forecast
    if k==1:
        BB[k] = np.inf # The "initial" prior uncertainty is infinite...
        bb[k] = 0      # ... thus the corresponding mean is inconsequential.
    else:
        BB[k] = ### INSERT ANSWER HERE ###
        bb[k] = ### INSERT ANSWER HERE ###
    # Analysis
    PP[k]    = ### INSERT ANSWER HERE ###
    xxhat[k] = ### INSERT ANSWER HERE ###

# Run estimations/computations
for k in 1+arange(K):
    KF(k)
```

```python
#show_answer('KF_k')
```

**Exc 3.10:** Go back to the animation above and uncomment the block that plots the KF estimates.
Visually: what is the relationship between the estimates provided by the KF and by linear regression?

```python
#show_answer('LinReg compare')
```

Exercises marked with an asterisk (*) are optional.

**Exc 3.12*:** This excercise proves (on paper) the conclusion of the previous excercise.

Firstly, note that the KF forecast step (here with $Q=0$) can be inserted in the analysis step, forming a single couple of recursions:
$$\begin{align}
\hat{x}_k &= P_k \big(y_k/R \;+\; \DynMod_{k-1} \hat{x}_{k-1} / [\DynMod_{k-1}^2 P_{k-1}] \big) \tag{11} \, , \\\
P_k &= 1/\big(1/R \;+\; 1/[\DynMod_{k-1}^2 P_{k-1}]\big) \tag{12} \, .
\end{align}$$

Use this and Exc 3.6 (a) to show that
$$\begin{align}
&\text{firstly,} &P_K &= R\frac{K^2}{\sum_{k=1}^K k^2} \, , \tag{13} \\\
&\text{secondly,} &\hat{x}_K &= K\frac{\sum_{k=1}^K k y_k}{\sum_{k=1}^K k^2} = K \hat{a}_K \tag{14} \, ,
\end{align}$$
where $\hat{a}_K$
is given by eqn. (6).

```python
#show_answer('x_KF == x_LinReg')
```

#### Exc 3.14:
Let $x_{k+1} = \DynMod x_k$, *for some constant $\DynMod>1$ (note: $Q=0$)*. What does the sequence of $P_k$ converge to?
Hint: Start from eqn (12), because eqn (13) is for the straight-line example only.

```python
#show_answer('Asymptotic P when M>1')
```

#### Exc 3.15:
Redo Exc 3.14, but assuming
 * (a) $\DynMod = 1$.
 * (b) $\DynMod < 1$.

```python
#show_answer('Asymptotic P when M=1')
#show_answer('Asymptotic P when M<1')
```

Thus, if $\DynMod>1$, the KF state's uncertainty variance, $P_k$ does not converge to 0. This is because, even though you keep gaining more information, this gets balanced out by the growth in uncertainty during the forecast. On the other hand, if $\DynMod \leq 1$ then the error converges to zero.

In general, however, $\DynMod$, $Q$, $R$  depend on time, $k$ (often to parameterize exogeneous/outside factors/forces/conditions), and there is no limit value that the state distribution (and its parameters) converges to.

A particular exception is the above straight-line example. As we found above, $\DynMod_k =\frac{k+1}{k}$, which depends on time, and yet its limiting value can be found through eqn. (13); moreover, eqn. (13) and [the pyramidal sum](https://en.wikipedia.org/wiki/Square_pyramidal_number) can be used to show that $P_k \rightarrow 0$, even though $\forall k, \; \DynMod_k > 1$.

**Exc 3.18*:** Set $Q$ to 1 or more in the KF code, and re-compute its estimates. Explain why the KF estimate is now closer to the obs (always at the latest time instance) than the linear regression estimate.

**Exc 3.20*:** Now change $R$ (but don't re-run the simulation of the truth and obs). The KF estimates should not change (in this particular example). Why?

### Summary
The KF consists of two steps:
 * Forecast
 * Analysis

In each step, the mean and variance must be updated.

As an example, we saw that the linear regression estimate is reproduced by the KF, although it is a bit tricky to initialize the KF with infinite uncertainty. However, the KF (i.e. state estimation) is much more general.

### Next: [Multivariate Kalman](T4%20-%20Multivariate%20Kalman.ipynb)

notebooks/T3 - Univariate Kalman filtering.ipynb


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

import numpy as np
import pandas as pd

import sympy
from sympy import Matrix, init_printing

from scipy.sparse.linalg import svds,eigs

import sklearn
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.metrics.pairwise import cosine_distances
from sklearn.metrics import pairwise_distances

from time import time

import surprise
from surprise import SVD
from surprise import Dataset
from surprise.model_selection import cross_validate

init_printing()
```

```python
data = pd.read_csv('top50.csv',encoding = "ISO-8859-1")

```

```python
data.index = data["Track.Name"]
data = data[['Beats.Per.Minute',
       'Energy', 'Danceability', 'Loudness..dB..', 'Liveness', 'Valence.',
       'Length.', 'Acousticness..', 'Speechiness.', 'Popularity']]

```

```python
data
```

      Artist.Name
      Genre
      Beats.Per.Minute
      Energy
      Danceability
      Loudness..dB..
      Liveness
      Valence.
      Length.
      Acousticness..
      Speechiness.
      Popularity

      Track.Name

      Señorita
      Shawn Mendes
      canadian pop
      117
      55
      76
      -6
      8
      75
      191
      4
      3
      79

      China
      Anuel AA
      reggaeton flow
      105
      81
      79
      -4
      8
      61
      302
      8
      9
      92

      boyfriend (with Social House)
      Ariana Grande
      dance pop
      190
      80
      40
      -4
      16
      70
      186
      12
      46
      85

      Beautiful People (feat. Khalid)
      Ed Sheeran
      pop
      93
      65
      64
      -8
      8
      55
      198
      12
      19
      86

      Goodbyes (Feat. Young Thug)
      Post Malone
      dfw rap
      150
      65
      58
      -4
      11
      18
      175
      45
      7
      94

      I Don't Care (with Justin Bieber)
      Ed Sheeran
      pop
      102
      68
      80
      -5
      9
      84
      220
      9
      4
      84

      Ransom
      Lil Tecca
      trap music
      180
      64
      75
      -6
      7
      23
      131
      2
      29
      92

      How Do You Sleep?
      Sam Smith
      pop
      111
      68
      48
      -5
      8
      35
      202
      15
      9
      90

      Old Town Road - Remix
      Lil Nas X
      country rap
      136
      62
      88
      -6
      11
      64
      157
      5
      10
      87

      bad guy
      Billie Eilish
      electropop
      135
      43
      70
      -11
      10
      56
      194
      33
      38
      95

      Callaita
      Bad Bunny
      reggaeton
      176
      62
      61
      -5
      24
      24
      251
      60
      31
      93

      Loco Contigo (feat. J. Balvin & Tyga)
      DJ Snake
      dance pop
      96
      71
      82
      -4
      15
      38
      185
      28
      7
      86

      Someone You Loved
      Lewis Capaldi
      pop
      110
      41
      50
      -6
      11
      45
      182
      75
      3
      88

      Otro Trago - Remix
      Sech
      panamanian pop
      176
      79
      73
      -2
      6
      76
      288
      7
      20
      87

      Money In The Grave (Drake ft. Rick Ross)
      Drake
      canadian hip hop
      101
      50
      83
      -4
      12
      10
      205
      10
      5
      92

      No Guidance (feat. Drake)
      Chris Brown
      dance pop
      93
      45
      70
      -7
      16
      14
      261
      12
      15
      82

      LA CANCIÓN
      J Balvin
      latin
      176
      65
      75
      -6
      11
      43
      243
      15
      32
      90

      Sunflower - Spider-Man: Into the Spider-Verse
      Post Malone
      dfw rap
      90
      48
      76
      -6
      7
      91
      158
      56
      5
      91

      Lalala
      Y2K
      canadian hip hop
      130
      39
      84
      -8
      14
      50
      161
      18
      8
      88

      Truth Hurts
      Lizzo
      escape room
      158
      62
      72
      -3
      12
      41
      173
      11
      11
      91

      Piece Of Your Heart
      MEDUZA
      pop house
      124
      74
      68
      -7
      7
      63
      153
      4
      3
      91

      Panini
      Lil Nas X
      country rap
      154
      59
      70
      -6
      12
      48
      115
      34
      8
      91

      No Me Conoce - Remix
      Jhay Cortez
      reggaeton flow
      92
      79
      81
      -4
      9
      58
      309
      14
      7
      83

      Soltera - Remix
      Lunay
      latin
      92
      78
      80
      -4
      44
      80
      266
      36
      4
      91

      bad guy (with Justin Bieber)
      Billie Eilish
      electropop
      135
      45
      67
      -11
      12
      68
      195
      25
      30
      89

      If I Can't Have You
      Shawn Mendes
      canadian pop
      124
      82
      69
      -4
      13
      87
      191
      49
      6
      70

      Dance Monkey
      Tones and I
      australian pop
      98
      59
      82
      -6
      18
      54
      210
      69
      10
      83

      It's You
      Ali Gatie
      canadian hip hop
      96
      46
      73
      -7
      19
      40
      213
      37
      3
      89

      Con Calma
      Daddy Yankee
      latin
      94
      86
      74
      -3
      6
      66
      193
      11
      6
      91

      QUE PRETENDES
      J Balvin
      latin
      93
      79
      64
      -4
      36
      94
      222
      3
      25
      89

      Takeaway
      The Chainsmokers
      edm
      85
      51
      29
      -8
      10
      36
      210
      12
      4
      84

      7 rings
      Ariana Grande
      dance pop
      140
      32
      78
      -11
      9
      33
      179
      59
      33
      89

      0.958333333333333
      Maluma
      reggaeton
      96
      71
      78
      -5
      9
      68
      176
      22
      28
      89

      The London (feat. J. Cole & Travis Scott)
      Young Thug
      atl hip hop
      98
      59
      80
      -7
      13
      18
      200
      2
      15
      89

      Never Really Over
      Katy Perry
      dance pop
      100
      88
      77
      -5
      32
      39
      224
      19
      6
      89

      Summer Days (feat. Macklemore & Patrick Stump of Fall Out Boy)
      Martin Garrix
      big room
      114
      72
      66
      -7
      14
      32
      164
      18
      6
      89

      Otro Trago
      Sech
      panamanian pop
      176
      70
      75
      -5
      11
      62
      226
      14
      34
      91

      Antisocial (with Travis Scott)
      Ed Sheeran
      pop
      152
      82
      72
      -5
      36
      91
      162
      13
      5
      87

      Sucker
      Jonas Brothers
      boy band
      138
      73
      84
      -5
      11
      95
      181
      4
      6
      80

      fuck, i'm lonely (with Anne-Marie) - from 13 Reasons Why: Season 3
      Lauv
      dance pop
      95
      56
      81
      -6
      6
      68
      199
      48
      7
      78

      Higher Love
      Kygo
      edm
      104
      68
      69
      -7
      10
      40
      228
      2
      3
      88

      You Need To Calm Down
      Taylor Swift
      dance pop
      85
      68
      77
      -6
      7
      73
      171
      1
      5
      90

      Shallow
      Lady Gaga
      dance pop
      96
      39
      57
      -6
      23
      32
      216
      37
      3
      87

      Talk
      Khalid
      pop
      136
      40
      90
      -9
      6
      35
      198
      5
      13
      84

      Con Altura
      ROSALÍA
      r&b en espanol
      98
      69
      88
      -4
      5
      75
      162
      39
      12
      88

      One Thing Right
      Marshmello
      brostep
      88
      62
      66
      -2
      58
      44
      182
      7
      5
      88

      Te Robaré
      Nicky Jam
      latin
      176
      75
      67
      -4
      8
      80
      202
      24
      6
      88

      Happier
      Marshmello
      brostep
      100
      79
      69
      -3
      17
      67
      214
      19
      5
      88

      Call You Mine
      The Chainsmokers
      edm
      104
      70
      59
      -6
      41
      50
      218
      23
      3
      88

      Cross Me (feat. Chance the Rapper & PnB Rock)
      Ed Sheeran
      pop
      95
      79
      75
      -6
      7
      61
      206
      21
      12
      82

```python
def index_to_instance(df,index=None):
    if index:
        return XYZ(df)[index][1]
    else:
        return XYZ(df)
def XYZ(df):
    return sorted(list(zip(list(df.index.codes[0].data),list(df.index.levels[0].array))))
def value_to_index_map(array):
    array1 = zip(array,range(len(array)))
    return array1
```

```python
class RecSysContentBased():
    def __init__(self):
        pass
    def fit(self,train):
        self.train_set = train
        df1 = cosine_similarity(train)
        self.similarity = df1
        self.distances = pairwise_distances(train,metric='euclidean')
    def evaluate(self,user):
        d = sorted(value_to_index_map(self.distances[user]))
        print(d)
        return list(index_to_instance(self.train_set,d[i][1]) for i in range(len(d)))
    def predict(self):
        pass
    def test(self,testset):
        pass

```

```python
model = RecSysContentBased()

```

```python
np.random.seed(1)
```

```python
w = "qwertyuiopasdfghjklzxcvbnm"
```

```python
model.fit(data)
```

```python
model.evaluate(1)
```

    [(0.0, 1), (7.306019863613199, 16), (7.395636885548911, 5), (7.704989210484981, 21), (7.710644461787914, 25), (7.9397938648414295, 12), (8.197515881045817, 19), (8.280594332831667, 3), (8.37692672434156, 24), (8.47324565388931, 2), (8.59168373061565, 9), (8.625026218429008, 17), (8.86561142209829, 8), (8.989000391884632, 0), (9.008214831460636, 22), (9.009691688370886, 18), (9.383269126207303, 13), (9.50125695752467, 6), (9.552747323085569, 10), (9.67897905295402, 14), (9.786878709359785, 23), (9.845251424981102, 7), (10.243872354987145, 11), (10.347373751522337, 15), (10.460654565092929, 4), (11.000486478954194, 20)]

    ['x',
     'a',
     'n',
     'w',
     't',
     'z',
     'e',
     'm',
     'f',
     'v',
     'q',
     'd',
     'h',
     [(0, 'k'),
      (1, 'x'),
      (2, 'v'),
      (3, 'm'),
      (4, 'c'),
      (5, 'n'),
      (6, 'o'),
      (7, 'p'),
      (8, 'h'),
      (9, 'q'),
      (10, 'r'),
      (11, 's'),
      (12, 'z'),
      (13, 'y'),
      (14, 'i'),
      (15, 'j'),
      (16, 'a'),
      (17, 'd'),
      (18, 'l'),
      (19, 'e'),
      (20, 'g'),
      (21, 'w'),
      (22, 'b'),
      (23, 'u'),
      (24, 'f'),
      (25, 't')],
     'b',
     'l',
     'y',
     'o',
     'r',
     'i',
     'u',
     'p',
     's',
     'j',
     'c',
     'g']

```python

```


# ニコニコAIスクール 第2回 機械学習入門 基礎演習
## 今日の目標
* 機械学習の目的及び種類とその評価方法を理解する。
* 第1回に引き続き、numpyで頻出する関数の使い方を実践的に理解する。
* csvファイルを題材として、実データの読み書きができる。
* k-NN法を理解し、それをnumpyを用いて実装できる。

## 目次
* 総和、平均、最大値、最小値(np.sum, np.mean, np.max, np.min)
* L2ノルム (np.linalg.norm)
* 数学関数 (np.sin, np.cos, np.tah, np.exp, np.log など)
* 乱数生成 (np.random.random, np.random.normal)
* ソート関連(np.sort, np.argsort)
* ユークリッド距離・コサイン類似度
* ファイル入出力

# はじめに

* エラーがでたら落ち着いてエラーを読んでみましょう(エラーを恐れる必要はありません)
* エラーを読んでも理解できない場合は，エラー文をそのままググると解決法が見つかることが多いです
* 詰まったな，と思ったら気軽に質問して下さい
* numpyはドキュメントが充実しています．こんな計算出来ないだろうか，この関数の引数について知りたい，といった場合はドキュメントを見ると解決することが多いです
(https://docs.scipy.org/doc/)

## Colaboratoryことはじめ
* コードの編集：各セルをクリックして、直接編集
* コードの実行：セル左上の再生ボタンをクリックまたはShift+Enter
* 実行停止：セル左上の停止ボタンをクリック
* Notebook中のコードを全て実行："Runtime"->"Run all"
* 再起動："Runtime"->"Restart runtime"
* 出力のクリア："Edit"->"Clear all outputs"

詳しい操作はチュートリアルを参照してください。

### Jupyter notebookとの比較
|操作|Colaboratory|Jupyter Notebook|
|:--|--:|--:|
|コードの編集|セルをクリック|セルをクリック|
|コードの実行|セル左上の再生ボタン|画面上の再生ボタン|
|実行停止|セル左上の停止ボタン|画面上の停止ボタン|
|全て実行|"Runtime"->"Run all"|"Cell"->"Run all"|
|再起動|"Runtime"->"Restart runtime"|"Kernel"->"Restart"|
|クリア|"Edit"->"Clear all outputs"|"Kernel"->"Restart and clear output"|

## この演習をJupyter notebook上で実行したい方へ
Pythonは通常インデントの幅を4とすることが多いのですが、Colaboratoryのデフォルトのインデント幅は例外的に2になっています。
そこで、本来デフォルトの4に設定されているJupyter notebook側の設定を変更する必要があります。

もしこの演習をJupyter notebook上で実行したい場合は、次の指示に従ってください:
1. "F12"キーを押して、出てきた画面内の「コンソール (Console)」タブをクリック
2. 次のコードをコンソールに入力、実行：

```
var cell = Jupyter.notebook.get_selected_cell();
var config = cell.config;
var patch = {
      CodeCell:{
        cm_config:{indentUnit:2}
      }
    }
config.update(patch)
```

もし設定を戻したい場合は、同様に次のコードを実行すると戻ります：
```
var cell = Jupyter.notebook.get_selected_cell();
var config = cell.config;
var patch = {
      CodeCell:{
        cm_config:{indentUnit: null} // only change here.
      }
    }
config.update(patch)
```

参考：http://jupyter-notebook.readthedocs.io/en/stable/frontend_config.html

ただし、一部の画像の表示が乱れる場合があります。

# 1. Numpy入門 (2)

```python
import numpy as np  # numpyを'np'という名前でインポートする
```

## 配列の総和、平均、最大値、最小値 (np.sum, np.mean, np.max, np.min)
* ベクトルの最大値を得る
* データの平均を計算する
* 行列の各行の最小値を得る

などの操作は非常に頻繁に現れます。Numpyでは、配列の形状にかかわらず上記の操作を1行で書くことができます。

```python
# 長さ5のベクトル
x = np.array([0.1, 0.2, 0.3, 0.4, 0.5])
x.shape
```

```python
# np.sum: 配列の総和
np.sum(x)
```

```python
# np.mean: 配列の平均
np.mean(x)
```

```python
# np.max: 配列の最大値
np.max(x)
```

```python
# np.min: 配列の最小値
np.min(x)
```

```python
# 同じ操作を多次元配列で行ってみる
X = np.random.random((5, 10))  # (5, 10)の乱数行列を生成 (後述)
X.shape
```

```python
print(np.sum(X))
print(np.mean(X))
print(np.max(X))
print(np.min(X))
```

### axis (軸) の指定
上記の関数は、デフォルトでは配列全体にたいして1つの値しか返しません。
「各行 (0軸目)」「各列 (1軸目)」などの特定の軸上で平均値等を計算したい場合には**axisキーワード**を指定します。
軸のインデックスは0から始まります。

```python
print(np.sum(X, axis=0))  # 各行 (0軸目) の総和を求める (各要素は1行=5つの総和)
print(np.sum(X, axis=0).shape)  # 結果は1次元配列になる
```

```python
print(np.sum(X, axis=1))  # 各列 (1軸目) の総和を求める (各要素は1列=10個の総和)
print(np.sum(X, axis=1).shape)  # 結果は1次元配列になる
```

## 最大/最小値のインデックスを取る (np.argmax, np.argmin)
さて、これで配列中の最大/最小値を取ることが出きました。
では、**最大・最小値をとる要素が配列中のどこにあるのか**がほしい場合はどうすればいいでしょう？
np.argmaxとnp.argminを使いましょう！

```python
# 長さ5のベクトル
x = np.array([0.1, 0.2, 0.3, 0.4, 0.5])
x.shape
```

```python
np.argmax(x)  # -> 4 (5つ目の要素)
```

```python
np.argmin(x)  # -> 0 (1つ目の要素)
```

## 書いてみよう：配列の操作
1. [1, 2, ..., 10]なるベクトルをnp.arange関数で作成し、その総和と平均を計算せよ。
2. 多値分類問題では、「サンプルiがクラスcに所属する確率」を(N, C)の行列に格納し、各サンプルにおいて最も大きい確率を取るインデックスをとる処理が行われる。

y = XW + bを計算し、np.argmax関数を使って最大値を得るインデックスを計算せよ。

```python
# Q1
# WRITE ME!
```

```python
# Q2
N = 10
D = 2
C = 5
X = np.random.random((N, D))
W = np.random.random((D, C))
b = np.random.random((1, C))  # ブロードキャスティングができるように0次元目に1を詰める

# WRITE ME!
# まずXW + bを計算 (行列積を使おう)
```

## データをどう格納するか？ (講義資料末尾参照)
実際にnumpyでアルゴリズムを書く時、「データ」はどういう形で入っているのでしょうか？
Pythonについては、基本的に **(サンプル数、次元数)** の形で格納することをおすすめします。
どちらでも全く問題なく書けるのですが、

* 「i番目のサンプル」にアクセスするときに data[i] と書ける (逆の場合data[:, i])
* scikit-learnなどの主要ライブラリが上記に従っている
* Numpyがデフォルトでrow-major orderを採用している (下記参照)

などの理由によりサンプル数を0次元目とするのが一般的です。

### *行アクセスと列アクセスの速度の違い
numpyがどのように配列をメモリ上に配置しているかは、次の簡単な実験からも類推することができます：
(参考： http://kaisk.hatenadiary.com/entry/2015/02/19/224531)

```python
a = np.random.rand(2000, 2000)
%timeit a[0,:].sum()  # 行へのアクセスは速い
%timeit a[:,0].sum()  # 列へのアクセスは遅い
```

## ノルム (np.linalg.norm)
np.linalg.normは、ベクトルを引数としてとった場合、L2ノルム (ざっくり言うとユークリッド距離)
\begin{equation}
\|x\|_2 = \sqrt{x_1^2 + x_2^2 + \dots + x_n^2}
\end{equation}

行列を引数としてとった場合、フロベニウスノルム
\begin{equation}
\|A\|_F = \sqrt{\sum_{i=1}^m \sum_{j=1}^n \left| a_{ij}^2 \right|}
\end{equation}

を返します。axisを指定することで、行列から各行または列のL2ノルムを求めるなど、頻繁に使われます。

```python
e = [-4, -3, -2, -1,  0,  1,  2,  3, 4]
np.linalg.norm(e)
```

```python
f = [1, 1]
np.linalg.norm(f)
```

```python
g = np.array([[1, 2],
              [3, 4]])
np.linalg.norm(g, axis=1)  # axis=1 (各行) に関してL2ノルムを計算
```

## 数学関数 (np.sin, np.cos, np.tah, np.exp, np.log など)
NumpyにはPythonの標準とは別に専用の数学関数が用意されています。
また、これらの関数は**ベクトルや行列を引数として**、要素ごとに演算を適用できます。

```python
np.sin(np.pi / 2.0)  # π/2=90度
```

```python
np.log([1, np.e, np.e ** 2, np.e ** 3])  # **演算子は累乗
```

```python
np.log(0)  # log0は定義できないのでnumpyではマイナス無限大が返る
```

## 乱数生成

続いて乱数の作り方を覚えましょう。
乱数は次のような場面で使います：
* 学習器のパラメータ (重み行列) をランダムに初期化する
* 複数のサンプルをランダムにシャッフルする

### np.random.seed(seed)
乱数の「種」を初期化します。「種」には自由に値を設定できます。
通常乱数は環境によって毎回結果が変わってしまいますが、「種」が一緒であれば異なる環境でも同じ数字を再現できます。
この機能は (この講義のように) 再現性が重要な場合に非常に重要です。

### np.random.random(size)
0以上1未満の一様分布を生成します (shapeには形状のタプルが入ります)。

### np.random.random(loc=0.0, scale=1.0, size)
平均がloc、分散がscaleの正規分布 (ガウス分布) N(loc, scale) を生成します。

わからなくなったらドキュメントを参照しましょう：
* https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.random.random.html#numpy.random.random
* https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.random.normal.html

```python
# Ctrl+Enterで同じセルを何回か実行してみよう (種を指定しない)
np.random.random()
```

```python
# Ctrl+Enterで同じセルを何回か実行してみよう (種を指定する)
np.random.seed(1701)
np.random.random()
```

```python
# 複数一気に作る
np.random.random(5)
```

```python
# 正規分布
np.random.normal(size=5)  # random.randomと微妙に呼び出しが違うので注意
```

試しに1000個程度値を生成してプロットしてみると一目瞭然です：

```python
import matplotlib.pyplot as plt
plt.figure()
plt.hist(np.random.random(1000), bins=20)
plt.title("Uniform distribution U[0, 1]")
plt.show()
```

```python
plt.figure()
plt.hist(np.random.normal(0, 1, 1000), bins=20)
plt.title("Normal (Gaussian) distribution N(0, 1)")
plt.show()
```

### 配列をシャッフル
配列のシャッフルにはnp.random.shuffleを、配列からランダムに選び出すにはnp.random.choiceを使います。

```python
x = np.arange(10)
print(x)
np.random.shuffle(x)  # ランダムにシャッフル
print(x)
```

```python
x = np.arange(12)
print(np.random.choice(x, 10))  # ランダムに10個選ぶ (重複を許す)
print(np.random.choice(x, 10, replace=False))  # ランダムに10個選ぶ (重複を許さない)
```

## 書いてみよう：乱数生成
1. -1から1までの一様な乱数を生成せよ
2. 深層学習では、重み行列を入力側の層のユニット数nに対して、
$$ N(0, \frac{1}{\sqrt{n}}) $$

なる正規分布に従う乱数で初期化するテクニックが知られている (Xavierの初期化)。
入力層のユニット数が100、出力層のユニット数が50である重み行列WをXavierの初期化に従って作成せよ。

### ヒント
* 重み行列の形状は(100, 50)
* 平方根はnp.sqrt
* np.random.normalでscaleを指定するかN(0, 1) * scaleとしてやれば良い (正規分布の性質)

```python
# Q1
# WRITE ME!
```

```python
# Q2
# WRITE ME!
W = np.zeros((100, 50))  # これを書き換える
```

```python
# プロットしてみよう
plt.figure()
plt.hist(W.flatten(), bins=20)
plt.title("Xavier initialization (red line is 2sigma)")
plt.axvline(x=0.2, c='r')
plt.axvline(x=-0.2, c='r')
plt.show()
```

### *整数の乱数 np.random.randint(low, high, size)
もちろん整数も作れます。
low <= x < highなる整数を作ります。
https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.random.randint.html

```python
np.random.randint(10, size=(100,))  # 0から9
```

```python
np.random.randint(-10, 11, size=(100,))  # -10から10
```

## ソート関連(np.sort, np.argsort)
k-NNアルゴリズムでは**距離が小さいベクトルをk本取得する**操作を使います。
高速なソートアルゴリズムもnumpyは備えています。

```python
f = np.array([5, 2, 3, 1, 4])
np.sort(f)
```

```python
np.argsort(f)
```

```python
f[np.argsort(f)]  # 確かにインデックスが取れている (advanced indexingを使用)
```

大きい順で欲しい場合には、スライシング [start:end:step]を使いましょう：

```python
np.sort(f)[::-1]  # 逆順で取るときにはstepを-1にすれば良い
```

## 書いてみよう：ソート
Q: np.sortを使用して，fの最大の数，上から2番目の数，最小の数を出力せよ。

```python
# WRITE ME!
```

## 2. 機械学習の距離尺度 (ユークリッド距離・マンハッタン距離・コサイン類似度)
### ユークリッド距離
講義で述べたとおり、
今日扱うk-NN法では、ユークリッド距離 (L2ノルム)
$$ \|p - q\|_2 = \sqrt{\sum_{i=1}^D (p_i - q_i)^2}$$
を使います。ユークリッド距離はnp.linalg.normを用いて書くことができます。

```python
x = np.array([1, 0])
y = np.array([0, -1])

def euclidian_distance(p, q):
  return np.linalg.norm(p - q)

euclidian_distance(x, y)
```

### マンハッタン距離
ユークリッド距離に続いてよく使われる距離尺度としては、マンハッタン距離 (L1ノルム)
$$ \|p - q\|_1 = \sum_{i=1}^D |p_i - q_i|$$
があります。これも同様に書くことができます。

```python
def manhattan_distance(p, q):
  return np.linalg.norm(p - q, ord=1)

manhattan_distance(x, y)
```

### コサイン類似度 (コサイン距離)
コサイン類似度は、次のように定義される尺度で、-1から1までの値が得られます。
コサイン類似度が1の時2本のベクトルは最も近く、-1の時は最も遠いと判断されます。
コサイン類似度は距離の公理を満たしませんが、自然言語処理などの分野でしばしば使用されます。

$$ cosine(p, q) = \frac{p \cdot q}{\| p \| \cdot \|q \|} $$

## 書いてみよう：コサイン類似度
Q: 上記のコサイン類似度を実装せよ。

```python
def cosine_similarity(p, q):
  # WRITE ME!

print(cosine_similarity(x, y))
print(cosine_similarity(x, x))
print(cosine_similarity(x, -x))
```

# 3. ファイルの入出力
本節では、実データに機械学習を適用する際に不可欠な、データの読み書きに焦点をあて、csvファイルの読み書きを題材にその基礎を解説・実践します。
実際には、以下に示すモジュールを用いればサンプルより簡単にデータを読み込み・処理できますが、時間の関係上今回は飛ばします。
* csvモジュール
* jsonモジュール
* pandas (データ解析支援ライブラリ)

とはいえ、pandasは大規模データを取り扱うための各種機能を搭載しているため、データがある程度複雑なら、**pandasを利用したほうが良い**です。

## osモジュール
通常、処理コードとデータは別々のディレクトリに保存されており、適切なファイルパスを指定し、それを読み込む必要があります。こうした
* ディレクトリ間の移動
* ディレクトリの作成
* パスの作成・加工
などを担うのがosモジュールです。今回は、
1. テキストファイルの読み込み
2. CSVファイル (irisデータセット) の読み書き
3. The Allen Mouse Brain Connectivity Atlasの読み込み
の3つの題材を通じて、osモジュールの主要関数の使い方を学習します。

### 今回のディレクトリ構成
```
+---nico2ai_lecture2_exercise.ipynb (このファイル)
+---nico2ai_lecture2_practice.ipynb
+---data/
    +---sample.txt
    +---iris.csv
    +---nature/
        +---nature13186-s3.csv
        +---nature13186-s4-w-ipsi.csv
        +---nature13186-s5.csv
```

### 事前準備 (一度のみ実行)

```python
!wget  https://www.dropbox.com/s/8fqayqdai15en2f/data.zip
!unzip -n data.zip
```

### ファイルの読み込み
ファイルの読み込みには、**open関数**を用いまず。第2引数のモードには
* "r": 読み込み専用
* "w": 書き込み専用 (ファイルが存在しない場合、新規作成)
* "r+": 読み書き療養
* "a": 追記用 (すでに同名のファイルが存在する場合、その末尾から追記)
の4種類がありますが、まずは読み込み専用の"r"を使いましょう。

```python
f = open("data/sample.txt", "r")  # open関数が成功すると、ファイルオブジェクトが返る
for line in f:
  print(line.strip("\n"))  # 余計な改行を除去

# 古い書き方：今は直接for文で行ごとに読み込める
#line = f.readline()
#while line:
#    print(line)
#    line = f.readline()

f.close()  # 必ずcloseする
```

### with ~ as文
上記の例では、openしたファイルを必ずcloseしなければならず、後処理を忘れる危険性がありました。
with文を使うと、同じ式をエレガントに書けます。

```python
with open("data/sample.txt", "r") as f:  # Closeする必要なし
  for line in f:
    print(line.strip("\n"))  # 余計な改行を除去
```

上記の原文は亀語 (単語の並び順が人間語と逆順) であるため、配列のアクセスを工夫して人間語に戻してあげましょう。
(サンプルの出典： Roald Dahl, "Esio Trot", Viking Press, 1990)
その際、次の文字列操作関数を用います：
* str.strip(str): 文字列中からstrを取り除く
* str.split(delimiter): delimiterで文字列を区切り、区切られた部分文字列のリストを返す
* str.join(str_list): splitと逆の操作を行い、strを区切り文字としてstr_listの各要素を連結して返す
* str.replace(str1, str2): 文字列中のstr1をstr2に置き換える

```python
with open("data/sample.txt", "r") as f:
  for line in f:
    line = line.strip("\n")
    words = line.split(" ")
    words_reversed = []
    for word in words:  # 始点・終点を変えずに逆順に進める
      words_reversed.append(word[::-1])  # 始点・終点を変えずに逆順に進める
      line_reversed = " ".join(words_reversed)  # スペースで連結
      line_reversed = line_reversed.replace("OISE ,TORT", "TORT OISE,")  # TORT OISEが本来1語であるのを逆にしてしまったので、元に戻す
    print(line_reversed)
```

句読点が若干変ですが、これで読めました。大きくなあれ！

## csvの読み込みとパース
続いて、csvファイルの読み込みを行います。csv (comma-separated values)は、その名の示す通り、ファイル内の要素がカンマで区切られています。そのため、カンマ区切りの各要素を読みだしたうえで、そのデータ型に合わせて適切にパース (parse) しなければなりません。
文字列から数値への変換及びその逆は
```
float("3.1") -> 3.1
int("5") -> 5
str(5) -> "5"
```
などで行うことができます。

### Irisデータセット
今回サンプルcsvとして用いるのは、"Iris"と呼ばれる非常に有名なデータセットです。
* Setosa
* Verisicolor
* Virginica

という3種類の品種のアヤメの

* がく片の長さ (1列目)
* がく片の幅 (2列目)
* 花弁の長さ (3列目)
* 花弁の幅 (4列目)

がcm単位で格納されています。5列目は品種名(文字列)です。
1列目〜4列目まではすべてfloatに変換できるので、5列目を除いたデータをnumpy配列に変換してみましょう。

```python
with open("data/iris.csv", "r") as f:
  lines = []
  for line in f:
    strs = line.split(",")[0:4]
    lines.append([float(x) for x in strs])  # すべての要素をfloatに変換

  data = np.array(lines)
  print(data)
```

### ファイルの書き込み
続いて、前項でnumpy配列として読み込んだデータを再びリストに戻し、スペース区切りのファイルとして再保存しましょう。
処理の流れは次のようになります：

1. os.makedirsとos.path.existsの組み合わせで出力先のフォルダを作成する
2. open関数で書き込み用にファイルを開く
3. numpy配列をtolist関数でリストに戻す
4. 各行をスペース区切りの文字列として、ファイルに書き込む

その際、使用する関数は次のとおりです。これらはPythonのデータ処理で頻出するので覚えておきましょう。

* os.path.join(str1, str2, ...)：パスの連結。"/"の有無を吸収してパスを結合してくれるので、文字列を単純に連結するより安全
* os.path.exists(path)：そのパスにファイルまたはディレクトリが存在するかを返す
* os.makedirs(path)：新規ディレクトリを作成する。ただし、すでに作成済みである場合にはエラーを返すので常にその存在性を注意する必要がある
* f.write(str)：ファイルの末尾の行に文字列を書き込む
* tolist()：numpy配列を通常のリストに変換する

```python
import os
outdir_name = "outputs"
outpath = os.path.join(outdir_name, "processed.txt")

if not os.path.exists(outdir_name):  # もしディレクトリが存在しなければ
  os.makedirs(outdir_name)  # outdir_nameと同じ名前のディレクトリを作成する

with open(outpath, "w") as f:  # 書き込み用
  for line_data in data.tolist():
    f.write(" ".join([str(x) for x in line_data]) + "\n")  # 末尾に改行を加える
```

### *Colaboratoryからのファイルの取得
colaboratoryからファイルを取得するには、次のコードを使用してください (Jupyter notebookでは動きません)

```python
# 使用するにはコメントアウト
# from google.colab import files
# files.download("outputs/processed.txt")
```

## 書いてみよう：irisデータの読み書き
Q: Irisデータセットの品種名は文字列で格納されているが、これはラベルとしては扱いにくい形をしている。

* Setosa -> 0
* Verisicolor -> 1
* Virginica -> 2

というIDを振り、5列目を品種名ではなく品種IDに書き換えたものをoutputs/iris_with_label.csvとしてカンマ区切りで出力せよ。

### ヒント：
* str.replaceを用いる
* 各行の文字列を置換するだけで今回の操作は実現できる (数値に変換する必要はない)

```python
# WRITE ME!
```

### *おまけ
余裕があれば、次の関数を用いて、irisデータの各特徴の平均や分散を求めてみましょう：
* np.mean
* np.var
* np.sum

各関数の解説は、numpyのリファレンスから読めます：
https://docs.scipy.org/doc/numpy/reference/

```python
#WRITE ME!
```

```python

```


# Band Math and Indices

This section discusses band math and spectral indicies.

This notebook is derived from a [Digital Earth Africa](https://www.digitalearthafrica.org/) notebook: [here](https://github.com/digitalearthafrica/deafrica-training-workshop/blob/master/docs/session_4/01_band_indices.ipynb)

## Background

Band math is the application of arithmetic to the values of the spectral bands of a dataset. One use of band math is in computing spectral indicies.

## Description

Topics covered include:

* What bands are
* What band math is
* Uses of band math
* Some of the most common spectral indicies that use band math
* How to compute spectral indicies in ODC notebooks

## What is a band?

*A section of the electromagnetic spectrum showing some of the Landsat 8 bands.* [[Source](http://www.geocarto.com.hk/edu/PJ-BCMBLSAT/main_BCLS.html)]

The range of data acquired by a satellite is defined by its **bands**. Bands are subdivisions of the electromagnetic spectrum dependent on the sensors on the satellite.

A selection of commonly-used Landsat 8 and Sentinel-2 bands are shown in the table below. We can see the spectral ranges between Landsat 8 and Sentinel-2 are similar, but not the same. Also note the band numbers do not always correspond to the same spectral range.

*Sources:* [Landsat 8 bands](https://www.usgs.gov/media/resources/es/landsat-8-band-designations), [Sentinel-2 bands](https://earth.esa.int/web/sentinel/technical-guides/sentinel-2-msi/msi-instrument)

## Bands and terrain features

Different types of ground cover absorb and reflect different amounts of radiation along the electromagnetic spectrum. This is dependent on the physical and chemical properties of the surface.

For example:

* **Water:** Open water bodies generally reflect light in the visible spectrum, and absorb more short-wave infrared than near-infrared radiation. This can change if the water is turbid.

* **Snow:** Ice and snow reflect most visible radiation, but do not reflect much short-wave infrared. Reflectance measurements depend on snow granule size and liquid water content.

* **Green vegetation:** Chlorophyll, a pigment in plants, absorbs a lot of visible light, including red light. When plants are healthy, they strongly reflect near-infrared light.

* **Bare soil:** The mineral composition of soil can be characterised using the visible and near-infrared spectrum. Soil moisture content can greatly influence the results.

Using these spectral differences, we can calculate ratios between bands to isolate and accentuate the specific terrain feature we are interested in. These metrics are known as band ratios or **band indices**.

In practice, variation within terrain feature classes, as well as the presence of multiple features in one area, can make different types of ground cover difficult to distinguish. This is one of the challenges of spectral data analysis.

### Example: Normalised Difference Vegetation Index (NDVI)

One of the most widely-used band indices is the Normalised Difference Vegetation Index (NDVI). It is used to show the presence of live green vegetation. Generally, green vegetation has a low red band measurement, as red light is absorbed by chlorophyll. In addition to this, healthy leaf cell structures reflect near-infrared light, giving a high near-infrared (NIR) band measurement.

NDVI is therefore typically calculated using a satellite's NIR band and red band. One value is calculated per pixel.

\begin{equation} \text{NDVI} \ = \ \frac{\text{NIR} - \text{Red}}{\text{NIR} + \text{Red}} \end{equation}

We see the index is calculated by the difference $(\text{NIR} - \text{Red})$ divided by the sum $(\text{NIR} + \text{Red})$. This normalises the index: all values will fall between $-1$ and $1$.

Large values of NDVI will occur for pixels where NIR is high and red is low. Conversely, NDVI can be close to 0 or even negative where NIR is low and red is high. This means we interpret NDVI as follows:

\begin{align}
\text{NDVI} &> 0, \text{ or close to }1 = \text{green vegetation}\\
\text{NDVI} &\leq 0 = \text{not green vegetation; water, soil, etc.}
\end{align}

But what is green vegetation? The US Geological Survey provides a more specific [guide to interpreting NDVI](https://www.usgs.gov/land-resources/eros/phenology/science/ndvi-foundation-remote-sensing-phenology?qt-science_center_objects=0#qt-science_center_objects).

> Areas of barren rock, sand, or snow usually show very low NDVI values (for example, 0.1 or less). Sparse vegetation such as shrubs and grasslands or senescing crops may result in moderate NDVI values (approximately 0.2 to 0.5). High NDVI values (approximately 0.6 to 0.9) correspond to dense vegetation such as that found in temperate and tropical forests or crops at their peak growth stage.

You can see even this definition does not exhaustively cover every kind of vegetation. It is important to remember that Earth observation data analysis is sensitive to the dataset location and time. The nature of climate and environment variations across the globe, and even just within the African continent, mean that band indices like NDVI need to be interpreted with knowledge and context of the area.

Normalising band indices so their values fall between -1 and 1 gives a relative scale that allows for easier data analysis. Values of the index can be better compared between different points in the area of analysis, and across time periods.

Below we have two plots of an area with river distributaries in Guinea-Bissau, a country that experiences monsoonal-like seasons. We see the amount of vegetation as detected by NDVI fluctuates over time. The top image shows NDVI in April, at the end of the dry season. NDVI readings are much lower than when compared to the same area in November (bottom image), after several months of rain during the wet season.

Notice the RGB images may show parts of the area to be visibly 'dry' or 'lush', but in places where this is less obvious, it is easier to analyse NDVI than the multispectral RGB dataset.

*NDVI calculated from Sentinel-2 data in Guinea-Bissau in April 2018 (top left) and November 2018 (bottom left). The NDVI values reflect typical seasonal patterns in a quantitative manner.*

Note that in the table of wavelength ranges for the bands for Landsat 8 and Sentinel-2, the satellites have slightly different band ranges for the same band name (e.g. 'Red' for Landsat 8 is slightly different than 'Red' for Sentinel-2). This means they will produce different band index values even at approximately the same time and place. It is good practice to ensure that datasets you are comparing match very closely in band ranges for the relevant bands.

## Band indices in research

NDVI is just one example of a useful band index. There are many other band indices used in Earth observation research to draw out terrain features. Selecting a band index is often dependent on environmental conditions and research purpose.

* **Vegetation:** As described above, NDVI is a good baseline vegetation index. It is simple to calculate, and only requires two bands — red and NIR. However, the Enhanced Vegetation Index (EVI) is often more accurate. EVI is calculated with three bands — red, blue and NIR — and requires some coefficients for scaling.

    In arid regions, where vegetative cover is low, consider using the Soil Adjusted Vegetation Index (SAVI), which incorporates a soil brightness correction factor.

* **Urbanisation:** Human settlements can be identified through urbanisation indices, one of which is the Normalised Difference Built-up Index (NDBI). NDBI uses SWIR 1 and NIR bands:

    $$ \text{NDBI} \ = \ \frac{\text{SWIR 1} - \text{NIR}}{\text{SWIR 1} + \text{NIR}} $$

    However, NDBI can be confused between built-up areas and bare soil, so in arid and semi-arid regions where this is problematic, it may be better to use the Dry Bare Soil Index (DBSI).

    $$ \text{DBSI} \ = \ \frac{\text{SWIR 1} - \text{Green}}{\text{SWIR 1} + \text{Green}} \ - \ \text{NDVI}$$

* **Water bodies:** Delineation between water and land can be defined using the Modified Normalised Difference Water Index (MNDWI). It is calculated using green and SWIR 1 bands:

    $$ \text{MNDWI} \ = \ \frac{\text{Green} - \text{SWIR 1}}{\text{Green} + \text{SWIR 1}} $$

    This should not be confused with indices for monitoring water content in vegetation.

It is important to remember band indices are not infallible; their usefulness relies on appropriate index selection and sensible interpretation. However, as the field of remote sensing grows, ongoing research into differentiating terrain types with different band combinations give rise to more nuanced and accurate data analysis. For instance, it is common to use more than one index to help distinguish feature classes with similar spectral characteristics.

## Calculating Indicies

As shown in the [NDVI Training](../day2/NDVI_Training.ipynb), to calculate spectral indicies, we use the `calculate_indices()` function (defined [here](https://github.com/GeoscienceAustralia/dea-notebooks/blob/develop/Tools/dea_tools/bandindices.py#L29)). In this environment, it is available at `utils.dea_tools.bandindices`.

Below is an example of calculating NDVI with Landsat Collection 2 data.

```
from utils.dea_tools.bandindices import calculate_indices
ds = calculate_indices(ds, index='NDVI', collection='c2')
```

## Conclusion

Band indices are an integral part of spatial data analysis, and are an efficient method of distinguishing different types of land cover. In the following sections, we will calculate NDVI for a cloud-free composite using the Sandbox.


# 第十二讲：$k$-means算法、高斯混合模型及最大期望算法

现在我们开始进入机器学习课程新的一章——无监督学习算法的应用。

## $k$-means聚类算法（$k$-means clustering algorithm）

在聚类问题（clustering problem）中，对于给定的训练集$\left\{x^{(1)},\cdots,x^{(m)}\right\}$，并且希望对样本分组，并找到能够使样本“紧密聚集”在一起的簇（clusters）。这里的$x^{(i)}\in\mathbb{R}^n$像原来一样，但是没有$y^{(i)}$。因此，这是一个无监督学习问题。

$k$-means聚类算法的过程如下：

1. 随机的初始化**簇质心（cluster centroids）**$\mu_1,\mu_2,\cdots,\mu_k\in\mathbb{R}^n$；
2. 重复直到收敛

  `{`

  * 对每一个样本$i$，令$c^{(i)}:=\mathrm{arg}\displaystyle\operatorname*{min}_j\left\lVert x^{(i)}-\mu_j\right\rVert^2$
  * 对每一个簇$j$，令$\mu_j:=\frac{\sum_{i=1}^m1\left\{c^{(i)}=j\right\}x^{(i)}}{\sum_{i=1}^m1\left\{c^{(i)}=j\right\}}$

  `}`

在这个算法中，参数$k$表示我们想要聚类的簇的数量，$\mu_j$表示我们当前对于簇质心位置的猜测。为了初始化簇质心（即算法第一步），我们通常随机选择$k$个训练样本，然后令$k$个质心等于这$k$个训练样本。（方法不唯一，其他初始化方法也是可以的。）

算法的内层循环重复这两步：(i)将每个训练样本$x^{(i)}$分配给距离它们最近的簇质心$\mu_j$，(ii)移动每个簇质心$\mu_j$到其下辖样本集合的中心。下图为$k$-means算法的简单演示：

图中圆点为训练样本，叉号为簇质心，演示的过程为：(a)原数据集；(b)随机初始化的簇质心（在本例中并不是由某两个随机样本作为簇质心）；(c-f)演示了$k$-means算法的两个迭代，在每次迭代中，我们都将训练样本分配给距离它们最近的簇质心（图中将样本与对应的簇质心标记为同一颜色）；然后将每个簇质心移动到其下辖样本的中心。

那么，$k$-means算法一定收敛吗？在特定的场景下，答案是肯定的。我们先定义**失真函数（distortion function）**：

$$
J(c,\mu)=\sum_{i=1}^m\left\lVert x^{(i)}-\mu_{c^{(i)}}\right\rVert^2
$$

$J$为每个样本$x^{(i)}$到其簇质心$\mu_{c^{(i)}}$的距离的平方之和，可以证明$k$-means正是在$J$上的坐标下降过程（参考[第八讲](chapter08.ipynb)）。尤其是$k$-means算法的内循环实际上是在重复：保持$\mu$不变最小化$J$关于$c$的函数，然后再保持$c$不变最小化$J$关于$\mu$的函数。因此，$J$是单调递减的，它的函数值一定收敛。（通常这意味着$c$和$\mu$也会收敛。但在理论上，$k$-means算法的最终结果可能在少数簇之间振荡，比如少数几组$c$或/且$\mu$具有完全一样的$J$值。不过在实际操作中，这种现象几乎不会发生。）

失真函数$J$是一个非凸函数，所以对于$J$的坐标下降并不能保证其收敛于全局最小值，即$k$-means会被局部最小值影响。尽管如此，$k$-means算法通常都都能很好的完成聚类任务。不过，如果真的怕算法落入“不好的”局部最优中，一个可行的解决方案是为簇质心$\mu_j$赋上不同的初始化值，多运行几次，然后选出失真函数$J(c,\mu)$最小的一组聚类结果即可。

## 混合高斯模型（Mixtures of Gaussians）与最大期望算法（EM algorithm）

在这一节我们将讨论对聚类样本密度估计（density estimation）的最大期望（EM: Expectation-Maximization）。

假设有给定训练集$\left\{x^{(1)},\cdots,x^{(m)}\right\}$，因为现在研究无监督学习算法，所以训练样本没有对应的$y$标记。

我们希望使用一个联合分布$p\left(x^{(i)},z^{(i)}\right)=p\left(x^{(i)}\mid z^{(i)}\right)p\left(z^{(i)}\right)$（链式法则）对数据建模。这里的$z^{(i)}\sim\mathrm{Multinomial}(\phi)$（即$\phi_j\gt0,\ \sum_{j=1}^k\phi_j=1,\ \phi_j=p\left(z^{(i)}=j\right)$，当只有两个高斯分布时，$z^{(i)}$只有两种取值的可能，此时它是一个伯努利分布。），并且有$x^{(i)}\mid z^{(i)}=j\sim\mathcal{N}\left(\mu_j,\varSigma_j\right)$。令$k$表示事件$z^{(i)}$可能取的值的总数，因此，我们的模型假设每个事件$x^{(i)}$均由事件“$z^{(i)}$随机的在$\{1,\cdots,k\}$做出选择”生成，而$x^{(i)}$可以表示为$k$个“由$z^{(i)}$确定的高斯分布”中的一个。这就是**混合高斯模型（mixture of Gaussians model）**。应注意$z^{(i)}$是**潜在（latent）**随机变量，这意味着它们是隐藏的、未知的，也正是这一点加大了我们估值难度。（变量$z^{(i)}$类似于样本$x^{(i)}$的标记，只是我们并不知道这个变量是多少，也就是说我们不知道每个样本属于哪个标记类，在后面的算法中，我们会尝试猜测这个标记。）

模型的参数为$\phi,\mu,\varSigma$，为了估计这些参数，可以写出参数在给定数据上的对数似然估计：

$$
\begin{align}
\mathscr{l}\left(\phi,\mu,\varSigma\right)&=\sum_{i=1}^m\log p\left(x^{(i)};\phi,\mu,\varSigma\right)\\
&=\sum_{i=1}^m\log\sum_{z^{(i)}=1}^kp\left(x^{(i)}\mid z^{(i)};\mu,\varSigma\right)p\left(z^{(i)};\phi\right)
\end{align}
$$

然而，如果对式中每个参数求偏导并置为零，并尝试解出极值时会发现，我们无法求出这些参数的最大似然估计的解析解。

随机变量$z^{(i)}$指出了$x^{(i)}$服从$k$个高斯分布中的哪一个。可以看出，如果我们能够确定$z^{(i)}$，那么这个最大似然问题就会好解很多，我们可以将似然函数写成：

$$
\mathscr{l}\left(\phi,\mu,\varSigma\right)=\sum_{i=1}^m\log p\left(x^{(i)}\mid z^{(i)};\mu,\varSigma\right)+\log p\left(z^{(i)};\phi\right)
$$

最大化上面这个关于$\phi,\mu,\varSigma$的式子就能求得各参数：

$$
\begin{align}
\phi_j&=\frac{1}{m}\sum_{i=1}^m1\left\{z^{(i)}=j\right\}\\
\mu_j&=\frac{\sum_{i=1}^m1\left\{z^{(i)}=j\right\}x^{(i)}}{\sum_{i=1}^m1\left\{z^{(i)}=j\right\}}\\
\varSigma_j&=\frac{\sum_{i=1}^m1\left\{z^{(i)}=j\right\}\left(x^{(i)}-\mu_j\right)\left(x^{(i)}-\mu_j\right)^T}{\sum_{i=1}^m1\left\{z^{(i)}=j\right\}}
\end{align}
$$

如果能确定$z^{(i)}$，则这里的参数估计与前面（[第五讲](chapter05.ipynb)）高斯判别分析模型中的参数估计几乎是一样的，只不过$z^{(i)}$代替了原来的类标记$y^{(i)}$。（式子里还有其它细节不同，在[问题集1](http://cs229.stanford.edu/materials/ps1.pdf)的高斯判别分析中我们一了解到：第一，不同与问题集中$y^{(i)}\in\{-1,1\}$服从伯努利分布，我们已经将$z^{(i)}$泛化为多项分布；第二，不同于问题集中不同的$y$共用一个协方差矩阵，我们已经给每个高斯分布赋上了不同的协方差矩阵$\varSigma_j$。）

然而，在我们的密度估计问题中，$z^{(i)}$是未知的，我们该怎么办呢？

EM算法（[中文](https://zh.wikipedia.org/wiki/%E6%9C%80%E5%A4%A7%E6%9C%9F%E6%9C%9B%E7%AE%97%E6%B3%95)，[英文](https://en.wikipedia.org/wiki/Expectation%E2%80%93maximization_algorithm)）是一种迭代算法，有两个主要步骤。对于上面的问题，E步骤中，算法尝试猜测每个$z^{(i)}$的值，在M步骤中算法会根据猜测更新模型。因为M步骤会假设E步骤中的猜测是正确的，那么，有了缺失的$z^{(i)}$，最大化就很简单了。算法的流程：

重复直到收敛：`{`

* (E步骤)对于每个$i,j$，令$w_j^{(i)}:=p\left(z^{(i)}=j\mid x^{(i)};\phi,\mu,\varSigma\right)$（样本$x^{(i)}$属于第$j$个高斯分布的概率）
* (M步骤)更新模型中的参数：
$$
\begin{align}
\phi_j&=\frac{1}{m}\sum_{i=1}^mw_j^{(i)}\\
\mu_j&=\frac{\sum_{i=1}^mw_j^{(i)}x^{(i)}}{\sum_{i=1}^mw_j^{(i)}}\\
\varSigma_j&=\frac{\sum_{i=1}^mw_j^{(i)}\left(x^{(i)}-\mu_j\right)\left(x^{(i)}-\mu_j\right)^T}{\sum_{i=1}^mw_j^{(i)}}
\end{align}
$$

`}`

在E步骤中，我们使用$x^{(i)}$以及当前的参数值计算所有的$z^{(i)}$，即使用贝叶斯定理：

$$
p\left(z^{(i)}=j\mid x^{(i)};\phi,\mu,\varSigma\right)=\frac{p\left(x^{(i)}\mid z^{(i)}=j;\mu,\varSigma\right)p\left(z^{(i)}=j;\phi\right)}{\sum_{l=1}^kp\left(x^{(i)}\mid z^{(i)}=l;\mu,\varSigma\right)p\left(z^{(i)}=l;\phi\right)}
$$

* 式中的$p\left(x^{(i)}\mid z^{(i)}=j;\mu,\varSigma\right)$为某个以$\mu_j$为期望、$\varSigma_j$为协方差的高斯分布在$x^{(i)}$处的密度估计，即$\frac{1}{\sqrt{(2\pi)^n\left\lvert\varSigma_j\right\rvert}}\exp\left(-\frac{1}{2}\left(x^{(i)}-\mu_j\right)^T\varSigma_j^{-1}\left(x^{(i)}-\mu_j\right)\right)$；
* $p\left(z^{(i)}=j;\phi\right)$为$\phi_j$。

E步骤中求出的$w_j^{(i)}$表示我们对$z^{(i)}$的“软”猜测。（“软”意味着一种概率上的猜测，从$[0,1]$区间取值；相对的“硬”猜测意味着最佳的一次性猜测，是一种对于值的猜测，比如从集合$\{0,1\}$或$\{1,\cdots,k\}$中取值。）

我们也应该对比M步骤中$z^{(i)}$已知时参数更新的式子，能够发现，只有式子里的由高斯分布中每个样本点求出的指示函数$1\left\{z^{(i)}=j\right\}$变为了$w_j^{(i)}$，其余部分同原来一样。

同样也可以用前面的$k$-means聚类算法做对比，不同之处在于$k$-means算法将样本“硬”分配给不同的簇，EM算法用$w_j^{(i)}$做“软”分配。相似之处在于，EM算法也存在落入局部最优的可能。所以，使用不同的初始参数多运行几次的方法对EM算法同样适用。

显然，可以很自然的解释EM算法对于$z^{(i)}$的迭代猜测，但是这个点子来自哪里？我们能对算法属性做出证明吗，比如证明其收敛性？在下一节，我们将给出一个更加泛化的EM算法的描述，它会使我们轻易的将EM算法应用在不同的具有潜在随机变量的估值问题中，而且也能够证明算法的收敛性。

# 第九部分：EM算法

在前面我们介绍了如何用EM算法应用在高斯混合模型的拟合上，在本节，我们将介绍EM算法更广泛的应用，并展示如何将其应用在含有潜在变量的估值问题族中。我们从**延森不等式（Jensen's inequality）**这一通途广泛的的结论开始本节的讨论。

## 1. 延森不等式

令函数$f$的值域为实数集，回忆以前的知识，如果$\forall x\in\mathbb{R},f''(x)\geq0$则$f$为凸函数（[中文](https://zh.wikipedia.org/wiki/%E5%87%B8%E5%87%BD%E6%95%B0)，[英文](https://en.wikipedia.org/wiki/Convex_function)）。在这里，一般化的$f$的输入为向量，而函数的海森矩阵$H$应为半正定矩阵（$H\geq0$）。如果如果$\forall x\in\mathbb{R},f''(x)\gt0$，则称其为**严格（strictly）**凸函数（再输入为向量的情形下，函数对应的$H$应为为正定矩阵，记为$H\gt0$）。于是，延森不等式为：

**定理：**令$f$为凸函数，$X$为随机变量，则有$\mathrm{E}\left[f(x)\right]\geq f(\mathrm{E}X)$。

此外，如果$f$严格凸，则当且仅当$X=\mathrm{E}[X]$的概率为$1$时（即$p(X=\mathrm{E}[X])=1$，也就是说$X$是常量），有$\mathrm{E}\left[f(x)\right]=f(\mathrm{E}X)$。

根据惯例，我们可以在写期望的时候省略方括号，$f(\mathrm EX)=f(\mathrm E[X])$。

可以用下图解释这个定理：

图中的实现就是凸函数$f$。X是一个随机变量，有$50%$的概率取到$a$、$50%$的概率取到$b$（沿$x$轴），则$X$的期望应在$a,b$的中点。

再看$y$轴上的$f(a),f(b),f(\mathrm{E}[X])$和$\mathrm E[f(x)]$，其中$\mathrm E[f(x)]$在$f(a),f(b)$的中点，从图中可以看出，因为$f$是凸函数，必然有$\mathrm E[f(x)]\geq f(\mathrm EX)$。（有些同学可能会把这个不等式的符号方向记反，记住上面的图就可以快速推导出符号的方向。）

**注意：**回忆以前的知识，当且仅当$-f$是[严格]凸函数时，$f$是[严格]凹函数（即$f''(x)\leq0$或$H\leq0$）。延森不等式同样适用于凹函数，只不过所有不等式的不等号的方向相反（如$\mathrm E[f(x)]\leq f(\mathrm EX)$等）。

## 2. EM算法

对于给定的包含$m$个相互独立的样本的训练集$\left\{x^{(1)},\cdots,x^{(m)}\right\}$，假设有估值问题：我们希望使用模型$p(x,z)$拟合样本数据，对数似然函数为：

$$
\begin{align}
\mathscr l(\theta)&=\sum_{i=1}^m\log p\left(x^{(i)};\theta\right)\\
&=\sum_{i=1}^m\log\sum_{z^{(i)}}p\left(x^{(i)},z^{(i)};\theta\right)
\end{align}
$$

但是直接计算出参数$\theta$的最大似然估计可能会很难，因为这里的$z^{(i)}$是潜在随机变量。不过，如果能够确定$z^{(i)}$的值，参数的最大似然估计通常很容易计算。

在这种情形下，EM算法给出了一个能够有效的求出参数最大似然估计的方法。直接最大化$\mathscr l(\theta)$可能很难，EM算法的思路是不停的构造$\mathscr l$函数的下界（E步骤），然后最大化这些下界（M步骤）。（也就是说，算法先初始化一套参数$\theta^{(0)}$，然后构造一个下界$\mathscr l$并求这个$\mathscr l$的最大值，而取最大值时的的这套参数就是$\theta^{(1)}$；算法再从$\theta^{(1)}$开始构造下一个下界并最大化得到$\theta^{(2)}$……直到收敛于一个局部最优解）。下面介绍EM算法的形式描述：

对每一个$i$，令$Q_i$为某个关于$z$的分布（$\displaystyle\sum_{z^{(i)}}Q_i\left(z^{(i)}\right)=1,Q_i\left(z^{(i)}\right)\geq0$），考虑下面的式子（若$z$是连续变量，则$Q_i$为概率密度，此时讨论中的所有对$z$的求和应变为对$z$的积分）：

$$
\begin{align}
\sum_i\log p\left(x^{(i)};\theta\right)&=\sum_i\log\sum_{z^{(i)}}p\left(x^{(i)},z^{(i)};\theta\right)\tag{1}\\
&=\sum_i\log\sum_{z^{(i)}}Q_i\left(z^{(i)}\right)\frac{p\left(x^{(i)},z^{(i)};\theta\right)}{Q_i\left(z^{(i)}\right)}\tag{2}\\
&\geq\sum_i\sum_{z^{(i)}}Q_i\left(z^{(i)}\right)\log\frac{p\left(x^{(i)},z^{(i)};\theta\right)}{Q_i\left(z^{(i)}\right)}\tag{3}
\end{align}
$$

$(2)$式到$(3)$式的推导使用了延森不等式，对于$f(x)=\log(x)$有$f''(x)=-\frac{1}{x^2}\lt0$，所以$f(x)$是一个定义在$x\in\mathbb R^+$上的凹函数，则有$f(\mathrm EX)\geq \mathrm E[f(X)]$。再看$(2)$式中的$\displaystyle\sum_{z^{(i)}}\underbrace{Q_i\left(z^{(i)}\right)}_\textrm{probability}\underbrace{\left[\frac{p\left(x^{(i)},z^{(i)};\theta\right)}{Q_i\left(z^{(i)}\right)}\right]}_\textrm{variable}$项，恰好是$z^{(i)}$服从$Q_i$的分布时，变量$\left[\displaystyle\frac{p\left(x^{(i)},z^{(i)};\theta\right)}{Q_i\left(z^{(i)}\right)}\right]$的期望表达式（期望的定义：$\mathrm E[g(x)]=\sum_xp(x)g(x)$）。那么，根据延森不等式，应有：

$$
f\left(\operatorname*E_{z^{(i)}\sim Q_i}\left[\frac{p\left(x^{(i)},z^{(i)};\theta\right)}{Q_i\left(z^{(i)}\right)}\right]\right)\geq\operatorname*E_{z^{(i)}\sim Q_i}\left[f\left(\frac{p\left(x^{(i)},z^{(i)};\theta\right)}{Q_i\left(z^{(i)}\right)}\right)\right]
$$

式中的$\displaystyle\operatorname*E_{z^{(i)}\sim Q_i}$指在随机变量$z^{(i)}$服从$Q_i$分布的情况下求期望。最终我们得到$(3)$式。

对于*任意*的分布$Q_i$，$(3)$式都能给出一个$\mathscr l(\theta)$的下界。而$Q_i$有很多种选择，我们接下来该怎么做？如果我们对当前的参数$\theta$做一次猜测，我们希望这个猜测能够紧贴着目标函数$\mathscr l(\theta)$，因为只有这样，我们才能确定每次迭代都是在逼近目标函数的局部最优解。也就是说我们希望特定的$\theta$猜测能够使这个“下界不等式”取到等号部分。（后面会解释这个等号如何保证$\mathscr l(\theta)$在每一次迭代中都单调递增。）

对于特定的$\theta$猜测，要使下界紧贴着目标函数，可以再观察引入延森不等式的那一步推导，我们的目标是使不等式取到等号。而上面已经介绍了，如果要取等号，只能是对一个“常量随机变量”求期望，也就是需要令$\displaystyle\frac{p\left(x^{(i)},z^{(i)};\theta\right)}{Q_i\left(z^{(i)}\right)}=c$，$c$是某个与$z^{(i)}$无关的常量（对于任意$z^{(i)}$，都取同一个常量$c$）。于是，令$Q_i\left(z^{(i)}\right)\propto p\left(x^{(i)},z^{(i)};\theta\right)$即可（令分母与分子保持同一个比例）。

实际上，因为我们知道$Q_i$是一个概率分布，有$\displaystyle\sum_zQ_i\left(z^{(i)}\right)=1=\sum_z\frac{p\left(x^{(i)},z^{(i)};\theta\right)}{c}=\frac{\sum_zp\left(x^{(i)},z^{(i)};\theta\right)}{c}\implies c=\sum_zp\left(x^{(i)},z^{(i)};\theta\right)$，则有：

$$
\begin{align}
Q_i\left(z^{(i)}\right)&=\frac{p\left(x^{(i)},z^{(i)};\theta\right)}{\sum_zp\left(x^{(i)},z;\theta\right)}\\
&=\frac{p\left(x^{(i)},z^{(i)};\theta\right)}{p\left(x^{(i)};\theta\right)}\\
&=p\left(z^{(i)}\mid x^{(i)};\theta\right)
\end{align}
$$

因此，我们只需要将$Q_i$设置为“对于给定的$x^{(i)}$下$z^{(i)}$的后验概率”即可，然后再此时的$\theta$就行了。

对于用上面的后验概率确定的$Q_i$，$(3)$式就能给出一个紧贴目标函数$\mathscr l(\theta)$的“下界函数”，这就是E步骤，而后面我们会尝试最大化这个“下界函数”，也就是在M步骤中，我们会尝试最大化$(3)$式得出的式子（用前面E步骤的$\theta$确定的式子），并得到一组新的参数$\theta$。不断重复这两部就是EM算法了：

重复直到收敛：`{`

* (E步骤)对于每个$i$，令$Q_i\left(z^{(i)}\right):=p\left(z^{(i)}\mid x^{(i)};\theta\right)$
* (M步骤)更新参数，令$\displaystyle\theta:=\arg\max_\theta\sum_i\sum_{z^{(i)}}Q_i\left(z^{(i)}\right)\log\frac{p\left(x^{(i)},z^{(i)};\theta\right)}{Q_i\left(z^{(i)}\right)}$

`}`

下一讲将继续EM算法的介绍。


```python
%load_ext autoreload
%autoreload 2
from sectional_v2.constants import path_eusaar_data# path_eusaar_data
import numpy as np
from sectional_v2.util import eusaar_data
from sectional_v2.util.eusaar_data.histc_vars import load_var_as_dtframe
from sectional_v2.util.eusaar_data import  distc_var, histc_vars, histsc_hists # import load_var_as_dtframe
import matplotlib.pyplot as plt
from useful_scit.plot import get_cmap_dic
```

    /home/ubuntu/mnts/nird/projects//Output_data_OAS-DEV/

## distc_var

```python
a = distc_var.get_distc_xarray_all()
```

```python
sts = a['station'].values[:4]
year='BOTH'
subs='TOT'
cma = get_cmap_dic(sts)
da = a.sel(year=year,subset=subs)
for st in sts:
    _da50 = da.sel(station=st, percentile='50th percentile')#
    _da50.plot(yscale='log',xscale='log', label='50th perc, %s'%st, color = cma[st])
    _da95 = da.sel(station=st, percentile='95th percentile')
    _da5 = da.sel(station=st, percentile='5th percentile')
    plt.fill_between(da.diameter, _da5,_da95, alpha=.2 , color = cma[st])
plt.ylim(1,3e4)
plt.xlim(5,1e3)
plt.legend()
plt.title('Test sizedistributions')
```

### Conversion dN/dlog10D to dN/dlnD:
$$ ln(x) = ln(10)\cdot log10(x)$$
So
\begin{align}
\frac{dN}{dlog10D} =& \frac{dN}{dlnD}\frac{dlnD}{dlog10D} \\
=& \frac{dN}{dlnD}\frac{d(ln(10)\cdot log10(D)}{dlog10D} \\
=& \frac{dN}{dlnD}ln(10) \\
\end{align}
and
$$\frac{dN}{dlnD} = 1/ln(10) \frac{dN}{dlog10D}$$

## histc_var

```python
b = histc_vars.get_histc_vars_xr()
_da = b['N30']#.plot()
for st in sts:
    _da.sel(station=st).plot(label=st, yscale='log')
plt.ylim([1e0,1e4])
plt.legend()
```

```python
histc.load_gd()
```

## histsc_hists

```python
c = histsc_hists.open_hists2xarray()
```

```python
c
```

<xarray.Dataset>
Dimensions:                 (number concentration: 121, station: 24)
Coordinates:
  * number concentration    (number concentration) float64 0.1 0.1122 ... 1e+05
  * station                 (station) object 'KPO' 'BEO' 'MHD' ... 'SMR' 'JFJ'
Data variables:
    N30-50 all time         (station, number concentration) float64 16.0 ... 0.0
    N50    all time         (station, number concentration) float64 1.0 ... 0.0
    N100   all time         (station, number concentration) float64 1.0 ... 0.0
    N30-50 Winter           (station, number concentration) float64 5.0 ... 0.0
    N30-50 Spring           (station, number concentration) float64 5.0 ... 0.0
    N30-50 Summer           (station, number concentration) float64 6.0 ... 0.0
    N30-50 Autumn           (station, number concentration) float64 0.0 ... 0.0
    N50 Winter              (station, number concentration) float64 0.0 ... 0.0
    N50 Spring              (station, number concentration) float64 0.0 ... 0.0
    N50 Summer              (station, number concentration) float64 1.0 ... 0.0
    N50 Autumn              (station, number concentration) float64 0.0 ... 0.0
    N100 Winter             (station, number concentration) float64 0.0 ... 0.0
    N100 Spring             (station, number concentration) float64 0.0 ... 0.0
    N100 Summer             (station, number concentration) float64 1.0 ... 0.0
    N100 Autumn             (station, number concentration) float64 0.0 ... 0.0
    N30-50 Night            (station, number concentration) float64 0.0 ... 0.0
    N30-50 Day              (station, number concentration) float64 16.0 ... 0.0
    N50 Night               (station, number concentration) float64 0.0 ... 0.0
    N50 Day                 (station, number concentration) float64 1.0 ... 0.0
    N100 Night              (station, number concentration) float64 0.0 ... 0.0
    N100 Day                (station, number concentration) float64 1.0 ... 0.0
    N30-50 ECHAM5 Sampling  (station, number concentration) float64 9.0 ... 0.0
    N50 ECHAM5 Sampling     (station, number concentration) float64 1.0 ... 0.0
    N100 ECHAM5 Sampling    (station, number concentration) float64 1.0 ... 0.0

```python
_da = c['N30-50 Summer']#.plot()
for st in sts:
    _da.sel(station=st).plot(label=st, xscale='log')
plt.legend()
```

```python
mn =  a['N50'].resample({'time':'1M'}).mean()
for station in mn.station:
    mn.sel(station=station).plot(label=station.values)
plt.legend()
```

```python
varl = eusaar_data.standard_varlist_histc
fig, axs = plt.subplots(len(varl),
                        figsize=[10,20])
for var, ax in zip(varl, axs):
    mn =  a[var].groupby('time.month').mean()
    for station in mn.station:
        mn.sel(station=station).plot(label=station.values, ax=ax)
    ax.set_title(var)
ax.legend()
```


# Selección óptima de portafolios II

Entonces, tenemos que:
- La LAC describe las posibles selecciones de riesgo-rendimiento entre un activo libre de riesgo y un activo riesgoso.
- Su pendiente es igual al radio de Sharpe del activo riesgoso.
- La asignación óptima de capital para cualquier inversionista es el punto tangente de la curva de indiferencia del inversionista con la LAC.

Para todo lo anterior, supusimos que ya teníamos el portafolio óptimo (activo riesgoso).

En la clase pasada aprendimos a hallar este portafolio óptimo si el conjunto de activos riesgosos estaba conformado únicamente por dos activos:

$$w_{1,EMV}=\frac{(E[r_1]-r_f)\sigma_2^2-(E[r_2]-r_f)\sigma_{12}}{(E[r_2]-r_f)\sigma_1^2+(E[r_1]-r_f)\sigma_2^2-((E[r_1]-r_f)+(E[r_2]-r_f))\sigma_{12}}.$$

- Sin embargo, la complejidad del problema crece considerablemente con el número de variables, y la solución analítica deja de ser viable cuando mencionamos que un portafolio bien diversificado consta aproximadamente de 50-60 activos.
- En esos casos, este problema se soluciona con rutinas numéricas que hagan la optimización por nosotros, porque son una solución viable y escalable a más variables.

**Objetivos:**
- ¿Cuál es el portafolio óptimo de activos riesgosos cuando tenemos más de dos activos?
 - ¿Cómo construir la frontera de mínima varianza cuando tenemos más de dos activos?

*Referencia:*
- Notas del curso "Portfolio Selection and Risk Management", Rice University, disponible en Coursera.
___

## 1. Maximizando el radio de Sharpe

### ¿Qué pasa si tenemos más de dos activos riesgosos?

En realidad es algo muy similar a lo que teníamos con dos activos.
- Para dos activos, construir la frontera de mínima varianza es trivial: todas las posibles combinaciones.
- Con más de dos activos, recordar la definición: la frontera de mínima varianza es el lugar geométrico de los portafolios que proveen el mínimo riesgo para un nivel de rendimiento dado.

 Ver en el tablero.

Analíticamente:
- $N$ activos,
- caracterizados por $(\sigma_i,E[r_i])$,
- cada uno con peso $w_i$, con $i=1,2,\dots,N$.

Entonces, buscamos los pesos tales que

\begin{align}
\min_{w_1,\dots,w_n} & \quad \sum_{i=1}^{N}w_i^2\sigma_i^2+\sum_{i=1}^{N}\sum_{j=1,j\neq i}^{N}w_iw_j\sigma_{ij}\\
\text{s.a.}          & \quad \sum_{i=1}^{N}w_i=1, w_i\geq0\\
                     & \quad \sum_{i=1}^{N}w_iE[r_i]=\bar{\mu},
\end{align}

donde $\bar{\mu}$ corresponde a un nivel de rendimiento objetivo.

**Obviamente, tendríamos que resolver este problema para muchos niveles de rendimiento objetivo.**

-  Explicar relación con gráfica.

-  Recordar clase 10.

Lo anterior se puede escribir vectorialmente como:

\begin{align}
\min_{\boldsymbol{w}} & \quad \boldsymbol{w}^T\Sigma\boldsymbol{w}\\
\text{s.a.}          & \quad \boldsymbol{1}^T\boldsymbol{w}=1, \boldsymbol{w}\geq0\\
                     & \quad E[\boldsymbol{r}^T]\boldsymbol{w}=\bar{\mu},
\end{align}

donde:
- $\boldsymbol{w}=\left[w_1,\dots,w_N\right]^T$ es el vector de pesos,
- $\boldsymbol{1}=\left[1,\dots,1\right]^T$ es un vector de unos,
- $E[\boldsymbol{r}]=\left[E[r_1],\dots,E[r_N]\right]^T$ es el vector de rendimientos esperados, y
- $\Sigma=\left[\begin{array}{cccc}\sigma_{1}^2 & \sigma_{12}  & \dots  & \sigma_{1N} \\
                                   \sigma_{21}  & \sigma_{2}^2 & \dots  & \sigma_{2N} \\
                                   \vdots       & \vdots       & \ddots & \vdots      \\
                                   \sigma_{N1}  & \sigma_{N2}  & \dots  & \sigma_{N}^2\end{array}\right]$ es la matriz de varianza-covarianza.

**Esta última forma es la que comúnmente usamos al programar, por ser eficiente y escalable a problemas de N variables.**

### Entonces, ¿para cuántos niveles de rendimiento objetivo tendríamos que resolver el anterior problema con el fin de graficar la frontera de mínima varianza?
- Observar que el problema puede volverse muy pesado a medida que incrementamos el número de activos en nuestro portafolio...
- Una tarea bastante compleja.

### Sucede que, en realidad, sólo necesitamos conocer dos portafolios que estén sobre la *frontera de mínima varianza*.
- Si logramos encontrar dos portafolios sobre la frontera, entonces podemos a la vez encontrar todas las posibles combinaciones de estos dos portafolios para trazar la frontera de mínima varianza.
- Ver el caso de dos activos.

### ¿Qué portafolios usar?
Hasta ahora, hemos estudiando profundamente como hallar dos portafolios muy importantes que de hecho yacen sobre la frontera de mínima varianza:
1. Portafolio de EMV: máximo SR.
2. Portafolio de mínima varianza: básicamente, el mismo problema anterior, sin la restricción de rendimiento objetivo.

Luego, tomar todas las posibles combinaciones de dichos portafolios usando las fórmulas para dos activos de medias y varianzas:
- w: peso para el portafolio EMV,
- 1-w: peso para le portafolio de mínima varianza.

## 2. Ejemplo ilustrativo.

Retomamos el ejemplo de mercados de acciones en los países integrantes del $G5$: EU, RU, Francia, Alemania y Japón.

```python
# Importamos pandas y numpy
import pandas as pd
import numpy as np
```

```python
# Resumen en base anual de rendimientos esperados y volatilidades
annual_ret_summ = pd.DataFrame(columns=['EU', 'RU', 'Francia', 'Alemania', 'Japon'], index=['Media', 'Volatilidad'])
annual_ret_summ.loc['Media'] = np.array([0.1355, 0.1589, 0.1519, 0.1435, 0.1497])
annual_ret_summ.loc['Volatilidad'] = np.array([0.1535, 0.2430, 0.2324, 0.2038, 0.2298])

annual_ret_summ.round(4)
```

      EU
      RU
      Francia
      Alemania
      Japon

      Media
      0.1355
      0.1589
      0.1519
      0.1435
      0.1497

      Volatilidad
      0.1535
      0.243
      0.2324
      0.2038
      0.2298

```python
# Matriz de correlación
corr = pd.DataFrame(data= np.array([[1.0000, 0.5003, 0.4398, 0.3681, 0.2663],
                                    [0.5003, 1.0000, 0.5420, 0.4265, 0.3581],
                                    [0.4398, 0.5420, 1.0000, 0.6032, 0.3923],
                                    [0.3681, 0.4265, 0.6032, 1.0000, 0.3663],
                                    [0.2663, 0.3581, 0.3923, 0.3663, 1.0000]]),
                    columns=annual_ret_summ.columns, index=annual_ret_summ.columns)
corr.round(4)
```

      EU
      RU
      Francia
      Alemania
      Japon

      EU
      1.0000
      0.5003
      0.4398
      0.3681
      0.2663

      RU
      0.5003
      1.0000
      0.5420
      0.4265
      0.3581

      Francia
      0.4398
      0.5420
      1.0000
      0.6032
      0.3923

      Alemania
      0.3681
      0.4265
      0.6032
      1.0000
      0.3663

      Japon
      0.2663
      0.3581
      0.3923
      0.3663
      1.0000

```python
# Tasa libre de riesgo
rf = 0.05
```

Esta vez, supondremos que tenemos disponibles todos los mercados de acciones y el activo libre de riesgo.

#### 1. Construir la frontera de mínima varianza

##### 1.1. Encontrar portafolio de mínima varianza

```python
# Importamos funcion minimize del modulo optimize de scipy
from scipy.optimize import minimize
```

```python
## Construcción de parámetros
# 1. Sigma: matriz de varianza-covarianza
S = np.diag(annual_ret_summ.loc['Volatilidad'])
Sigma = S.dot(corr).dot(S)
# 2. Eind: rendimientos esperados activos individuales
Eind = annual_ret_summ.loc['Media'].values
```

    array([0.1355, 0.1589, 0.1519, 0.1435, 0.1497], dtype=object)

```python
# Función objetivo
def var(w, Sigma):
    return w.dot(Sigma).dot(w)
```

```python
# Dato inicial
n = len(Eind)
w0 = np.ones((n,)) / n
# Cotas de las variables
bnds = ((0, 1),) * n
# Restricciones
cons = {'type': 'eq', 'fun': lambda w: w.sum() - 1}
############# Otra forma ############
# Restriccion suma(w)=1
#def sum_w_1(w):
#    return w.sum() - 1
#cons = {'type': 'eq', 'fun': sum_w_1}
```

    tuple

```python
# Portafolio de mínima varianza
minvar = minimize(fun=var,
                  x0=w0,
                  args=(Sigma,),
                  bounds=bnds,
                  constraints=cons
                 )
minvar
```

         fun: 0.01861776391061502
         jac: array([0.03718246, 0.03881475, 0.03859101, 0.03755156, 0.0370423 ])
     message: 'Optimization terminated successfully.'
        nfev: 49
         nit: 7
        njev: 7
      status: 0
     success: True
           x: array([6.17797049e-01, 3.46944695e-18, 0.00000000e+00, 2.09394358e-01,
           1.72808594e-01])

```python
# Pesos, rendimiento y riesgo del portafolio de mínima varianza
w_minvar = minvar.x
E_minvar = Eind.dot(w_minvar)
s_minvar = np.sqrt(var(w_minvar, Sigma))
RS_minvar = (E_minvar - rf) / s_minvar
w_minvar, E_minvar, s_minvar, RS_minvar
```

    (array([6.17797049e-01, 3.46944695e-18, 0.00000000e+00, 2.09394358e-01,
            1.72808594e-01]),
     0.13962903688859557,
     0.13644692708381168,
     0.6568783834431207)

##### 1.2. Encontrar portafolio EMV

```python
# Función objetivo
def menos_RS(w, Eind, Sigma, rf):
    Ep = Eind.dot(w)
    sp = np.sqrt(w.dot(Sigma).dot(w))
    RS = (Ep - rf) / sp
    return -RS
```

```python
# Dato inicial
n = len(Eind)
w0 = np.ones((n,)) / n
# Cotas de las variables
bnds = ((0, 1),) * n
# Restricciones
cons = {'type': 'eq', 'fun': lambda w: w.sum() - 1}
```

```python
# Portafolio EMV
EMV = minimize(fun=menos_RS,
               x0=w0,
               args=(Eind, Sigma, rf),
               bounds=bnds,
               constraints=cons
              )
EMV
```

         fun: -0.6644372965632436
         jac: array([-0.36088951, -0.36076408, -0.36036385, -0.36108153, -0.36062376])
     message: 'Optimization terminated successfully.'
        nfev: 35
         nit: 5
        njev: 5
      status: 0
     success: True
           x: array([0.50714174, 0.07470888, 0.02471533, 0.18943972, 0.20399434])

```python
# Pesos, rendimiento y riesgo del portafolio EMV
w_EMV = EMV.x
E_EMV = Eind.dot(w_EMV)
s_EMV = np.sqrt(var(w_EMV, Sigma))
RS_EMV = (E_EMV - rf) / s_EMV
w_EMV, E_EMV, s_EMV, RS_EMV
```

    (array([0.50714174, 0.07470888, 0.02471533, 0.18943972, 0.20399434]),
     0.1420657564717856,
     0.13856199365687238,
     0.6644372965632436)

##### 1.3. Construir frontera de mínima varianza

También debemos encontrar la covarianza (o correlación) entre estos dos portafolios:

```python
# Covarianza entre los portafolios
s_MV_EMV = w_minvar.dot(Sigma).dot(w_EMV)
s_MV_EMV
```

    0.018690275386034134

```python
# Correlación entre los portafolios
r_MV_EMV = s_MV_EMV / (s_minvar * s_EMV)
r_MV_EMV
```

    0.9885708894197612

```python
# Vector de w
N = 101
w = np.linspace(0, 4, N)
```

```python
# DataFrame de portafolios:
# 1. Índice: i
# 2. Columnas 1-2: w, 1-w
# 3. Columnas 3-4: E[r], sigma
# 4. Columna 5: Sharpe ratio
front_minvar = pd.DataFrame(index = range(1, N+1),
                            data={'w_EMV': w,
                                  'w_minvar': 1 - w,
                                  'Media': w * E_EMV + (1 - w) * E_minvar,
                                  'Vol': np.sqrt((w * s_EMV)**2 + ((1 - w) * s_minvar)**2 + 2 * w * (1 - w) * s_MV_EMV)
                                 }
                           )
front_minvar['RS'] = (front_minvar['Media'] - rf) / front_minvar['Vol']
front_minvar.head(10)
```

      w_EMV
      w_minvar
      Media
      Vol
      RS

      1
      0.00
      1.00
      0.139629
      0.136447
      0.656878

      2
      0.04
      0.96
      0.139727
      0.136471
      0.657478

      3
      0.08
      0.92
      0.139824
      0.136500
      0.658053

      4
      0.12
      0.88
      0.139921
      0.136534
      0.658602

      5
      0.16
      0.84
      0.140019
      0.136573
      0.659127

      6
      0.20
      0.80
      0.140116
      0.136617
      0.659627

      7
      0.24
      0.76
      0.140214
      0.136666
      0.660102

      8
      0.28
      0.72
      0.140311
      0.136721
      0.660552

      9
      0.32
      0.68
      0.140409
      0.136780
      0.660978

      10
      0.36
      0.64
      0.140506
      0.136845
      0.661378

```python
# Importar librerías de gráficos
import matplotlib.pyplot as plt
%matplotlib inline
```

```python
# Gráfica de dispersión de puntos coloreando
# de acuerdo a SR, los activos individuales
# y los portafolios hallados
plt.figure(figsize=(6,4))
plt.plot(front_minvar['Vol'],
         front_minvar['Media'],
         lw=2,
         color='lightcoral',
         label='Frontera de minima varianza'
        )
plt.plot(s_EMV, E_EMV, 'or', ms=5, label='Port. EMV')
plt.plot(s_minvar, E_minvar, 'ob', ms=5, label='Port. Min. Var.')
plt.grid()
plt.xlabel('Volatilidad $\sigma$')
plt.ylabel('Rendimiento esperado $E[r]$')
plt.legend(loc = 'best')
```

**A partir de lo anterior, solo restaría construir la LAC y elegir la distribución de capital de acuerdo a las preferencias (aversión al riesgo).**
___

## 3. Comentarios finales

### 3.1. Restricciones adicionales

Los inversionistas pueden tener restricciones adicionales:
1. Restricciones en posiciones cortas.
2. Pueden requerir un rendimiento mínimo.
3. Inversión socialmente responsable: prescinden de inversiones en negocios o paises considerados éticamente o políticamente indeseables.

Todo lo anterior se puede incluir como restricciones en el problema de optimización, y puede ser llevado a cabo a costa de un cociente de Sharpe menor.

### 3.2. Críticas a la optimización media varianza
1. Solo importan medias y varianzas: recordar que la varianza subestima el riesgo en algunos casos.
2. Preferencias media-varianza tratan las ganancias y pérdidas simétricamente: el sentimiento de insatisfacción de una perdida es mayor al sentimiento de satisfacción de una ganancia (aversión a pérdidas).
3. La aversión al riesgo es constante: la actitud frente al riesgo puede cambiar, por ejemplo con el estado de la economía.
4. Horizonte corto (un periodo).
5. Basura entra - basura sale: la optimización media varianza es supremamente sensible a las entradas: estimaciones de rendimientos esperados y varianzas.
___

# Anuncios parroquiales

## 1. Revisar archivo Tarea 6. Definir fecha.
## 2. Fin de módulo. Abrir Clase0 y revisar objetivos del módulo.
## 3. [Nota interesante](http://yetanothermathprogrammingconsultant.blogspot.com/2016/08/portfolio-optimization-maximize-sharpe.html)

Created with Jupyter by Esteban Jiménez Rodríguez.


# Deep Learning Bootcamp November 2017, GPU Computing for Data Scientists

## 13  PyTorch Logistic Regression

Web: https://www.meetup.com/Tel-Aviv-Deep-Learning-Bootcamp/events/241762893/

Notebooks:  On GitHub

*Shlomo Kashani*

# PyTorch Imports

```python
# !pip install pycuda
%reset -f
import numpy
import numpy as np
from __future__ import print_function
from __future__ import division
import math
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import pandas as pd
import os
import torch
from torch.utils.data.dataset import Dataset
from torch.utils.data import DataLoader
from torchvision import transforms
from torch import nn
import torch.nn.functional as F
import torch.optim as optim
from torch.autograd import Variable
from sklearn.preprocessing import MultiLabelBinarizer
import time
%matplotlib inline
from pylab import rcParams
rcParams['figure.figsize'] = (6, 6)      # setting default size of plots
import tensorflow as tf
print("tensorflow:" + tf.__version__)
!set "KERAS_BACKEND=tensorflow"
import torch
import sys
print('__Python VERSION:', sys.version)
print('__pyTorch VERSION:', torch.__version__)
print('__CUDA VERSION')
from subprocess import call
print('__CUDNN VERSION:', torch.backends.cudnn.version())
print('__Number CUDA Devices:', torch.cuda.device_count())
print('__Devices')

# !pip install http://download.pytorch.org/whl/cu75/torch-0.2.0.post1-cp27-cp27mu-manylinux1_x86_64.whl
# !pip install torchvision
# ! pip install cv2
# import cv2
```

    tensorflow:1.2.1
    __Python VERSION: 2.7.12 (default, Nov 19 2016, 06:48:10)
    [GCC 5.4.0 20160609]
    __pyTorch VERSION: 0.1.12+4eb448a
    __CUDA VERSION
    __CUDNN VERSION: 5110
    __Number CUDA Devices: 1
    __Devices

#  Load a CSV file for Binary classification

```python
% reset -f

# ! pip install tables

import torch
from torch.autograd import Variable
import numpy as np
import pandas
import numpy as np
import pandas as pd
from sklearn import cross_validation
from sklearn import metrics
from sklearn.metrics import roc_auc_score, log_loss, roc_auc_score, roc_curve, auc
import matplotlib.pyplot as plt
from sklearn import cross_validation
from sklearn import metrics
from sklearn.metrics import roc_auc_score, log_loss, roc_auc_score, roc_curve, auc
from sklearn.cross_validation import StratifiedKFold, ShuffleSplit, cross_val_score, train_test_split
import logging
handler=logging.basicConfig(level=logging.INFO)
lgr = logging.getLogger(__name__)

%matplotlib inline

F_NAME_TRAIN= 'data-03-diabetes.csv'
# F_NAME_TRAIN='numerai/numerai_training_data.csv'

# X_df_train= pd.read_csv(F_NAME_TRAIN)
X_df_train= pd.read_csv(F_NAME_TRAIN,header=None, dtype=np.float32)
X_df_train_SINGLE=X_df_train.copy(deep=True)

# X_df_train_SINGLE.drop('id', axis=1, inplace=True)
# X_df_train_SINGLE.drop('era', axis=1, inplace=True)
# X_df_train_SINGLE.drop('data_type', axis=1, inplace=True)
# drop the header
# X_df_train_SINGLE.to_csv('numerai/numerai_training_data_clean.csv', header=False)
# X_df_train_SINGLE= pd.read_csv('numerai/numerai_training_data_clean.csv', header=None, dtype=np.float32)

# X_df_train_SINGLE=X_df_train_SINGLE.dropna()

answers_1_SINGLE = list (X_df_train_SINGLE[X_df_train_SINGLE.columns[-1]].values)
answers_1_SINGLE= map(int, answers_1_SINGLE)
X_df_train_SINGLE = X_df_train_SINGLE.drop(X_df_train_SINGLE.columns[-1], axis=1)
# X_df_train_SINGLE=X_df_train_SINGLE.apply(lambda x: pandas.to_numeric(x, errors='ignore'))

print(X_df_train_SINGLE.shape)
X_df_train_SINGLE.head(5)

# (np.where(np.isnan(X_df_train_SINGLE)))
# (np.where(np.isinf(X_df_train_SINGLE)))

X_df_train_SINGLE.info()
```

    (759, 8)
    <class 'pandas.core.frame.DataFrame'>
    Int64Index: 759 entries, 0 to 758
    Data columns (total 8 columns):
    0    759 non-null float32
    1    759 non-null float32
    2    759 non-null float32
    3    759 non-null float32
    4    759 non-null float32
    5    759 non-null float32
    6    759 non-null float32
    7    759 non-null float32
    dtypes: float32(8)
    memory usage: 29.6 KB

#  Create PyTorch GPU tensors

- Note how we transfrom the np arrays

```python
use_cuda = False
FloatTensor = torch.cuda.FloatTensor if use_cuda else torch.FloatTensor
LongTensor = torch.cuda.LongTensor if use_cuda else torch.LongTensor
Tensor = FloatTensor

# fix seed
seed=17*19
np.random.seed(seed)
torch.manual_seed(seed)
if use_cuda:
    torch.cuda.manual_seed(seed)

# sk learn
trainX, testX, trainY, testY = train_test_split(X_df_train_SINGLE, answers_1_SINGLE, test_size=.33, random_state=999)

# Train data
x_data_np = np.array(trainX.values, dtype=np.float32)
y_data_np = np.array(trainY, dtype=np.float32)
y_data_np=y_data_np.reshape((y_data_np.shape[0],1)) # Must be reshaped for PyTorch!

print(x_data_np.shape, y_data_np.shape)
print(type(x_data_np), type(y_data_np))

if use_cuda:
    lgr.info ("Using the GPU")
    X = Variable(torch.from_numpy(x_data_np).cuda()) # Note the conversion for pytorch
    Y = Variable(torch.from_numpy(y_data_np).cuda())
else:
    lgr.info ("Using the CPU")
    X = Variable(torch.from_numpy(x_data_np)) # Note the conversion for pytorch
    Y = Variable(torch.from_numpy(y_data_np))
print(type(X.data), type(Y.data)) # should be 'torch.cuda.FloatTensor'

print(type(X.data), type(Y.data)) # should be 'torch.cuda.FloatTensor'
```

    INFO:__main__:Using the CPU

    (508, 8) (508, 1)
    <type 'numpy.ndarray'> <type 'numpy.ndarray'>
    <class 'torch.FloatTensor'> <class 'torch.LongTensor'>
    <class 'torch.FloatTensor'> <class 'torch.LongTensor'>

#  Define the NN model

- First a simple two leyer network and then a more involved version

#  Initial weights selection

- There are many ways to select the initial weights to a neural network architecture. A common initialization scheme is random initialization, which sets the biases and weights of all the nodes in each hidden layer randomly, so they are in a random point of the space, and objective function, and then find a nearby local minima using an algorithm like SGD or Adam.
- We use a *xavier initializer*, in effect (according to theory) initializing the weights of the network to values that would be closer to the optimal, and therefore require less epochs to train.

### References:
* **`nninit.xavier_uniform(tensor, gain=1)`** - Fills `tensor` with values according to the method described in ["Understanding the difficulty of training deep feedforward neural networks" - Glorot, X. and Bengio, Y.](http://jmlr.org/proceedings/papers/v9/glorot10a/glorot10a.pdf), using a uniform distribution.
* **`nninit.xavier_normal(tensor, gain=1)`** - Fills `tensor` with values according to the method described in ["Understanding the difficulty of training deep feedforward neural networks" - Glorot, X. and Bengio, Y.](http://jmlr.org/proceedings/papers/v9/glorot10a/glorot10a.pdf), using a normal distribution.
* **`nninit.kaiming_uniform(tensor, gain=1)`** - Fills `tensor` with values according to the method described in ["Delving deep into rectifiers: Surpassing human-level performance on ImageNet classification" - He, K. et al.](https://arxiv.org/abs/1502.01852) using a uniform distribution.
* **`nninit.kaiming_normal(tensor, gain=1)`** - Fills `tensor` with values according to the method described in ["Delving deep into rectifiers: Surpassing human-level performance on ImageNet classification" - He, K. et al.]

```python
keep_prob=0.85
# p is the probability of being dropped in PyTorch
dropout = torch.nn.Dropout(p=1 - keep_prob)

# hiddenLayer1Size=32
# hiddenLayer2Size=16
# # # Hypothesis using sigmoid
# linear1=torch.nn.Linear(x_data_np.shape[1], hiddenLayer1Size, bias=True) # size mismatch, m1: [5373 x 344], m2: [8 x 1] at /pytorch/torch/lib/TH/generic/THTensorMath.c:1293
# # xavier initializer
# torch.nn.init.xavier_uniform(linear1.weight)
# linear2=torch.nn.Linear(hiddenLayer1Size, hiddenLayer2Size)
# # xavier initializer
# torch.nn.init.xavier_uniform(linear2.weight)
# linear3=torch.nn.Linear(hiddenLayer2Size, 1)
# # xavier initializer
# torch.nn.init.xavier_uniform(linear3.weight)
# sigmoid = torch.nn.Sigmoid()
# tanh=torch.nn.Tanh()
# model = torch.nn.Sequential(linear1,dropout, tanh, linear2,dropout, tanh, linear3,dropout, sigmoid)

#Hypothesis using sigmoid
linear1=torch.nn.Linear(x_data_np.shape[1], 1, bias=True)
# xavier initializer
torch.nn.init.xavier_uniform(linear1.weight)
sigmoid = torch.nn.Sigmoid()
# model = torch.nn.Sequential(linear1,dropout, sigmoid)
model = torch.nn.Sequential(linear1, sigmoid)

if use_cuda:
    lgr.info ("Using the GPU")
    model = model.cuda() # On GPU
else:
    lgr.info ("Using the CPU")

lgr.info('Model {}'.format(model))
```

    INFO:__main__:Using the CPU
    INFO:__main__:Model Sequential (
      (0): Linear (8 -> 1)
      (1): Sigmoid ()
    )

```python
# see https://github.com/facebookresearch/SentEval/blob/master/senteval/tools/classifier.py
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
# optimizer = torch.optim.SGD(model.parameters(), lr=1e-1,momentum=0.9, weight_decay=1e-4)
# optimizer = torch.optim.Adam(model.parameters())
lgr.info('Optimizer {}'.format(optimizer))
```

    INFO:__main__:Optimizer <torch.optim.sgd.SGD object at 0x7fc855dbc350>

# The cross-entropy loss function

A binary cross-entropy `Criterion` (which expects 0 or 1 valued targets) :

```lua
criterion = nn.BCECriterion()
```

The BCE loss is defined as :

```python
import sympy as sp
sp.interactive.printing.init_printing(use_latex=True)
from IPython.display import display, Math, Latex
maths = lambda s: display(Math(s))
latex = lambda s: display(Latex(s))

#the loss function is as follows:
maths("\mathbf{Loss Function:} J(x, z) = -\sum_k^d[x_k \log z_k + (1-x_k)log(1-z_k)]")
```

$$\mathbf{Loss Function:} J(x, z) = -\sum_k^d[x_k \log z_k + (1-x_k)log(1-z_k)]$$

# Start training in Batches

```python
import time
start_time = time.time()
epochs=20000
all_losses = []

for step in range(epochs):
    optimizer.zero_grad()
    hypothesis = model(X)
    # cost/loss function
    cost = -(Y * torch.log(hypothesis) + (1 - Y)
             * torch.log(1 - hypothesis)).mean()
    cost.backward()
    optimizer.step()

    # Keep loss
    if step % 150 == 0:
        loss = cost.data[0]
        all_losses.append(loss)

    if step % 4000 == 0:
        print(step, cost.data.cpu().numpy())
        # RuntimeError: can't convert CUDA tensor to numpy (it doesn't support GPU arrays).
        # Use .cpu() to move the tensor to host memory first.
        predicted = (model(X).data > 0.5).float()
#         predicted = (model(X).data ).float() # This is like predict proba
        predictions=predicted.cpu().numpy()
        accuracy = (predicted == Y.data).float().mean()
        print('TRAINNING Accuracy:' + str(accuracy))
#         print ('TRAINING LOG_LOSS=' + str(log_loss(trainY, predictions)))
#         R_SCORE=roc_auc_score(Y.data.cpu().numpy(),predictions )
#         print ('TRAINING ROC AUC:' + str(R_SCORE))

end_time = time.time()
print ('{} {:6.3f} seconds'.format('GPU:', end_time-start_time))

%matplotlib inline
import matplotlib.pyplot as plt
plt.plot(all_losses)
plt.show()
```

# Cross validation, metrics, ROC_AUC etc

```python
model.eval()

# Validation data
x_data_np_val = np.array(testX.values, dtype=np.float32)
y_data_np_val = np.array(testY, dtype=np.float32)
y_data_np_val=y_data_np_val.reshape((y_data_np_val.shape[0],1)) # Must be reshaped for PyTorch!

print(x_data_np_val.shape, y_data_np_val.shape)
print(type(x_data_np_val), type(y_data_np_val))

if use_cuda:
    lgr.info ("Using the GPU")
    X_val = Variable(torch.from_numpy(x_data_np_val).cuda()) # Note the conversion for pytorch
    Y_val = Variable(torch.from_numpy(y_data_np_val).cuda())
else:
    lgr.info ("Using the CPU")
    X_val = Variable(torch.from_numpy(x_data_np_val)) # Note the conversion for pytorch
    Y_val = Variable(torch.from_numpy(y_data_np_val))

# VALIDATION
predicted_val = (model(X_val).data).float()
predictions_val=predicted_val.cpu().numpy()
accuracy_val = (predicted_val == Y_val.data).float().mean()
R_SCORE_VAL=roc_auc_score(Y_val.data.cpu().numpy(),predictions_val)
print ('VALIDATION ROC AUC:' + str(R_SCORE_VAL))

false_positive_rate, true_positive_rate, thresholds = roc_curve(testY, predictions_val)
roc_auc = auc(false_positive_rate, true_positive_rate)
plt.title('LOG_LOSS=' + str(log_loss(testY, predictions_val)))
plt.plot(false_positive_rate, true_positive_rate, 'b', label='AUC = %0.6f' % roc_auc)
plt.legend(loc='lower right')
plt.plot([0, 1], [0, 1], 'r--')
plt.xlim([-0.1, 1.2])
plt.ylim([-0.1, 1.2])
plt.ylabel('True Positive Rate')
plt.xlabel('False Positive Rate')
plt.show()
```

```python
# Lab 5 Logistic Regression Classifier
import torch
from torch.autograd import Variable
import numpy as np

torch.manual_seed(777)  # for reproducibility

xy = np.loadtxt('data-03-diabetes.csv', delimiter=',', dtype=np.float32)
x_data = xy[:, 0:-1]
y_data = xy[:, [-1]]

# Make sure the shape and data are OK
print(x_data.shape, y_data.shape)

X = Variable(torch.from_numpy(x_data))
Y = Variable(torch.from_numpy(y_data))

# Hypothesis using sigmoid
linear = torch.nn.Linear(8, 1, bias=True)
sigmoid = torch.nn.Sigmoid()
model = torch.nn.Sequential(linear, sigmoid)

optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

for step in range(10001):
    optimizer.zero_grad()
    hypothesis = model(X)
    # cost/loss function
    cost = -(Y * torch.log(hypothesis) + (1 - Y)
             * torch.log(1 - hypothesis)).mean()
    cost.backward()
    optimizer.step()

    if step % 200 == 0:
        print(step, cost.data.numpy())

# Accuracy computation
predicted = (model(X).data > 0.5).float()
accuracy = (predicted == Y.data).float().mean()
print("\nHypothesis: ", hypothesis.data.numpy(), "\nCorrect (Y): ", predicted.numpy(), "\nAccuracy: ", accuracy)
```

```python

```

day02-PyTORCH-and-PyCUDA/PyTorch/13  PyTorch Logistic Regression .ipynb