# Pré-processamento

O objetivo de pré-processar os dados é **aprimorar** o desempenho dos modelos de ML. Assim, um modelo treinado com dados pré-processados deve ter desempenho no mínimo equivalente a um modelo treinado com dados crus.

## Por que pré-processar os dados?

Dados no mundo real vêm

* incompletos

  ```person.occupation=''```

* ruidosos

  ```person.salary=-10; person.age=999```

  esses dados também podem ser chamados de *outliers*: são pontos que estão muito distantes da distribuição geral dos dados e em geral indicam algum erro na coleta ou no registro do dado

* inconsistentes

   ```personA.grade=B; personB.grade=8```

   pode acontecer ao integrar bases de dados diferentes

E os modelos de ML são sensíveis a tudo isso.

Além disso, lembrando que o problema de aprendizado é um problema de otimização, precisamos aplicar transormações nos dados de forma a facilitar a convergência dos otimizadores.

## As duas principais etapas de pré-processamento dos dados são

* limpeza: preenchimento de valores faltantes, remoção de *outliers*, resolução de inconsistências
* transformação: adequação dos dados para serem usados pelos modelos de ML; inclui normalização, agregação, redução e extração de *features*

Para a primeira etapa, limpeza, é necessário algum conhecimento sobre a natureza dos dados (por exemplo, saber que idade e data de nascimento são *features* equivalentes). Dependendo da natureza dos dados, é recomendável conversar com especialistas. Essa é uma etapa que parece simples mas costuma ser trabalhosa. Não deve ser menosprezada :)

Aqui, vamos usar um dataset já limpo e focar na tarefa de **transformar os dados** de forma que possam ser adequadamente interpretados pelos modelos.

## Nosso dataset

Vamos usar o dataset *Boston Housing Prices*, organizado pelo grupo de *Machine Learning* da *University of California Irvine* e disponível para baixar diretamente através do pacote `sklearn`.

Importando os pacotes que serão usados:

```python
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.datasets import load_boston
from sklearn.feature_selection import SelectKBest, f_regression
from sklearn.preprocessing import OneHotEncoder, StandardScaler, MinMaxScaler
```

Usamos a função `load_boston` do `sklearn` para carregar os dados e ver a descrição das variáveis, e depois o pacote `pandas` para facilitar a manipulação dos dados tabulares.

```python
data = load_boston()

print(data.keys())
print(data.DESCR)
```

    dict_keys(['data', 'target', 'feature_names', 'DESCR', 'filename'])
    .. _boston_dataset:

    Boston house prices dataset
    ---------------------------

    **Data Set Characteristics:**

        :Number of Instances: 506

        :Number of Attributes: 13 numeric/categorical predictive. Median Value (attribute 14) is usually the target.

        :Attribute Information (in order):
            - CRIM     per capita crime rate by town
            - ZN       proportion of residential land zoned for lots over 25,000 sq.ft.
            - INDUS    proportion of non-retail business acres per town
            - CHAS     Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)
            - NOX      nitric oxides concentration (parts per 10 million)
            - RM       average number of rooms per dwelling
            - AGE      proportion of owner-occupied units built prior to 1940
            - DIS      weighted distances to five Boston employment centres
            - RAD      index of accessibility to radial highways
            - TAX      full-value property-tax rate per $10,000
            - PTRATIO  pupil-teacher ratio by town
            - B        1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town
            - LSTAT    % lower status of the population
            - MEDV     Median value of owner-occupied homes in $1000's

        :Missing Attribute Values: None

        :Creator: Harrison, D. and Rubinfeld, D.L.

    This is a copy of UCI ML housing dataset.
    https://archive.ics.uci.edu/ml/machine-learning-databases/housing/

    This dataset was taken from the StatLib library which is maintained at Carnegie Mellon University.

    The Boston house-price data of Harrison, D. and Rubinfeld, D.L. 'Hedonic
    prices and the demand for clean air', J. Environ. Economics & Management,
    vol.5, 81-102, 1978.   Used in Belsley, Kuh & Welsch, 'Regression diagnostics
    ...', Wiley, 1980.   N.B. Various transformations are used in the table on
    pages 244-261 of the latter.

    The Boston house-price data has been used in many machine learning papers that address regression
    problems.

    .. topic:: References

       - Belsley, Kuh & Welsch, 'Regression diagnostics: Identifying Influential Data and Sources of Collinearity', Wiley, 1980. 244-261.
       - Quinlan,R. (1993). Combining Instance-Based and Model-Based Learning. In Proceedings on the Tenth International Conference of Machine Learning, 236-243, University of Massachusetts, Amherst. Morgan Kaufmann.

```python
df = pd.DataFrame(data.data, columns=data.feature_names)
target = data.target

df.head()
```

      CRIM
      ZN
      INDUS
      CHAS
      NOX
      RM
      AGE
      DIS
      RAD
      TAX
      PTRATIO
      B
      LSTAT

      0
      0.00632
      18.0
      2.31
      0.0
      0.538
      6.575
      65.2
      4.0900
      1.0
      296.0
      15.3
      396.90
      4.98

      1
      0.02731
      0.0
      7.07
      0.0
      0.469
      6.421
      78.9
      4.9671
      2.0
      242.0
      17.8
      396.90
      9.14

      2
      0.02729
      0.0
      7.07
      0.0
      0.469
      7.185
      61.1
      4.9671
      2.0
      242.0
      17.8
      392.83
      4.03

      3
      0.03237
      0.0
      2.18
      0.0
      0.458
      6.998
      45.8
      6.0622
      3.0
      222.0
      18.7
      394.63
      2.94

      4
      0.06905
      0.0
      2.18
      0.0
      0.458
      7.147
      54.2
      6.0622
      3.0
      222.0
      18.7
      396.90
      5.33

## Uma pipeline de pré-processamento de dados básica deve incluir
* tratamento de variáveis categóricas
* normalização/scaling
* seleção de *features*

### Tratamento de variáveis categóricas
Variáveis categóricas são variáveis discretizadas, que representam atributos qualitativos (por exemplo, raça) ou intervalos de atributos quantitativos (por exemplo, faixa etária).

Ao incluir essas variáveis num modelo de ML, é importante evitar introduzir relações de ordem inexistentes. Por exemplo, se há uma variável faixa etária que recebe 0 para [0-10], 1 para [11-20], 2 para [21-30], e assim por diante, é razoável que existam relações do tipo $0<1<2$ embutidas nos dados. Por outro lado, se há uma variável raça que recebe 0 para branco, 1 para amarelo, 2 para preto e 3 para outro, é recomendável criar $n$ novas variáveis binárias, uma para cada valor possível, a fim de evitar a introdução de relações do tipo branco > amarelo > preto. Este procedimento é chamado de **one-hot encoding**.

No nosso dataset, as seguintes variáveis são consideradas categóricas:
* CHAS: variável binária; não precisa de tratamento
* AGE: variável discretizada com 9 valores possíveis; podemos fazer one-hot encoding

```python
df.CHAS.unique()
```

    array([0., 1.])

```python
df.RAD.unique()
```

    array([ 1.,  2.,  3.,  5.,  4.,  8.,  6.,  7., 24.])

O one-hot encoding pode ser feito pelo `pandas`:

```python
# one-hot encoding com pandas
rad_onehot = pd.get_dummies(df.RAD, prefix='RAD')
df = pd.concat([df, rad_onehot], axis=1)

df.head()
```

      CRIM
      ZN
      INDUS
      CHAS
      NOX
      RM
      AGE
      DIS
      RAD
      TAX
      ...
      LSTAT
      RAD_1.0
      RAD_2.0
      RAD_3.0
      RAD_4.0
      RAD_5.0
      RAD_6.0
      RAD_7.0
      RAD_8.0
      RAD_24.0

      0
      0.00632
      18.0
      2.31
      0.0
      0.538
      6.575
      65.2
      4.0900
      1.0
      296.0
      ...
      4.98
      1
      0
      0
      0
      0
      0
      0
      0
      0

      1
      0.02731
      0.0
      7.07
      0.0
      0.469
      6.421
      78.9
      4.9671
      2.0
      242.0
      ...
      9.14
      0
      1
      0
      0
      0
      0
      0
      0
      0

      2
      0.02729
      0.0
      7.07
      0.0
      0.469
      7.185
      61.1
      4.9671
      2.0
      242.0
      ...
      4.03
      0
      1
      0
      0
      0
      0
      0
      0
      0

      3
      0.03237
      0.0
      2.18
      0.0
      0.458
      6.998
      45.8
      6.0622
      3.0
      222.0
      ...
      2.94
      0
      0
      1
      0
      0
      0
      0
      0
      0

      4
      0.06905
      0.0
      2.18
      0.0
      0.458
      7.147
      54.2
      6.0622
      3.0
      222.0
      ...
      5.33
      0
      0
      1
      0
      0
      0
      0
      0
      0

5 rows × 22 columns

### Normalização/scaling

É necessário para padronizar o valor das *features* quando elas estão em intervalos muito variados (por exemplo, idade e salário têm ordens de grandeza diferentes). Mantendo todas as *features* em intervalos de valores similares, a convergência dos modelos é acelerada e, em alguns casos, o desempenho final é melhorado.

As duas principais formas de se normalizar os dados são:

Min max scaling

$
\begin{align}
x_{scaled} = \frac{x - x_{min}}{x_{max} - x_{min}}
\end{align}
$

Standard scaling

$
\begin{align}
x_{scaled} = \frac{x - \mu}{\sigma}
\end{align}
$

Note que a normalização é feita para cada uma das *features*, ou seja, para cada coluna do vetor de *features* $X$.

O `sklearn` fornece funções para ambos os casos:

```python
scaler = MinMaxScaler()
dff = df.copy()
dff[dff.columns] = scaler.fit_transform(df.values)
dff.describe()
```

      CRIM
      ZN
      INDUS
      CHAS
      NOX
      RM
      AGE
      DIS
      RAD
      TAX
      ...
      LSTAT
      RAD_1.0
      RAD_2.0
      RAD_3.0
      RAD_4.0
      RAD_5.0
      RAD_6.0
      RAD_7.0
      RAD_8.0
      RAD_24.0

      count
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      ...
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000
      506.000000

      mean
      0.040544
      0.113636
      0.391378
      0.069170
      0.349167
      0.521869
      0.676364
      0.242381
      0.371713
      0.422208
      ...
      0.301409
      0.039526
      0.047431
      0.075099
      0.217391
      0.227273
      0.051383
      0.033597
      0.047431
      0.260870

      std
      0.096679
      0.233225
      0.251479
      0.253994
      0.238431
      0.134627
      0.289896
      0.191482
      0.378576
      0.321636
      ...
      0.197049
      0.195035
      0.212769
      0.263812
      0.412879
      0.419485
      0.220997
      0.180367
      0.212769
      0.439543

      min
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      ...
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000

      25%
      0.000851
      0.000000
      0.173387
      0.000000
      0.131687
      0.445392
      0.433831
      0.088259
      0.130435
      0.175573
      ...
      0.144040
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000

      50%
      0.002812
      0.000000
      0.338343
      0.000000
      0.314815
      0.507281
      0.768280
      0.188949
      0.173913
      0.272901
      ...
      0.265728
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000

      75%
      0.041258
      0.125000
      0.646628
      0.000000
      0.491770
      0.586798
      0.938980
      0.369088
      1.000000
      0.914122
      ...
      0.420116
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      0.000000
      1.000000

      max
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000
      ...
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000
      1.000000

8 rows × 22 columns

```python
scaler = StandardScaler()
dff = df.copy()
dff[dff.columns] = scaler.fit_transform(df.values)
dff.describe()
```

      CRIM
      ZN
      INDUS
      CHAS
      NOX
      RM
      AGE
      DIS
      RAD
      TAX
      ...
      LSTAT
      RAD_1.0
      RAD_2.0
      RAD_3.0
      RAD_4.0
      RAD_5.0
      RAD_6.0
      RAD_7.0
      RAD_8.0
      RAD_24.0

      count
      5.060000e+02
      5.060000e+02
      5.060000e+02
      5.060000e+02
      5.060000e+02
      5.060000e+02
      5.060000e+02
      5.060000e+02
      5.060000e+02
      5.060000e+02
      ...
      5.060000e+02
      5.060000e+02
      5.060000e+02
      5.060000e+02
      5.060000e+02
      5.060000e+02
      5.060000e+02
      5.060000e+02
      5.060000e+02
      5.060000e+02

      mean
      -8.513173e-17
      3.306534e-16
      2.804081e-16
      -3.100287e-16
      -8.071058e-16
      -5.189086e-17
      -2.650493e-16
      8.293761e-17
      1.514379e-15
      -9.934960e-16
      ...
      -1.595123e-16
      6.354162e-16
      -1.068974e-15
      5.487486e-16
      -1.534126e-15
      3.304340e-16
      3.081637e-16
      5.910950e-16
      -5.813312e-16
      3.427649e-15

      std
      1.000990e+00
      1.000990e+00
      1.000990e+00
      1.000990e+00
      1.000990e+00
      1.000990e+00
      1.000990e+00
      1.000990e+00
      1.000990e+00
      1.000990e+00
      ...
      1.000990e+00
      1.000990e+00
      1.000990e+00
      1.000990e+00
      1.000990e+00
      1.000990e+00
      1.000990e+00
      1.000990e+00
      1.000990e+00
      1.000990e+00

      min
      -4.197819e-01
      -4.877224e-01
      -1.557842e+00
      -2.725986e-01
      -1.465882e+00
      -3.880249e+00
      -2.335437e+00
      -1.267069e+00
      -9.828429e-01
      -1.313990e+00
      ...
      -1.531127e+00
      -2.028602e-01
      -2.231424e-01
      -2.849501e-01
      -5.270463e-01
      -5.423261e-01
      -2.327373e-01
      -1.864533e-01
      -2.231424e-01
      -5.940885e-01

      25%
      -4.109696e-01
      -4.877224e-01
      -8.676906e-01
      -2.725986e-01
      -9.130288e-01
      -5.686303e-01
      -8.374480e-01
      -8.056878e-01
      -6.379618e-01
      -7.675760e-01
      ...
      -7.994200e-01
      -2.028602e-01
      -2.231424e-01
      -2.849501e-01
      -5.270463e-01
      -5.423261e-01
      -2.327373e-01
      -1.864533e-01
      -2.231424e-01
      -5.940885e-01

      50%
      -3.906665e-01
      -4.877224e-01
      -2.110985e-01
      -2.725986e-01
      -1.442174e-01
      -1.084655e-01
      3.173816e-01
      -2.793234e-01
      -5.230014e-01
      -4.646726e-01
      ...
      -1.812536e-01
      -2.028602e-01
      -2.231424e-01
      -2.849501e-01
      -5.270463e-01
      -5.423261e-01
      -2.327373e-01
      -1.864533e-01
      -2.231424e-01
      -5.940885e-01

      75%
      7.396560e-03
      4.877224e-02
      1.015999e+00
      -2.725986e-01
      5.986790e-01
      4.827678e-01
      9.067981e-01
      6.623709e-01
      1.661245e+00
      1.530926e+00
      ...
      6.030188e-01
      -2.028602e-01
      -2.231424e-01
      -2.849501e-01
      -5.270463e-01
      -5.423261e-01
      -2.327373e-01
      -1.864533e-01
      -2.231424e-01
      1.683251e+00

      max
      9.933931e+00
      3.804234e+00
      2.422565e+00
      3.668398e+00
      2.732346e+00
      3.555044e+00
      1.117494e+00
      3.960518e+00
      1.661245e+00
      1.798194e+00
      ...
      3.548771e+00
      4.929503e+00
      4.481443e+00
      3.509386e+00
      1.897367e+00
      1.843909e+00
      4.296689e+00
      5.363274e+00
      4.481443e+00
      1.683251e+00

8 rows × 22 columns

### Seleção de features

Dado um dataset de *shape* $M x N$ (M amostras, N features), uma *rule of thumb* para um tamanho de dataset adequado é $M \geq 10N$. Nosso dataset tem shape 506x13, de um tamanho adequado, então não é preciso selecionar *features*.

Mas e se tivéssemos poucas amostras ou muitas *features*? Nesses casos, é necessário **extrair** ou **selecionar** *features* que contenham as informações mais relevantes.

Uma forma de fazer isso é avaliar as correlações entre *features* e *target* e descartar *features* sejam menos correlacionadas com o *target*. O método `SelectKBest` do `sklearn` faz isso.

