A blog about Front End Development, Data Science, Machine Leaning and Python.

Uno dei primi algoritmi descritti agli albori del Machine Learning, utilizzato per compiti di classificazione, fu il Perceptron.

Warren McCullock e Walter Pitts pubblicarono nel 1943 un primo schema di cellula del cervello, il neurone di McCullock-Pitts.

I neuroni sono le nostre cellule nervose le quali, attraverso segnali chimici ed elettrici, permettono il passaggio e l'elaborazione di "dati" all'interno del nostro cervello.

Il neurone di McCullock-Pitts presenta un gate logico con output binario; ai dendriti arrivano i segnali, entrano nel nucleo della cellula e se il segnale supera una certa soglia di attivazione, allora la cellula produce un segnale di output passato tramite l'assone che viene propagato come segnale di output dai terminali dell'assone.

Pochi anni dopo Frank Rosenblatt pubblicΓ² il primo concetto di apprendimento basato su Perceptron. Rosenblatt propose un algoritmo che avrebbe appreso i coefficienti di peso ottimali da moltiplicare con i segnali di input, in maniera tale da decretare se il neurone si dovesse attivare oppure no.

Nel contesto del Machine Learning un algoritmo di questo tipo potrebbe assegnare l'appartenenza di un campione ad una determinata classe.

Questo problema puΓ² essere classificato in maniera binaria, dove due classi, una positiva 1, ed una negativa -1, possono essere decretate attraverso una funzione di attivazione 𝝓(z). Tale funzione puΓ² prendere una combinazione lineare di valori di input x e un corrispondente vettore di pesi w, dove z rappresenta l'input della rete.

$$(z = w_{1} x_{1} + ... + w_{m} x_{m})$$

$ w = \left[
\begin{matrix}
w_{1} \\
\vdots \\
w_{m}
\end{matrix}
\right] $

$ x = \left[
\begin{matrix}
x_{1} \\
\vdots \\
x_{m}
\end{matrix}
\right] $

Se un campione $x^{i}$ Γ¨ maggiore della soglia di attivazione determinata con 𝝓, allora il campione rientrerΓ  nella classe 1, altrimenti nel caso fosse minore alla soglia verrebbe classificato in Β -1.

Nell'algoritmo di Perceptron, la funzione di attivazione Γ¨ una semplice funzione di passo unitario, chiamata a volte funzione di passo Heaviside.

$$ 𝝓(z) =
\begin{cases}
1 Β & \text{if $z$ is β‰₯ ΞΈ} \\
-1 & \text{altrimenti}
\end{cases} $$

L'input della rete $z = w^{T} x$ passando per la funzione di attivazione del Perceptron, viene classificato come output binario in -1 o 1. Questo algoritmo permette di discriminare tra due classi separabili linearmente.

La convergenza del Perceptron avviene solo nei casi in cui le due classi risultino separabili linearmente e se il tasso di apprendimento Γ¨ ridotto. Si puΓ² definire eventualmente un numero di passi sul dataset di apprendimento (epoch) e una soglia di classificazioni errate. In caso contrario il Perceptron continuerebbe sempre a correggere i pesi.

Durante la fase di apprendimento l'output serve per calcolare l'errore nella previsione e per aggiornare di conseguenza i pesi.

Implementazione Perceptron con Python

Il seguente codice addestra un modello del Perceptron sul dataset Iris. Le due caratteristiche che verranno considerate nella visualizzazione sono la lunghezza del sepalo (sepal length) e del petalo (petal length).

Il seguente codice Γ¨ dato dalla classificazione attraverso l'algoritmo classico del Perceptron che troviamo in "Python Machine Learning by Sebastian Raschka, 2015".

Per comoditΓ  consiglio l'utilizzo dello strumento Colab per l'implementazione del seguente codice.

import pandas as pd
df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data', header=None)
df.tail()
Vengono estratte le prime 100 etichette delle classi di fiori corrispondenti a 50 Iris Setosa e 50 Iris Versicolor
import matplotlib.pyplot as plt
import numpy as np
y = df.iloc[0:100, 4].values
y

#Risultato
array(['Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
       'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
       'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
       'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
       'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
       'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
       'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
       'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
       'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
       'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
       'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
       'Iris-setosa', 'Iris-setosa', 'Iris-setosa', 'Iris-setosa',
       'Iris-setosa', 'Iris-setosa', 'Iris-versicolor', 'Iris-versicolor',
       'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
       'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
       'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
       'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
       'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
       'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
       'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
       'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
       'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
       'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
       'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
       'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
       'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
       'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
       'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor',
       'Iris-versicolor', 'Iris-versicolor', 'Iris-versicolor'],
      dtype=object)


