Search

Adaline – The Perceptron Evolution

¿Y si quisiéramos evolucionar nuestro perceptrón, haciéndolo algo más ‘inteligente’?. Excepto algunas personas con las que la evolución ha fracasado estrepitosamente, eso es lo que llevamos haciendo miles de millones de años. Una evolución del Perceptrón es ADALINE, Adaptative Linear Neuron.

En 1960 Bernard Widrow y Marcian Hoff publicaron ‘An Adaptative “Adaline” Neuron using chemical “memistors”‘donde describen cómo, utilizando circuitos resistivos con memoria, el sistema puede aprender de forma automática, cambiando su propia estructura para mejorar su rendimiento, teniendo en cuenta sus experiencias anteriores.

La principal diferencia con el modelo del Perceptrón, es que, mientras en éste, la función de activación era una función escalón, en Adaline la función de activación es una función lineal. Ahora utilizaremos el valor de salida de la función lineal para ir calculando el error e ir actualizando los pesos.

Nuestra función de entrada, z, sigue siendo el producto de las entradas por los pesos:

    \[ z = \sum_{i=0}^{n} x_{i}w_{i} = x_{0}w_{0} + x_{1}w_{1} + \cdots + x_{n}w_{n} = \mathbf{w}^{T}\mathbf{x} \]

Nuestra función de activación será:

    \[ \varphi(z)=\varphi(\mathbf{w}^{T}\mathbf{x}) = \mathbf{w}^{T}\mathbf{x}\]

y la utilizaremos para actualizar los pesos.

La función cuantificador nos servirá para predecir a qué clase pertenece cada muestra.

Adaline sigue siendo una red neuronal de una sola capa, y tiene la misma limitación que el perceptron, ya que únicamente puede resolver problemas linealmente separables.

Otra diferencia importante con respecto al Perceptrón, es que con Adaline se actualizan todos los pesos de todas las muestras del conjunto de datos a la vez en cada iteración.

El algoritmo de aprendizaje que utiliza Adaline es conocido como la regla de Widrow-Hoff, en la que se busca minimizar la función que calcula el error entre la entrada y el valor real.

Para el cálculo de dicho error se utiliza el Error Cuadrático Medio.

Y para minimizarlo se utiliza el método de Descenso por Gradiente o Gradient Descent, del que hablé en mi post: Beam me up, Gradient Descent!.

Así pues,  iremos actualizando los pesos utilizando el gradiente de la función de costo y una tasa de aprendizaje (η) que tendremos que elegir.

Si nuestra función de costo es:

    \[ J= \frac{1}{2}\sum_{i=1}^n (y_{i}- \hat{y}_{i})^{2} \]

