05.0 Redes Neuronales Utilidades

En esta carpeta se encuentran una serie de utilidades necesarias para los cuadernos sobre Redes Neuronales (RNN)

Utilidad que dibuja las curvas de nivel que determina el valor y en la region X

from matplotlib.colors import ListedColormap
import matplotlib.pyplot as plt

def plot_decision_regions(X, y, classifier, test_idx=None,
    resolution=0.02):
    # Se define el generador de marcadores y el mapa de colores
    markers = ('s', 'x', 'o', '^', 'v')
    colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
    cmap = ListedColormap(colors[:len(np.unique(y))])
    # Se imprime la superficie de decisión
    x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),
    np.arange(x2_min, x2_max, resolution))
    Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
    Z = Z.reshape(xx1.shape)
    # contourf describe las curvas de nivel de una función sobre la malla
    plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap)
    plt.xlim(xx1.min(), xx1.max())
    plt.ylim(xx2.min(), xx2.max())
    for idx, cl in enumerate(np.unique(y)):
        plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1],
            alpha=0.8, c=colors[idx],
            marker=markers[idx], label=cl,
            edgecolor='black')
    # highlight test samples
    if test_idx:
        # plot all samples
        X_test, y_test = X[test_idx, :], y[test_idx]
        plt.scatter(X_test[:, 0], X_test[:, 1],
            c='', edgecolor='black', alpha=1.0,
            linewidth=1, marker='o',
            s=100, label='test set')

Las funciones contour y contourf

La función contour(X, Y, Z) tiene los siguientes parámetros de entrada

  • X : Matriz 2D con los valores X de los puntos de una malla creada con numpy.meshgrid()

  • Y : Matriz 2D con los valores Y de los puntos de una malla creada con numpy.meshgrid()

  • Z : Matriz 2D con el valor de la función

Contour dibuja la curva de nivel. Contourf dibuja la curva de nivel rellenada con color

Se dibujan las curvas de nivel del paraboloide elíptico \(y=x^2+y^2\) y del paraboloide hiperbólico \(y=x^2-y^2\)

## Curvas de nivel de la función 
import matplotlib.pyplot as plt
import numpy as np
A=np.array([-3,-2,-1,0,1,2,3])
B=A
A,B=np.meshgrid(A,B)
fig = plt.figure()
#plt.contour(A,B,A**2+B**2)  # Paraboloide elíptico. Curvas de nivel estándar
#plt.contour(A,B,A**2-B**2)  # Paraboloide hiperbólico. Curvas de nivel estándar
#plt.contourf(A,B,A**2+B**2)  # Paraboloide elíptico. Curvas de nivel con contorno relleno de color
#plt.contourf(A,B,A**2-B**2)  # Paraboloide hiperbólico. Curvas de nivel con contorno relleno de color
#plt.show()
<Figure size 432x288 with 0 Axes>

Maqueta de neurona multicapa

def sigmoid(x):
    #return 1.0/(1.0 + np.exp(-x))  ## versión básica con problemas de desbordamiento en valores x<<<0
    #return np.where(x < 0, np.exp(x)/(1.0 + np.exp(x)), 1.0/(1.0 + np.exp(-x)))
    #return 1. / (1. + np.exp(-np.clip(x, -250, 250)))
    from scipy.special import expit
    return expit(x)  ##Función sigmoidea de scipy; algo más lenta
 
def sigmoid_derivada(x):
    return sigmoid(x)*(1.0-sigmoid(x))
 
def tanh(x):
    return np.tanh(x)
 
def tanh_derivada(x):
    return 1.0 - np.tanh(x)**2

def ReLU(x):
    return np.maximum(0, x)

def ReLU_derivada(x):
    return np.where(x <= 0, 0, 1)