```python
fs = SelectKBest(score_func=f_regression, k=10)
X_selected = fs.fit_transform(dff, target)

cols = fs.get_support()
names = dff.columns.values[cols]
scores = fs.scores_[cols]
names_scores = list(zip(names, scores))

names_scores
```

    [('CRIM', 89.48611475768118),
     ('INDUS', 153.95488313610983),
     ('NOX', 112.59148027969944),
     ('RM', 471.84673987638683),
     ('AGE', 83.47745921923611),
     ('RAD', 85.91427766984044),
     ('TAX', 141.76135657742293),
     ('PTRATIO', 175.10554287569468),
     ('LSTAT', 601.6178711098955),
     ('RAD_24.0', 93.90104712598881)]

## E se os dados não forem tabulares?

A extração de *features* é especialmente importante para dados com dimensão muito alta. Por exemplo, se tivermos imagens 32x32 e quisermos usar o valor de cada pixel como *feature*, teríamos 32x32 = 1024 *features*. Por isso, em campos como visão computacional e processamento de texto (NLP), são usados extratores de *features* especializados.

Alguns dos extratores de *features* tradicionais para imagens são:
* SURF
* SIFT

Alguns dos extratores de *features* tradicionais para texto são:
* bag of words
* TF-IDF

*Deep learning* é uma forma de se extrair *features* automaticamente e aprender uma tarefa ao mesmo tempo. Veremos um pouco sobre isso na última aula.


```python
%%capture
%run utils.ipynb
%run quadrotor_model.ipynb
```

# Sensors

This notebook implements the sensors that are used on-board a quadcopter: accelerometer, gyro, GPS, camera, etc. Each sensor implemented herein inherits from the following abstract `Sensor` class and must override the `read` method.

```python
class Sensor(object):
    """Sensor

    An abstract base class for all sensors.
    """
    def __init__(self):
        self.name = "Abstract Sensor"

    def __str__(self):
        return self.name

    def read(self, quad, n, Ts):
        return 0
```

## Sensor Manager

In order to allow flexibility in sensor configurations, a `SensorManager` class is created. A custom set of sensors is registered with the manager for each simulation. During a simulation, the [quadsim](quadsim.ipynb) `Simulator` class asks the sensor manager to produce a data packet that represents the current sensor readings from the suite of on-board sensors.

```python
class SensorManager(object):
    """Sensor Manager
    """
    def __init__(self):
        # create a list for sensor objects
        self.sensors = []

    def register(self, sensor):
        self.sensors += [sensor]

    def get_data_packet(self, quad, i, Ts):
        # dictionary of sensor data, keyed by sensor name
        pkt = {}

        for s in self.sensors:
            pkt[s.name] = s.read(quad, i, Ts)

        return pkt
```

## Camera

Electro-optical (EO) cameras are extremely useful in autonomy and robotics. Their rich source of visual information enables a wide variety of applications. Robotic vision is a very active research area, with common themes such as: vision-based simultaneous localization and mapping (SLAM), visual-inertial odometry, object recognition with convolutional neural networks, image-based visual servoing, and target tracking.

In this simulation, the purpose of the camera is to measure normalized bearing vectors to interesting points such as pixel features, targets, or landmarks. As such, we will focus solely on aspects of camera geometry (as opposed to semantic understanding, pixel intensities, etc). Feature locations in the inertial frame are given to the camera which then projects them onto the pixel plane.

### Coordinate Frames
There are a number of important coordinate frames associated with a camera sensor.

### Camera Geometry

The pinhole camera model is the most commonly used camera model. This allows us to simplify the optical characteristics and focus on the geometry of how 3D objects are imaged. Suppose that the point $P$ exists in 3D space and can be expressed in the inertial frame as $P^i = \begin{bmatrix} x^i & y^i & z^i\end{bmatrix}^\top$. Using the pinhole camera model, the perspective projection model is used to image this point as
$$
\begin{equation}
\lambda \begin{bmatrix} u \\ v \\ 1 \end{bmatrix} =
\begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix}
\left[ R_i^c \mid t_i^c \right]
\begin{bmatrix} x^i \\ y^i \\ z^i \\ 1 \end{bmatrix},
\end{equation}
$$
where $\lambda = z^i$ is the unknown scale factor associated with depth, $u$ and $v$ are respectively the $x$ and $y$ pixels, and $K$ is the *camera calibration matrix*, *intrinsic camera matrix*, or simply *camera matrix* and can be found via camera calibration.

This gives a result in pixels in the image plane attached to the camera frame, see the figure below.

Written out in component form, the perspective projection equations are
$$
\begin{align}
u &= fx' + c_x = f\frac{x}{z} + c_x \\
v &= fy' + c_y = f\frac{y}{z} + c_y
\end{align}
$$

<div style="text-align:center">

Figure 1: Camera geometry
</div>

```python
class Camera(Sensor):
    """Camera
    """
    def __init__(self, fps=30, size=(800,600), hfovd=33.99, pxnoise=0, tick=None):
        self.name = "Camera"

        # Register a callback that gets called when a new
        # frame is to be registered. The user can use this
        # callback to propagate features in the camera's
        # FOV. The function can return inertial positions
        # to the camera to be imaged and projected onto
        # the pixel plane. The function signature is:
        #
        #     def tick_fn(capture, n, Ts)
        #
        self.fns = [tick] if tick else []

        # Calculate camera parameters
        self.fps = fps
        self.size = size
        self.hfov = np.radians(hfovd) # angular FOV for width (degrees)
        self.f_px = f = (size[0]/2)/np.tan(self.hfov/2)
        self.pxnoise_std = pxnoise
        cx = size[0]/2
        cy = size[1]/2
        self.K = np.array([[f, 0, cx],
                           [0, f, cy],
                           [0, 0, 1]])

        # Camera transformation from quadrotor body frame
        self.t = np.zeros((3,1))
        self.R = np.eye(3)

        # Last camera measurement
        self.meas = None

        # how many sensor reads have there been
        self.ticks = 0

    def _camera_projection(self, quad, inertial):
        """Camera Projection

        Uses the camera matrix to project features onto the pixel plane.
        """

        # given inertial measurements of features, calculate
        # the feature positions in the vehicle frame
        p_veh = inertial - quad.r

        # transform to the camera frame
        # TODO: Add in translation from body to camera
        Rv2b = Rot_v_to_b(*quad.Phi.flatten())
        Rb2c = self.R
        p_cam = Rb2c.dot(Rv2b.dot(p_veh))

        # perspective transformation using camera matrix
        # force homogeneous coordinates (normalize out depth)
        hpx = self.K.dot(p_cam)
        hpx = hpx / hpx[2,:]

        # extract pixels from homogeneous coordiantes
        # and add pixel noise (AWGN)
        px = hpx[:2,:]
        px += self.pxnoise_std*np.random.randn(*px.shape)

        # TODO: remove measurements outside of FOV

        return px

    def set_transformation(self, t=None, R=None):
        """Set Transformation

        Sets the transformation from the quadrotor body frame
        to the camera frame. The transformation is represented
        as a translation t and rotation R.
        """
        self.t = t if t is not None else np.zeros((3,1))
        self.R = R if R is not None else np.eye(3)

    def register_feature(self, feature):
        self.fns += [feature.tick]

    def read(self, quad, n, Ts):

        # empty list of measurements to be filled by tick functions
        inertial_measurements = None

        # Call any associated tick functions
        capture = True
        for fn in self.fns:
            data = fn(capture, n, Ts)
            if data is not None:
                if inertial_measurements is None:
                    inertial_measurements = data
                else:
                    inertial_measurements = np.hstack((inertial_measurements, data))

        # if no measurements were received, bail now
        if inertial_measurements is None:
            return None

        # Enforce fps (to closest Ts) before doing camera stuff
        if np.mod(self.ticks,round(1/(self.fps*Ts))) == 0:
            # project inertial measurements onto pixel plane
            self.meas = self._camera_projection(quad, inertial_measurements)

        self.ticks += 1

        return self.meas
```

```python
def target_tick(capture, n, Ts):

    # ellipse
    f = 0.01 # Hz
    x = 10*np.sin(2*np.pi*f*n*Ts)
    y = 5*np.cos(2*np.pi*f*n*Ts)

    return np.array([[x,y,0]]).T

camera = Camera(tick=target_tick)
camera.set_transformation(R=rot3d(45,0,90))

# Instantiate a quadrotor model with the given initial conditions
quad = Quadrotor(r=np.array([[-30],[0],[-30]]),
                 v=np.array([[0],[0],[0]]),
               Phi=np.array([[0],[0],[0]]))

# How many iterations are needed
Ts = 0.01
Tf = 0.1
N = int(Tf/Ts)

for i in range(N):
    x = camera.read(quad, i, Ts)
    print(x.T)
```

    [[ 554.238  300.000]]
    [[ 554.238  300.000]]
    [[ 554.238  300.000]]
    [[ 554.189  299.589]]
    [[ 554.189  299.589]]
    [[ 554.189  299.589]]
    [[ 554.140  299.178]]
    [[ 554.140  299.178]]
    [[ 554.140  299.178]]
    [[ 554.090  298.768]]

## Accelerometer

## Rate Gyro

## GPS


