for i in range(self.n_iter):
net_input = self.net_input(X)
output = self.activation(net_input)
errors = (y - output)
self.w_[1:] += self.eta * X.T.dot(errors)
self.w_[0] += self.eta * errors.sum()
cost = (errors**2).sum() / 2.0
self.cost_.append(cost)
return self
def net_input(self, X):
"""Calculate net input"""
return np.dot(X, self.w_[1:]) + self.w_[0]
def activation(self, X):
"""Compute linear activation"""
return X
def predict(self, X):
"""Return class label after unit step"""
return np.where(self.activation(self.net_input(X))
>= 0.0, 1, -1)
En lugar de actualizar los pesos después de evaluar cada muestra de entrenamiento individual, como en el perceptrón, calculamos el gradiente en base a todo el conjunto de datos de entrenamiento mediante self.eta * errors.sum() para el parámetro del sesgo (peso cero) y mediante self.eta * X.T.dot(errors) para los pesos 1 a m, donde X.T.dot(errors) es una multiplicación matriz por vector entre nuestra matriz de características y el vector de error.
Observa que el método activation no tiene ningún efecto sobre el código, puesto que es simplemente una función de identidad. En este caso, hemos añadido la función de activación (calculada mediante el método activation) para mostrar cómo fluye la información a través de una red neuronal de una sola capa: características a partir de los datos de entrada, entrada de red, activación y salida. En el siguiente capítulo, conoceremos un clasificador de regresión logística que utiliza una función de activación no lineal sin identidad. Veremos que un modelo de regresión logística está estrechamente relacionado con Adaline, siendo la única diferencia su activación y función de coste.
Ahora, de forma parecida a la implementación del perceptrón anterior, recogemos los valores de coste en una lista self.cost_ para comprobar si el algoritmo converge después del entrenamiento.
Llevar a cabo una multiplicación matriz por vector es similar a calcular un producto escalar, donde cada fila de la matriz es tratada como un único vector de fila. Este enfoque vectorizado representa una notación más compacta y da como resultado un cálculo más eficiente con NumPy. Por ejemplo: |
A la práctica, esto suele requerir algo de experimentación para encontrar un buen rango de aprendizaje
El rango de aprendizaje |
Ahora veamos en un diagrama el coste contra el número de épocas para los dos rangos de aprendizaje distintos:
>>> fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(10, 4))
>>> ada1 = AdalineGD(n_iter=10, eta=0.01).fit(X, y)
>>> ax[0].plot(range(1, len(ada1.cost_) + 1),
... np.log10(ada1.cost_), marker='o')
>>> ax[0].set_xlabel('Epochs')
>>> ax[0].set_ylabel('log(Sum-squared-error)')
>>> ax[0].set_title('Adaline - Learning rate 0.01')
>>> ada2 = AdalineGD(n_iter=10, eta=0.0001).fit(X, y)
>>> ax[1].plot(range(1, len(ada2.cost_) + 1),
... ada2.cost_, marker='o')
>>> ax[1].set_xlabel('Epochs')
>>> ax[1].set_ylabel('Sum-squared-error')
>>> ax[1].set_title('Adaline - Learning rate 0.0001')
>>> plt.show()
Como podemos ver en los diagramas de función coste obtenidos, nos encontraríamos con dos tipos de problemas. El gráfico de la izquierda muestra qué pasaría si eligiéramos un rango de aprendizaje demasiado amplio. En lugar de minimizar la función de coste, el error es mayor en cada época, porque sobrepasamos el mínimo global. Por otro lado, podemos ver que el coste disminuye en el diagrama de la derecha, pero el rango de aprendizaje elegido
La siguiente imagen ilustra qué pasaría si cambiáramos el valor de un parámetro de peso concreto para minimizar la función de coste
Mejorar el descenso de gradiente mediante el escalado de características
Muchos de los algoritmos de aprendizaje automático con los que nos encontraremos en este libro requieren algún tipo de escalado de características para un rendimiento óptimo, como veremos con mayor detalle en el Capítulo 3, Un recorrido por los clasificadores de aprendizaje automático con scikit-learn y en el Capítulo 4, Generar buenos modelos de entrenamiento - Preprocesamiento de datos.
El descenso de gradiente es uno de los muchos algoritmos que se benefician del escalado de características. En esta sección, utilizaremos un método de escalado de características denominado normalización, que proporciona a nuestros datos la propiedad de una distribución normal estándar, la cual ayuda al descenso de gradiente a converger más rápidamente. La normalización cambia la media de cada característica para que se centre en cero y para que cada característica tenga una desviación estándar de 1. Por ejemplo, para normalizar la característica j, podemos simplemente sustraer la media de muestra