class NeuralNetwork(object):
    """MultiLayer LInear NEuron classifier.
    Parametros
    ------------
    eta : float Ratio de aprendizaje (entre 0.0 y 1.0)
    n_iter : int Pasos sobre el conjunto de datos de entrenamiento.
    capas: capas[0] las neuronas de entrada, capas[1] las neuronas de salida
    random_state : int Generador de semillas de números aleatorios para inicializar los pesos.
    shuffle : boolean. Hace una mezcla o barajado aleatorio del conjunto de entrenamiento en los minibatch
    minibatch_size: tamaño del minibatch o subconjuntos en que se divide el conjunto de entrada para el entrenamiento
    hiddenLayers: Neuronas en cada capa oculta. Aquí se excluye la capa de entrada y la de salida que la obtiene
                  automáticamente el proceso fit() de X e y
    Atributos
    -----------
    w_ : Array de dimensión 1 con los pesos después del ajuste.
    cost_ : lista de Suma-de-cuadrados de los valores de la función coste en cada Paso del algoritmo.
    """
    def __init__(self, eta=0.01, epocas=50, hiddenLayers=[], shuffle=False, minibatch_size=0, seed=None, activacion='logistic'):
        self.eta = eta
        self.epocas = epocas
        ##self.random_state = random_state
        #self.L = len(capas)-1  ### Número de Capas
        #self.capas = capas
        self.hiddenLayers = hiddenLayers
        self.shuffle = shuffle
        self.minibatch_size = minibatch_size
        self.random = np.random.RandomState(seed)
        if activacion == 'logistic':
            self.f_activacion = sigmoid
            self.f_activacion_deriv = sigmoid_derivada
        #elif activacion == 'ReLU':
        #    self.f_activacion = ReLU
        #    self.f_activacion_deriv = ReLU_derivada
        else:
            self.f_activacion = tanh
            self.f_activacion_deriv = tanh_derivada
            
    
    def _onehot(self, y, n_classes):
        """Convierte las etiquetas en una representación de vectores de base canónica R^K siendo K el total de etiquetas
        Parameters
        ------------
        y : array, shape = [n_samples]
           Valores objetivo.
        Returns
        -----------
        onehot : array, shape = (n_samples, n_labels)
        """
        if n_classes == 2:
            onehot = np.zeros((1, y.shape[0]))
            for idx, val in enumerate(y.astype(int)):
                ilabel = self.Clases_y.tolist().index(val)
                onehot[0, idx] = int(ilabel)
        else:
            onehot = np.zeros((n_classes, y.shape[0]))
            for idx, val in enumerate(y.astype(int)):
                ilabel = self.Clases_y.tolist().index(val)
                onehot[ilabel, idx] = 1.
        return onehot.T
        
    def fit(self, X, y_inicial):
        """ Ajuste con los datos de entrenamiento.
        Parametros
        ----------
        X : {Tipo array}, shape = [n_ejemplo, n_caracteristicas]
        Vectores de entrenamiento, donde n_ejemplo es el numero de ejemplos y 
        n_caracteristicas es el número de características.
        y : tipo array, shape = [n_ejemplo] Valores Objetivo.
        Retorno
        -------
        self : objecto
        """
        ## Se obtiene las capas finales concatenando la de entrada y la de salida a las ocultas
        #self.L = len(capas)-1  ### Número de Capas
        #self.capas = capas
        self.capas = [X.shape[1]] + self.hiddenLayers + [len(np.unique(y_inicial))]
        self.L = len(self.capas)-1
        self.minibatch_size = len(X) if (self.minibatch_size == 0) else self.minibatch_size
        self.Clases_y = np.unique(y_inicial)
        y = self._onehot(y_inicial, len(self.Clases_y))
        
        self.weights = [[self.random.normal(loc=0.0, scale=0.01,size= self.capas[c])
                    for m in range(self.capas[c+1])] for c in range(self.L)]
        self.bias = [self.random.normal(loc=0.0, scale=0.01,size= self.capas[c+1]) for c in range(self.L)]
        
        ##print("weights", self.weights)
        ##print("bias", self.bias)
        self.coste = []
        
                
        for t in range(self.epocas):
            # iterate over minibatches
            cost=0
            indices = np.arange(X.shape[0])
            if self.shuffle:
                self.random.shuffle(indices)
            for start_idx in range(0, indices.shape[0] - self.minibatch_size + 1, self.minibatch_size):
                batch_idx = indices[start_idx:start_idx + self.minibatch_size]
                Z = []  ## lista donde guarda las salidas del sumatorio en cada capa
                A = [X[batch_idx]] ## lista para guardar la activación de cada capa. En la capa 1 es la X, en resto f(W(X))
                ##print("y", y)
                ### Primero se hace el avance hacia adelante
                for l in range(self.L):
                    ##print("FORWARD - CAPA ====>", l+1)
                    ##print("entrada", A[l])
                    sumatorioPesos = self.aplicaPesosLinealmente(l, A[l])
                    activacion = self.activation(sumatorioPesos)
                    Z.append(sumatorioPesos)  ## Se guarda la lista de sumatorios en A
                    A.append(activacion)  ## La activación de esta capa es la entrada de la próxima
                    ##print("sumatorioPesos", sumatorioPesos)
                    ##print("activacion", activacion)
                ## Se obtiene la matriz Delta en la capa L con la tasa de cambio del error
                errors = activacion - y[batch_idx]   ## Los errores y - la activacion de la ultima capa
                ##print("errors", errors)
                ##print("sigmoide Prima", self.activation_prima(Z[-1]))
                deltas = [errors * self.activation_prima(Z[-1])] ## delta de la capa última, la capa L
                ##print("deltas[ult]", len(deltas), len(deltas[0]), deltas)
                ### Retropropagación. Calcula desde la capa L-1 hacia atrás la matriz Delta 
                ## propagando la tasa de cambio del error obtenido en la capa L
                for l in reversed(range(self.L - 1)):
                    ##print("self.weights[", l+1, "]=", self.weights[l+1])
                    ##print("activacion_prima-cap[", l, "]", self.activation_prima(Z[l]))
                    delt_prop = np.dot(deltas[-1], self.weights[l+1])
                    ##print("delta propagado=", delt_prop)
                    delta = delt_prop * self.activation_prima(Z[l])
                    deltas.append(delta)
                    ##print("deltas[", l, "]=", delta)
            
                # invertir
                # Se han calculado las deltas de L hasta 1 (de salida a 1ª oculta).
                # Se ponen al reves de 1(1º oculta hasta salida)
                deltas.reverse()
            
                # Recalculo de pesos y bias usando la matriz delta de cada capa
                # Este proceso se puede hacer de la capa 1 a la L.
                # 1. Multiplcar los delta de salida con las activaciones de entrada 
                #    para obtener el gradiente del peso.
                # 2. actualizo el peso restandole un porcentaje del gradiente
                for l in range(self.L):
                    A_ = np.atleast_2d(A[l])
                    delta = np.atleast_2d(deltas[l])
                    ##print("Ajuste - Weights Incremental[", l, "]=", -self.eta * np.transpose(A_.T.dot(delta)))
                    ##print("Ajuste - Bias Incremental[", l, "]", -self.eta * np.sum(delta, axis=0))
                    self.weights[l] -= self.eta * np.dot(delta.T, A_) 
                    self.bias[l] -= self.eta * np.sum(delta, axis=0)
                    ##print("Ajuste - self.weights[", l, "]=", self.weights[l])
                    ##print("Ajuste - self.bias[", l, "]=", self.bias[l])
                
                cost_minbatch = (errors**2).sum() / (2.0 * X[batch_idx].shape[0])
                cost += cost_minbatch
            if t % 5000 == 0: print("Epoca =====>", t+1, "Coste ====>", cost)
            self.coste.append(cost)
        print("Epoca =====>", self.epocas, "Coste ====>", cost)
        return self
    
    def aplicaPesosLinealmente(self, l, X):
        """Calculate net input"""
        # Con esto agregamos la unidad de Bias a la capa de entrada
        return np.dot(X, np.transpose(self.weights[l])) + np.transpose(self.bias[l])
    
    def activation(self, X):
        """Compute linear activation"""
        return self.f_activacion(X)
    
    def activation_prima(self, X):
        """Compute linear activation"""
        return self.f_activacion_deriv(X)
    
    def predict(self, X):
        """Return class label after unit step"""
        # Se aplica un forward a través de todas las capas
        A_=X
        for l in range(self.L):
            A_ = self.activation(self.aplicaPesosLinealmente(l, A_))
        
        i_labels = np.argmax(A_, axis=1)
        y_pred = [self.Clases_y[i] for i in i_labels] 
        
        return np.array(y_pred)
    
    def predict_proba(self, X):
        """Return la probabilidad de cada clase"""
        # Se aplica un forward a través de todas las capas
        A_=X
        for l in range(self.L):
            A_ = self.activation(self.aplicaPesosLinealmente(l, A_))
        
        return A_