# FFM232, Klassisk fysik och vektorfält - Föreläsningsanteckningar

 **[Christian Forssén](http://fy.chalmers.se/subatom/tsp/), Institutionen för fysik, Chalmers, Göteborg, Sverige**

Date: **Sep 22, 2016**

## Repetition: Integralsatser

* Gauss sats

$$
\begin{equation}
  \int_V \nabla \cdot \vec{F} \mbox{d}V = \oint_{\partial V} \vec{F} \cdot \mbox{d} \vec{S}.
\end{equation}
$$

* Stokes sats

$$
\begin{equation}
  \int_S \left( \nabla \times \vec{F}\right) \cdot \mbox{d}\vec{S} = \oint_{\partial S} \vec{F}\cdot
\mbox{d}\vec{r},
\end{equation}
$$

Gäller för kontinuerligt deriverbara vektorfält. Vad händer om fältet har en singularitet?

# 6. Singulära fält

Det finns tre viktiga typer av singulariteter, vilka dyker upp för speciella vektorfält:

* Punktkälla

* Linjeälla

* Virveltråd

## Punktkällor

Ett fält av formen

$$
\begin{equation}
  \vec{F} = \frac{q}{4 \pi r^2}\hat{e}_{r}
\end{equation}
$$

kallas för fältet från en punktkälla med styrkan $q$. Med lämplig tolkning av konstanten $q$ kan detta t.ex. föreställa det elektriska fältet från en laddning i origo, eller gravitationsfältet från en massa i origo.

[Comment 1: Notera att detta fält är singulärt i origo. Skissa gärna fältlinjerna.]

Detta fält är rotationsfritt (åtminstone för $r>0$)

$$
\begin{equation}
\nabla\times\vec F = \frac{1}{r^2\sin\theta}
\begin{vmatrix}
\hat{e}_r&r\hat{e}_\theta&r\sin\theta\hat{e}_\varphi \\

\frac{\partial}{\partial r}&
\frac{\partial}{\partial \theta}&
\frac{\partial}{\partial \varphi} \\
F_r(r)&0&0
\end{vmatrix}=0
\end{equation}
$$

Rotationsfriheten är också en direkt följd av att $\vec F = - \nabla \phi$ med den skalära potentialen

$$
\begin{equation}
\phi=\frac{q}{4\pi r}
\end{equation}
$$

Vektorfältet $\vec F$ är också divergensfritt (för $r>0$)

$$
\begin{equation}
\nabla\cdot\vec F=\frac{1}{r^2}
 \frac{\partial}{\partial r}\left(r^2 \frac{q}{4\pi
r^2}\right)=0, \quad r>0
\end{equation}
$$

[Comment 2: Trots frånvaron av divergens får vi ett resultat skilt från
noll om vi beräknar normalytintegralen av $\vec F$ över en yta som
omsluter origo. Detta görs enklast genom att utföra
integralen över en sfär med radien $a$.]

Normalytintegralen för en yta (sfäriskt skal, radie $a$) som omsluter origo:

$$
\begin{equation}
\label{eq:punktkalla} \tag{1}
\int_S\vec F\cdot d\vec S=
\frac{q}{4\pi a^2} 4\pi a^2=q
\end{equation}
$$

medan Gauss sats skulle ge $\int_V \nabla \cdot \vec{F} \mbox{d}V = 0 $!

[Comment 3: En viktig slutsats är att det vore fel att naivt tillämpa Gauss sats på en volym innehållande origo, trots att fältet ser divergensfritt ut. Det beror på att det är singulärt där. Med vår nuvarande kunskap får vi acceptera att vi måste undvika
punkter där fält är singulära när vi använder integralsatser.]

Mer allmänt:

$$
\begin{equation}
\oint_S \vec F\cdot d\vec S=
\left\{
\begin{array}{ll}
0 & \mathrm{om~ytan~inte~omsluter~origo.} \\
q & \mathrm{om~ytan~omsluter~origo.}
\end{array}
\right.
\end{equation}
$$

Anledningen till att vi inte kan använda Gauss sats är

$$
\begin{equation}
\nabla \cdot \vec{F} =
\left\{
\begin{array}{ll}
0 & r \neq 0 \\
\infty & r=0 \mathrm{~(bättre~definition~kommer~senare)}
\end{array}
\right.
\end{equation}
$$

### Flöde från punktkälla genom allmän yta

En mer förfinad version av ekv. [(1)](#eq:punktkalla) för normalytintegralen av fältet från en punktkälla över en godtycklig yta $S$ kan fås genom att dela upp normalytelementet i $r$-, $\theta$- och $\varphi$-ytor. Den relevanta projektionen är den på $r$-ytan eftersom fältet har den riktningen

$$
\begin{equation}
\mbox{d}\vec{S}_r = \pm \hat{e}_r h_\theta h_\varphi \mbox{d}\theta \mbox{d}\varphi
\end{equation}
$$

där tecknet beror på om normalen för $S$ är utåtriktad (positiv) eller inåtriktad (negativ). Detta ger

$$
\begin{equation}
\frac{q}{4\pi} \int_S \frac{\hat{e}_r\cdot d\vec S}{r^2} = \pm \frac{q}{4\pi} \iint \sin\theta \mbox{d}\theta \mbox{d}\varphi = q \frac{\Omega}{4\pi}
\end{equation}
$$

där $\Omega$ är den rymdvinkel ytan $S$ tar upp sedd från origo. Läs mer om begreppet rymdvinkel: <http://mathworld.wolfram.com/SolidAngle.html>

T.ex. blir rymdvinkeln för en yta som omsluter origo

$$
\begin{equation}
\int_{\theta=0}^\pi \int_{\varphi=0}^{2\pi} \sin\theta \mbox{d}\theta \mbox{d}\varphi = 4\pi.
\end{equation}
$$

## Linjekällor

Ett fält av typen

$$
\begin{equation}
  \vec{F} = \frac{k}{2 \pi \rho} \hat{e}_{\rho}
\end{equation}
$$

svarar fysikaliskt till exempel emot en laddning som är jämnt fördelad längs med en linje (i det här fallet $z$-axeln). Storheten $k$ motsvarar då laddning/längdenhet. Linjekällan är då källan till ett fält som överallt pekar radiellt ut från $z$-axeln

[Rita 4: Fält för linjekälla genom $z$-axeln.].

Fältet är divergensfritt $\nabla \cdot \vec{F} = 0$, förutom för $\varrho=0$.

Det kan erhållas från potentialen

$$
\begin{equation}
	\phi=-\frac{k}{2\pi}\log\frac{\varrho}{\varrho_0}
\end{equation}
$$

Med konstant källtäthet fås normalytintegralen för en cylinder med längden $L$ som omsluter linjekällan längs $z$-axeln

$$
\begin{equation}
	\int_S \vec{F} \cdot \mbox{d}\vec{S} = k L
\end{equation}
$$

[Comment 5: Mer generellt blir normalytintegralen av fältstyrkan över en smal tub nära en godtycklig kurva lika med den inneslutna källan $q(C)=\int_C k(\vec{r}) ds$, där $C$ är den del av kurvan som innesluts av tuben.]

## Ytkälla

På motsvarande sätt kan vi tala om ytkällor i tre dimensioner. Fältet

$$
\begin{equation}
\vec F=\frac{\sigma}{2}\mathrm{sign}(z)\hat{z}
\end{equation}
$$

motsvar en ytkälla i $xy$-planet (vid $z=0$), där $\sigma$, i det elektrostatiska fallet, skulle kallas för en ytladdningstäthet.

Närvaron av en ytkälla på ytan $S$ med styrkan $\sigma$ är liktydigt med att
normalkomponenten av $\vec F$ har en diskontinuitet enligt $\hat n\cdot(\vec F_+-\vec F_-)=\sigma$, där $\vec F_+$ är fältets värde på den sida dit normalen pekar, och $\vec F_-$ dess värde på motsatta sidan.

### Källtäthet

Vi har nu sett hur olika singulariteter kan vara källor till ett fält $\vec{F}$.  Analogt med detta kan vi tolka $\nabla \cdot \vec{F}$ som en utbredd källa till $\vec{F}$.  Därför kallar man ibland $\nabla \cdot \vec{F} = \rho$ för källtäthet.

* Singulära källor: $\nabla \cdot \vec{F} = 0$ utom på vissa ställen

## Virveltråd

Fältlinjerna till det singulära fältet

$$
\begin{equation}
  \vec{F} = \frac{J}{2 \pi \rho} \hat{e}_{\varphi}
\end{equation}
$$

bildar koncentriska cirklar kring $z$-axeln.  Därför säger vi att det finns en virveltråd med styrkan $J$ på $z$-axeln.

Divergensfritt: $\nabla \cdot \vec{F} = 0$.

Rotationen av fältet:

$$
\begin{equation}
\nabla\times\vec F=\frac{1}{\varrho}
	\begin{vmatrix}
        \hat{e}_\varrho&\varrho\hat{e}_\varphi&\hat z \\
        \frac{\partial}{\partial \varrho} &
\frac{\partial}{\partial \varphi}
        &
\frac{\partial}{\partial z} \\
        0 & \varrho \frac{J}{2\pi\varrho} & 0
    \end{vmatrix} = 0, \quad \mathrm{för~} \varrho > 0.
\end{equation}
$$

Stokes sats skulle ge

$$
\begin{equation}
	\oint_{\partial S} \vec{F} \cdot \mbox{d} \vec{r} = \int_S \left( \nabla \times \vec{F} \right) \cdot \mbox{d} \vec{S} = 0,
\end{equation}
$$

men vad skulle ske om ytan $S$ gick genom singulariteten?

[Rita 6: en cirkel med radien $a$ runt $z$-axeln, $\mbox{d}\vec{r} = a \hat{e}_\varphi \mbox{d} \varphi$]

Linjeintegralen blir

$$
\begin{equation}
\int_C\vec F\cdot \mbox{d}\vec r = \int_{\varphi=0}^{2\pi} \frac{J}{2 \pi a} a \mbox{d} \varphi = J.
\end{equation}
$$

Generellt gäller detta resultat för en kurva $C$ som omsluter $z$-axeln ett varv i positiv led, medan integralen blir noll för kurvor som inte omsluter $z$-axeln.

Det vanligaste exemplet är det statiska magnetiska fältet från en ström. Dvs fältet är $\vec{B}$ och $J$ motsvarar den elektriska strömmen.

### Virveltäthet

Vi har nu sett hur en virveltråd kan alstra en virvel i ett vektorfält. Det kan också uppstå virvlar i fält som saknar singulariteter, och för att få ett mått på omfattningen av sådana virvlar inför man virveltätheten $\nabla \times \vec{F}$.

* Singulära virvlar: $\nabla \times \vec{F} = 0$ utom på vissa ställen

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

### Exempel: 6.3

Beräkna integralen $\oint_C \vec F \cdot d\vec{r}$, $C$: $x^2 + \frac{y^2}{4} = a^2$ och $z = 0$ (som genomlöps i positiv riktning) och

$$
\begin{equation}
  \vec F = F_0 \left[\frac{\varrho \sin 2\varphi}{2a} \hat{e}_\varrho
+ \left(\frac{a}{\varrho} - \frac{\varrho \sin^2 \varphi}{a}\right)\hat{e}_\varphi
\right]
\end{equation}
$$

$F_0$ och $a$ är konstanter.

*Lösning*:

[Comment 7: Kurvan $C$ är en ellips med centrum i origo och halvaxlarna
$a$ och $2a$. Enligt högerhandsregeln väljer vi $\hat z$ som
normal till ellipsskivan.]

$C$ kan parametriseras $(x,y) = (a\cos\varphi,2a\sin\varphi)$.

Dela upp $\vec{F} = \vec{F}_1 + \vec{F}_2$ där $\vec F_1=F_0 \frac{a}{\varrho}\hat{e}_\varphi$ innehåller fältets singularitet
(motsvarar en virveltråd på $z$-axeln med styrkan $2\pi F_0a$).

Kurvan $C$ omsluter virveltråden en gång i positiv led så att $\oint_C \vec{F}_1 \cdot d\vec{r} = 1 \cdot 2\pi F_0a$

Bidraget från $\vec F_2$ fås enklast med hjälp av Stokes sats. Rotationen blir

$$
\begin{equation}
  \nabla \times\vec{F}_2 = \frac{F_0}{\rho}
  	\begin{vmatrix}
	\hat{e}_\varrho & \varrho \hat{e}_\varphi & \hat z \\
	\frac{\partial}{\partial \varrho} & \frac{\partial}{\partial \varphi} &
                       \frac{\partial}{\partial z} \\
	\frac{\varrho \sin 2\varphi}{2a} & -\frac{\varrho^2\sin^2 \varphi}{a} & 0
	\end{vmatrix} =- \frac{F_0}{a}\hat z
\end{equation}
$$

Detta ger

$$
\begin{equation}
\oint_C \vec{F}_2 \cdot \mbox{d}\vec{r} = \int_S \left( - \frac{F_0}{a}\hat z \right) \cdot \hat{z} \mbox{d}S = - \frac{F_0}{a} \pi \cdot a \cdot 2a = - 2 \pi F_0 a
\end{equation}
$$

Värdet på den totala integralen blir alltså $\oint_C (\vec{F}_1 + \vec{F}_2) \cdot \mbox{d}\vec{r} = 0$.

## Superposition: beräkning av fält från allmänna källfördelningar

### Punktkälla i origo

Fältet i punkten $\vec{r}$

$$
\begin{equation}
  \vec{F}(\vec{r}) = \frac{q}{4 \pi r^2} \hat{e}_r,
\end{equation}
$$

vilket fås av potentialen

$$
\begin{equation}
  \phi(\vec{r}) = \frac{q}{4 \pi r},
\end{equation}
$$

eftersom

$$
\begin{equation}
  \vec{F}(\vec{r}) = -\nabla \phi(\vec{r}) = - \hat{e}_r \partial_r \frac{q}{4 \pi r} = \frac{q}{4 \pi r^2} \hat{e}_r.
\end{equation}
$$

### Punktkälla i punkten $\vec{r}\,'$

Potentialen i punkten $\vec{r}$ blir uppenbart lika med

$$
\begin{equation}
  \phi(\vec{r}) = \frac{q}{4 \pi \left| \vec{r} - \vec{r}\,' \right|}.
\end{equation}
$$

Detta ger fältet

$$
\begin{equation}
  \vec{F}(\vec{r}) = -\nabla \phi(\vec{r}) = - \left( \hat{x}\partial_x + \hat{y}\partial_y + \hat{z}\partial_z \right) \frac{q}{4 \pi \sqrt{(x-x')^2 + (y-y')^2 + (z-z')^2}} \nonumber
\end{equation}
$$

$$
\begin{equation}
  = - \frac{q}{4 \pi} \left( -\frac{1}{2} \right) \frac{ 2(x-x')\hat{x} + 2(y-y')\hat{y} + 2(z-z')\hat{z}}{ \left[ (x-x')^2 + (y-y')^2 + (z-z')^2 \right]^{3/2}} \nonumber
\end{equation}
$$

$$
\begin{equation}
  = \frac{q (\vec{r} - \vec{r}\,' )}{4 \pi \left| \vec{r} - \vec{r}\,' \right|^3}.
\end{equation}
$$

### Allmän linjekälla

Betrakta en linjekälla längs kurvan $C$ som ges av $\vec{r} = \vec{r}(\tau)$. Linjekällan har linjekälltätheten $k(\tau)$, som alltså inte nödvändigtvis är konstant.

VI kan dela upp linjen i infinitesimala linjeelement $\mbox{d}s$, vardera med laddningen $\mbox{d}q = k\left(\vec{r}(\tau)\right) \mbox{d}s$ (som alltså blir en funktion av $\tau$). Vi kan betrakta dessa linjelement som punktkällor. Bidraget från en sådan källa till potentialen i en (godtycklig) punkt $\vec{r}$ är

$$
\begin{equation}
\mbox{d}\phi(\vec{r}) = \frac{k\left(\vec{r}\,'(\tau)\right) \mbox{d}s'}{4 \pi \left| \vec{r} - \vec{r}\,' \right|}
\end{equation}
$$

Genom att summera alla dessa bidrag, dvs integrera längs linjen $C$, får vi den totala potentialen i punkten $\vec{r}$

$$
\begin{equation}
\phi(\vec{r}) = \int_C \frac{k\left(\vec{r}\,'\right) \mbox{d}s'}{4 \pi \left| \vec{r} - \vec{r}\,' \right|}.
\end{equation}
$$

Notera att $\vec{r}\,' = \vec{r}\,'(\tau)$ och att $\mbox{d}s' = \left| \frac{\mbox{d}\vec{r}\,'}{\mbox{d}\tau} \right| \mbox{d}\tau$

### Allmän ytkälla

Betrakta en ytkälla längs kurvan $S$ som ges av $\vec{r} = \vec{r}(s,t)$. Ytkällan har ytkälltätheten $\sigma\left( \vec{r}(s,t) \right)$.

Genom att summera alla bidrag från infinitesimala ytelement får vi den totala potentialen i punkten $\vec{r}$

$$
\begin{equation}
\phi(\vec{r}) = \int_S \frac{\sigma\left(\vec{r}\,'\right) \mbox{d}S'}{4 \pi \left| \vec{r} - \vec{r}\,' \right|}.
\end{equation}
$$

### Allmän rymdkälla

En allmän rymdkälla har en rymdkälltätheten som ges av $\rho\left( \vec{r} \right) = \nabla \cdot \vec{F} \left( \vec{r} \right)$. Återigen kan vi använda superposition för att få den totala potentialen i punkten $\vec{r}$

$$
\begin{equation}
\phi(\vec{r}) = \int_V \frac{\rho\left(\vec{r}\,'\right) \mbox{d}V'}{4 \pi \left| \vec{r} - \vec{r}\,' \right|}.
\end{equation}
$$

### Exempel: 6.4

Vad är potentialen från en ytkälla med konstant täthet $\sigma_0$, belägen på cirkelskivan med radie $a$ i $(x,y)$-planet med $z$-axeln som symmetriaxel? Speciellt, vad är
potentialen på $z$-axeln?

*Lösning*:
Beteckna ortvektorn för en punkt på källan med $\vec{r}\,'$  och ortvektorn för punkten vi vill veta potentialen i med $\vec{r}$.

Problemets symmetri gör det lämpligt att använda cylindriska koordinater. Vi får lägesvektorn $\vec{r} = \rho \hat{\rho} + z \hat{z}$ för punkten. Punkter på källan kan beskrivas med lägesvektorn $\vec{r}\,' = \rho' \hat{\rho}$ (notera att $z'=0$ eftersom ytkällan ligger i $xy$-planet).

Vi får avståndet mellan de två punkterna som

$$
\begin{equation}
|\vec{r}-\vec{r}\,'|
= \sqrt{d^2 + z^2}
=\sqrt{\varrho^2+\varrho'^2-2\varrho\varrho'\cos(\varphi-\varphi')+z^2}
\end{equation}
$$

med hjälp av cosinusteoremet.
Med superposition kan vi teckna ett explicit uttryck för potentialen:

$$
\begin{equation}
\phi(\vec{r})=\int_0^a d\varrho'\int_0^{2\pi}d\varphi'\,
\varrho'\frac{\sigma_0}{4\pi \sqrt{\varrho^2+\varrho'^2-2\varrho\varrho'\cos(\varphi-\varphi')+z^2}}
\end{equation}
$$

Om $\vec{r}$ ligger på $z$-axeln förenklas uttrycket till

$$
\begin{equation}
\phi(0,0,z) = \int_0^a d\varrho'\int_0^{2\pi}\mbox{d}\varphi'\,
\varrho'\frac{\sigma_0}{4\pi \sqrt{\varrho'^2+z^2}}
=\frac{\sigma_0}{2}\left[\sqrt{\varrho'^2+z^2}\right]_{\varrho'=0}^a
\end{equation}
$$

$$
\begin{equation}
=\frac{\sigma_0}{2}\left(\sqrt{z^2+a^2}-|z|\right).
\end{equation}
$$

Vi kan kontrollera vad som händer då $|z|\rightarrow+\infty$. Då är

$$
\begin{equation}
\sqrt{z^2+a^2}-|z|=|z|(\sqrt{1+a^2/z^2}-1) = |z|
\left( 1+\frac{a^2}{2z^2}+\mathcal{O}(\frac{a^3}{z^3})-1\right) \approx \frac{a^2}{2|z|},
\end{equation}
$$

så $\phi(0,0,z)\approx \frac{a^2\sigma_0}{4|z|}$, vilket är fältet från en punktkälla med styrka $\pi a^2\sigma_0$. Befinner vi oss långt bort från ytkällan (dvs $z \gg a$) så ser fältet ut som fältet från en punktkälla. Det verkar rimligt!

## Greensfunktion

Funktionen

$$
\begin{equation}
G\left( \vec{r},\vec{r}\,' \right) = \frac{1}{4 \pi \left| \vec{r} - \vec{r}\,' \right|},
\end{equation}
$$

kallas *Greensfunktionen* och kan sägas motsvara bidraget till potentialen i punkten $\vec{r}$ från en punktkälla med styrkan 1 belägen i punkten $\vec{r}\,'$. Dessa funktioner dyker upp igen i kapitel 9. Vi kan alltså skriva fältet från en allmän rymdkälla

$$
\begin{equation}
\phi(\vec{r}) = \int_V \rho\left(\vec{r}\,'\right) G\left( \vec{r},\vec{r}\,' \right) \mbox{d}V'.
\end{equation}
$$

## Multipoler

Fältet

$$
\begin{equation}
  \vec{F} = \frac{q}{4 \pi r^2} \hat{e}_{r}
\end{equation}
$$

kallas för ett monopolfält.  Det kan finnas fält vars styrka avtar snabbare med $r$, men sådana fält innehåller praktiskt taget alltid ett vinkelberoende. Ett exempel på ett sådant fält är dipolfältet, dvs. två lika och motsatta laddningar nära varandra.

Lägg en laddning $q = \frac{\mu}{\varepsilon}$ på $(x,y,z)=(0,0,\varepsilon)$ och en laddning
$-q = -\frac{\mu}{\varepsilon}$ i origo. Potentialen från de båda laddningarna tillsammans blir

$$
\begin{equation}
\phi(\vec{r})=\frac{\mu/\varepsilon}{4\pi|\vec{r}-\varepsilon\hat z|}-\frac{\mu/\varepsilon}{4\pi r}
\end{equation}
$$

Vi kan skriva $|\vec{r}-\varepsilon \hat z|=\sqrt{\varrho^2+(z-\varepsilon)^2}$, och om
$\varepsilon$ är litet ($\varepsilon/\varrho,\varepsilon/z \ll 1$) blir detta $\sqrt{\varrho^2+z^2-2\varepsilon
z}=\sqrt{r^2-2\varepsilon z}\approx r(1-\frac{\varepsilon z}{r^2})$. Så $\frac{1}{|\vec{r}-\varepsilon\hat z|}\approx \frac{1}{r}(1+\frac{\varepsilon z}{r^2})$.

[Comment 8: Här har vi använt Taylorutvecklingarna $\sqrt{1-x} = 1 - \frac{x}{2} + \mathcal{O}(x^2)$ samt $\frac{1}{1-x} = 1 + x + \mathcal{O}(x^2)$.]

Potentialen blir

$$
\begin{equation}
\phi(\vec{r})\approx\frac{\mu}{4\pi\varepsilon}\left[\frac{1}{r}\left(1+\frac{\varepsilon z}{r^2}\right)-\frac{1}{r}\right]=\frac{\mu z}{4\pi r^3}=\frac{\mu\cos\theta}{4 \pi r^2}
\end{equation}
$$

Mer generellt kan man skriva $\phi=\frac{\vec\mu\cdot\vec{r}}{4 \pi r^3}$. I vårt fall var alltså $\vec\mu=\mu\hat z$ dipolmomentet (som alltså ges av produkten av laddningen $\frac{\mu}{\varepsilon}$ och separationsvektorn $\varepsilon\hat z$).

Fältet blir

$$
\begin{equation}
  \vec{F} = \frac{\mu}{4 \pi r^3} \left(\cos \theta \hat{e}_{r} + \sin \theta
\hat{e}_{\theta}\right).
\end{equation}
$$

Notera att fältlinjerna är vinkelräta mot nivåkurvorna (eftersom $\vec{F} = -\nabla \phi$). I detta fall kommer de alltså att vara cirklar med centrum längs $x$-axeln.

Om man integrerar dipolfältet över en sfär ser man att integralen blir 0.  Detta beror på att man kan se fältet som sammansatt av en positiv och en negativ laddning på ett litet avstånd från varandra. Det går också att definiera kvadrupolfält, oktopolfält och så vidare.

```
%matplotlib inline

import numpy as np
import pylab as p

# Make an x,z grid
grid = np.linspace(-6,6,200)
x, z = np.meshgrid(grid,grid)
mu=4*np.pi  # dipole strength (in z-direction)
# and the 2D cross section of the potential (scalar field)
phi = mu * z / (4 * np.pi * (x**2+z**2)**(3/2))
# End initialization

# Start contour plot
from matplotlib import colors
p.figure(figsize=(6,6))
heights = np.linspace(-0.6,0.6,7)
CS = p.contour(x,z,phi,heights,colors='k')
p.clabel(CS, inline=1, fontsize=10,fmt='%3.1f')
p.xlabel(r'$x$')
p.ylabel(r'$z$')
# End contour plot

# Start fieldline plot
Fx = mu / (4 * np.pi * (x**2+z**2)**(5/2)) * 2*x*z
Fz = mu / (4 * np.pi * (x**2+z**2)**(5/2)) * (z**2-x**2)
p.streamplot(x,z,Fx,Fz)
```


# FFM234, Klassisk fysik och vektorfält - Veckans tal

**Christin Rhen och Christian Forssén, Chalmers**

Date: **Aug 10, 2019**

## Uppgift 7.5.2: Derivator av deltafunktioner

Konstruera approximationerna till de första tre derivatorna av en deltafunktion svarande mot approximationerna (7.7) och (7.8) av deltafunktionen! Skissera funktionernas beteende (m.h.a. dator om du vill) och reflektera över varför deras integraler
mot en funktion $f(x)$ ger de resultat de gör i gränsen $\epsilon \rightarrow 0$.

**Hint.**
Det är instruktivt att lösa uppgiften på två sätt:
1. Att använda de explicita uttrycken för olika ordningars derivator av de givna distributionerna och utföra integralerna.

  * Gör först en Taylorexpansion av funktionen $f(x)$ runt $x=0$.

  * Notera att distributionerna är jämna funktioner så att integraler över dessa gånger udda funktioner måste bli noll för ett symmetriskt intervall.

  * Se formelsamling för analytiska uttryck för de relevanta integralerna.

2. Studera även de skissade funktionerna och deras derivator. Vad får man om man multiplicerar dessa med en funktion f(x) och utför integralen i gränsen $\epsilon \to 0$?

  * Jämför med uttrycken för olika ordningars derivator av en funktion $f(x)$ i termer av finita differenser.

**Answer.**
Integralerna skall plocka ut funktionens första tre derivator i punkten $x=0$ med tecken $(-1)^n$ för derivata av ordningen $n$.

**Solution.**
Rättfram derivering ger

$$
\begin{equation}
    h_\epsilon(x)=\frac1{\epsilon\sqrt\pi}e^{-x^2/\epsilon^2},
\label{_auto1} \tag{1}
\end{equation}
$$

$$
\begin{equation}
    h_\epsilon'(x)=-\frac{2x}{\epsilon^2}h_\epsilon(x),
\label{_auto2} \tag{2}
\end{equation}
$$

$$
\begin{equation}
    h_\epsilon''(x)=\frac{4x^2-2\epsilon^2}{\epsilon^4}h_\epsilon(x),
\label{_auto3} \tag{3}
\end{equation}
$$

$$
\begin{equation}
    h_\epsilon'''(x)=-\frac{8x^3-12x\epsilon^2}{\epsilon^6}h_\epsilon(x),
\label{_auto4} \tag{4}
\end{equation}
$$

och

$$
\begin{equation}
    h_\epsilon(x)= \frac\epsilon{\pi\left(x^2+\epsilon^2\right)},
\label{_auto5} \tag{5}
\end{equation}
$$

$$
\begin{equation}
    h_\epsilon'(x)= -\frac{2 x}{x^2+\epsilon^2}h_\epsilon(x) ,
\label{_auto6} \tag{6}
\end{equation}
$$

$$
\begin{equation}
    h_\epsilon''(x)= \frac{6x^2-2\epsilon^2}{\left(x^2+\epsilon^2\right)^2}h_\epsilon(x) ,
\label{_auto7} \tag{7}
\end{equation}
$$

$$
\begin{equation}
    h_\epsilon'''(x)= -\frac{24 x^3 -24x\epsilon^2}{\left(x^2+\epsilon^2\right)^3}h_\epsilon(x) .
\label{_auto8} \tag{8}
\end{equation}
$$

Dessa funktioner och derivator är skisserade för $\epsilon = 0.02,0.04,0.06$ i figur [fig:71](#fig:71) och [fig:72](#fig:72).

Två distributioner och deras förstaderivator i gränsen $\epsilon \to 0$.

Andra- och tredjederivator av två distributioner i gränsen $\epsilon \to 0$.

Deltafunktionens derivator ska gå att partialintegrera (se diskussionen av ekvation (7.9) i kurskompendiet). Det vill säga (alla integraler går över hela $\mathbb R$, så alla randtermer är lika med noll):

$$
\begin{equation}
    \int \mathrm dx\ \delta'(x)f(x)=-\int \mathrm dx\ \delta(x)f'(x)=-f'(0),
\label{_auto9} \tag{9}
\end{equation}
$$

$$
\begin{equation}
    \int \mathrm dx\ \delta''(x)f(x)=\int \mathrm dx\ \delta(x)f''(x)=f''(0),
\label{_auto10} \tag{10}
\end{equation}
$$

$$
\begin{equation}
    \int \mathrm dx\ \delta'''(x)f(x)=-\int \mathrm dx\ \delta(x)f'''(x)=-f'''(0).
\label{eq:tredje} \tag{11}
\end{equation}
$$

### Explicit integrering

Låt oss visa detta explicit för de två distributionerna som betraktas här. För en godtycklig slät funktion $f(x)$ kan vi Taylorutveckla:

$$
\begin{equation}
    f(\epsilon)\approx f(0)+f'(0)x+\frac12f''(0)x^2+\frac16f'''(0)x^3+\ldots.
\label{_auto11} \tag{12}
\end{equation}
$$

I båda fallen ovan är $h_\epsilon(x)$ en jämn funktion, så alla termer av typen $x^{2n+1}h_\epsilon(x)$, $n\in\mathbb Z$ integreras till noll.

Vi börjar titta på den Gaussiska approximationen. Inför notationen $f^{(n)}(0)\equiv f^{(n)}_0$. För förstaderivatan får vi följande genom att utföra integralerna (se formelsamling) eller genom att partialintegrera upprepade gånger,

$$
\int\mathrm dx\ f(x)h'_\epsilon(x)\approx-\frac{2}{\epsilon^2}\int\mathrm dx\ xh_\epsilon(x)\left[f'_0x+\frac16f'''_0x^3+\ldots\right] \nonumber
$$

$$
\begin{equation}
    =-f'_0-\frac14f'''_0\epsilon^2+\ldots.
\label{_auto12} \tag{13}
\end{equation}
$$

Det är lätt att inse att alla termer som innehåller högre ordningens derivator  av $f(x)$ kommer vara proportionella mot $\epsilon$, och gå mot noll. Kvar finns bara den väntade $-f'(0)$.

På samma sätt studerar vi integralen över andraderivatan:

$$
\int\mathrm dx\ f(x)h''_\epsilon(x)\approx\int\mathrm dx\ \frac{4x^2-2\epsilon^2}{\epsilon^4}h_\epsilon(x)\left[f_0+\frac12f''_0x^2+\ldots\right] \nonumber
$$

$$
\begin{equation}
    =\frac{2-2}{\epsilon^2}f(0)+\frac{3-1}2f''_0 + \epsilon^2 [\ldots].
\label{_auto13} \tag{14}
\end{equation}
$$

De högre ordningens termer kommer återigen vara proprotionella mot $\epsilon$, och försvinner när gränsvärdet tas, vilket resulterar i att bara $f''(0)$ finns kvar.

Vi övergår nu till att studera den Lorentzianska approximationen. På samma sätt som ovan får vi för förstaderivatan

$$
\begin{gather}
    \int\mathrm dx\ f(x)h'_\epsilon(x)\approx-\int\mathrm dx\ \frac{2 x}{x^2+\epsilon^2}h_\epsilon(x)\left[f'_0x+\frac16f'''_0x^3+\ldots\right] \nonumber\\
    =-f'_0-\frac\epsilon{3\pi}\left[x+\frac{\epsilon^2x}{2\epsilon^2+2x^2}-\frac{3\epsilon}2\tan^{-1}\frac x\epsilon\right]_{-\infty}^\infty+\ldots.
\end{gather}
$$

Eftersom att $x$ här är en integrationsvariabel har den inget konstigt för sig, utan går linjärt mot oändligheten. I gränsen $\epsilon\rightarrow 0$ går därför de högre ordningens termer mot noll, och vi får igen det väntade resultatet.

För andraderivatan får vi

$$
\begin{gather}
    \int\mathrm dx\ f(x)h''_\epsilon(x)\approx\int\mathrm dx\ \frac{6x^2-2\epsilon^2}{\left(x^2+\epsilon^2\right)^2}h_\epsilon(x)\left[f_0+\frac12f''_0x^2+\ldots\right] \nonumber\\
    =\frac{3-3}{4\epsilon^2}f(0)+\frac{9-1}8f''_0 + \epsilon [\ldots]
\end{gather}
$$

där alltså högre ordningens termer är proportionella mot $\epsilon$.

Att bevisa ekvation ([eq:tredje](#eq:tredje)) för de båda fallen av $h_\epsilon(x)$ lämnas som en övning.

### Resonemang utgående från funktionernas form

Vi kan också resonera utgående från figurerna [fig:71](#fig:71) och [fig:71](#fig:71) varför vi får dessa resultat. För att göra detta drar vi oss först till minnes uttrycken för olika ordningars derivator i termer av finita differenser:

$$
\begin{equation}
f'(x) = \lim_{\epsilon \to 0} \frac{f(x+\epsilon/2)-f(x-\epsilon/2)}{\epsilon}
\label{_auto14} \tag{15}
\end{equation}
$$

$$
\begin{equation}
f''(x) = \lim_{\epsilon \to 0} \frac{f(x+\epsilon)-2f(x)+f(x-\epsilon)}{\epsilon^2}
\label{_auto15} \tag{16}
\end{equation}
$$

$$
\begin{equation}
f'''(x) = \lim_{\epsilon \to 0} \frac{f(x+3\epsilon/2)-3f(x+\epsilon/2)+3f(x-\epsilon/2)-f(x-3\epsilon/2)}{\epsilon^3}.
\label{_auto16} \tag{17}
\end{equation}
$$

Det är inte svårt att föreställa sig att förstaderivatorna som ritats upp i den andra raden av figur [fig:71](#fig:71) kommer att plocka upp $-f(0+\epsilon/2)$ och $+f(0-\epsilon/2)$. Notera också att amplituden på de två topparna är lika stor och växer som $1/\epsilon$. Resultatet motsvarar alltså den finita differensen som definierar förstaderivatan av en funktion $f(x)$ i punkten $x=0$, fast med motsatt tecken.

På liknande sätt kan vi resonera kring de högre ordningarnas derivator och jämföra figurerna med uttrycken från finita differenser. Notera speciellt den relativa storleken på de olika topparna och att amplituderna blir högre för andraderivatorna ($\sim 1/\epsilon^2$) och ännu högre för tredjederivatorna ($\sim 1/\epsilon^3$).


# 線形回帰モデル

Pythonの機械学習用ライブラリ`scikit-learn`を使って，線形回帰モデルを使って簡単な回帰問題にチャレンジしてみましょう.

---

### 0.ライブラリのインポート

```python
import pandas as pd
import numpy as np

import sklearn

import seaborn as sns
import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline

np.set_printoptions(precision=4)
```

```python
print("numpy :", np.__version__)
print("pandas :", pd.__version__)
print("sklearn :", sklearn.__version__)
print("seaborn :", sns.__version__)
print("matplotlib :", matplotlib.__version__)
```

    numpy : 1.16.1
    pandas : 0.24.2
    sklearn : 0.20.2
    seaborn : 0.9.0
    matplotlib : 3.0.2

### 1. データの読込・整形

`sklearn.datasets`からBostonデータセットを読み込みましょう．

```python
# make data samples
from sklearn.datasets import load_boston
boston = load_boston()
```

次に，pandas DataFrame()クラスのインスタンスとして，変数`df_data`, `df_target`, `df`を定義します．

参考: [pandas.DataFrame — pandas 1.0.1 documentation](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html)

```python
df_data   = pd.DataFrame(boston.data, columns = boston.feature_names)
df_target = pd.DataFrame(boston.target, columns = ['TARGET'])
df = pd.concat([df_target, df_data], axis = 1)
df = df.loc[:, ['TARGET', 'RM', 'DIS', 'INDUS', 'AGE', 'LSTAT', 'CRIM']]

df.head(10)
```

      TARGET
      RM
      DIS
      INDUS
      AGE
      LSTAT
      CRIM

      0
      24.0
      6.575
      4.0900
      2.31
      65.2
      4.98
      0.00632

      1
      21.6
      6.421
      4.9671
      7.07
      78.9
      9.14
      0.02731

      2
      34.7
      7.185
      4.9671
      7.07
      61.1
      4.03
      0.02729

      3
      33.4
      6.998
      6.0622
      2.18
      45.8
      2.94
      0.03237

      4
      36.2
      7.147
      6.0622
      2.18
      54.2
      5.33
      0.06905

      5
      28.7
      6.430
      6.0622
      2.18
      58.7
      5.21
      0.02985

      6
      22.9
      6.012
      5.5605
      7.87
      66.6
      12.43
      0.08829

      7
      27.1
      6.172
      5.9505
      7.87
      96.1
      19.15
      0.14455

      8
      16.5
      5.631
      6.0821
      7.87
      100.0
      29.93
      0.21124

      9
      18.9
      6.004
      6.5921
      7.87
      85.9
      17.10
      0.17004

データの要約統計量(サンプル数, 平均, 標準偏差, 四分位数, 中央値, 最小値, 最大値など)をみましょう．

```python
df.describe().T
```

      count
      mean
      std
      min
      25%
      50%
      75%
      max

      TARGET
      506.0
      22.532806
      9.197104
      5.00000
      17.025000
      21.20000
      25.000000
      50.0000

      RM
      506.0
      6.284634
      0.702617
      3.56100
      5.885500
      6.20850
      6.623500
      8.7800

      DIS
      506.0
      3.795043
      2.105710
      1.12960
      2.100175
      3.20745
      5.188425
      12.1265

      INDUS
      506.0
      11.136779
      6.860353
      0.46000
      5.190000
      9.69000
      18.100000
      27.7400

      AGE
      506.0
      68.574901
      28.148861
      2.90000
      45.025000
      77.50000
      94.075000
      100.0000

      LSTAT
      506.0
      12.653063
      7.141062
      1.73000
      6.950000
      11.36000
      16.955000
      37.9700

      CRIM
      506.0
      3.613524
      8.601545
      0.00632
      0.082045
      0.25651
      3.677083
      88.9762

データの共分散行列を描画します．

対角成分は自分との共分散(相関)を表すため常に1.0となります．

```python
df.corr()
```

      TARGET
      RM
      DIS
      INDUS
      AGE
      LSTAT
      CRIM

      TARGET
      1.000000
      0.695360
      0.249929
      -0.483725
      -0.376955
      -0.737663
      -0.388305

      RM
      0.695360
      1.000000
      0.205246
      -0.391676
      -0.240265
      -0.613808
      -0.219247

      DIS
      0.249929
      0.205246
      1.000000
      -0.708027
      -0.747881
      -0.496996
      -0.379670

      INDUS
      -0.483725
      -0.391676
      -0.708027
      1.000000
      0.644779
      0.603800
      0.406583

      AGE
      -0.376955
      -0.240265
      -0.747881
      0.644779
      1.000000
      0.602339
      0.352734

      LSTAT
      -0.737663
      -0.613808
      -0.496996
      0.603800
      0.602339
      1.000000
      0.455621

      CRIM
      -0.388305
      -0.219247
      -0.379670
      0.406583
      0.352734
      0.455621
      1.000000

データの散布図行列を描画します．

相関が大きい説明変数のペアについては, 多重共線性を考えるべきです.

```python
sns.set()
sns.pairplot(df, height=2.0, diag_kind='hist', markers='+')
plt.show()
```

### 2. データの分割

pandas DataFrame()クラスの変数`df`から，説明変数と目的変数に相当するデータをそれぞれ取り出し，numpy.ndarray()クラスの変数`x`, `y`へ格納します．

```python
x = df.iloc[:, 1:] # set x to explanatory variable
y = df.iloc[:, 0] # set y to response variable
```

全データをtrainデータとtestデータに分割します．
すなわち，変数`x`を`x_train`と`x_test`に，
変数`y`を`y_train`と`y_test`に分けます．

```python
# split data by Hold-out-method
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size = 0.3, random_state = 0)
```

`print()`で配列の形状を確認してみましょう．

```python
print("x_train: ", x_train.shape)
print("y_train: ", y_train.shape)
print("x_test: ", x_test.shape)
print("y_test: ", y_test.shape)
```

    x_train:  (354, 6)
    y_train:  (354,)
    x_test:  (152, 6)
    y_test:  (152,)

- x_train: 6次元データが354コ格納されている．
- y_train: 1次元データが354コ格納されている．
- x_test: 6次元データが152コ格納されている．
- y_test: 1次元データが152コ格納されている．

### 3. モデルの作成

ボストン市の住宅価格TARGETを目的変数にして線形回帰モデル$f$を作ります.$\beta$はモデルパラメータです．

$$
\begin{align}
y
&= f({\bf x} ; \beta) + u \\
&= f(x_1, \dots, x_6 ; \beta) + u \\
&= \beta_0 + \beta_1 x_1 + \cdots + \beta_6 x_6 + u \\ \\
u
&\sim \mathcal{N}(0, \sigma^2)
\end{align}
$$

- 目的変数 (1コ)
  - $y~~$ : TARGET - 住宅価格の中央値（単位: 千ドル）
- 説明変数 (6コ)
  - $x_1$ : RM - 住居の平均部屋数
  - $x_2$ : DIS - 5つのボストン市の雇用施設からの重み付き距離
  - $x_3$ : INDUS - 非小売業の土地割合（単位: ％）
  - $x_4$ : AGE - 1940年より前に建てられた物件割合（単位: ％）
  - $x_5$ : LSTAT - 低所得労働者の割合（単位: ％）
  - $x_6$ : CRIM - 人口あたり犯罪発生率（単位: ％）
- 誤差項
  - $u$ ： 平均0の正規分布に従うと仮定．

```python
# Linear Regression
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
```

### 4. モデルへデータを適合させる

データ$\mathcal{D} := \left\{ \left( \begin{array}{c} {\bf x}_1 \\ y_1 \end{array} \right), \dots, \left( \begin{array}{c} {\bf x}_n \\ y_n \end{array} \right) \right\}$を用いて計算される平均二乗誤差(Mean Squared Error, MSE)をパラメータ$\beta$の目的関数$J$とすれば，$J$を最小化するようなパラメータ$\beta^*$を求めることができます．

$$
\begin{align}
\beta^* &= \underset{\bf \beta}{\rm argmin} ~ J(\beta) \\
J(\beta) &:= \sum_{i=1}^{n} {( y_i - f({\bf x}_i ; \beta) )}^2
\end{align}
$$

実装上は，sklearn.linear_model.LinearRegression()クラスのインスタンス`lr`を作成し，trainデータ`x_train`,`y_train`を与えることで最適なモデルパラメータ$\beta^* = [\beta_0^* \dots,\beta_6^*]$を求めます．

```python
lr.fit(x_train, y_train)
```

    LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None,
             normalize=False)

trainデータから推定された最適なモデルパラメータ$\beta^* = [\beta_0^* \dots,\beta_6^*]$の値を確認してみます．

```python
print('  beta0  : {:.4e}'.format(lr.intercept_))
print('  beta1  : {:.4e}'.format(lr.coef_[0]))
print('  beta2  : {:.4e}'.format(lr.coef_[1]))
print('  beta3  : {:.4e}'.format(lr.coef_[2]))
print('  beta4  : {:.4e}'.format(lr.coef_[3]))
print('  beta5  : {:.4e}'.format(lr.coef_[4]))
print('  beta6  : {:.4e}'.format(lr.coef_[5]))
```

      beta0  : 8.6252e+00
      beta1  : 4.9834e+00
      beta2  : -1.2951e+00
      beta3  : -2.4170e-01
      beta4  : -3.6665e-02
      beta5  : -5.4298e-01
      beta6  : -1.4359e-01

### 5. モデルの評価

モデル`lr`に，入力として`x_train`,`x_test`を与えて，`y_train`,`y_test`の値をそれぞれ予測させてみましょう．

```python
# predictions
y_train_pred = lr.predict(x_train)
y_test_pred = lr.predict(x_test)
```

評価指標(Metrics)として，決定係数(R^2)，平均二乗誤差(MSE), 平均絶対誤差(MAE)の値を計算してみましょう．

```python
# model evaluation
from sklearn.metrics import mean_absolute_error, mean_squared_error

print('  R^2 (train) : {:.4f}'.format(lr.score(x_train, y_train)))
print('  R^2(test)   : {:.4f}'.format(lr.score(x_test, y_test)))
print('')
print('  MSE   (train) : {:.4e}'.format(mean_squared_error(y_train, y_train_pred)))
print('  MSE   (test) : {:.4e}'.format(mean_squared_error(y_test, y_test_pred)))
print('')
print('  MAE  (train) : {:.4e}'.format(mean_absolute_error(y_train, y_train_pred)))
print('  MAE  (test)  : {:.4e}'.format(mean_absolute_error(y_test, y_test_pred)))
```

      R^2 (train) : 0.6941
      R^2(test)   : 0.6063

      MSE   (train) : 2.5930e+01
      MSE   (test) : 3.2780e+01

      MAE  (train) : 3.6727e+00
      MAE  (test)  : 3.8834e+00

目的関数$J$をみれば明らかですが，trainデータに適合する(fitする)ようにモデル`lr`は設計されています．
（モデルにとって）未知のデータであるtestデータに対する評価指標の値は，trainデータに対するものと比べて低くなります．

モデルがtrainデータに対して過度に適合した結果，testデータに対する性能が悪化する現象を「過学習(Over fitting)」といいます．


## 3-2. ニューラルネットと誤差関数
ニューラルネットワークを構成する基本要素を説明した後、深層ニューラルネットを導入します。

### 特徴量
入力ベクトル ${\bf x}$ が与えられたときの何らかの変数 $y$ についての条件付き確率 $q_\theta(y|{\bf x})$ を構成するのですが、また具体例を見てみましょう

```python
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd

def get_2d_simple_data(t, v1=.02, v2=.02):
    return np.random.multivariate_normal([np.cos(t), np.sin(t)], np.diagflat([v1, v2]), 100).astype(np.float32)

def two_pairplot(x12):
    df_x1 = pd.DataFrame(x12[0], columns=[r"${\bf x}^1$", r"${\bf x}^2$"]); df_x1["class"] = 'y=A'
    df_x2 = pd.DataFrame(x12[1], columns=[r"${\bf x}^1$", r"${\bf x}^2$"]); df_x2["class"] = 'y=B'
    df = pd.concat([df_x1, df_x2])
    sns.pairplot(df, hue="class");
    plt.show()
```

例えば2次元データとラベル $\{ ({\bf x}_n, y_n  )\}$ で以下のようなデータがあったとします：

```python
x_A = get_2d_simple_data(t=np.pi/4)
x_B = get_2d_simple_data(t=np.pi/4+np.pi/2)
two_pairplot([x_A, x_B])
```

この二次元データは ${\bf x}$ の 第一座標 ${\bf x}^1$ でみると、クラスAとクラスBが分かれているけれど、第二座標 ${\bf x}^2$ でデータを見ると、両者の違いが無いように見えます。例えば、このような場合は ${\bf x}$ に適当な線形変換 $l$ をかけた

$$
{\bf z} = l({\bf x}) = W {\bf x} + {\bf b}
$$

を考えると、どちらの座標でも統計量が区別されるのがわかります。線形変換は深層学習ライブラリで簡単に実装できます。

#### 実装その1(Tensorflow)
Tensorflowはバージョン2以後、eager modeと呼ばれる仕様がデフォルトとなり、格段に使いやすくなりました。あんまり難しいことをしない場合、最良だと思います：

```python
# tensorflowの場合
import tensorflow as tf

l = tf.keras.layers.Dense(2, input_shape=(2,)) # これが線形変換
z_A = l(x_A)
z_B = l(x_B)

two_pairplot([z_A, z_B])
```

#### 実装その2(PyTorch)
PyTorchは上のtensorflow eager modeの元となった**define-and-run**方式と呼ばれる方式を早くから取り込んだライブラリで、近年では研究者への人気が高まっていけいるようです。
> define-and-run方式を「最初に発明」したのは日本の企業[Preferred Networks](https://preferred.jp/ja/)の、[Chainer](https://chainer.org)という深層学習ライブラリです。

tensorflow eager modeでは忖度してくれていた配列の「型のようなもの」を明示的に指定する必要があります。pytorch(torch)内の関数などを使うときは`np.array`ではなく`torch.tensor`に変換せねばならず、tensorflow eager modeのように楽ちんな書き方は今のところできないようです。

```python
# pytorchの場合
import torch

l = torch.nn.Linear(2,2) # これが線形変換
z_A = l(torch.from_numpy(x_A))
z_B = l(torch.from_numpy(x_B))

two_pairplot([z_A.data.numpy(), z_B.data.numpy()])
```

#### 一般の特徴量
より一般に、非線形変換 $f$ で

$$
{\bf z} = f({\bf x})
$$

が分類などのタスクをより処理しやすくする、データの背後にある何らかの特徴を抽出した量になっているとき、これを**特徴量(feature)**と言います。以下ではニューラルネットワークにおける特徴量の設計が、何らかの確率モデルについての**期待値**を層状に重ねたものと解釈できることを説明します。

### 2値ユニット
2値分類とは入力$x$を2値のラベルに分類する問題です。ここでは慣習に従い1ビット$\{ 0, 1\}$への分類とします。また、入力は簡単のため1成分の実数としましょう
\begin{align}
y \in \{0, 1\},
\quad
x \in \mathbb{R}
\end{align}
このような状況で条件付き確率$q_\theta(y|x)$を構成するにあたって、統計力学の考えを使いましょう。
統計力学の教えるところによると、
\begin{align}
\text{自由度}: y,
\quad
\text{そのエネルギー}: E(y)
\end{align}
の系が温度$T$の環境下で定常状態に落ち着けば、$y$の実現確率は
\begin{align}
q_E(y)
=
\frac{e^{- \frac{E(y)}{k_B T}}}{Z}
\end{align}
になるのでした。あとはエネルギーを考えれば良さそうです。
まずは外部から力$F(x)$がかかっている状況を考えます。このとき$y$が実数であれば、これを変化分と考えて、仕事は$F(x) y$と考えるのが自然でしょう。いま$y$は2値ですが、スピン変数とみなせば$F$は外部磁場と考えれば同じことです。注がれた仕事がエネルギーになるので
\begin{align}
E(y) = -F(x) y
\end{align}
でしょう。
$x$の依存性がまだないので、$F(x)$として一番単純な
\begin{align}
F(x) = w x + b
\end{align}
を取ることにしてみます。
ここで$w, b$のスケールを適当に合わせれば$k_B T$はこれらの値に吸収できるので
\begin{align}
q_E(y)
=
q_{w, b} (y|x)
=
\frac{e^{(w x + b)y}}{Z}
\end{align}
となります。$Z$は$y=0,1$についての足し算が1になるようにすれば良いので
\begin{align}
Z
=
\sum_{y=0, 1}
e^{(w x + b)y}
=
1 + e^{wx +b}
\end{align}
であることがわかります\footnote{
統計力学で分配関数と呼ばれる量です。
}。
従って
\begin{align}
q_{w, b} (y|x)
=
\frac{e^{(w x + b)y}}{1 + e^{wx +b}}
\end{align}
ということになりました。
これは非常にシンプルな確率モデルです。$y=0,1$である確率はそれぞれ
\begin{align}
&q_{w, b} (y=1|x)
=
\frac{e^{w x + b}}{1 + e^{wx +b}}
=
\frac{1}{e^{-(w x + b)} + 1}
:=
\sigma(wx + b)
\\
&q_{w, b} (y=0|x)
=
1- q_{w, b} (y=1|x)
=
1 - \sigma(wx + b)
\end{align}
と書くことができます。関数$\sigma$には名前がついており、(ロジスティック)シグモイド関数と呼ばれます。
特に$y$の期待値
\begin{align}
\bra y \ket_{q_{w, b}(y|x)}
&=
1 \cdot q_{w, b} (y=1|x) + 0 \cdot q_{w, b} (y=0|x)
\notag \\
&= %%%%
\sigma(wx + b)
\label{211}
\end{align}
がシグモイド関数だけで書けているのが、後々のポイントとなります。
この場合の「{\color{red}汎化誤差}」の近似、「{\color{blue}経験誤差}」${\color{blue}
- \bra \log q_\theta(y|x) \ket_{\hat{p}_N}}$はどうなるか見てみますと、
\begin{align}
- \bra \log q_{w, b}(y|x) \ket_{\hat{p}_N}
&=
- \frac{1}{N} \sum_{n=1}^N \log q_{w, b} (y_n | x_n)
\notag \\
&= %%%%
- \frac{1}{N} \Big(
 \sum_{y_n=1} \log q_{w, b} (y_n=1 | x_n)
 +
  \sum_{y_n=0} \log q_{w, b} (y_n=0 | x_n)
 \Big)
\notag \\
&= %%%%
- \frac{1}{N} \Big(
 \sum_{y_n=1} \log \underbrace{ \sigma (wx_n+b) }_{\bra y \ket_{q_{w, b}(y|x_n)}}
 +
  \sum_{y_n=0} \log \{ 1 - \underbrace{ \sigma (wx_n+b)}_{\bra y \ket_{q_{w, b}(y|x_n)}} \}
 \Big)
\notag \\
&= %%%%
\frac{1}{N}
\sum_{n=1}^N
\underbrace{
-
\Big(
y_n \log \bra y \ket_{q_{w, b}(y|x_n)}
 +
(1-y_n) \log \{ 1 - \bra y \ket_{q_{w, b}(y|x_n)} \}
 \Big)
 }_{=: l_\text{sig} \Big( \bra y \ket_{q_{w, b}(y|x_n)} ,\ y_n \Big)}
 \notag \\
&= %%%%
\frac{1}{N}
\sum_{n=1}^N
 l_\text{sig} \Big( \bra y \ket_{q_{w, b}(y|x_n)} ,\ y_n \Big)
\end{align}
と、シグモイドクロスエントロピーと呼ばれる関数
\begin{align}
l_\text{sig} (\bra y \ket, y)
:=
- \Big(y \log \bra y \ket + (1-y) \log (1- \bra y \ket) \Big)
\end{align}
のサンプル平均として書くことができます。
入力の次元が上がって
\begin{align}
\vec{x} = (x^1, x^2, \dots, x^L) \in \mathbb{R}^L
\end{align}
となった場合も、エネルギーを
\begin{align}
E(y)
=
- (\vec{w}^\top \vec{x} +b)y
\end{align}
として同じことが繰り返せるため、
\begin{align}
&q_{\vec{w}, b}(y=1|\vec{x})
=
\sigma (\vec{w}^\top \vec{x} +b)
=
\bra y \ket_{q}
,
\\
&-\bra \log q_{\vec{w}, b}(y|\vec{x}) \ket_{\hat{p}_N}
=
\frac{1}{N} \sum_{n=1}^N
l_\text{sig} \Big(\bra y \ket_{q} , y_n \Big)
\end{align}
がわかります。

```python

```


# Results

```python
# %load imports.py
%load_ext autoreload
%autoreload 2
%reload_kedro
%config Completer.use_jedi = False  ## (To fix autocomplete)

import pandas as pd
from src.models.vmm import ModelSimulator
import matplotlib.pyplot as plt
import matplotlib
#matplotlib.rcParams["figure.figsize"] = (10,10)
plt.style.use('presentation')
from src.visualization.plot import track_plots, plot, captive_plot
import kedro
import numpy as np
import os.path
import anyconfig

from myst_nb import glue
from src.symbols import *
import src.symbols as symbols
from src.system_equations import *

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

from src.models.regression import MotionRegression

from src.parameters import df_parameters
p = df_parameters["symbol"]

# Read configs:
conf_path = os.path.join("../../conf/base/")
runs_globals_path = os.path.join(
    conf_path,
    "runs_globals.yml",
)

runs_globals = anyconfig.load(runs_globals_path)
model_test_ids = runs_globals["model_test_ids"]

join_globals_path = os.path.join(
    conf_path,
    "join_globals.yml",
)

joins = runs_globals["joins"]
join_runs_dict = anyconfig.load(join_globals_path)

globals_path = os.path.join(
    conf_path,
    "globals.yml",
)
global_variables = anyconfig.load(globals_path)

vmm_names = global_variables["vmms"]
only_joined = global_variables[
    "only_joined"
]  # (regress/predict with only models from joined runs)S

vmms = {}
derivatives = {}
vmm_names.remove('vmm_abkowitz_expanded')
for vmm_name in vmm_names:
    vmms[vmm_name] = catalog.load(vmm_name)
    derivatives[vmm_name] = catalog.load(f"{ vmm_name }.motion_regression.joined.derivatives")

```

    UsageError: Line magic function `%reload_kedro` not found.

```python
id = 22773

ship_data = catalog.load("ship_data")
data = catalog.load(f"{ id }.data_ek_smooth")

dataframes = {}
styles = {}

for vmm_name in vmm_names:

    #if vmm_name == 'vmm_linear':
    #    continue

    df_ = catalog.load(f"{ vmm_name }.motion_regression.joined.{ id }.data_resimulate")

    #dataframes[vmm_name] = df_.iloc[0:-8000].copy()
    dataframes[vmm_name] = df_.copy()
    styles[vmm_name] = {'alpha':1}

#dataframes['model test'] = data.iloc[0:-8000]
dataframes['model test'] = data
styles['model test'] = {'style':'k-', 'alpha':1, 'lw':1}
```

    2022-03-28 16:28:00,852 - kedro.io.data_catalog - INFO - Loading data from `ship_data` (YAMLDataSet)...
    2022-03-28 16:28:00,855 - kedro.io.data_catalog - INFO - Loading data from `22773.data_ek_smooth` (CSVDataSet)...
    2022-03-28 16:28:00,875 - kedro.io.data_catalog - INFO - Loading data from `vmm_abkowitz.motion_regression.joined.22773.data_resimulate` (CSVDataSet)...
    2022-03-28 16:28:00,893 - kedro.io.data_catalog - INFO - Loading data from `vmm_abkowitz_simple.motion_regression.joined.22773.data_resimulate` (CSVDataSet)...
    2022-03-28 16:28:00,909 - kedro.io.data_catalog - INFO - Loading data from `vmm_martin.motion_regression.joined.22773.data_resimulate` (CSVDataSet)...
    2022-03-28 16:28:00,926 - kedro.io.data_catalog - INFO - Loading data from `vmm_linear.motion_regression.joined.22773.data_resimulate` (CSVDataSet)...
    2022-03-28 16:28:00,943 - kedro.io.data_catalog - INFO - Loading data from `vmm_martins_simple.motion_regression.joined.22773.data_resimulate` (CSVDataSet)...

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

fig.set_size_inches(matplotlib.rcParams["figure.figsize"][0]*0.4, matplotlib.rcParams["figure.figsize"][1])

track_plots(dataframes, lpp=ship_data['L'], beam=ship_data['B'], plot_boats=True, styles=styles, N=7, ax=ax, flip=False);
ax.legend(loc='lower left');
```

```python
number_of_parameters = pd.Series(dtype=float)
for vmm_name, parameters in derivatives.items():
    number_of_parameters[vmm_name] = len(parameters)

fig,ax=plt.subplots()
ax.tick_params(labelsize=15)

fig.set_size_inches(7,3)
number_of_parameters.sort_values().plot.bar(ax=ax);
```


# FFM234, Klassisk fysik och vektorfält - Veckans tal

**Christin Rhen, Chalmers**

Date: **Aug 10, 2019**

## Uppgift 9.4.10: Vektorfält från Greensfunktion

På en sfär med radien $a$ befinner sig en ytkälla med tätheten $\sigma(\vec r)=\sigma_0\cos\theta$, där $\theta$ är vinkeln från en punkt på sfären. Bestäm vektorfältet för en punkt på symmetriaxeln.

**Hint.**
* Betrakta punkter $\vec{r} = z\hat{z}$, dvs längs symmetriaxeln.

* Teckna Greensfunktionen $G(\vec{r},\vec{r}\,') = (4\pi|\vec{r}-\vec{r}\,'|)^{-1}$ och fundera på hur avståndet kan uttryckas när $\vec{r}=z\hat{z}$.

* Ytkällan motsvarar en laddningsfördelning $\rho(\vec{r}) = \sigma_0 \cos\theta \delta(r-a)$.

* Resultatet blir en ganska krånglig integral där variablsubstitutionen $t = z^2 +a^2−2za\cos\theta'$ kan komma väl till pass.

* Notera sedan att man får olika fall beroende på tecknen på $(z+a)$ och $(z-a)$.

**Answer.**

$$
\begin{equation}
\vec F = \left\{
\begin{array}{ll}
-\frac{\sigma_0}3\hat z, & |z| a
\end{array}
\right.
\label{_auto1} \tag{1}
\end{equation}
$$

**Solution.**
Eftersom att punkten $\vec r$ befinner sig på ytkällans symmetriaxel, kan vi definiera ett kartesiskt koordinatsystem så att denna symmetriaxel sammanfaller med $z$-axeln. Då blir $\theta$ samma vinkel som den vi normalt kallar $\theta$ i sfäriska koordinater, och $\vec r=z\hat z$.

Vektorfältet $\vec F(\vec r)=-\nabla\phi(\vec r)$, där potentialen $\phi(\vec r)$ ges av

$$
\begin{equation}
    \phi(\vec r)=\int_{\mathrm R^3}G(\vec r,\vec r')\rho(\vec r')\mathrm dV'.
\label{_auto2} \tag{2}
\end{equation}
$$

Eftersom att vi är i tre dimensioner är Greensfunktionen $G(\vec r,\vec r')=[4\pi|\vec r-\vec r'|]^{-1}$. Den angivna laddningstätheten är en ytkälla, så $\rho(\vec r\,')=\delta(r'-a)\sigma_0\cos\theta'$. Vi betraktar $\vec{r} = z\hat{z}$ och får alltså

$$
\begin{equation}
    \phi(z\hat z)=\int_{\mathbb R^3}\frac{\delta(r'-a)\sigma_0\cos\theta'}{4\pi\sqrt{z^2+r'^2-2zr'\cos\theta'}}r'^2\sin\theta'\mathrm dr'\mathrm d\theta'\mathrm d\varphi'
\label{_auto3} \tag{3}
\end{equation}
$$

$$
\begin{equation}
    =\frac{\sigma_0a^2}2\int_0^\pi \frac{\cos\theta'\sin\theta'}{\sqrt{z^2+a^2-2za\cos\theta'}}\mathrm d\theta'.
\label{_auto4} \tag{4}
\end{equation}
$$

Vi gör nu variabelsubstitutionen

$$
\begin{gather}
t=z^2+a^2-2za\cos\theta',\nonumber\\
\mathrm dt=2za\sin\theta'\mathrm d\theta',\nonumber\\
\theta'=0\to t=(z-a)^2,\nonumber\\
\theta'=\pi\to t=(z+a)^2.
\end{gather}
$$

Nämnaren i integranden blir nu $\sqrt t$, och $\sin\theta'$ i täljaren blir en del av $\mathrm dt$. Det kvarvarande $\cos\theta'$ i täljaren får vi genom att lösa ut $\cos\theta'$ ur definitionen av $t$.

Integralen är nu

$$
\begin{equation}
    \phi(\vec r)=\frac{\sigma_0}{4z^2}\int_{(z-a)^2}^{(z+a)^2} \frac{z^2+a^2-t}{2\sqrt t}\mathrm dt
\label{_auto5} \tag{5}
\end{equation}
$$

$$
\begin{equation}
    =\frac{\sigma_0}{4z^2}\left[(z^2+a^2)\int_{(z-a)^2}^{(z+a)^2} \frac1{2\sqrt t}\mathrm dt-\int_{(z-a)^2}^{(z+a)^2} \frac{\sqrt t}2\mathrm dt\right]
\label{_auto6} \tag{6}
\end{equation}
$$

$$
\begin{equation}
    =\frac{\sigma_0}{4z^2}\left[(z^2+a^2)\Big[\sqrt t\Big]_{(z-a)^2}^{(z+a)^2} -\frac13\Big[t^{3/2}\Big]_{(z-a)^2}^{(z+a)^2}\right]
\label{_auto7} \tag{7}
\end{equation}
$$

$$
\begin{equation}
    =\frac{\sigma_0}{4z^2}\left[(z^2+a^2)\Big[|z+a|-|z-a|\Big]-\frac13\Big[|z+a|^3-|z-a|^3)\Big]\right].
\label{phi} \tag{8}
\end{equation}
$$

Kom ihåg att för alla reella tal $x$ så är $\sqrt{x^2}=|x|$. Vi får alltså fyra fall - $|z|>a$ och $|z|0$ och $z<0$ - som måste behandlas var för sig.

### Inuti sfären

Eftersom att $a>z$ så är $|z+a|=z+a$ och $|z-a|=a-z$, oavsett om $z$ är positiv eller negativ. Två av de fyra fallen kan alltså studeras samtidigt.

Insättning av absolutbeloppen ovan i ekvation ([phi](#phi)) ger, efter en smula algebra, att $\phi(\vec r)=\sigma_0 z/3$. Vektorfältet blir då

$$
\begin{equation}
    \vec F =-\hat z\frac{\partial\phi}{\partial z}=-\frac{\sigma_0}3\hat z.
\label{_auto8} \tag{9}
\end{equation}
$$

### Utanför sfären

Här måste positiva och negativa $z$ studeras var för sig.

När $z>a>0$ är $|z+a|=z+a$ och $|r-a|=r-a$. Substitution i ([phi](#phi)) och förenkling av resultatet ger att $\phi(\vec r)=\sigma_0a^3/3z^2$. Vektorfältet blir

$$
\begin{equation}
    \vec F =-\hat z\frac{\partial\phi}{\partial r}=\frac{2\sigma_0a^3}{3z^3}\hat z,\quad z>0. \label{z+} \tag{10}
\end{equation}
$$

När $|z|>a$ men $z<0$ så blir $|z+a|=-(z+a)$ och $|z-a|=-(z-a)$. Den här gången blir $\phi(\vec r)=-\sigma_0a^3/3z^2$ och

$$
\begin{equation}
    \vec F =-\hat z\frac{\partial\phi}{\partial r}=-\frac{2\sigma_0a^3}{3z^3}\hat z,\quad z<0.\label{z-} \tag{11}
\end{equation}
$$

Notera att alla kvantiteter utom $z$ i ([z+](#z+)) och ([z-](#z-)) är konstanter; de ändrar alltså aldrig tecken. Därför kan vi kombinera dessa två ekvationer, och uttrycka fältet överallt utanför sfären som

$$
\begin{equation}
    \vec F =-\hat z\frac{\partial\phi}{\partial r}=-\frac{2\sigma_0a^3}{3|z|^3}\hat z.
\label{_auto9} \tag{12}
\end{equation}
$$

### Kontroll: ytladdning

Notera att vårt fält, betraktat längs symmetriaxeln, har en diskontinuitet vid $z=\pm a$. Denna motsvarar ytladdningen. T.ex., vid $\theta = 0$ (dvs $z=+a$) har vi enligt uppgiftsformuleringen en ytladdning med styrkan $\sigma_0$. Detta kan vi verifiera genom att räkna ut

$$
\begin{equation}
\hat{z} \cdot \left( \vec{F}_+ - \vec{F}_- \right)_{z=a} = \frac{2\sigma_0}{3} - \frac{-\sigma_0}{3} = \sigma_0.
\label{_auto10} \tag{13}
\end{equation}
$$


```python
#finds equilibrium concentrations given a list of reactants, products, coefficients of reactants, coefficients of products,
#and equilibrium constant k(c)
#If UseAllReactants is set to True, the reaction will proceed until a reactant is completely used up... "there's no stopping this party!"
#It is worth noting that this function cannot handle situations where a reactant creates creates like two products... I
#would have to implement an element class or something. That's a lot of work.
def FindEquilibrium(reactants, products, coRe, coPro, k, UseAllReactants=False):
    import numpy as np
    import sympy as sp
    from sympy import solveset, S, FiniteSet
    x= sp.Symbol("x", positive=True)
    lhs=1
    rhs=k
    for i in range(len(reactants)):
        rhs*=(reactants[i]-coRe[i]*x)**coRe[i]
    for i in range(len(products)):
        lhs*=(products[i]+coPro[i]*x)**coPro[i]
    if(not UseAllReactants):
        print(rhs,"=",lhs)
        ans=solveset(lhs-rhs,x, domain=S.Reals)
    else:
        ans=FiniteSet(min(reactants))
        print("UseAllReactants is set to True. Reaction will proceed until a reactant is consumed completely.")
    print("x=",ans)
    valid_x_list=[]
    react_out=[]
    prod_out=[]
    for i in range(len(ans)):
        broken=False
        for j in range(len(reactants)):
            if(reactants[j]-ans.args[i]<0):
                broken=True
                break
        for j in range(len(products)):
            if(products[j]+ans.args[i]<0):
                broken=True
                break
        if(not broken):
            valid_x_list.append(ans.args[i])
            print("\nusing valid x: ",ans.args[i])
            for j in range(len(reactants)):
                print("r"+str(j+1)+": ",reactants[j]-ans.args[i]*coRe[j])
                react_out.append(reactants[j]-ans.args[i]*coRe[j])
            for j in range(len(products)):
                print("p"+str(j+1)+": ",products[j]+ans.args[i]*coPro[j])
                prod_out.append(products[j]+ans.args[i]*coPro[j])
    return np.array(valid_x_list,dtype=np.float),np.array(react_out,dtype=np.float),np.array(prod_out,dtype=np.float)

#FindEquilibrium([0.218],[1.34e-2,1.93],[0],[1,1],1.8e-4)

#finds pH using salt molarity and Ka or Kb of the salt. Make sure you give Ka of known substance
#if the acid is on LHS and Kb if base is on LHS
#eg. If acid Ka is known and it combines into some salt Conj. Acid+Conj. Base, input is Kb=1e-14/Ka
def FindPhOfSalt(molarity,Ka=None,Kb=None):
    import numpy as np
    pH=np.array([]);
    if(Ka):
        x_val,_,_=FindEquilibrium([molarity],[0,0],[1],[1,1],Ka)
        pH=-np.log10(np.array(x_val).astype(np.float))
    elif(Kb):
        x_val,_,_=FindEquilibrium([molarity],[0,0],[1],[1,1],Kb)
        pH=14+np.log10(np.array(x_val).astype(np.float))
    else:
        assert(Ka is not None and Kb is not None)
    print('pH is: ',pH)
    return pH
#FindPhOfSalt(0.217,Kb=1e-14/1.8e-5)

#Finds pH given H+ concentration
def pH(Hydronium_Concentration):
    import numpy as np
    return -np.log10(Hydronium_Concentration)

#Finds H+ concentration given pH
def Hp(pH):
    import numpy as np
    return 10**(-pH)

#Finds pH using the Henderson-Hasselbalch equation. Provide either Ka or Kb, the concentrations and whether or not
#to get a more accurate ratio of products/reactants by using ICE tables with the Ka.
def HendersonHasselbalch(react_concentration, product_concentration, Ka=None, Kb=None, PerformIce=False):
    print("--calling HendersonHasselbalch",react_concentration, product_concentration, Ka, Kb, PerformIce)
    assert(Ka is not None or Kb is not None) #ensure an equilibrium constant exists
    import numpy as np
    if(not PerformIce):
        print("pKa=",-np.log10(Ka or Kb))
        if(Ka):
            return -np.log10(Ka)+np.log10(product_concentration/react_concentration)
        else:
            return 14-(-np.log10(Kb)+np.log10(product_concentration/react_concentration))
    else:
        raise Exception('Ice tables not yet implemented.')

#Finds the change in pH, and the old and new pH when a strong acid is added to buffer solution. Note, if you use strong base
#you have to put the molarity of buffer base into molarityBufferAcid and vice versa. You also need to use 1e-14/Ka for your
#Ka. Common sense not included.
def pHChangeOfAcidInBuffer(molarityStrongAcid,molarityBufferAcid,molarityBufferBase,Ka):
    ph_i=HendersonHasselbalch(molarityBufferAcid,molarityBufferBase,Ka)
    print("initial ph :",ph_i)
    out=FindEquilibrium([molarityStrongAcid,molarityBufferBase],[molarityBufferAcid],[1,1],[1],1,UseAllReactants=True)
    product_con=out[1][1]
    react_con=out[2][0]
    ph_f=HendersonHasselbalch(react_con,product_con,Ka)
    print("final ph :",ph_f)
    dPh=ph_f-ph_i
    print("dPh :",dPh)
    return dPh

#
def BufferBuilder(TargetPh, Ka):
    import numpy as np
    ratio=10**(TargetPh+np.log10(Ka))
    print("moles reactant: ",1/ratio,"\nmoles product: ",ratio/ratio)
    return np.array([ratio/ratio,1/ratio],dtype=np.float)

BufferBuilder(9.85,4.8e-11)
#pHChangeOfAcidInBuffer(0.089,0.419,0.341,4.5e-4)
#HendersonHasselbalch(0.463, 0.22, Ka=4.8e-11)
#pH(3.01e-3)
#Hp(3.516)

#k2=exp(-dH/8.3145e-3*(1/T2-1/T1)+log(k1))
#np.exp(198/8.3145e-3*(1/1260-1/1150)+np.log(0.365))

#Gets you the new reaction constant given the old and new temperatures and the enthalpy of something.
def GetNewKConstant(k1,T1,T2,deltaH):
    return np.exp(-deltaH/8.3145e-3*(1/T2-1/T1)+np.log(k1))

#no clue what's goin on here. Logic galore.
def GetPhTitrate(molarityReact1,volReact1,molarityReact2,volReact2,kaReact1,type1Weak=False,type2Weak=False,type1="acid",type2="base",r1Coeff=1,r2Coeff=1):
    #moles OH-/H+ from weak reactant
    assert((type1 is "acid") or (type1 is "base"))#assert they are acids or bases
    assert((type2 is "acid") or (type2 is "base"))
    assert(type1 is not type2)#assert they are not the same
    print("--calling GetPhTitrate")#log entry
    mols1=molarityReact1*volReact1*r1Coeff
    mols2=molarityReact2*volReact2*r2Coeff
    excess=np.abs(mols1-mols2)
    totalVol=volReact1+volReact2
    if((not type1Weak) or (not type2Weak)):#if either are strong
        print("One of inputs is strong. Reaction will proceed 100%.")
        if(excess<1e-15):#same amount of acid and base, one of them is strong
            print("Equal amounts detected")
            if(type1Weak):#if type1 is the weak one
                if(type1=="acid"):
                    return 14-pH(mols1/r2Coeff/totalVol)#return pH of the conjugate base
                else:
                    return pH(mols1/r2Coeff/totalVol)#return pH of conjugate acid
            elif(type2Weak):#type2 is weak one
                if(type2=="acid"):
                    return 14-pH(mols2/r1Coeff/totalVol)#return pH of the conjugate base
                else:
                    return pH(mols2/r1Coeff/totalVol)#return pH of conjugate acid
            else:#both are strong
                return 7
        else:#not equal amounts
            if(type1Weak^type2Weak):#xor, one strong, one weak
                molsStrong=(not type1Weak)*mols1+(not type2Weak)*mols2
                molsWeak=(type1Weak)*mols1+(type2Weak)*mols2
                if(molsStrong>molsWeak): #more strong than weak
                    print("Excess strong\nStrong concentration: ",molsStrong,"\nWeak concentration: ",molsWeak)
                    strongMolarity=excess/(volReactWeak1+volReactStrong)
                    pHr=pH(strongMolarity)
                    if(weaktype=='Acid'):
                        print("pH is: ",14-pHr)
                        return pHr
                    else:
                        print("pH is: ",pHr)
                        return 14-pHr
                else: #more weak than strong
                    print("Excess weak\nStrong concentration: ",molsStrong,"\nWeak concentration: ",molsWeak)
                    molsUsed=molsStrong
                    molsRem=molsWeak-molsUsed
                    molsSaltFormed=molsUsed
                    #for debugging
                    #print(molsUsed,molsRem,molsSaltFormed)
                    kaAcid=kaReact1*(type1=="acid")+1e-14/kaReact1*(type=="base")
                    if(weaktype=='Acid'):
                        pHr=HendersonHasselbalch(molsRem,molsUsed,Ka=kaAcid)
                        print("pH is: ",pHr)
                        return pHr
                    else:
                        pHr=HendersonHasselbalch(molsRem,molsUsed,Kb=kaAcid)
                        print("pH is: ",14-pHr)
                        return 14-pHr
            elif((not type1Weak) and (not type2Weak)):#both strong, not equal amounts
                excessAcid=(mols1>mols2)==(type1=="acid")
                #false,type1 acid=true --greater base
                #true ,type1 acid=true --greater acid
                #false,type1 acid=false -greater acid
                #true ,type1 acid=false -greater base
                return (not excessAcid)*14+pH(excess)*((excessAcid*2)-1)
    else:
        raise Exception("both are weak, what the hell do you want from me?")

#GetNewKConstant(322,350,453,-11.7)
```

    moles reactant:  2.9427865512974045
    moles product:  1.0

```python
#workspace
import numpy as np
import sympy as sp
from sympy.abc import x, g,n
from math import degrees, radians
from math import *

pHChangeOfAcidInBuffer(0,0.181,0.327,4e-10)
-np.log10(1e-14/4.2e-4)+np.log10(0.294/0.288)
```

    --calling HendersonHasselbalch 0.181 0.327 4e-10 None False
    pKa= 9.397940008672037
    initial ph : 9.654809186463138
    UseAllReactants is set to True. Reaction will proceed until a reactant is consumed completely.
    x= {0}

    using valid x:  0
    r1:  0
    r2:  0.327000000000000
    p1:  0.181000000000000
    --calling HendersonHasselbalch 0.181 0.327 4e-10 None False
    pKa= 9.397940008672037
    final ph : 9.654809186463138
    dPh : 0.0

    10.632204133050827


# 第五讲：生成学习算法、高斯判别分析、朴素贝叶斯算法

# 第四部分：生成学习算法（Generative Learning algorithms）

截至目前，我们主要在讨论关于条件$x$下$y$的期望$p(y\mid x;\theta)$的学习模型。比如逻辑回归模型$p(y\mid x;\theta)$中的假设$h_\theta(x)=g(\theta^Tx)$，其中$g$是S型函数。在后面的学习中，我们将介绍一些其他类型的学习算法。

考虑这样一种分类问题，我们想要根据一些动物的特征，区分大象（$y=1$）和小狗（$y=0$）。对于已知的训练集，诸如逻辑回归、感知算法基本上都是尝试找到一条直线，也就是判别边界，以区分大象样本和小狗样本。当需要判别一个新的动物样本时，算法会计算新动物落在判别边界的哪边，进而给出预测结果。

现在来看另一种算法实现。首先，我们观察大象的样本，然后建立一个描述大象的模型；接着，观察小狗样本，建立另一个描述小狗的模型；最后，当需要判断一个新的动物样本是大象还是小狗时，先用样本和大象模型做比对，再用样本和小狗模型做比对，然后看新样本究竟是更像大象还是更像小狗。

那些想要直接学习到$p(y\mid x)$的算法（如逻辑回归），或是想要从输入的特征值空间$\mathcal{X}$直接映射到结果空间${0,1}$上的算法（如感知算法），通常被称作**判别（discriminative）**学习算法。下面我们将要介绍的算法并不对$p(y\mid x)$或$p(y)$建模，这些算法称作**生成（generative）**学习算法，它主要研究给定类别下某种特定特征的概率分布。举个例子，用$y$表示样本是大象（$1$）或是小狗（$0$），则$p(x\mid y=1)$就是类别为大象时，特征的分布模型；同样的，$p(x\mid y=0)$就是类别为小狗时，特征的分布模型。

在对$p(y)$（称为**类先验（class priors）**）和$p(x\mid y)$建模之后，我们就可以使用贝叶斯公式推导出条件$x$下$y$的后验概率：

$$p(y\mid x)=\frac{p(x\mid y)p(y)}{p(x)}$$

上式中的分母来自$p(y)=p(x\mid y=1)p(y=1)+p(x\mid y=0)p(y=0)$（这也是全概率公式的应用），也可以通过前面学到的方式，使用$p(x\mid y)$和$p(y)$表示。实际上，如果是为了做出预测而需要知道$p(y\mid x)$，则并不需要计算出分母的值，因为：

$$\begin{align}\mathrm{arg}\operatorname*{max}_yp(y\mid x)&=\mathrm{arg}\operatorname*{max}_y\frac{p(x\mid y)p(y)}{p(x)}\\&=\mathrm{arg}\operatorname*{max}_yp(x\mid y)p(y)\end{align}$$

$\mathrm{arg}\displaystyle\operatorname*{arg}_y$表示使能够后面跟的式子取最大值的$y$的取值，比如$\mathrm{arg}\displaystyle\operatorname*{min}_x(x-5)^2=5$。这个式子的意思是，求给定$x$的条件下，最有可能的$y$。然后根据贝叶斯公式求最大的$y$，因为$p(x)$独立于$y$，所以分母的取值并不影响式式子取到最大值时$y$的取值。而如果$p(y)$恰好是均匀分布（即对每一种$y$取值，$p(y)$都是一样的，如$p(y=0)=p(y=1)$），那么最后要求的就是使$p(x\mid y)$最大的那个$y$。

## 1. 高斯判别分析（Gaussian discriminant analysis）

我们的第一个生成学习算法的例子是高斯判别分析（GDA）。在这个模型中，我们假设$p(x\mid y)$服从多元正态分布（multivariate normal distribution）。在讨论高斯判别分析之前，我们先来简要回顾一下多元正态分布的特性。

### 1.1 多元正态分布

$n$维多元正态分布也称作多元高斯分布，由一个**期望向量（mean vector）**$\mu\in\mathbb{R}^n$和一个**协方差矩阵（covariance matrix）**$\varSigma\in\mathbb{R}^{n\times n}$参数化，其中$\varSigma\geq 0$是一个对称且半正定的矩阵，通常记为$\mathcal{N}(\mu,\varSigma)$，其概率密度函数为：

$$p(x;\mu,\varSigma)=\frac{1}{(2\pi)^{n/2}\lvert\varSigma\rvert^{1/2}}\exp\left(-\frac{1}{2}(x-\mu)^T\varSigma^{-1}(x-\mu)\right)$$

式中的$\lvert\varSigma\rvert$表示$\varSigma$的行列式。

对于服从$\mathcal{N}(\mu,\varSigma)$的随机变量$X$的期望为：

$$\mathrm{E}[X]=\int_xx\ p(x;\mu,\varSigma)\mathrm{d}x=\mu$$

随即向量$Z$（是一组指[联合分布](http://www.cnblogs.com/vamei/p/3224111.html)的随机变量$(X_1,X_2,\cdots,X_n)$组成的向量）的[协方差（covariance）](https://en.wikipedia.org/wiki/Covariance#Properties)定义为：

$$\mathrm{Cov}(Z,Z)=\mathrm{E}\left[(Z-\mathrm{E}[Z])(Z-\mathrm{E}(Z))^T\right]$$

这个公式也将单一实随机变量的[方差一般化](https://en.wikipedia.org/wiki/Variance#Generalizations)了。方差也可以通过$\mathrm{Cov}(Z,Z)=\mathrm{E}[ZZ^T]-\mathrm{E}[Z](\mathrm{E}[Z])^T$。（两式是等价的，将第一个式子展开即可证明，留意常数的期望不变，而随机变量的期望本身是常数。）如果$X\sim\mathcal{N}(\mu,\varSigma)$，则：

$$\mathrm{Cov}=\varSigma$$

高斯分布概率密度的图像：

左图为期望为零（$2\times 1$零向量）协方差为$\varSigma=I$（$2\times 2$单位矩阵），这样的高斯分布也叫作标准正态分布（standard normal distribution）。中图的$\varSigma=0.6I$，右图的$\varSigma=2I$。可以看出$\varSigma$越大图像越“分散”，反之则越集中。

再来看几个例子：

这三幅图的期望仍旧是零，协方差为别为$\varSigma=\begin{bmatrix}1&0\\0&1\end{bmatrix};\quad\varSigma=\begin{bmatrix}1&0.5\\0.5&1\end{bmatrix};\quad\varSigma=\begin{bmatrix}1&0.8\\0.8&1\end{bmatrix}$。当我们将副对角线上的元素值增大时，图像沿$45^\circ$线（因为$x_1=x_2$）方向压缩了。看这三个分布的等高线图时更加明显：

接下来一组图像：

这一组图像的协方差分别为：$\varSigma=\begin{bmatrix}1&-0.5\\-0.5&1\end{bmatrix};\quad\varSigma=\begin{bmatrix}1&-0.8\\-0.8&1\end{bmatrix};\quad\varSigma=\begin{bmatrix}3&0.8\\0.8&1\end{bmatrix}$。看前两个图能够发现，当减小协方差矩阵副对角线元素至负数时，图像沿$-45^\circ$线方向压缩了。当我们取更加一般化的协方差矩阵时，等高线变为右图中的椭圆。

来看最后一组例子，这次我们取$\varSigma=I$不变，调整$\mu$：

上面的图像的期望分别为：$\mu=\begin{bmatrix}1\\0\end{bmatrix};\quad\begin{bmatrix}-0.5\\0\end{bmatrix};\quad\begin{bmatrix}-1\\-1.5\end{bmatrix}$。

### 1.2 高斯判别分析模型

现在来考虑这样一个分类问题，它的输入特征$x$为一系列连续取值的随机变量，这时我们就可以使用高斯判别分析（GDA: Gaussian Discriminant Analysis）模型了，使用多元正态分布对$p(x\mid y)$建模，有：

$$\begin{align}y&\sim\mathrm{Bernoulli}(\phi)\\x\mid y=0&\sim\mathcal{N}(\mu_0,\varSigma)\\x\mid y=1&\sim\mathcal{N}(\mu_1,\varSigma)\end{align}$$

写出分布：

$$\begin{align}p(y)&=\phi^y(1-\phi)^{1-y}\\p(x\mid y=0)&=\frac{1}{(2\pi)^{n/2}\lvert\varSigma\rvert^{1/2}}\exp\left(-\frac{1}{2}(x-\mu_0)^T\varSigma^{-1}(x-\mu_0)\right)\\p(x\mid y=1)&=\frac{1}{(2\pi)^{n/2}\lvert\varSigma\rvert^{1/2}}\exp\left(-\frac{1}{2}(x-\mu_1)^T\varSigma^{-1}(x-\mu_1)\right)\\p(x)&=p(x\mid y=0)p(y=0)+p(x\mid y=1)p(y=1)\end{align}$$

模型中的参数为$\phi,\ \varSigma,\ \mu_0,\ \mu_1$（尽管有两个不同的期望$\mu_0,\ \mu_1$，该模型通常也只是使用一个协方差矩阵$\varSigma$）。参数的对数似然函数为：

$$\begin{align}\mathscr{l}(\phi,\mu_0,\mu_1,\varSigma)&=\log\prod_{i=1}^mp\left(x^{(i)},y^{(i)};\phi,\mu_0,\mu_1,\varSigma\right)\\&=\log\prod_{i=1}^mp\left(x^{(i)}\mid y^{(i)};\mu_0,\mu_1,\varSigma\right)p\left(y^{(i)};\phi\right)\end{align}$$

对比一下前面学过的逻辑回归的对数似然函数：$\mathscr{l}(\theta)=\log\displaystyle\prod_{i=1}^mp\left(y^{(i)}\mid x^{(i)};\theta\right)$，我们称此式为条件似然函数，而上面的式子叫联合似然函数。（化简到第二步时使用了链式法则，后文中有详细介绍。）

为了选择适当的参数使$\mathscr{l}$能够取到最大值，我们需要计算下列参数的最大似然估计：

$$\begin{align}\phi&=\frac{1}{m}\sum_{i=1}^m1\{y^{(i)}=1\}\\\mu_0&=\frac{\sum_{i=1}^m1\{y^{(i)}=0\}x^{(i)}}{\sum_{i=1}^m1\{y^{(i)}=0\}}\\\mu_1&=\frac{\sum_{i=1}^m1\{y^{(i)}=1\}x^{(i)}}{\sum_{i=1}^m1\{y^{(i)}=1\}}\\\varSigma&=\frac{1}{m}\sum_{i=1}^m\left(x^{(i)}-\mu_{y^{(i)}}\right)\left(x^{(i)}-\mu_{y^{(i)}}\right)^T\end{align}$$

将该算法用图像表示如下：

图中除了训练集样本外，还有已经对两类数据做过拟合的高斯分布的等高线图。注意到这两个高斯分布的等高线图形状、方向都是是一样的，因为我们使用了同一个协方差矩阵$\varSigma$，只是期望$\mu_0,\ \mu_1$不同。图中的直线就是由$p(y=1\mid x)=0.5$得到的判定边界了。在边界的一侧，样本将得到$y=1$的预测，另一侧则会得到$y=0$的预测。

### 1.3 讨论：高斯判别分析与逻辑回归

高斯判别分析模型与逻辑回归有着有趣的联系。如果我们将$p(y=1\mid x;\phi,\mu_0,\mu_1,\varSigma)$看作是关于$x$的函数，则有：

$$p(y=1\mid x;\phi,\mu_0,\mu_1,\varSigma)=\frac{1}{\exp\left(\theta^Tx\right)}$$

式中的$\theta$是一个关于$\phi,\mu_0,\mu_1,\varSigma$的函数。（同前面一样，我们令$x_0^{(i)}=1$，于是的$x^{(i)}$变为$n+1$维向量。）这正是逻辑回归中的假设函数，用于对$p(y=1\mid x)$建模。（也就是说，如果我们在使用高斯判别建模时假设$x\mid y\sim\mathrm{Gaussian}$，则可以推导出$p(y\mid x)$是一个逻辑函数。）

通常来讲，针对同一训练集，高斯判别分析和逻辑回归将给出不同的判定边界，那么，我们该如何在这两种算法里取舍？

在上面我们证明了，如果$p(x\mid y)$服从多元高斯分布（各元变量使用同一$\varSigma$）则$p(y\mid x)$是一个逻辑函数。但是反过来则不一定，如果$p(y\mid x)$是一个逻辑函数，并不能推导出$p(x\mid y)$是多元高斯分布。这说明，相对于逻辑回归，高斯判别分析对数据做出了*更强*的模型假设（我们假设了在$y$条件下$x$服从高斯分布，这是一个很强的假设）。也就是说，在假设模型正确（或$y\mid x$大致服从高斯分布）的前提下，同逻辑回归相比，高斯判别分析能够对数据做出更好的拟合，得到更好的模型（我们显示的认定了训练集数据服从高斯分布，则如果我们在算法中使用了这个假设，那么这个算法的表现将更好，因为这个算法利用了跟多关于数据的信息，算法预先就“知道”数据服从高斯分布）。特别是当$p(x\mid y)$确实大致服从多元高斯分布（各元变量使用同一$\varSigma$）时，高斯判别分析是**渐近有效（asymptotically efficient）**的。非正式的讲，对于较大的的有限训练集（$m$足够大），没有任何算法能够比高斯判别分析做的更好。（即度量算法对$p(y\mid x)$评估的准确度）。特别的，对于本例中的数据，高斯判别分析确实是更好的算法。对于更加普通的例子，即使面对较小的训练集，高斯判别分析也通常更优秀（因为我们预先做了足够强的假设，即使仅使用非常少的数据，高斯判别分析也能得出一个还不错的模型。）

但从另一个角度讲，对于很弱的假设，逻辑回归更加健壮，它对异常模型假设更加不敏感。有许多不同的假设最终都能以逻辑函数的形式推导出$p(y\mid x)$。举个例子，如果$x\mid y=0\sim\mathrm{Poisson}(\lambda_0);\quad x\mid y=1\sim\mathrm{Poisson}(\lambda_1)$，则$p(y\mid x)$也将表达为逻辑函数的形式。可见逻辑回归对服从泊松分布的数据依然有着良好的效果，但是如果使用高斯判别分析，使用高斯分布拟合非高斯分布的数据，那么得出的结果将变得不可靠，也就是使用高斯判别分析的效果会大打折扣。（如果在不确定$x\mid y$的分布情况的情况下，那么逻辑回归的表现可能更好。因为逻辑回归做的预先假设更少，所以对模型假设方面更为健壮，不过与高斯模型比起来，可能会需要更多的训练样本。）

（实际上，更一般的有：$x\mid y=0\sim\mathrm{ExponentialFamily}(\eta_0);\quad x\mid y=1\sim\mathrm{ExponentialFamily}(\eta_1)$，则能够推导出$p(y\mid x)$会是一个逻辑函数。也就是说，即使$x\mid y$服从伽马、贝塔等任何指数族中的分布时，所得的后验概率$p(y=1\mid x)$都将是逻辑函数。）

综上，当模型假设正确或近似正确时，高斯判别分析能够做出更强的模型假设，且对训练数据效率更高（即使使用少量样本依然能够得到较好的效果，收敛更加迅速）。逻辑回归得到的假设较弱，对偏离模型假设的数据表现出良好的健壮性（更加抗噪音）。特别是对于明显不属于高斯分布，在较大的有限训练集支持下，逻辑回归得到的结果通常都比高斯判别分析更好。也正是因为这个原因，实际使用中我们更加偏向于选择逻辑回归。（一些关于判别模型与生成模型的对比同样适用于我们接下来介绍的朴素贝叶斯算法，不过朴素贝叶斯算法仍然不失为一种良好的、应用广泛的分类算法。）

## 2. 朴素贝叶斯算法（Naive Bayes）

在高斯判别分析中，输入特征$x$是一个连续的实向量。现在我们来看另一种学习算法，其输入特征$x$是离散的。

继续考虑前面关于垃圾邮件过滤器的例子，这次我们希望判断一封邮件是强推广告（垃圾）还是正常邮件。如果实现了这个功能，我们的邮件阅读器就可以实现自动过滤或者将垃圾邮件放在不同的文件夹中了。邮件分类问题属于一个叫做**文本分类（text classification）**问题的大类。

假设有训练集（标有垃圾或正常标签的邮件集合），我们通过选取特征$x_i$描述邮件，进而设计我们自己的邮件过滤器。

邮件特征值的个数为我们“邮件词典”中单词的数量，将这些特征值组成向量。如果某邮件中含有词典里第$i$个词，则该邮件特征值向量的第$i$个分量置为$1$，即$x_i=1$，反之$x_i=0$，比如特征值向量$x=\begin{array}{|c|c}1&\mathrm{a}\\0&\mathrm{aardvark}\\0&\mathrm{aardwolf}\\\vdots&\vdots\\1&\mathrm{buy}\\\vdots&\vdots\\0&\mathrm{zygmurgy}\end{array}$，表示的该邮件中包含“a”和“buy”，而不包含“aardvark”、“aardwolf”和“zygmurgy”。

编码在特征值向量中的词集称为**词汇表（vocabulary）**，所以邮件特征值向量的维数和词汇表的大小是一致的。（为了节省内存、CPU资源，我们会精心设计词汇表，对诸如“a”、“the”、“of”、“and”等使用频率非常高，而对邮件内容影响很小的词（这些词几乎会出现在所有文章中，也称为**stop words**），我们通常将他们排除在词典之外。）

选定邮件特征值向量之后，就可以着手设计生成学习算法了，我们需要对$p(x\mid y)$建模。但是，假设我们的词汇表有$50000$个条目，则$x\in\{0,1\}^{50000}$（即$x$是一个由由$0,\ 1$组成的$50000$维向量），如果我们按正常步骤使用上一讲的softmax回归对$x$建模，则会得到一个有$2^{50000}$种可能输出的模型，为了给每一种可能的输出设定一个参数，接下来还需要设计一个$(2^{50000}-1)$维的参数向量……很明显，参数过多了。

为了给$y(x\mid y)$建模，我们将做出一个很强（严格）的假设：设$x^{(i)}$对于给定的条件$y$是独立的，这个假设叫做**朴素贝叶斯假设（Naive Bayes (NB) assumption）**，而根据此假设得到的算法称为**朴素贝叶斯分类器（Naive Bayes classifier）**。举个例子，设$y=1$表示垃圾邮件，“buy”是词汇表中第$2087$个词，“price”是词汇表中第$39831$个词，于是我们做出假设：一旦给定$y=1$（则该邮件已经被确认是垃圾邮件），则$x_{2087}$（即“buy”是否出现在邮件中）不会对我们关于$x_{39831}$（“price”是否出现）的见解构成任何影响。更加形式化的表达：$p(x_{2087}\mid y)=p(x_{2087}\mid y,x_{39831})$（“price”是否出现并不影响条件$y$下“buy”出现的概率）。（注意，该式并*不是*用来描述$x_{2087},\ x_{39831}$相互独立这一假设，描述这一假设的写法应为$p(x_{2087})=p(x_{2087}|x_{39831})$；我们这里做出的假设是在*给定的条件$y$下*$x_{2087}$与$x_{39831}$相互独立。）

于是有：

$$\begin{align}p(x_1,\cdots,x_{50000}\mid y)&=p(x_1\mid y)p(x_2\mid y,x_1)p(x_3\mid y,x_1,x_2)\cdots\ p(x_{50000}\mid y,x_1,\cdots,x_{49999})\\&=p(x_1\mid y)p(x_2\mid y)p(x_3\mid y)\cdots p(x_{50000}\mid y)\\&=\prod_{i=1}^n p(x_i\mid y)\end{align}$$

第一个等式使用了[联合分布的链式法则](https://en.wikipedia.org/wiki/Joint_probability_distribution#Density_function_or_mass_function)（参见[链式法则](https://en.wikipedia.org/wiki/Chain_rule_%28probability%29)），第二个等式使用了前面的的朴素贝叶斯假设。尽管朴素贝叶斯假设是一个很强的假设条件，但是该算法得到的模型对于很多问题都有良好的效果。

我们的模型由参数$\phi_{i\mid y=1}=p(x_i=1\mid y=1),\ \phi_{i\mid y=0}=p(x_i=1\mid y=0),\ \phi_y=p(y=1)$确定。与以往的步骤一样，有了训练集$\left\{\left(x^{(i)},y^{(i)}\right);i=1,\cdots,m\right\}$，我们可以写出关于参数的联合似然函数：

$$\begin{align}\mathcal{L}\left(\phi_y,\phi_{j\mid y=0},\phi_{j\mid y=1}\right)&=\prod_{i=1}^mp\left(x^{(i)},y^{(i)}\right)\end{align}$$

求$\mathcal{L}$关于$\phi_y,\phi_{j\mid y=0},\phi_{j\mid y=1}$的最大值，得到三个参数的最大似然估计：

$$\begin{align}\phi_{j\mid y=1}&=\frac{\sum_{i=1}^m1\left\{x_j^{(i)}=1\land y^{(i)}=1\right\}}{\sum_{i=1}^m1\left\{y^{(i)}=1\right\}}\\\phi_{j\mid y=0}&=\frac{\sum_{i=1}^m1\left\{x_j^{(i)}=1\land y^{(i)}=0\right\}}{\sum_{i=1}^m1\left\{y^{(i)}=0\right\}}\\\phi_y&=\frac{\sum_{i=1}^m1\left\{y^{(i)}=1\right\}}{m}\end{align}$$

上式中的$\land$表示逻辑“且”。这些参数都有很自然的表达，比如$\phi_{j\mid y=1}$就是指含有单词$x_j$的垃圾邮件在所有垃圾邮件中出现的概率。

求参数拟合后，若要对具有特征$x$的新样本进行预测我们就可以使用贝叶斯公式进行预测了：

$$
\begin{align}p(y=1\mid x)&=\frac{p(x\mid y=1)p(y=1)}{p(x)}\\
&=\frac{\left(\prod_{i=1}^np(x_i\mid y=1)\right)p(y=1)}{\left(\prod_{i=1}^np(x_i\mid y=1)\right)p(y=1)+\left(\prod_{i=1}^np(x_i\mid y=0)\right)p(y=0)}\end{align}
$$

然后选取后验概率较大的那一类即可做出预测。

到目前为止，我们学习的朴素贝叶斯算法，主要针对二元特征值$x_i$（取$\{0,1\}$），下面介绍的算法适用于一般化的在$1,2,\cdots,k$取值的特征值$x_i$。

我们只需要将$p(x_i\mid y)$从伯努利分布改为多项分布即可，即使存在一些取连续值的特征值（比如在前几讲例子中提到的的“房屋实用面积”），我们也可以把它**离散化（discretize）**（方法很简单，就是将连续的取值分区间映射在一个较小的集合中），进而继续使用朴素贝叶斯算法。举个例子：我们使用特征值$x_i$描述“实用面积”，将连续值离散化的方法为：

$$\begin{array}{c|c|c|c|c|c}\mathrm{Living area(sq.\ feet)}&\lt 400&400-800&800-1200&1200-1600&\gt 1600\\\hline x_i&1&2&3&4&5\end{array}$$

因此，如果有面积为$890$平尺实用面积的房子，我们就将它的特征值$x_i$设为$3$。然后像前面一样，继续使用朴素贝叶斯算法，使用多项分布对$p(x_i\mid y)$建模。如果原始特征值取的是连续值，不能通过使用多项分布来建立良好的模型时，则可以离散化这些特征值，然后选择朴素贝叶斯算法（而不是高斯判别分析）通常会得到较好的分类器模型。

## 2.1 拉普拉斯平滑（Laplace smoothing）

上面介绍的朴素贝叶斯算法，对于很多问题都有良好的效果，不过我们只要做一个小改动，就能让它变得更好，特别是对文本分类问题。现在我们来简单的演示一个在当前的朴素贝叶斯算法下可能会遇到的“漏洞”，之后我们会讨论如何修补这个“漏洞”。

仍旧是垃圾邮件分类器，假设我们在本课程结束（CS229）之后的结题项目中表现优秀，于是打算在2003年六月将我们的项目投稿在NIPS会议上。（NIPS是最顶尖的机器学习会议之一，其投稿截止时间通常在六月底七月初。）因为这个原因，我们会在截止日期前的邮件中使用“nips”这个词语，同时也会在这段时间开始收到带有“nips”这个词语的邮件。不过，这是我们在NIPS上的第一次投稿，此前我们从没在邮件中使用过“nips”。我们说的更绝对一些，“nips”从未出现在我们的“垃圾/正常邮件训练集”中。假设“nips”是词典中的第35000个单词，于是我们的贝叶斯邮件过滤器将计算参数$\phi_{35000\mid y}$的最大似然估计：

$$\begin{align}\phi_{35000\mid y=1}&=\frac{\sum_{i=1}^m1\left\{x_{35000}^{(i)}=1\land y^{(i)}=1\right\}}{\sum_{i=1}^m1\left\{y^{(i)}=1\right\}}=0\\\phi_{35000\mid y=0}&=\frac{\sum_{i=1}^m1\left\{x_{35000}^{(i)}=1\land y^{(i)}=0\right\}}{\sum_{i=1}^m1\left\{y^{(i)}=0\right\}}=0\end{align}$$

也就是说，如果以前（训练集）从未见过“nips”这个词，则模型认为该词在任何一类邮件中出现的概率皆为零。于是，当我们需要预测一封带有“nips”的邮件是否为垃圾邮件时，模型会计算其后验概率：

$$\begin{align}p(y=1\mid x)&=\frac{p(x\mid y=1)p(y=1)}{p(y=1)}\\&=\frac{\left(\prod_{i=1}^np(x_i\mid y=1)\right)p(y=1)}{\left(\prod_{i=1}^np(x_i\mid y=1)\right)p(y=1)+\left(\prod_{i=1}^np(x_i\mid y=0)\right)p(y=0)}\\&=\frac{0}{0}\end{align}$$

容易看出，$\prod_{i=1}^np(x_i\mid y)$项中有$p(x_{35000}\mid y)=0$作为乘数，所以最终得到$\frac{0}{0}$，然后就不知所措了。

扩展这个问题，从统计的角度讲，“认为从未发生过（即训练集中没有的）的事件不可能发生”是一个坏点子。带着这个问题，我们来看一下在$\{1,\cdots,k\}$中取值的多项随机变量$z$的期望估计：我们可以使用$\phi_i=p(z=i)$参数化这个多项分布，对于有$m$个独立观测集合$\left\{z^{(1)},\cdots,z^{(i)}\right\}$，参数的极大似然估计为：

$$\phi_j=\frac{\sum_{i=1}^m1\left\{z^{(i)}=j\right\}}{m}$$

同我们前面遇到的问题一样，如果对参数使用极大似然估计，则某些$\phi_j$可能会为零。为了避免这个现象，我们使用**拉普拉斯平滑（Laplace smoothing）**，将上面的估计替换为：

$$\phi_j=\frac{\sum_{i=1}^m1\left\{z^{(i)}=j\right\}+1}{m+k}$$

可以看到，我们给分子加了$1$，给分母加了$k$，这样，有$\sum_{j=1}^k\phi_j=1$仍然成立（这只是我们需要的性质，因为$\phi_j$是各可能结果的概率估计，其和必须为$1$）；同时，对于所有$\phi_j$有$\phi_j\neq0$成立。在特定条件下（存在争议），拉普拉斯平滑给力参数$\phi_j$一个理想的估计。

回到我们的朴素贝叶斯分类器，对参数加入拉普拉斯平滑，得到如下参数估计：

$$\begin{align}\phi_{j\mid y=1}&=\frac{\left(\sum_{i=1}^m1\left\{x_j^{(i)}=1\land y^{(i)}=1\right\}\right)+1}{\left(\sum_{i=1}^m1\left\{y^{(i)}=1\right\}\right)+2}\\\phi_{j\mid y=0}&=\frac{\left(\sum_{i=1}^m1\left\{x_j^{(i)}=1\land y^{(i)}=0\right\}\right)+1}{\left(\sum_{i=1}^m1\left\{y^{(i)}=0\right\}\right)+2}\end{align}$$

（在实际问题中，我是否对$\phi_y$做拉普拉斯平滑并不重要，因为在结果集中，对于于正常或垃圾邮件的概率$p(y=1),p(y=0)$，应该都不会出现零值。）

如此，我们的垃圾邮件分类器在第一次遇到”nips“时，也可以做出有意义的预测了。