np.linalg.norm es una función que calcula la longitud de un vector. La razón por la cual hemos extraído los números aleatorios de una distribución normal aleatoria –en lugar de una distribución uniforme, por ejemplo– y por la que hemos utilizado una desviación estándar de 0.01 es arbitraria; recuerda que solo nos interesan valores pequeños aleatorios para evitar las propiedades de vectores todo cero, como hemos dicho anteriormente.
La indexación de NumPy para matrices unidimensionales funciona de forma similar a las listas de Python, utilizando la notación de corchetes ([]). Para matrices bidimensionales, el primer indexador se refiere al número de fila y el segundo al número de columna. Por ejemplo, utilizaríamos X[2, 3] para seleccionar la tercera fila y la cuarta columna de una matriz X bidimensional X. |
Tras haber puesto a cero los pesos, el método fit recorre todas las muestras individuales del conjunto de entrenamiento y actualiza los pesos según la regla de aprendizaje del perceptrón tratada en la sección anterior. Las etiquetas de clase son predichas por el método predict, que es llamado en el método fit para predecir la etiqueta de clase para la actualización del peso, aunque también puede ser utilizado para predecir las etiquetas de clase de nuevos datos una vez ajustado nuestro modelo. Además, también recopilamos el número de errores de clasificación durante cada época en la lista self.errors_, de manera que posteriormente podemos analizar si nuestro perceptrón ha funcionado bien durante el entrenamiento. La función np.dot que se utiliza en el método net_input simplemente calcula el producto escalar de un vector
En lugar de utilizar NumPy para calcular el producto escalar entre dos matrices a y b mediante a.dot(b) o np.dot(a, b), también podemos realizar el cálculo con Python puro mediante sum([j * j for i, j in zip(a, b)]). Sin embargo, la ventaja de utilizar NumPy frente a las estructuras clásicas de Python for loop es que sus operaciones aritméticas son vectorizadas. La vectorización significa que una operación aritmética elemental se aplica automáticamente a todos los elementos de una matriz. Formulando nuestras operaciones aritméticas como una secuencia de instrucciones sobre una matriz, en lugar de llevar a cabo un conjunto de operaciones para cada elemento cada vez, se utilizan mejor las arquitecturas de CPU modernas con soporte SIMD (Single Instruction, Multiple Data o, en español, Una Instrucción, Múltiples Datos). Además, NumPy utiliza librerías de álgebra lineal altamente optimizadas como la Basic Linear Algebra Subprograms (BLAS) y la Linear Algebra Package (LAPACK), escritas en C o Fortran. Por último, NumPy también nos permite escribir nuestro código de un modo más compacto e intuitivo utilizando los conceptos básicos del álgebra lineal, como productos escalares de matrices y vectores. |
Entrenar un modelo de perceptrón en el conjunto de datos Iris
Para probar nuestra implementación del perceptrón, vamos a cargar dos clases de flor, Setosa y Versicolor, del conjunto de datos Iris. Aunque la regla del perceptrón no está restringida a dos dimensiones, por razones de visualización solo tendremos en cuenta las características de longitud de sépalo y longitud de pétalo. Además, por razones prácticas, elegimos solo las dos clases de flor: Setosa y Versicolor. Sin embargo, el algoritmo perceptrón se puede ampliar a una clasificación multidimensional –por ejemplo, la técnica One-versus-All (OvA)–.
OvA, a veces también llamada One-versus-Rest (OvR), es una técnica que nos permite ampliar un clasificador binario a problemas multiclase. Mediante OvA, podemos entrenar un clasificador por clase, donde cada clase individual se trata como una clase positiva y las muestras procedentes de otras clases se consideran clases negativas. Si tuviéramos que clasificar una nueva muestra de datos, utilizaríamos nuestros clasificadores n, donde n es el número de etiquetas de clase, y asignaríamos la etiqueta de clase con la fiabilidad más alta a cada muestra individual. En el caso del perceptrón, utilizaríamos OvA para elegir la etiqueta de clase asociada al mayor valor absoluto de entrada de red. |
Primero, utilizaremos la librería pandas para cargar el conjunto de datos Iris directamente del UCI Machine Learning Repository dentro de un objeto DataFrame e imprimir las últimas cinco líneas mediante el método tail para comprobar que los datos se han cargado correctamente:
>>> import pandas as pd
>>> df = pd.read_csv('https://archive.ics.uci.edu/ml/'
... 'machine-learning-databases/iris/iris.data',
... header=None)
>>> df.tail()
Puedes encontrar una copia del conjunto de datos Iris (y de todos los otros conjuntos de datos utilizados en este libro) en el paquete de código de este libro, que puedes utilizar si estás trabajando offline o si el servidor UCI https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data está temporalmente no disponible. Por ejemplo, para cargar el conjunto de datos Iris desde el directorio local, puedes sustituir esta línea:df = pd.read_csv('https://archive.ics.uci.edu/ml/' 'machine-learning-databases/iris/iris.data', header=None)por esta otra:df = pd.read_csv('your/local/path/to/iris.data', header=None) |
A continuación, extraemos las 100 primeras etiquetas de clase que corresponden a las 50 flores Iris-setosa y a las 50 Iris-versicolor, y convertimos las etiquetas de clase en las dos etiquetas de clase enteras 1 (versicolor) y -1 (setosa) que asignamos a un vector y, donde el método de valores de un DataFrame pandas produce la correspondiente representación NumPy.
De forma similar, extraemos la primera columna de características (longitud del sépalo) y la tercera columna de características (longitud del pétalo) de las 100 muestras de entrenamiento y las asignamos a una matriz X de características, que podemos ver a través de un diagrama de dispersión bidimensional:
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> # seleccionar setosa y versicolor
>>> y = df.iloc[0:100, 4].values
>>> y = np.where(y == 'Iris-setosa', -1, 1)
>>> # extraer longitud de sépalo y longitud de pétalo
>>> X = df.iloc[0:100, [0, 2]].values
>>> # representar los datos
>>> plt.scatter(X[:50, 0], X[:50, 1],
... color='red', marker='o', label='setosa')
>>> plt.scatter(X[50:100, 0], X[50:100, 1],
... color='blue', marker='x', label='versicolor')
>>> plt.xlabel('sepal length [cm]')
>>> plt.ylabel('petal length [cm]')
>>> plt.legend(loc='upper left')