Función para imprimir una cifra manuscrita y su predicción

import matplotlib.pyplot as plt
import numpy as np

def plotCifra(matImag, probs):
    ##ps = probs.data.numpy().squeeze()
    fig, (ax1, ax2) = plt.subplots(figsize=(6,9), ncols=2)
    ax1.imshow(matImag.reshape(28, 28))
    ax1.axis('off')
    ax2.barh(np.arange(10), probs)
    ax2.set_aspect(0.1)
    ax2.set_yticks(np.arange(10))
    ax2.set_yticklabels(np.arange(10))
    ax2.set_title('Probabilidad')
    ax2.set_xlim(0, 1.1)

    plt.tight_layout()
import matplotlib.pyplot as plt

def plotCifra_old(matImag, probs):
    fig1, ax1 = plt.subplots()
    img = matImag.reshape(28, 28)
    ax1.imshow(img, cmap='Greys')
    ax1.set_xticks([])
    ax1.set_yticks([])
    plt.tight_layout()
    plt.show()

    fig, ax = plt.subplots()
    # Example data
    cifras = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
    y_pos = np.arange(len(cifras))
    ax.barh(y_pos, probs, align='center')
    ax.set_yticks(y_pos)
    ax.set_yticklabels(cifras)
    ax.invert_yaxis()  # labels read top-to-bottom
    ax.set_xlabel('Probabilidad')
    ax.set_title('Predicción de cifra manuscrita')
    plt.show()

