Come abbiamo imparato dal capitolo precedente, la differenza sostanziale tra il Perceptron e la regola Adaline sta nell’aggiornamento dei pesi che, nel primo caso avviene tramite l’utilizzo di una funzione a passo unitario mentre per il restante caso attraverso una funzione di attivazione lineare.
Nel modello Adaline inoltre, viene utilizzato un quantizzatore, che permette di prevedere le etichette delle classi.
Il grafico dei costi di Adaline
Come per tuttigli algoritmi di Machine Learning, dobbiamo trovare il tasso di apprendimento tale da garantire una convergenza ottimale. Per l’algoritmo Adaline decidiamo di partire con due tassi di apprendimento corrispondenti a n = 0.1 e n = 0.0001 e successivamente di tracciare le funzioni dei costi rispetto al numero di epoch.
Tutto ciò per vedere con quale efficacia Adaline in Python impara dai dati di addestramento.
Per una migliore gestione del codice, consiglio di proseguire in maniera progressiva a cascata sul file creato per l’implementazione del Perceptron così da avere i due modelli in un singolo file.
Riporto ora il codice necessario per implementare la classe Adaline in Python. Questa sezione l’abbiamo già analizzata nel capitolo precedente.
class AdalineGD(object):
"""ADAptive LInear NEuron classifier.
Parameters
------------
eta : float
Learning rate (between 0.0 and 1.0)
n_iter : int
Passes over the training dataset.
Attributes
-----------
w_ : 1d-array
Weights after fitting.
cost_ : list
Sum-of-squares cost function value in each epoch.
"""
def __init__(self, eta=0.01, n_iter=50):
self.eta = eta
self.n_iter = n_iter
def fit(self, X, y):
""" Fit training data.
Parameters
----------
X : {array-like}, shape = [n_samples, n_features]
Training vectors, where n_samples is the number of samples and
n_features is the number of features.
y : array-like, shape = [n_samples]
Target values.
Returns
-------
self : object
"""
self.w_ = np.zeros(1 + X.shape[1])
self.cost_ = []
for i in range(self.n_iter):
net_input = self.net_input(X)
# Please note that the "activation" method has no effect
# in the code since it is simply an identity function. We
# could write `output = self.net_input(X)` directly instead.
# The purpose of the activation is more conceptual, i.e.,
# in the case of logistic regression, we could change it to
# a sigmoid function to implement a logistic regression classifier.
output = self.activation(X)
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 self.net_input(X)
def predict(self, X):
"""Return class label after unit step"""
return np.where(self.activation(X) >= 0.0, 1, -1)
E ora non ci resta che tracciare il grafico dei costi rispetto al numero di epoch con i tassi di apprendimento precedentemente descritti:
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(8, 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.tight_layout()
# plt.savefig('./adaline_1.png', dpi=300)
plt.show()
Ed ecco ora il risultato:
Analisi dei grafici di appredimento di Adaline in Python
I due grafici presentano in maniera evidente dei problemi:
- Il primo grafico mostra ciò che accade se scegliamo un tasso di apprendimento troppo ampio, infatti, invece di minimizzare la funzione di costo, l‘errore diviene sempre più ampio ad ogni epoch.
- Nel grafico a destra invece, il tasso di apprendimento troppo piccolo permette all’algoritmo di convergere dopo troppi epoch.
Riprendendo il concetto di gradiente, come illustrato dall’immagine:
Il grafico a sinistra ci mostra come possiamo cambiare il valore del parametro del peso per minimizzare la funzione di costo J. Invece, come nel caso della figura a destra, notiamo cosa succede se scegliamo un tasso di apprendimento troppo ampio: destabilizziamo sempre il minimo globale.
Necessitiamo quindi di una riduzione di scala per permettere all’algoritmo di performare nel migliore dei modi. La discesa del gradiente è proprio uno di questi algoritmi.
Standardizzazione per la riduzione in scala
La standardizzazione dei dati è il processo di ridimensionamento di uno o più attributi in modo che abbiano un valore medio di 0 e una deviazione standard di 1. Come risultato otterremo che le caratteristiche saranno ridimensionate in modo da avere le proprietà di una distribuzione normale con media nulla e deviazione standard pari a 1. Il valore Z-standard viene calcolato come segue:
dove u è la media dei campioni di addestramento, σ è la deviazione standard dei campioni di addestramento e xi è il valore che si vuole standardizzare.
Per effettuare la standardizzazione di Adaline in Python utilizzeremo i metodi mean e std di NumPy:
X_std = np.copy(X)
X_std[:, 0] = (X[:, 0] - X[:, 0].mean()) / X[:, 0].std()
X_std[:, 1] = (X[:, 1] - X[:, 1].mean()) / X[:, 1].std()
A questo punto non ci resta che riaddestrare nuovamente Adaline in Python e vedere come converge con tasso di apprendimento n = 0.01.
ada = AdalineGD(n_iter=15, eta=0.01)
ada.fit(X_std, y)
plot_decision_regions(X_std, y, classifier=ada)
plt.title('Adaline - Gradient Descent')
plt.xlabel('sepal length [standardized]')
plt.ylabel('petal length [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
# plt.savefig('./adaline_2.png', dpi=300)
plt.show()
plt.plot(range(1, len(ada.cost_) + 1), ada.cost_, marker='o')
plt.xlabel('Epochs')
plt.ylabel('Sum-squared-error')
plt.tight_layout()
# plt.savefig('./adaline_3.png', dpi=300)
plt.show()
Notiamo così che l’algoritmo Adaline in Python converge con un tasso di apprendimento n = 0.01.