# Sampled Softmax

For classification and prediction problems a typical criterion function is cross-entropy with softmax. If the number of output classes is high the computation of this criterion and the corresponding gradients could be quite costly. Sampled Softmax is a heuristic to speed up training in these cases. (see: [Adaptive Importance Sampling to Accelerate Training of a Neural Probabilistic Language Model](http://www.iro.umontreal.ca/~lisa/pointeurs/importance_samplingIEEEtnn.pdf), [Exploring the Limits of Language Modeling](https://arxiv.org/pdf/1602.02410v1.pdf), [What is Candidate Sampling](https://www.tensorflow.org/extras/candidate_sampling.pdf))

#### Select the notebook runtime environment devices / settings

Before we dive into the details we run some setup that is required for automated testing of this notebook.

```python
import os
import cntk as C

# Select the right target device when this notebook is being tested:
C.device.try_set_default_device(C.device.gpu(0))
```

    True

## Basics

The softmax function is used in neural networks if we want to interpret the network output as a probability distribution over a set of classes $C$ with $|C|=N_C$.

Softmax maps an $N_C$-dimensional vector $z$, which has unrestricted values, to an $N_C$ dimensional vector $p$ with non-negative values that sum up to 1 so that they can be interpreted as probabilities. More precisely:

$$
\begin{align}
p_i &= softmax(z, i)\\
    &= \frac{exp(z_i)}{\sum_{k\in C} exp(z_k)}\\
\end{align}
$$

In what follows we assume that the input $z$ to the softmax is computed from some hidden vector $h$ of dimension $N_h$  in a specific way, namely:

$$ z = W h + b $$

where $W$ is a learnable weight matrix of dimension $(N_c, N_h)$ and $b$ is a learnable bias vector.
We restrict ourselves to this specific choice of $z$ because it helps in implementing an efficient sampled softmax.

In a typical use-case like for example a recurrent language model, the hidden vector $h$ would be the output of the recurrent layers and $C$ would be the set of words to predict.

As a training criterion, we use cross-entropy which is a function of the expected (true) class $t\in C$ and the probability predicted for it:

$$cross\_entropy := -log(p_t)$$

## Sampled Softmax from the outside

For the normal softmax the CNTK Python-api provides the function [cross_entropy_with_softmax](https://cntk.ai/pythondocs/cntk.ops.html?highlight=softmax#cntk.ops.cross_entropy_with_softmax). This takes as input the $N_C$-dimensional vector $z$. As mentioned for our sampled softmax implementation we assume that this z is computed by $ z = W h + b $. In sampled softmax this has to be part of the whole implementation of the criterion.

Below we show the code for `cross_entropy_with_sampled_softmax_and_embedding`. Let’s look at the signature first.

One fundamental difference to the corresponding function in the Python-api (`cross_entropy_with_softmax`) is that in the Python api function the input corresponds to $z$ and must have the same dimension as the target vector, while in cross_entropy_with_full_softmax the input corresponds to our hidden vector $h$ can have any dimension (hidden_dim).
Actually, hidden_dim will be typically much lower than the dimension of the target vector.

We also have some additional parameters `num_samples, sampling_weights, allow_duplicates` that control the random sampling.
Another difference to the api function is that we return a triple (z, cross_entropy_on_samples, error_on_samples).

We will come back to the details of the implementation below.

```python
from __future__ import print_function
from __future__ import division

# Creates a subgraph computing cross-entropy with sampled softmax.
def cross_entropy_with_sampled_softmax_and_embedding(
    hidden_vector,            # Node providing hidden input
    target_vector,            # Node providing the expected labels (as sparse vectors)
    num_classes,              # Number of classes
    hidden_dim,               # Dimension of the hidden vector
    num_samples,              # Number of samples to use for sampled softmax
    sampling_weights,         # Node providing weights to be used for the weighted sampling
    allow_duplicates = True,  # Boolean flag to control whether to use sampling with replacemement
                              # (allow_duplicates == True) or without replacement.
    ):
    # define the parameters learnable parameters
    b = C.Parameter(shape = (num_classes, 1), init = 0)
    W = C.Parameter(shape = (num_classes, hidden_dim), init = C.glorot_uniform())

    # Define the node that generates a set of random samples per minibatch
    # Sparse matrix (num_samples * num_classes)
    sample_selector = C.random_sample(sampling_weights, num_samples, allow_duplicates)

    # For each of the samples we also need the probablity that it in the sampled set.
    inclusion_probs = C.random_sample_inclusion_frequency(sampling_weights, num_samples, allow_duplicates) # dense row [1 * vocab_size]
    log_prior = C.log(inclusion_probs) # dense row [1 * num_classes]

    # Create a submatrix wS of 'weights
    W_sampled = C.times(sample_selector, W) # [num_samples * hidden_dim]
    z_sampled = C.times_transpose(W_sampled, hidden_vector) + C.times(sample_selector, b) - C.times_transpose (sample_selector, log_prior)# [num_samples]

    # Getting the weight vector for the true label. Dimension hidden_dim
    W_target = C.times(target_vector, W) # [1 * hidden_dim]
    z_target = C.times_transpose(W_target, hidden_vector) + C.times(target_vector, b) - C.times_transpose(target_vector, log_prior) # [1]

    z_reduced = C.reduce_log_sum_exp(z_sampled)

    # Compute the cross entropy that is used for training.
    # We don't check whether any of the classes in the random samples conincides with the true label, so it might
    # happen that the true class is counted
    # twice in the normalising demnominator of sampled softmax.
    cross_entropy_on_samples = C.log_add_exp(z_target, z_reduced) - z_target

    # For applying the model we also output a node providing the input for the full softmax
    z = C.times_transpose(W, hidden_vector) + b
    z = C.reshape(z, shape = (num_classes))

    zSMax = C.reduce_max(z_sampled)
    error_on_samples = C.less(z_target, zSMax)
    return (z, cross_entropy_on_samples, error_on_samples)

```

To give a better idea of what the inputs and outputs are and how this all differs from the normal softmax we give below a corresponding function using normal softmax:

```python
# Creates subgraph computing cross-entropy with (full) softmax.
def cross_entropy_with_softmax_and_embedding(
    hidden_vector,  # Node providing hidden input
    target_vector,  # Node providing the expected labels (as sparse vectors)
    num_classes,    # Number of classes
    hidden_dim      # Dimension of the hidden vector
    ):
    # Setup bias and weights
    b = C.Parameter(shape = (num_classes, 1), init = 0)
    W = C.Parameter(shape = (num_classes, hidden_dim), init = C.glorot_uniform())

    z = C.reshape( C.times_transpose(W, hidden_vector) + b, (1, num_classes))

    # Use cross_entropy_with_softmax
    cross_entropy = C.cross_entropy_with_softmax(z, target_vector)

    zMax = C.reduce_max(z)
    zT = C.times_transpose(z, target_vector)
    error_on_samples = C.less(zT, zMax)

    return (z, cross_entropy, error_on_samples)
```

As you can see the main differences to the api function `cross_entropy_with_softmax` are:
* We include the mapping $ z = W h + b $ into the function.
* We return a triple (z, cross_entropy, error_on_samples) instead of just returning the cross entropy.

## A toy example

To explain how to integrate sampled softmax let us look at a toy example. In this toy example we first transform one-hot input vectors via some random projection into a lower dimensional vector $h$. The modeling task is to reverse this mapping using (sampled) softmax. Well, as already said this is a toy example.

```python
import numpy as np
from math import log, exp, sqrt
from cntk.logging import ProgressPrinter
import timeit

# A class with all parameters
class Param:
    # Learning parameters
    learning_rate = 0.03
    minibatch_size = 100
    num_minbatches = 100
    test_set_size = 1000
    momentum_time_constant = 5 * minibatch_size
    reporting_interval = 10
    allow_duplicates = False

    # Parameters for sampled softmax
    use_sampled_softmax = True
    use_sparse = True
    softmax_sample_size = 10

    # Details of data and model
    num_classes = 50
    hidden_dim = 10

data_sampling_distribution = lambda: np.repeat(1.0 / Param.num_classes, Param.num_classes)

softmax_sampling_weights = lambda: np.repeat(1.0 / Param.num_classes, Param.num_classes)

# Creates random one-hot vectors of dimension 'num_classes'.
# Returns a tuple with a list of one-hot vectors, and list with the indices they encode.
def get_random_one_hot_data(num_vectors):
    indices = np.random.choice(
        range(Param.num_classes),
        size=num_vectors,
        p = data_sampling_distribution()).reshape((num_vectors, 1))
    list_of_vectors = C.Value.one_hot(indices, Param.num_classes)
    return (list_of_vectors, indices.flatten())

# Create a network that:
# * Transforms the input one hot-vectors with a constant random embedding
# * Applies a linear decoding with parameters we want to learn
def create_model(labels):
    # random projection matrix
    random_data = np.random.normal(scale = sqrt(1.0/Param.hidden_dim), size=(Param.num_classes, Param.hidden_dim)).astype(np.float32)
    random_matrix = C.constant(shape = (Param.num_classes, Param.hidden_dim), value = random_data)

    h = C.times(labels, random_matrix)

    # Connect the latent output to (sampled/full) softmax.
    if Param.use_sampled_softmax:
        sampling_weights = np.asarray(softmax_sampling_weights(), dtype=np.float32)
        sampling_weights.reshape((1, Param.num_classes))
        softmax_input, ce, errs = cross_entropy_with_sampled_softmax_and_embedding(
            h,
            labels,
            Param.num_classes,
            Param.hidden_dim,
            Param.softmax_sample_size,
            softmax_sampling_weights(),
            Param.allow_duplicates)
    else:
        softmax_input, ce, errs = cross_entropy_with_softmax_and_embedding(
            h,
            labels,
            Param.num_classes,
            Param.hidden_dim)

    return softmax_input, ce, errs

def train(do_print_progress):
    labels = C.input(shape = Param.num_classes, is_sparse = Param.use_sparse)
    z, cross_entropy, errs = create_model(labels)

    # Setup the trainer
    learning_rate_schedule = C.learning_rate_schedule(Param.learning_rate, C.UnitType.sample)
    momentum_schedule = C.momentum_as_time_constant_schedule(Param.momentum_time_constant)
    learner = C.momentum_sgd(z.parameters, learning_rate_schedule, momentum_schedule, True)
    progress_writers = None
    if do_print_progress:
        progress_writers = [ProgressPrinter(freq=Param.reporting_interval, tag='Training')]
    trainer = C.Trainer(z, (cross_entropy, errs), learner, progress_writers)

    minbatch = 0
    average_cross_entropy = compute_average_cross_entropy(z)
    minbatch_data = [0] # store minibatch values
    cross_entropy_data = [average_cross_entropy] # store cross_entropy values

    # Run training
    t_total= 0

    # Run training
    for minbatch in range(1,Param.num_minbatches):
        # Specify the mapping of input variables in the model to actual minibatch data to be trained with
        label_data, indices = get_random_one_hot_data(Param.minibatch_size)
        arguments = ({labels : label_data})

        # If do_print_progress is True, this will automatically print the progress using ProgressPrinter
        # The printed loss numbers are computed using the sampled softmax criterion
        t_start = timeit.default_timer()
        trainer.train_minibatch(arguments)
        t_end = timeit.default_timer()

        t_delta = t_end - t_start
        samples_per_second = Param.minibatch_size / t_delta

        # We ignore the time measurements of the first two minibatches
        if minbatch > 2:
            t_total += t_delta

        # For comparison also print result using the full criterion
        if minbatch % Param.reporting_interval == int(Param.reporting_interval/2):
            # memorize the progress data for plotting
            average_cross_entropy = compute_average_cross_entropy(z)
            minbatch_data.append(minbatch)
            cross_entropy_data.append(average_cross_entropy)

            if do_print_progress:
                print("\nMinbatch=%d Cross-entropy from full softmax = %.3f perplexity = %.3f samples/s = %.1f"
                    % (minbatch, average_cross_entropy, exp(average_cross_entropy), samples_per_second))

    # Number of samples we measured. First two minbatches were ignored
    samples_measured = Param.minibatch_size * (Param.num_minbatches - 2)
    overall_samples_per_second = samples_measured / t_total
    return (minbatch_data, cross_entropy_data, overall_samples_per_second)

def compute_average_cross_entropy(softmax_input):
    vectors, indices = get_random_one_hot_data(Param.test_set_size)
    total_cross_entropy = 0.0
    arguments = (vectors)
    z = softmax_input.eval(arguments).reshape(Param.test_set_size, Param.num_classes)

    for i in range(len(indices)):
        log_p = log_softmax(z[i], indices[i])
        total_cross_entropy -= log_p

    return total_cross_entropy / len(indices)

# Computes log(softmax(z,index)) for a one-dimensional numpy array z in an numerically stable way.
def log_softmax(z,    # numpy array
                index # index into the array
            ):
    max_z = np.max(z)
    return z[index] - max_z - log(np.sum(np.exp(z - max_z)))

np.random.seed(1)

print("start...")
train(do_print_progress = True)
print("done.")
```

    start...
    Learning rate per sample: 0.03
    Momentum per sample: 0.9980019986673331

    Minbatch=5 Cross-entropy from full softmax = 3.844 perplexity = 46.719 samples/s = 29558.6
     Minibatch[   1-  10]: loss = 2.331738 * 1000, metric = 81.10% * 1000;

    Minbatch=15 Cross-entropy from full softmax = 3.473 perplexity = 32.244 samples/s = 29879.9
     Minibatch[  11-  20]: loss = 2.026159 * 1000, metric = 52.90% * 1000;

    Minbatch=25 Cross-entropy from full softmax = 3.067 perplexity = 21.486 samples/s = 29903.2
     Minibatch[  21-  30]: loss = 1.687327 * 1000, metric = 36.30% * 1000;

    Minbatch=35 Cross-entropy from full softmax = 2.803 perplexity = 16.495 samples/s = 30030.6
     Minibatch[  31-  40]: loss = 1.463247 * 1000, metric = 27.50% * 1000;

    Minbatch=45 Cross-entropy from full softmax = 2.478 perplexity = 11.921 samples/s = 29880.8
     Minibatch[  41-  50]: loss = 1.211603 * 1000, metric = 15.00% * 1000;

    Minbatch=55 Cross-entropy from full softmax = 2.285 perplexity = 9.824 samples/s = 29293.8
     Minibatch[  51-  60]: loss = 1.061343 * 1000, metric = 12.10% * 1000;

    Minbatch=65 Cross-entropy from full softmax = 2.082 perplexity = 8.017 samples/s = 30090.2
     Minibatch[  61-  70]: loss = 0.965689 * 1000, metric = 8.50% * 1000;

    Minbatch=75 Cross-entropy from full softmax = 1.923 perplexity = 6.842 samples/s = 31495.9
     Minibatch[  71-  80]: loss = 0.901830 * 1000, metric = 5.50% * 1000;

    Minbatch=85 Cross-entropy from full softmax = 1.767 perplexity = 5.852 samples/s = 31901.2
     Minibatch[  81-  90]: loss = 0.793732 * 1000, metric = 5.60% * 1000;

    Minbatch=95 Cross-entropy from full softmax = 1.659 perplexity = 5.256 samples/s = 30736.1
    done.

In the above code we use two different methods to report training progress:
1. Using a function that computes the average cross entropy on full softmax.
2. Using the built-in ProgressPrinter

ProgressPrinter reports how the value of the training criterion changes over time.
In our case the training criterion is cross-entropy from **sampled** softmax.
The same is true for the error rate computed by progress printer, this is computed only for true-class vs sampled-classes and will therefore underestimate the true error rate.

Therefore while ProgressPrinter already gives us some idea how training goes on, if we want to compare the behavior for different sampling strategies (sample size, sampling weights, ...) we should not rely on numbers that are computed only using the sampled subset of classes.

## Importance sampling

Often the we don't have uniform distribution for the classes on the output side. The typical example is when we have words as output classes. A typical example are words where e.g. 'the' will be much more frequent than most others.

In such cases one often uses a non uniform distribution for drawing the samples in sampled softmax but instead increases the sampling weight for the frequent classes. This is also called importane sampling.
In our example the sampling distribution is controlled by the weight array `softmax_sampling_weights`.

As an example let's look at the case where the classes are distrubted according to zipf-distrubtion like:
$$
p[i] \propto \frac{1}{i+5},
$$
actually we use this distribution already in our example.

How does training behavior change if we switch uniform sampling to sampling with the zipfian distribution in sampled softmax?

```python
# We want to lot the data
import matplotlib.pyplot as plt
%matplotlib inline

# Define weights of zipfian distributuion
def zipf(index):
    return 1.0 / (index + 5)

# Use zipifian distribution for the classes
def zipf_sampling_weights():
    return np.asarray([ zipf(i) for i in range(Param.num_classes)], dtype=np.float32)

data_sampling_distribution = lambda: zipf_sampling_weights() / np.sum(zipf_sampling_weights())

print("start...")

# Train using uniform sampling (like before)
np.random.seed(1)
softmax_sampling_weights = lambda: np.repeat(1.0/Param.num_classes, Param.num_classes)
minibatch_data, cross_entropy_data, _ = train(do_print_progress = False)

# Train using importance sampling
np.random.seed(1)
softmax_sampling_weights = zipf_sampling_weights
minibatch_data2, cross_entropy_data2, _ = train(do_print_progress = False)

plt.plot(minibatch_data, cross_entropy_data, 'r--',minibatch_data, cross_entropy_data2, 'b--')
plt.xlabel('number of mini-batches')
plt.ylabel('cross entropy')
plt.show()

```

In the example above we compare uniform sampling (red) vs sampling with the same distribution the classes have (blue).
You will need to experiment to find the best settings for all the softmax parameters.

## What speedups to expect?

The speed difference between full softmax and sampled softmax in terms of training instances depends strongly on the concrete settings, namely
* Number of classes. Typically the speed-up will increase the more output classes you have.
* Number of samples used in sampled softmax
* Dimension of hiddlen layer input
* Minibatch size
* Hardware

Also you need to test how much you can reduce sample size without degradation of the result.

```python
print("start...")

# Reset parameters
class Param:
    # Learning parameters
    learning_rate = 0.03
    minibatch_size = 8
    num_minbatches = 100
    test_set_size = 1 # we are only interrested in speed
    momentum_time_constant = 5 * minibatch_size
    reporting_interval = 1000000 # Switch off reporting to speed up
    allow_duplicates = False

    # Parameters for sampled softmax
    use_sampled_softmax = True
    use_sparse = True
    softmax_sample_size = 10

    # Details of data and model
    num_classes = 50000
    hidden_dim = 10

data_sampling_distribution = lambda: np.repeat(1.0 / Param.num_classes, Param.num_classes)
softmax_sampling_weights = lambda: np.repeat(1.0 / Param.num_classes, Param.num_classes)

sample_sizes = [5, 10, 100, 1000]
speed_with_sampled_softmax = []

# Get the speed with sampled softmax for different sizes
for sample_size in sample_sizes:
    print("Measuring speed of sampled softmax for sample size %d ..." % (sample_size))
    Param.use_sampled_softmax = True
    Param.softmax_sample_size = sample_size
    _, _,  samples_per_second = train(do_print_progress = False)
    speed_with_sampled_softmax.append(samples_per_second)

# Get the speed with full softmax
Param.use_sampled_softmax = False
print("Measuring speed of full softmax ...")
_, _,  samples_per_second = train(do_print_progress = False)
speed_without_sampled_softmax = np.repeat(samples_per_second, len(sample_sizes))

# Plot the speed of sampled softmax (blue) as a function of sample sizes
# and compare it to the speed with full softmax (red).
plt.plot(sample_sizes, speed_without_sampled_softmax, 'r--',sample_sizes, speed_with_sampled_softmax, 'b--')
plt.xlabel('softmax sample size')
plt.ylabel('speed: instances / second')
plt.title("Speed 'sampled softmax' (blue) vs. 'full softmax' (red)")
plt.ylim(ymin=0)
plt.show()

```

Deep Learning and the Microsoft Cognitive Toolkit/Introduction to Deep Learning & CNTK Hands-on/CNTK_207_Training_with_Sampled_Softmax.ipynb


| Conceitos matemáticos envolvidos: soma, somatória, média aritmética, desigualdades, intervalo, semi-reta
| -------:

# Distinção entre os dígitos 6, 0 e 7 por meio da simetria vertical

```python
# Carregando o banco de dados
import pandas as pd #pacote para manipulação do banco de dados
import numpy as np #pacote numérico, matrizes, vetores, somatórios, ...
from matplotlib import pyplot as plt #pacote gráfico
from sklearn.model_selection import train_test_split #pacote para separar conjuntos de treino e de teste
import random

# selecting a set of random register on the file train.csv
records = 42000
n_sample_records = 42000
skip = sorted(random.sample(range(1, records), records - n_sample_records))

# Carregando o banco de dados para a variável df
df = pd.read_csv('train.csv', skiprows = skip)

# organizando o banco de dados em X (imagens no formato 28x28) e Y (rótulos das imagens)
Y = df['label'].to_numpy()
X = df.drop(['label'], axis='columns', inplace=False).to_numpy()
X = X.reshape(-1, 28, 28)

# Dividindo as imagens e rótulos entre conjuntos de treino e de teste
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=42)
print('As dimensões dos conjuntos são:')
print('dim(X_train)={}'.format(X_train.shape))
print('dim(Y_train)={}'.format(Y_train.shape))
print('dim(X_test)={}'.format(X_test.shape))
print('dim(Y_test)={}'.format(Y_test.shape))
```

    As dimensões dos conjuntos são:
    dim(X_train)=(33600, 28, 28)
    dim(Y_train)=(33600,)
    dim(X_test)=(8400, 28, 28)
    dim(Y_test)=(8400,)

# Cálculo de Simetria Vertical

Observe que, aparentemente, os dígitos 6, 0 e 7 podem ser razoavalmente distinguidos a partir da simetria vertical. Enquanto o dígito 6 parece tender a ter intensiade maior na região de baixo das imagens, e o dígito 7 aparenta ter maior intensidade na região superior das imagens, o dígito 0 aparenta não ter distinção para preencher intensidades na direção vertical. Vamos, então, definir a simetria vertical da imagem como a soma das intensidades na região superior subtraída pela soma das intensidades na região inferior das imagens. Para isso delimitamos a região superior como os pixels que estão representados entre as linhas 0 e 13 da matriz que representa o dígito. Similarmente a região inferior será composta pelas linhas entre 14 e 27 da matriz que representa o dígito. Assim, propomos a seguinte definição para a simetria vertical das imagens:

\begin{align}
    S= & \biggl( (A_{0,0} + A_{1,0} + A_{13,0}) - ( A_{14,0} + A_{15,0} + ... + A_{27,0}) +\\
    & (A_{0,1} + A_{1,1} + A_{13,1}) - ( A_{14,1} + A_{15,1} + ... + A_{27,1}) +\\
     & ... \\
    & (A_{0,27} + A_{1,27} + A_{13,27}) - ( A_{14,27} + A_{17,27} + ... + A_{27, 27} ) \biggr) \\
\end{align}

Ou, novamente, denotando as somas com o somatório:

$$S = \sum_{j=0}^{27} \left( \sum_{i=0}^{13}A_{i,j} - \sum_{i=14}^{27}A_{i,j} \right)$$

Dessa forma, quando mais as intensidades dos pixels de uma imagem estiverem na região fuperior, maior será o número, S, definido como a simetria vertical da imagem. Alguns exemplos:

```python
# Função que calcula a Simetria Vertical das imagens
def enquadra_digito(img): # parece que as figuras já estão bem centralizadas?
    #print(img)
    v_cumsum = img.sum(axis=1).cumsum(axis=0)
    v_top = np.argwhere(v_cumsum == v_cumsum.min())[-1,0]
    v_botton = np.argwhere(v_cumsum == v_cumsum.max())[0,0]

    h_cumsum = img.sum(axis=0).cumsum(axis=0)
    h_left = np.argwhere(h_cumsum == h_cumsum.min())[-1,0]
    h_right = np.argwhere(h_cumsum == h_cumsum.max())[0,0]

    #print(img[v_top+1:v_botton+1, h_left+1:h_right+1])
    return img[v_top+1:v_botton+1, h_left+1:h_right+1]

# Função que calcula a Simetria Lateral das imagens
def sim_vert(A):
    result = []
    for n in range(A.shape[0]):
        img = enquadra_digito(A[n,:,:])
        vtop_half = img.shape[0]//2
        vdown_half = (img.shape[0]+1)//2
        result = result + [(img[:vtop_half,:].sum() - img[vdown_half:,:].sum())/100]
    #return (A[:,:14,:].sum(axis=(1,2)) - A[:,14:,:].sum(axis=(1,2)))/100
    return np.array(result)

# Alguns exemplos simples para testar a função calc_intens
# matriz entradas valoradas em 0
ex1 = np.zeros((1,28,28))
# matriz entradas valoradas em 255
ex2 = np.zeros((1,28,28))
ex2[:,:,:] = 255
# matriz entradas da metade superior valoradas em 255 e o restante das entradas valoradas em 0
ex3 = np.zeros((1,28,28))
ex3[:,:14,:] = 255
ex3[:,-1:,:] = 255
# matriz entradas da metade inferior valoradas em 255 e o restante das entradas valoradas em 0
ex4 = np.zeros((1,28,28))
ex4[:,14:,:] = 255
ex4[:,:1,:] = 255

# Imprimindo as imagens representadas pelas matrizes ex1, ex2, ex3 e ex4 em conjunto com suas Simetrias Laterais
print("Exemplo 1:")
plt.imshow(ex1[0], interpolation="nearest", cmap='gray', vmin=0, vmax=255)
plt.show()
print("Simetria Lateral: ", sim_vert(ex1), '\n')

print("Exemplo 2:")
plt.imshow(ex2[0], interpolation="nearest", cmap='gray', vmin=0, vmax=255)
plt.show()
print("Simetria Lateral: ", sim_vert(ex2), '\n')

print("Exemplo 3:")
plt.imshow(ex3[0], interpolation="nearest", cmap='gray', vmin=0, vmax=255)
plt.show()
print("Simetria Lateral: ", sim_vert(ex3), '\n')

print("Exemplo 4:")
plt.imshow(ex4[0], interpolation="nearest", cmap='gray', vmin=0, vmax=255)
plt.show()
print("Simetria Lateral: ", sim_vert(ex4), '\n')
```

Obeserve que a simetria vertical das figuras que são verticalmente simétricas é 0. Observe também que a simetria do exemplo 3 é 99960 e que a simetria do exemplo 4 é -99960. A simetria do exemplo 3 é muito maior do que a do exemplo 4 porque região superior do exemplo 3 é toda acesa (branca ou com muita intensidade), enquanto que a região inferior deste mesmo exemplo é toda apagada (preta ou com pouca intensidade); enquanto o contrário ocorre com o exemplo 4.

```python
# Calculo de intensidades das imagens com dígitos numéricos
SV_train = sim_vert(X_train)

# Imprimindo as 10 primeiras imagens do conjunto de treino juntamente com as respectivas Simetrias Verticais
for i in range(0,10):
    print('Imagem:')
    plt.imshow(X_train[i], interpolation="nearest", cmap='gray')
    plt.show()
    print('Simetria Vertical da imagem de rótulo {}: {:.2f}'.format(Y_train[i], SV_train[i]))
```

```python
# Esboçando os pontos de X_train que representam Simetrias Verticais dos dígitos 6, 0 e 7
digit1=6
digit2=0
digit3=7

#coordenadas dos dígitos em X_train correspondentes a digit1, digit2 e digit3
coord1_train = np.argwhere(Y_train==digit1)
coord2_train = np.argwhere(Y_train==digit2)
coord3_train = np.argwhere(Y_train==digit3)
n_train1 = len(coord1_train)
n_train2 = len(coord2_train)
n_train3 = len(coord3_train)

#Matrizes e Simetrias Verticais das imagens dos dígitos em X_train correspondentes a digit1, digit2 e digit3
X_train1 = X_train[coord1_train].reshape(-1, 28, 28)
X_train2 = X_train[coord2_train].reshape(-1, 28, 28)
X_train3 = X_train[coord3_train].reshape(-1, 28, 28)
SV1_train = sim_vert(X_train1)
SV2_train = sim_vert(X_train2)
SV3_train = sim_vert(X_train3)

# médias
m1 = SV1_train.mean()
m2 = SV2_train.mean()
m3 = SV3_train.mean()
print('A média das Simetrias Verticais das imagens com dígito {} é {:.2f}'.format(digit1, m1))
print('A média das Simetrias Verticais das imagens com dígito {} é {:.2f}'.format(digit2, m2))
print('A média das Simetrias Verticais das imagens com dígito {} é {:.2f}'.format(digit3, m3))

# parâmetro separador, alpha = média entre m1 e m2
alpha12 = (m1+m2)/2
alpha23 = (m2+m3)/2
print('\nA média entre as médias das Simetrias Verticais entre os dígitos {} e {} respectivamente é {:.2f}'.format(digit1, digit2, alpha12))
print('\nA média entre as médias das Simetrias Verticais entre os dígitos {} e {} respectivamente é {:.2f}'.format(digit2, digit3, alpha23))

#figura
plt.scatter(SV1_train, 0.5*np.ones(n_train1), label=digit1, alpha=1, marker='.', s=0.1)
plt.scatter(SV2_train, 0.52*np.ones(n_train2), label=digit2, alpha=1, marker='.', s=0.1)
plt.scatter(SV3_train, 0.54*np.ones(n_train3), label=digit3, alpha=1, marker='.', s=0.1)
plt.plot([m1, m1], [0.49, 0.51], 'blue', marker='|')
plt.plot([m2, m2], [0.51, 0.53], 'orange', marker='|')
plt.plot([m3, m3], [0.53, 0.55], 'green', marker='|')
plt.plot([alpha12, alpha12], [0.49, 0.53], 'black', marker='|')
plt.plot([alpha23, alpha23], [0.51, 0.55], 'black', marker='|')
plt.arrow(alpha12, 0.51, 50, 0, head_width=0.005, head_length=10, fc='k', ec='k')
plt.arrow(alpha23, 0.53, 50, 0, head_width=0.005, head_length=10, fc='k', ec='k')

plt.ylim(0.4, 0.6)
plt.legend(loc=3, markerscale=10)
plt.savefig('medias_mnist1dim'+str(digit1)+'_'+str(digit2)+'_'+str(digit3)+'.eps', format='eps')
plt.show()
```

Parece ser possível distinguir razoavelmente entre os três dígitos. Para encontrar um parâmetro de separação entre os dígitos 6 e 0 e outro parâmetro para distinguir os dígitos 0 e 7, basta repetir os procedimentos feitos anteriormente. Calcula-se as médias, por exemplo, das simetrias dos dígitos 6 e também as dos dígitos 0. Após isso, calcula-se a média aritmética entre as duas últimas calculadas. Esta pode ser tida como um parâmetro para distinguir os dígitos 6 e 0 através de desigualdade. E repete-se o mesmo argumento para os dígitos 0 e 7.

```python
# Accurácia
menor1_train = (SV1_train <= alpha12).sum()
entre2_train = ((SV2_train <= alpha23) & (alpha12 < SV2_train)).sum()
maior3_train = (SV3_train > alpha23).sum()

print('{} imagens correspondentes ao dígito {} têm Simetria Vertical menor do que {:.2f}.'.format(menor1_train, digit1, alpha12))
print('{} imagens correspondentes ao dígito {} têm Simetria Vertical entre {:.2f} e {:.2f}.'.format(entre2_train, digit2, alpha12, alpha23))
print('{} imagens correspondentes ao dígito {} têm Simetria Vertical Maior do que {:.2f}.'.format(maior3_train, digit3, alpha23))

print('Alem disso:')
acc_train = (menor1_train + entre2_train + maior3_train)/(n_train1+n_train2+n_train3)
print('Esta proposta tem acurácia de {:.1f}% para distinguir os dígitos {}, {} e {}'.format(acc_train*100, digit1, digit2, digit3))
```

    2964 imagens correspondentes ao dígito 6 têm Simetria Vertical menor do que -56.60.
    3018 imagens correspondentes ao dígito 0 têm Simetria Vertical entre -56.60 e 32.78.
    3132 imagens correspondentes ao dígito 7 têm Simetria Vertical Maior do que 32.78.
    Alem disso:
    Esta proposta tem acurácia de 89.6% para distinguir os dígitos 6, 0 e 7

### Performance da prosposta que distingue os dígitos 6, 0 e 7 por meio de Simetrias Verticais no conjunto de teste (X_test):

Agora, vamos usar os parâmetro alpha12 e alpha23, que foram calculados usando médias aritméticas apenas no conjunto de treino e usá-los para testar a proposta no conjunto de teste (X_test), que nunca foi usado para calcular o parâmetro alpha12 e alpha23.

```python
# Performance (acurácia) no conjunto de test (X_test)

# Calculo de Simetria Vertical das imagens no conjunto de teste com dígitos numéricos
SV_test = sim_vert(X_test)

#coordenadas dos dígitos em X_test correspondentes a digit1, digit2 e digit3
coord1_test = np.argwhere(Y_test==digit1)
coord2_test = np.argwhere(Y_test==digit2)
coord3_test = np.argwhere(Y_test==digit3)
n_test1 = len(coord1_test)
n_test2 = len(coord2_test)
n_test3 = len(coord3_test)

#Matrizes e intensidades das imagens dos dígitos em X_test, conjunto de teste, correspondentes a digit1, digit2 e digit3
X_test1 = X_test[coord1_test].reshape(-1, 28, 28)
X_test2 = X_test[coord2_test].reshape(-1, 28, 28)
X_test3 = X_test[coord3_test].reshape(-1, 28, 28)
SV1_test = sim_vert(X_test1)
SV2_test = sim_vert(X_test2)
SV3_test = sim_vert(X_test3)

# Cálculo de Acurácia
# quantidade de imagens correspondentes aos dígitos 6, 0 e 7 que os parâmetros alpha12 e alpha23 separam no conjunto de test (X_test)
menor1_test = (SV1_test <= alpha12).sum()
entre2_test = ((SV2_test <= alpha23) & (alpha12 < SV2_test)).sum()
maior3_test = (SV3_test > alpha23).sum()

acc_test = (menor1_test+ entre2_test + maior3_test)/(n_test1 + n_test2 + n_test3)
print('Esta proposta tem acurácia de {:.1f}% para distinguir os dígitos {}, {} e {}'.format(acc_test*100, digit1, digit2, digit3))
```

    Esta proposta tem acurácia de 89.6% para distinguir os dígitos 6, 0 e 7


# !!! D . R . A . F . T !!!

# Lightness

[Lightness](http://en.wikipedia.org/wiki/Lightness) is defined as the brightness of an area judged relative to the brightness of a similarly illuminated area that appears to be white or highly transmitting. [1]

[Colour](https://github.com/colour-science/colour/) defines the following *Lightness* computation methods:

```python
import colour

sorted(colour.LIGHTNESS_METHODS.keys())
```

    ['CIE 1976',
     'Fairchild 2010',
     'Fairchild 2011',
     'Glasser 1958',
     'Lstar1976',
     'Wyszecki 1963']

> Note: `'Lstar1976'` is a convenient aliases for `'CIE 1976'`.

## Glasser, Mckinney, Reilly and Schnelle (1958) Method

Glasser, Mckinney, Reilly and Schnelle (1958) described a visually uniform colour coordinate system close to *Adams* chromatic-value system but where the quintic-parabola function has been replaced with a cube-root function: the *Cube-Root Color Coordinate System*.

*Lightness* $L$ in the *Cube-Root Color Coordinate System* is calculated as follows: [2]

$$
\begin{equation}
L=25.29Y^{1/3}-18.38
\end{equation}
$$

where $Y$ defines the *luminance* in domain [0, 100].

The `colour.lightness_Glasser1958` definition is used to compute *Lightness* $L$:

```python
colour.colorimetry.lightness_Glasser1958(10.08)
```

    36.250562645752595

> Note: Input *luminance* $Y$ is in domain [0, 100], output *Lightness* $L$ is in domain [0, 100].

The `colour.lightness` definition is implemented as a wrapper for various lightness computation methods:

```python
colour.lightness(10.08, method='Glasser 1958')
```

    36.250562645752595

```python
from colour.plotting import *
```

```python
colour_style();
```

```python
# Plotting the "Glasser (1958)" "Lightness" function.
plot_single_lightness_function('Glasser 1958');
```

## Wyszecki (1963) Method

Wyszecki (1963) recommended the following cube root function to compute *Lightness* $W$ as a function of the luminance factor $Y$ within the practically important range of $1.0\%<Y<98\%$: [3]

$$
\begin{equation}
W=25Y^{1/3}-17
\end{equation}
$$

The `colour.lightness_Wyszecki1963` definition is used to compute *Lightness* $W$:

```python
colour.colorimetry.lightness_Wyszecki1963(10.08)
```

    37.004114912764535

> Note: Input *luminance* $Y$ is in domain [0, 100], output *Lightness* $W$ is in domain [0, 100].

Using the `colour.lightness` wrapper definition:

```python
colour.lightness(10.08, method='Wyszecki 1963')
```

    37.004114912764535

```python
# Plotting the "Wyszecki (1963)" "Lightness" function.
with colour.utilities.suppress_warnings():
    plot_single_lightness_function('Wyszecki 1963');
```

## CIE 1976 Method

The *CIE $L^*a^*b^*$* approximately uniform colourspace defined in 1976 computes the *Lightness* $L^*$ quantity as follows: [4]

$$
\begin{equation}
L^*=\begin{cases}116\biggl(\cfrac{Y}{Y_n}\biggr)^{1/3}-16 & for\ \cfrac{Y}{Y_n}>\epsilon\\
\kappa*\biggl(\cfrac{Y}{Y_n}\biggr) & for\ \cfrac{Y}{Y_n}<=\epsilon
\end{cases}
\end{equation}
$$
where $Y_n$ is the reference white *luminance*.
with
$$
\begin{equation}
\begin{aligned}
\epsilon&\ =\begin{cases}0.008856 & Actual\ CIE\ Standard\\
216\ /\ 24389 & Intent\ of\ the\ CIE\ Standard
\end{cases}\\
\kappa&\ =\begin{cases}903.3 & Actual\ CIE\ Standard\\
24389\ /\ 27 & Intent\ of\ the\ CIE\ Standard
\end{cases}
\end{aligned}
\end{equation}
$$

The original $\epsilon$ and $\kappa$ constants values have been shown to exhibit discontinuity at the junction point of the two functions grafted together to create the *Lightness* $L^*$ function. [5]

[Colour](https://github.com/colour-science/colour/) uses the rational values instead of the decimal values for these constants.

> See Also: The [CIE $L^*a^*b^*$ Colourspace](../models/cie_lab.ipynb) notebook for in-depth informations about the *CIE $L^*a^*b^*$* colourspace.

The `colour.lightness_CIE1976` definition is used to compute *Lightness* $L^*$:

```python
colour.colorimetry.lightness_CIE1976(10.08)
```

    37.985629097653039

> Note: Input *luminance* $Y$ and $Y_n$ are in domain [0, 100], output *Lightness* $L^*$ is in domain [0, 100].

Using the `colour.lightness` wrapper definition:

```python
colour.lightness(10.08)
```

    37.985629097653039

```python
colour.lightness(10.08, method='CIE 1976', Y_n=95)
```

    38.916598757092821

```python
colour.lightness(10.08, method='Lstar1976', Y_n=95)
```

    38.916598757092821

```python
# Plotting the "CIE 1976" "Lightness" function.
plot_single_lightness_function('CIE 1976');
```

```python
# Plotting multiple "Lightness" functions for comparison.
plot_multi_lightness_functions(['CIE 1976', 'Glasser 1958']);
```

## Fairchild and Wyble (2010) Method

```python
colour.colorimetry.lightness_Fairchild2010(10.08 / 100, 1.836)
```

    24.902290269546651

```python
colour.lightness(10.08 / 100, method='Fairchild 2010', epsilon=1.836)
```

    0.027048835900068357

```python
# Plotting the "Fairchild and Wyble (2010)" "Lightness" function.
plot_single_lightness_function('Fairchild 2010');
```

```python
# Plotting multiple "Lightness" functions for comparison.
plot_multi_lightness_functions(['CIE 1976', 'Fairchild 2010']);
```

## Fairchild and Chen (2011) Method

```python
colour.colorimetry.lightness_Fairchild2011(10.08 / 100, 0.710)
```

    26.459509817572265

```python
colour.lightness(10.08 / 100, method='Fairchild 2011', epsilon=0.710)
```

    1.1405928270854113

```python
# Plotting the "Fairchild and Chen (2011)" "Lightness" function.
plot_single_lightness_function('Fairchild 2011');
```

```python
# Plotting multiple "Lightness" functions for comparison.
plot_multi_lightness_functions(['CIE 1976', 'Fairchild 2011']);
```

## Bibliography

1. ^ CIE. (n.d.). 117-680 lightness (of a related colour). Retrieved July 09, 2014, from http://eilv.cie.co.at/term/680
2. ^ Glasser, L. G., McKinney, A. H., Reilly, C. D., & Schnelle, P. D. (1958). Cube-Root Color Coordinate System. J. Opt. Soc. Am., 48(10), 736–740. doi:10.1364/JOSA.48.000736
3. ^ Wyszecki, G. (1963). Proposal for a New Color-Difference Formula. J. Opt. Soc. Am., 53(11), 1318–1319. doi:10.1364/JOSA.53.001318
4. ^ Wyszecki, G., & Stiles, W. S. (2000). CIE 1976 (L\*u\*v\*)-Space and Color-Difference Formula. In *Color Science: Concepts and Methods, Quantitative Data and Formulae* (p. 167). Wiley. ISBN:978-0471399186
5. ^ Lindbloom, B. (2003). A Continuity Study of the CIE L* Function. Retrieved February 24, 2014, from http://brucelindbloom.com/LContinuity.html


# Aprendizado Supervisionado - Naive Bayes

Vamos falar um pouco sobre um algoritmo supervisionado de classificação que é dos mais simples, mas também dos mais utilizados. Estamos falando do algoritmo ```Naive Bayes```.

Vamos relembrar rapidamente o que é um algoritmo supervisionado de classificação.

Um algoritmo de aprendizado supervisionado é aquele que conta com um conjunto de preditores e variável resposta que são utilizados no treinamento de um modelo, isto é, tem sua utilidade quando os "resultados" estão disponíveis para um dado conjunto de "variáveis explicativas" do problema a ser solucionado. Esses "resultados" são importantes no momento em que o modelo está aprendendo como trabalhar com os dados. E o modelo é de classificação quando seu objetivo é separar os elementos em classes, binárias ou não.

Pra ficar mais claro essa questão de aprendizado supervisionado e modelo de classificação, imagine o seguinte exemplo:

```

Suponha que Thiago, um jovem ávido por andar de skate, mora longe da pista de skate que mais gosta. Além disso, a pista  não é coberta. Portanto, nos dias de chuva ele não pode andar. Nos dias nublados, as vezes, fica receoso de chegar ao    skate park e estar chovendo. Entretanto, conseguimos seguir os passos de Thiago por alguns dias para entender um pouco   sobre sua rotina e seus dilemas para andar de skate dadas as condições do tempo na região próxima de sua casa. Os dados  coletados são os seguintes, seguidos do resultado, isto é, se Thiago conseguiu ou não andar de skate naqueles dias:

```
Dia | Vento | Nublado | Sol | Chuva | Resultado
:--:|:-----:|:-------:|:---:|:-----:|----------
1   |sim    |sim      |nao  |sim    |nao andou
2   |nao    |sim      |nao  |nao    |andou
3   |sim    |nao      |sim  |nao    |andou
4   |nao    |sim      |nao  |nao    |andou
5   |sim    |nao      |sim  |nao    |andou
6   |nao    |sim      |nao  |sim    |nao andou
7   |sim    |nao      |nao  |sim    |nao andou
8   |nao    |sim      |nao  |sim    |nao andou
9   |sim    |nao      |sim  |nao    |andou
10  |nao    |nao      |nao  |nao    |andou

O exemplo acima é uma clara situação onde poderíamos empregar um algoritmo de classificação, pois o que queremos é poder responder a pergunta: "Thiago andou ou não de skate em um dado dia?". Para respondermos a essa pergunta temos que avaliar as condições de tempo e informá-las ao nosso modelo, para que este possa treinar (aprender como relacionar as condições do tempo e o resultado, a partir dos dados da tabela acima) e então conseguir responder a novas questões baseado apenas nas características oferecidas como informação. Portanto, a tarefa do modelo é classificar um registro (com as diversas informações sobre tempo) quanto à duas classes: "andou" ou "não andou" de skate.

## Algoritmo Naive Bayes

O algoritmo Naive Bayes é um classificador probabilístico baseado no “Teorema de Bayes”, o qual foi criado por Thomas Bayes (1701 - 1761) para tentar provar a existência de Deus [curiosidade]. A seguir apresentamos a regra de Bayes onde o algoritmo baseia seu funcionamento.

\begin{equation}
    p(b|a) = \frac{p(a,b)}{p(a)}
\end{equation}

que (pela lei da multiplicação de probabilidades) também pode ser escrita como:

\begin{equation}
    p(b|a) = \frac{p(a|b).p(b)}{p(a)}
\end{equation}

Para mais informações sobre o Teorema de Bayes, [clique aqui](https://pt.wikipedia.org/wiki/Teorema_de_Bayes).

É um algoritmo muito utilizado na área de Aprendizado de Máquina para categorizar/classificar textos baseado na frequência das palavras usadas, e assim podendo ser usado para identificar se determinado e-mail é um SPAM ou sobre qual assunto se refere determinado texto, por exemplo.

Por ser muito simples e rápido, possui um desempenho relativamente maior do que outros classificadores. Além disso, o Naive Bayes só precisa de um pequeno número de dados de teste para concluir classificações com uma boa precisão.

A principal característica do algoritmo, e também o motivo de receber “naive” (ingênuo) no nome, é que ele desconsidera completamente a correlação entre as variáveis. Ou seja, se o tempo, no exemplo da tabela acima, é dividido em "sol", "nublado", "chuva" ou "vento", o algoritmo não vai levar em consideração a correlação entre esses fatores, mas vai tratar cada um de forma independente.

Se o problema for classificar um texto ou algo do gênero, o Naive Bayes é uma das melhores alternativas, mas se a correlação entre os fatores for extremamente importante, o Naive Bayes pode falhar na predição da nova informação.

Dessa forma, temos que esse algoritmo é muito útil para "análise de sentimentos", "filtragem de spam", "classificação/filtragem de textos" e além disso, pode ser utilizado para previsões em tempo real, dada sua velocidade e a necessidade de poucos dados para realizar as classificações.

## Determinando probabilidades

Para entender um pouco melhor como funciona o classificador, vamos a um exemplo rápido: Digamos que estamos trabalhando no diagnóstico de uma nova doença, e que fizemos testes em 100 pessoas distintas. Após coletarmos os dados e fazer as primeiras análises, descobrimos que 20 pessoas possuíam a doença (20%) e 80 pessoas estavam saudáveis (80%), sendo que das pessoas que possuíam a doença, 90% receberam Positivo no teste da doença, e 30% das pessoas que não possuíam a doença também receberam o teste positivo.

Listando esses dados de uma forma mais clara, temos:
- 100 pessoas realizaram o teste.
- 20% das pessoas que realizaram o teste possuíam a doença.
- 90% das pessoas que possuíam a doença, receberam positivo no teste.
- 30% das pessoas que não possuíam a doença, receberam positivo no teste.

A pergunta neste caso seria: se uma nova pessoa realizar o teste e receber um resultado positivo, qual a probabilidade de ela possuir a doença?

O algoritmo Naive Bayes consiste em encontrar uma probabilidade a posteriori (possuir a doença, dado que recebeu um resultado positivo), multiplicando a probabilidade a priori (possuir a doença) pela probabilidade de “receber um resultado positivo, dado que tem a doença”.

Devemos também calcular a probabilidade a posteriori da negação (Não possuir a doença, dado que recebeu um resultado Positivo). Ou seja:

- P(doença|positivo) = 20% * 90%
- P(doença|positivo) = 0,2 * 0,9
- P(doença|positivo) = 0,18
- P(não doença|positivo) = 80% * 30%
- P(não doença|positivo) = 0,8 * 0,3
- P(não doença|positivo) = 0,24

Após isso precisamos normalizar os dados, para que a soma das duas probabilidades resulte em 1 (100%). Para isso, dividimos o resultado pela soma das duas probabilidades.

Exemplo:
- P(doença|positivo) = 0,18/(0,18+0,24) = 0,4285
- P(não doença|positivo) = 0,24/(0,18+0,24) = 0,5714

Dessa forma, podemos concluir que se o resultado do teste da nova pessoa for positivo, ela possui aproximadamente 57% de chance de não estar doente.

## Exemplo

Vamos ver na prática como esse negócio funciona. Para isso, vamos utilizar uma base de dados chamada **Iris**. Trata-se de um conjunto de dados muito conhecido e muito utilizado no aprendizado de modelos de classificação. Este BD se refere às flores Iris. Nele encontramos 150 registros de medidas de largura e comprimento de sépalas e pétalas das flores. Vou importar os dados aqui e mostrar a carinha deles para que fiquemos familiarizados.

```python
from sklearn import datasets
iris = datasets.load_iris()
```

Abaixo são mostradas as medidas preditoras:

```python
iris.data
```

    array([[5.1, 3.5, 1.4, 0.2],
           [4.9, 3. , 1.4, 0.2],
           [4.7, 3.2, 1.3, 0.2],
           [4.6, 3.1, 1.5, 0.2],
           [5. , 3.6, 1.4, 0.2],
           [5.4, 3.9, 1.7, 0.4],
           [4.6, 3.4, 1.4, 0.3],
           [5. , 3.4, 1.5, 0.2],
           [4.4, 2.9, 1.4, 0.2],
           [4.9, 3.1, 1.5, 0.1],
           [5.4, 3.7, 1.5, 0.2],
           [4.8, 3.4, 1.6, 0.2],
           [4.8, 3. , 1.4, 0.1],
           [4.3, 3. , 1.1, 0.1],
           [5.8, 4. , 1.2, 0.2],
           [5.7, 4.4, 1.5, 0.4],
           [5.4, 3.9, 1.3, 0.4],
           [5.1, 3.5, 1.4, 0.3],
           [5.7, 3.8, 1.7, 0.3],
           [5.1, 3.8, 1.5, 0.3],
           [5.4, 3.4, 1.7, 0.2],
           [5.1, 3.7, 1.5, 0.4],
           [4.6, 3.6, 1. , 0.2],
           [5.1, 3.3, 1.7, 0.5],
           [4.8, 3.4, 1.9, 0.2],
           [5. , 3. , 1.6, 0.2],
           [5. , 3.4, 1.6, 0.4],
           [5.2, 3.5, 1.5, 0.2],
           [5.2, 3.4, 1.4, 0.2],
           [4.7, 3.2, 1.6, 0.2],
           [4.8, 3.1, 1.6, 0.2],
           [5.4, 3.4, 1.5, 0.4],
           [5.2, 4.1, 1.5, 0.1],
           [5.5, 4.2, 1.4, 0.2],
           [4.9, 3.1, 1.5, 0.1],
           [5. , 3.2, 1.2, 0.2],
           [5.5, 3.5, 1.3, 0.2],
           [4.9, 3.1, 1.5, 0.1],
           [4.4, 3. , 1.3, 0.2],
           [5.1, 3.4, 1.5, 0.2],
           [5. , 3.5, 1.3, 0.3],
           [4.5, 2.3, 1.3, 0.3],
           [4.4, 3.2, 1.3, 0.2],
           [5. , 3.5, 1.6, 0.6],
           [5.1, 3.8, 1.9, 0.4],
           [4.8, 3. , 1.4, 0.3],
           [5.1, 3.8, 1.6, 0.2],
           [4.6, 3.2, 1.4, 0.2],
           [5.3, 3.7, 1.5, 0.2],
           [5. , 3.3, 1.4, 0.2],
           [7. , 3.2, 4.7, 1.4],
           [6.4, 3.2, 4.5, 1.5],
           [6.9, 3.1, 4.9, 1.5],
           [5.5, 2.3, 4. , 1.3],
           [6.5, 2.8, 4.6, 1.5],
           [5.7, 2.8, 4.5, 1.3],
           [6.3, 3.3, 4.7, 1.6],
           [4.9, 2.4, 3.3, 1. ],
           [6.6, 2.9, 4.6, 1.3],
           [5.2, 2.7, 3.9, 1.4],
           [5. , 2. , 3.5, 1. ],
           [5.9, 3. , 4.2, 1.5],
           [6. , 2.2, 4. , 1. ],
           [6.1, 2.9, 4.7, 1.4],
           [5.6, 2.9, 3.6, 1.3],
           [6.7, 3.1, 4.4, 1.4],
           [5.6, 3. , 4.5, 1.5],
           [5.8, 2.7, 4.1, 1. ],
           [6.2, 2.2, 4.5, 1.5],
           [5.6, 2.5, 3.9, 1.1],
           [5.9, 3.2, 4.8, 1.8],
           [6.1, 2.8, 4. , 1.3],
           [6.3, 2.5, 4.9, 1.5],
           [6.1, 2.8, 4.7, 1.2],
           [6.4, 2.9, 4.3, 1.3],
           [6.6, 3. , 4.4, 1.4],
           [6.8, 2.8, 4.8, 1.4],
           [6.7, 3. , 5. , 1.7],
           [6. , 2.9, 4.5, 1.5],
           [5.7, 2.6, 3.5, 1. ],
           [5.5, 2.4, 3.8, 1.1],
           [5.5, 2.4, 3.7, 1. ],
           [5.8, 2.7, 3.9, 1.2],
           [6. , 2.7, 5.1, 1.6],
           [5.4, 3. , 4.5, 1.5],
           [6. , 3.4, 4.5, 1.6],
           [6.7, 3.1, 4.7, 1.5],
           [6.3, 2.3, 4.4, 1.3],
           [5.6, 3. , 4.1, 1.3],
           [5.5, 2.5, 4. , 1.3],
           [5.5, 2.6, 4.4, 1.2],
           [6.1, 3. , 4.6, 1.4],
           [5.8, 2.6, 4. , 1.2],
           [5. , 2.3, 3.3, 1. ],
           [5.6, 2.7, 4.2, 1.3],
           [5.7, 3. , 4.2, 1.2],
           [5.7, 2.9, 4.2, 1.3],
           [6.2, 2.9, 4.3, 1.3],
           [5.1, 2.5, 3. , 1.1],
           [5.7, 2.8, 4.1, 1.3],
           [6.3, 3.3, 6. , 2.5],
           [5.8, 2.7, 5.1, 1.9],
           [7.1, 3. , 5.9, 2.1],
           [6.3, 2.9, 5.6, 1.8],
           [6.5, 3. , 5.8, 2.2],
           [7.6, 3. , 6.6, 2.1],
           [4.9, 2.5, 4.5, 1.7],
           [7.3, 2.9, 6.3, 1.8],
           [6.7, 2.5, 5.8, 1.8],
           [7.2, 3.6, 6.1, 2.5],
           [6.5, 3.2, 5.1, 2. ],
           [6.4, 2.7, 5.3, 1.9],
           [6.8, 3. , 5.5, 2.1],
           [5.7, 2.5, 5. , 2. ],
           [5.8, 2.8, 5.1, 2.4],
           [6.4, 3.2, 5.3, 2.3],
           [6.5, 3. , 5.5, 1.8],
           [7.7, 3.8, 6.7, 2.2],
           [7.7, 2.6, 6.9, 2.3],
           [6. , 2.2, 5. , 1.5],
           [6.9, 3.2, 5.7, 2.3],
           [5.6, 2.8, 4.9, 2. ],
           [7.7, 2.8, 6.7, 2. ],
           [6.3, 2.7, 4.9, 1.8],
           [6.7, 3.3, 5.7, 2.1],
           [7.2, 3.2, 6. , 1.8],
           [6.2, 2.8, 4.8, 1.8],
           [6.1, 3. , 4.9, 1.8],
           [6.4, 2.8, 5.6, 2.1],
           [7.2, 3. , 5.8, 1.6],
           [7.4, 2.8, 6.1, 1.9],
           [7.9, 3.8, 6.4, 2. ],
           [6.4, 2.8, 5.6, 2.2],
           [6.3, 2.8, 5.1, 1.5],
           [6.1, 2.6, 5.6, 1.4],
           [7.7, 3. , 6.1, 2.3],
           [6.3, 3.4, 5.6, 2.4],
           [6.4, 3.1, 5.5, 1.8],
           [6. , 3. , 4.8, 1.8],
           [6.9, 3.1, 5.4, 2.1],
           [6.7, 3.1, 5.6, 2.4],
           [6.9, 3.1, 5.1, 2.3],
           [5.8, 2.7, 5.1, 1.9],
           [6.8, 3.2, 5.9, 2.3],
           [6.7, 3.3, 5.7, 2.5],
           [6.7, 3. , 5.2, 2.3],
           [6.3, 2.5, 5. , 1.9],
           [6.5, 3. , 5.2, 2. ],
           [6.2, 3.4, 5.4, 2.3],
           [5.9, 3. , 5.1, 1.8]])

Abaixo é mostrada a variável *target*:

```python
iris.target
```

    array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
           0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
           0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
           1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
           1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
           2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
           2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])

Pra ficarmos mais familiarizados com essa base de dados, vamos usar o comando 'shape' para saber quantas linhas e colunas têm esses arquivos.

```python
iris.data.shape, iris.target.shape
```

    ((150, 4), (150,))

Podemos observar que temos 150 registros de dados e 4 atributos e como classes temos apenas um vetor de 150 posições relativos a classe de cada instância, ou seja, nossa variável target é um vetor com 150 entradas.

Seguindo com nosso exemplo, vamos dividir nosso conjunto de dados acima em duas partes: uma amostra para treinamento do modelo e outra para o teste do modelo. Para isso, vamos utilizar o método ```train_test_split``` disponível na biblioteca ```scikit-learn``` por meio do módulo ```model_selection```.

```python
X_train, X_test, y_train, y_test = train_test_split (iris.data, iris.target, test_size=0.4, random_state=0)
```

Observe que estamos dividindo os dados em uma porcentagem de 40% para teste e 60% para treinamento, através do parâmetro test_size. Os dados de treino são armazenados nas variáveis X_train e y_train. Já os dados de teste são armazenados nas variáveis X_test e y_teste.

A seguir continuamos nosso exemplo com a construção de um modelo baseado no algoritmo Naive Bayes, sob o método multinomial.

Lembre que nossa ideia é criar um modelo que deve aprender como classificar os registros de flores Iris, de acordo com as medidas das folhas em três grupos: setosa, versicolor e virginica.

```python
from sklearn.naive_bayes import GaussianNB
```

Construção e treinamento do modelo:

```python
modelo = MultinomialNB()
modelo.fit(X_train, y_train)
```

    MultinomialNB(alpha=1.0, class_prior=None, fit_prior=True)

Fazendo a previsão dos registros da amostra de teste, para posterior comparação com a variável resposta (y_test):

```python
preditos = modelo.predict(X_test)
print(preditos)
```

    [2 1 0 2 0 2 0 1 1 1 2 1 1 1 2 0 2 2 0 0 2 2 0 0 2 0 0 1 1 0 2 2 0 2 2 2 0
     2 1 1 2 0 2 0 0 1 2 2 2 2 1 2 1 1 2 2 2 2 1 2]

Por fim, calculando a taxa de acerto do modelo.

```python
dif = preditos - y_test
acertos = [i for i in dif if i == 0]
total_acertos = len(acertos)
total_elementos = len(y_test)
taxa_acertos = total_acertos / total_elementos
print(taxa_acertos*100)
```

    88.33333333333333

Conforme observado na célula acima, o modelo que criamos nesta aula teve uma taxa de acerto de 88%.

Acho que dessa forma chegamos ao nosso objetivo com essa aula: dar uma introdução ao algoritmo Naive Bayes e mostrar algum exemplo de aplicação.

Para aprofundar no assunto, sugiro muito a página da biblioteca [Scikit-Learn](http://scikit-learn.org/stable/).

```python

```

materiais_pdf/Data_Science(curso_de_2019)/Machine Learning/Jupyter notebooks/Machine Learning 2 - NaiveBayes.ipynb


# Typesetting Math in your book

Jupyter Book uses [MathJax](http://docs.mathjax.org/) for typesetting math in your
book. This allows you to have LaTeX-style mathematics in your online content.
This page shows you a few ways to control this.

For more information about equation numbering, see the
[MathJax equation numbering documentation](http://docs.mathjax.org/en/v2.7-latest/tex.html#automatic-equation-numbering).

# In-line math

To insert in-line math use the `$` symbol within a Markdown cell.
For example, the text `$this_{is}^{inline}$` will produce: $this_{is}^{inline}$.

# Math blocks

You can also include math blocks for separate equations. This allows you to focus attention
on more complex or longer equations, as well as link to them in your pages. To use a block
equation, wrap the equation in either `$$` or `\begin` statements.

For example,

```latex
\begin{equation}
  \int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15}
\end{equation}
```
results in

\begin{equation}
  \int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15}
\end{equation}

and

```latex
$$
  \int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15}
$$
```

results in

$$
  \int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15}
$$

## Numbering equations

MathJax has built-in support for numbering equations. This makes it possible to
easily reference equations throughout your page. To do so, add this tag
to a block equation:

`\tag{<number>}`

The `\tag` provides a number for the equation that will be inserted when you refer
to it in the text.

For example, the following code:

```latex
equation 1
$$
  \int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15}
  \tag{1}
$$

equation 2

$$
  \int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15}
  \tag{2}
$$

equation 999

$$
  \int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15}
  \tag{999}
$$
```

Results in these math blocks:

equation 1
$$
  \int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15}
  \tag{1}
$$

equation 2

$$
  \int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15}
  \tag{2}
$$

equation 999

$$
  \int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15}
  \tag{999}
$$

### Automatic numbering

If you'd like **all** block equations to be numbered with MathJax, you can activate
this with the following configuration in your `_config.yml` file:

```yaml
number_equations: true
```

In this case, all equations will have numbers. If you'd like to deactivate
an equation's number, include a `\notag` with your equation, like so:

```latex
$$
  \int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15}
  \notag
$$
```

## Linking to equations

Adding `\label{mylabel}` to an equation allows you to refer to the equation elsewhere in the page. You
can define a human-friendly label and MathJax will insert an anchor with the following form:

```html
#mjx-eqn-mylabel
```

If you use `\label` in conjunction with `\tag`, then you can insert references directly to an equation
by using the `\ref` syntax. For example, here's an equation with a tag and label:

```latex
$$
\int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15}
\label{mylabel1}\tag{24}
$$

$$
\int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15}
\label{mylabel2}\tag{25}
$$
```

$$
\int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15}
\label{mylabel1}\tag{24}
$$

$$
\int_0^\infty \frac{x^3}{e^x-1}\,dx = \frac{\pi^4}{15}
\label{mylabel2}\tag{25}
$$

Now, we can refer to these math blocks with `\ref` elements. For example,
we can mention Equation \ref{mylabel1} using `\ref{mylabel1}` and
Equation \ref{mylabel2} with `\ref{mylabel2}`.

Note that these equations also have anchors on them, which can be used to link
to an equation from elsewhere, for example with this link text:

```html
<a href="#mjx-eqn-mylabel2">My link</a>
```

```python

```