Vengono convertite le due etichette delle classi Setosa e Versicolor in due classi intere 1 e -1, le quali vengono assegnate ad un vettore y dove il metodo dei valori di un padas DataFrame fornisce la corrispondente rappresentazione NumPy.

y = np.where(y == 'Iris-setosa', -1, 1)
y

#Risultato
array([-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
       -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
       -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
        1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1])

Vengono estratte la prima colonna delle caratteristiche (sepal length) e la terza colonna di caratteristiche (petal length) dei 100 campioni di addestramento. Vengono poi assegnati tali valori ad una matrice X che viene rappresentata tramite un diagramma a dispersione bidimensionale.

X = df.iloc[0:100, [0, 2]].values
X

#Risultato
array([[5.1, 1.4],
       [4.9, 1.4],
       [4.7, 1.3],
        ........
       [5.1, 3. ],
       [5.7, 4.1]])

Attraverso matplotlib viene visualizzato il diagramma a dispersione.

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('petal length')
plt.ylabel('sepal length')
plt.legend(loc='upper left')
plt.show()
Diagramma a dispersione

Training del modello di Perpectron

Viene addestrato l'algoritmo di Perceptron sul sottoinsieme dei dati del dataset Iris. Ad ogni epoca viene tracciato l'errore di mancata classificazione, al fine di controllare che l'algoritmo converga e riesca a trovare un confine decisionale per le due classi di fiori Iris.

Scarica il file perceptron.py
from google.colab import files
src = list(files.upload().values())[0]
open('perceptron.py','wb').write(src)
import perceptron
from perceptron import Perceptron

pn = Perceptron(0.1, 10)
pn.fit(X, y)
plt.plot(range(1, len(pn.errors) + 1), pn.errors, marker='o')
plt.xlabel('Epoche')
plt.ylabel('Numero errori classificazione')
plt.show()
Grafico degli errori di classificazione rispetto al numero di epoche.

Il Perceptron converge dopo 6 epoche (iterazioni). Adesso Γ¨ possibile fare una classificazione.

Visualizzare la classificazione

Viene implementata una funzione per rappresentare il confine decisionale.

from matplotlib.colors import ListedColormap

def plot_decision_regions(X, y, classifier, resolution=0.02):
   # setup marker generator and color map
   markers = ('s', 'x', 'o', '^', 'v')
   colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
   cmap = ListedColormap(colors[:len(np.unique(y))])

   # plot the decision surface
   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)
   plt.contourf(xx1, xx2, Z, alpha=0.4, cmap=cmap)
   plt.xlim(xx1.min(), xx1.max())
   plt.ylim(xx2.min(), xx2.max())

   # plot class samples
   for idx, cl in enumerate(np.unique(y)):
      plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1],
      alpha=0.8, c=cmap(idx),
      marker=markers[idx], label=cl)

Nel codice sopra, definiamo un numero di colori e marcatori e creiamo una mappa dei colori dall'elenco dei colori tramite ListedColormap.

Determiniamo i valori minimo e massimo per le due caratteristiche e utilizziamo questi vettori per creare una coppia di matrici della griglia xx1 e xx2 tramite la funzione meshgrid di NumPy.

PoichΓ© abbiamo addestrato il nostro classificatore perceptron su due dimensioni di caratteristiche, dobbiamo appiattire gli array a griglia e creare una matrice che abbia lo stesso numero di colonne del sottoinsieme di training set Iris in modo da poter utilizzare il metodo predict per prevedere le etichette di classe Z del punti sulla griglia.

Dopo aver rimodellato le etichette della classe prevista Z in una griglia con le stesse dimensioni di xx1 e xx2, possiamo disegnare un diagramma di contorno tramite la funzione di contorno di matplotlib che mappa le diverse regioni di decisione su colori diversi per ogni classe prevista nell'array a griglia:

plot_decision_regions(X, y, classifier=pn)
plt.xlabel('sepal length [cm]')
plt.ylabel('petal length [cm]')
plt.legend(loc='upper left')
plt.show()
Grafico regioni decisionali per la classificazione del sottoinsieme di addestramento Iris
Background vector created by freepik - www.freepik.com

You've successfully subscribed to Data Science - Machine Learning e Python
Welcome back! You've successfully signed in.
Great! You've successfully signed up.
Success! Your account is fully activated, you now have access to all content.