El gradiente de dicha función nos quedará:

    \[ \frac{\partial J}{\partial w_{j}} = \frac{\partial }{\partial w_{j}}\left ( \frac{1}{2}\sum_{i}^{ } \left ( \delta_{i} - \varphi(z_{i}) \right )^{2}\right )\]

    \[ \frac{\partial J}{\partial w_{j}} = - \sum_{i}^{ }\left ( \delta_{i} -\varphi(z_{i} \right )x_{ij}\]

Y por lo tanto, para el incremento de los pesos tendremos:

    \[\Delta w_{n} = - \eta\nabla J(w) = \eta\sum_{i}^{ }\left ( \delta_{i} -\varphi(z_{i} \right )x_{ij}\]

Los pasos del algortimo son los siguiente:

  1. Al comienzo del algoritmo se escoge un valor aleatorio, cercano a cero, para los pesos. Se elige una tasa de aprendizaje entre 0 y 1. Se elige el número de iteraciones (epochs) que realizará el algoritmo. Se podría decir al algoritmo que parara al llegar a un cierto valor de la función de coste. Pero lo que haremos es ir haciendo pruebas con el número de iteraciones y la tasa de aprendizaje. Después de cada prueba dibujaremos el valor de la función de coste con respecto al número de iteración, para comprobar cómo se va ajustando el modelo. Dicho valor deberá disminuir con cada iteración, hasta alcanzar un mínimo.
  2. Calculamos nuestra función z, esto es, el sumatorio de los productos de las entradas por sus pesos.
  3. Calculamos los errores entre lo que resulta de nuestra función z  y las salidas esperadas.
  4. Actualizamos los pesos. Así, en cada iteración, los pesos se verán actualizados de la siguiente forma:

    \[w = w + \Delta w\]

Bien! Ahora que ya tenemos claro cómo funciona el algoritmo, vamos a meternos en harina.

Como siempre, primero los datos. Vamos a probar con dos dataset distintos. El primero será el mismo que utilizamos en el modelo del Perceptrón. Y también probaremos con otro dataset creado de la misma manera que el del Perceptrón. Te puedes descargar ambos aquí.

Empezamos importando las librerías que vamos a necesitar.

Importamos los datos y vemos qué pinta tienen.

Podemos sacar una tabla resumen de las características de los datos, mediante la siguiente función. Es la función describe para dataframes de Pandas, a la que he añadido la cuenta de valores nulos, la cuenta de los valores únicos, la moda y la desviación típica por característica.

Vamos a preparar un poco los datos, normalizando todas las características de los dataframes al mismo rango. El uso de ésta técnica es frecuente en métodos de Machine Learning, y permite un mejor comportamiento de los algoritmos. Gradient Descent es uno de los algoritmos a los que sienta bien realizar un escalado previo de los datos. Sin embargo, hay que utilizar estas técnicas con cuidado, ya que un mal uso puede dar al traste con tus datos y en consecuencia con tu modelo.

Para normalizar los datos utilizaremos la normalización estandar, que consiste en, a cada muestra de nuestros datos, restarle la media y dividir el resultado por la desviación típica:

    \[ Xnorm_{i} = \frac{x_{i} - \overline{x}_{i}}{\sigma_{i}}\]

Después de aplicar la normalización a los dos dataframes, df_perceptron y df_adaline, vamos a dividir los datos en datos de entrenamiento y datos de prueba. A continuación,  meteremos éstos en un diccionario que llamaremos train_test_data, en el que la clave será el nombre del dataframe y los valores los obtenidos después de usar el la función train_test_split del módulo model_selection de scikit-learn.

Lo haremos así:

Vale. Ahora que ya tenemos los datos preparados, vamos a por nuestro modelo. Para ellos crearemos una clase que llamaremos Adaline. Lo primero será ‘construir’ la clase  con el método __init__ , en el que pondremos como parámetros de entrada la tasa de aprendizaje y el número de iteraciones:

Lo siguiente será añadir un método, al que vamos a llamar zeta que nos calcule el valor de la función z, esto es, el producto de las características por sus pesos:

También añadimos la función de activación, que en nuestro caso hemos dicho que tomará el mismo valor que zeta:

Ahora vamos a añadir el método fit, que se encargará de ir comprobando los errores, actualizar los pesos y calcular el valor de la función de coste:

Por último, añadimos el método predict, que será el encargado de clasificar en función del valor de la función de activación:

Con esto ya tendríamos la clase Adaline preparada. Así que vamos al lío.

Vamos a entrenar el algoritmo con los dos dataset que tenemos. Después dibujaremos los datos clasificados y la región a la que pertenecen,  y una gráfica del valor de la función de coste vs el número de iteración (epoch). Para dibujar los datos clasificados utilizaremos esta función, plot_decision_regions, que representará los datos de entrenamiento, las zonas de clasificación, y los datos de test.

Además, evaluaremos el modelo, calculando la matriz de confusión, y algunas métricas como la Sensibilidad (Recall), Accuracy y Precisión, de las que hable en mi post: Matriz de confusión.

El código anterior muestra lo siguiente:

¡Oh!, tenemos un 100% de acierto sobre nuestros datos de test!. Y además el modelo necesita muy pocas iteraciones para converger!

Anímate y entrena con tus propios datos a Adaline!

Puedes descargarte el código de mi 

Bibliografía: Python Machine Learning, Sebastian Raschka.

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.