Utilidades para pruebas con pytorch

import matplotlib.pyplot as plt
import numpy as np
from torch import nn, optim
from torch.autograd import Variable


def test_network(net, trainloader):

    criterion = nn.MSELoss()
    optimizer = optim.Adam(net.parameters(), lr=0.001)

    dataiter = iter(trainloader)
    images, labels = dataiter.next()

    # Create Variables for the inputs and targets
    inputs = Variable(images)
    targets = Variable(images)

    # Clear the gradients from all Variables
    optimizer.zero_grad()

    # Forward pass, then backward pass, then update weights
    output = net.forward(inputs)
    loss = criterion(output, targets)
    loss.backward()
    optimizer.step()

    return True


def imshow(image, ax=None, title=None, normalize=True):
    """Imshow for Tensor."""
    if ax is None:
        fig, ax = plt.subplots()
    image = image.numpy().transpose((1, 2, 0))

    if normalize:
        mean = np.array([0.485, 0.456, 0.406])
        std = np.array([0.229, 0.224, 0.225])
        image = std * image + mean
        image = np.clip(image, 0, 1)

    ax.imshow(image)
    ax.spines['top'].set_visible(False)
    ax.spines['right'].set_visible(False)
    ax.spines['left'].set_visible(False)
    ax.spines['bottom'].set_visible(False)
    ax.tick_params(axis='both', length=0)
    ax.set_xticklabels('')
    ax.set_yticklabels('')

    return ax


def view_recon(img, recon):
    ''' Function for displaying an image (as a PyTorch Tensor) and its
        reconstruction also a PyTorch Tensor
    '''

    fig, axes = plt.subplots(ncols=2, sharex=True, sharey=True)
    axes[0].imshow(img.numpy().squeeze())
    axes[1].imshow(recon.data.numpy().squeeze())
    for ax in axes:
        ax.axis('off')
        ax.set_adjustable('box-forced')
        
def view_classify(img, ps, version="MNIST"):
    ''' Function for viewing an image and it's predicted classes.
    '''
    ps = ps.data.numpy().squeeze()

    fig, (ax1, ax2) = plt.subplots(figsize=(6,9), ncols=2)
    ax1.imshow(img.resize_(1, 28, 28).numpy().squeeze())
    ax1.axis('off')
    ax2.barh(np.arange(10), ps)
    ax2.set_aspect(0.1)
    ax2.set_yticks(np.arange(10))
    if version == "MNIST":
        ax2.set_yticklabels(np.arange(10))
    elif version == "Fashion":
        ax2.set_yticklabels(['Camiseta/top',
                            'Pantalones',
                            'Jersey',
                            'Vestido',
                            'Abrigo',
                            'Sandalia',
                            'Camisa',
                            'Deportivas',
                            'Maleta',
                            'Bota'], size='small');
    ax2.set_title('Probabilidad')
    ax2.set_xlim(0, 1.1)

    plt.tight_layout()