In questo articolo si propone di esplorare il mondo dei perceptron, concentrandosi in particolare sul Single Layer Perceptron (SLP), il quale, sebbene costituisca solo una piccola frazione dell’architettura complessiva delle reti neurali profonde, fornisce una solida base per comprendere i meccanismi fondamentali del Deep Learning. Introdurremo anche degli esempi pratici di implementazione in Python, illustrando come costruire e visualizzare un SLP utilizzando librerie come NumPy, NetworkX e Matplotlib.
[wpda_org_chart tree_id=46 theme_id=50]
Il Single Layer Perceptron (SLP)
Il Single Layer Perceptron (SLP), o perceptron a singolo strato, è un tipo di rete neurale artificiale che costituisce il fondamento dei modelli più complessi di Deep Learning. Tuttavia, va notato che il termine “deep” in deep learning si riferisce a reti neurali profonde con più di uno strato nascosto, mentre il perceptron a singolo strato è considerato un modello di apprendimento superficiale.
Il SLP è composto da un solo strato di nodi, anche chiamati perceptron o neuroni, che ricevono input pesati, li sommano e applicano una funzione di attivazione per produrre l’output. La struttura base di un perceptron è la seguente:
Input: Gli input vengono moltiplicati per i pesi associati e sommati.
Funzione di attivazione: La somma ponderata viene poi passata attraverso una funzione di attivazione per produrre l’output del perceptron.
La funzione di attivazione introduce non linearità nella rete, consentendo al perceptron di apprendere relazioni complesse tra gli input e gli output.
Il SLP è in grado di apprendere solo modelli lineari e risolvere problemi di classificazione binaria, dove l’output è 0 o 1. Tuttavia, la sua limitazione principale è che non può affrontare problemi che richiedono la modellazione di relazioni non lineari complesse. Per superare queste limitazioni, sono stati sviluppati modelli più complessi come i Multi-Layer Perceptron (MLP), che consistono in più strati nascosti di perceptron, permettendo alla rete di apprendere rappresentazioni più complesse e di affrontare problemi più difficili.
Sviluppiamo un Single Layer Perceptron (SLP) in Python
Adesso svilupperemo un Single Layer Perceptron(SLP) con il codice Python, senza utilizzare alcuna libreria. Questo potrà essere un ottimo punto di partenza per i nostri studi sull’argomento delle reti neurali, e per effettuare alcune sperimentazioni.
Partiamo dalla costruzione della classe corrispondente che chiameremo SingleLayerPerceptron.
import numpy as np
class SingleLayerPerceptron:
def __init__(self, input_size):
# Inizializzazione dei pesi e del bias con valori casuali
self.weights = np.random.rand(input_size)
self.bias = np.random.rand(1)
def sigmoid(self, x):
# Funzione di attivazione Sigmoid
return 1 / (1 + np.exp(-x))
def predict(self, inputs):
# Calcolo della somma ponderata degli input e applicazione della funzione di attivazione
z = np.dot(inputs, self.weights) + self.bias
return self.sigmoid(z)
In questo esempio, la classe SingleLayerPerceptron
ha un metodo predict
che prende un vettore di input e restituisce l’output del perceptron dopo aver applicato la funzione di attivazione sigmoidale. I pesi e il bias vengono inizializzati casualmente durante la creazione del perceptron.
- Creazione del Perceptron: con __init__() stiamo definendo la classe
SingleLayerPerceptron
con un costruttore che inizializza casualmente i pesi e il bias del perceptron. - Funzione di Attivazione Sigmoid: con sigmoid() si calcola la sigmoid di un dato valore, che viene utilizzata come funzione di attivazione nel perceptron.
- Funzione di Predizione: con predict() si prende un vettore di input, calcola la somma ponderata degli input pesati e applica la funzione di attivazione sigmoidale.
Adesso scriviamo in Python un esempio del suo utilizzo. Creiamo un Perceptron in grado di ricevere 3 input e sottoponiamogli poi un array di 3 elementi corrispondenti ai tre valori di input. Chiamiamo la funzione predict() per ottenere il valore di output corrispondente.
if __name__ == "__main__":
# Creazione di un perceptron con 3 input
input_size = 3
perceptron = SingleLayerPerceptron(input_size)
# Esempio di input
input_data = np.array([0.5, 0.3, 0.8])
# Predizione dell'output
output = perceptron.predict(input_data)
# Stampare i risultati
print("Input:", input_data)
print("Output:", output)
Eseguendo si ottiene il seguente valore:
Input: [0.5 0.3 0.8]
Output: [0.67700351]
Aggiungiamo una rappresentazione grafica della rete neurale
Per visualizzare la struttura di un perceptron a singolo strato, possiamo utilizzare la libreria networkx
per creare un grafo dei nodi (neuroni) e dei collegamenti (pesi). In questo esempio, useremo un perceptron con 3 input e un nodo di output. Installa prima la libreria, se non l’hai già fatto, eseguendo:
pip install networkx
Dopo di che, possiamo implementare all’interno della classe utilizzare il seguente codice Python:
import networkx as nx
import matplotlib.pyplot as plt
def plot_network(self):
# Creazione di un grafo diretto
G = nx.DiGraph()
# Aggiungi nodi
G.add_nodes_from(['Input {}'.format(i+1) for i in range(len(self.weights))])
G.add_node('Summation Node')
G.add_node('Output')
# Aggiungi archi
for i, weight in enumerate(self.weights):
G.add_edge('Input {}'.format(i+1), 'Summation Node', weight=weight)
G.add_edge('Summation Node', 'Output', weight=self.bias[0])
# Posiziona i nodi
m = np.mean(range(len(self.weights)))
pos = {'Summation Node': (1, m)}
for i in range(len(self.weights)):
pos['Input {}'.format(i+1)] = (0, i)
pos['Output'] = (2, m)
# Disegna il grafo
nx.draw(G, pos, with_labels=True, font_weight='bold', node_size=1000, node_color='skyblue', font_size=8, arrowsize=20)
# Etichette degli archi con i pesi
edge_labels = {(edge[0], edge[1]): str(round(edge[2]['weight'], 2)) for edge in G.edges(data=True)}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels)
# Mostra il plot
plt.show()
In questo esempio, la classe SingleLayerPerceptronVisualization
ha un metodo plot_network
che crea e visualizza il grafo della rete neurale utilizzando networkx
. I nodi rappresentano gli input e l’output, mentre gli archi rappresentano i pesi associati. Gli archi sono etichettati con i rispettivi pesi.
E estendere con la nuova funzione il codice di utilizzo dell’esempio precedente:
# Esempio di utilizzo
if __name__ == "__main__":
# Creazione di un perceptron con 3 input
input_size = 3
perceptron = SingleLayerPerceptron(input_size)
# Esempio di input
input_data = np.array([0.5, 0.3, 0.8])
# Predizione dell'output
output = perceptron.predict(input_data)
# Stampare i risultati
print("Input:", input_data)
print("Output:", output)
# Visualizzazione della rete
perceptron.plot_network()
Eseguendo il codice otterremo sia in output sia i risultati precedenti che la visualizzazione del single Layer perceptron con i valori dei pesi di ciascun singolo nodo.
Input: [0.5 0.3 0.8]
Output: [0.7432648]
Puoi esplorare questo esempio cambiando il numero di input o modificando i pesi in modo da vedere come influiscono sulla struttura del grafo della rete neurale.
Visualizziamo il Decision Boundary del Single Layer Perceptron
Estendiamo l’esempio di utilizzo del nostro Single Layer Perceptron inserendo una serie di valori input casuali, in modo da definire in maniera grafica un Decision Boundary. Per tale scopo aggiungiamo un altra funzione alla nostra classe.
def plot_decision_boundary(self, inputs, labels):
# Estrai i pesi e il bias
w1, w2 = self.weights[:2]
b = self.bias[0]
# Trova i valori minimi e massimi degli input per creare una griglia
x_min, x_max = np.min(inputs[:, 0]) - 0.1, np.max(inputs[:, 0]) + 0.1
y_min, y_max = np.min(inputs[:, 1]) - 0.1, np.max(inputs[:, 1]) + 0.1
# Crea una griglia di punti
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 100), np.linspace(y_min, y_max, 100))
grid_points = np.c_[xx.ravel(), yy.ravel()]
# Calcola i valori previsti per ogni punto sulla griglia
predictions = self.predict(grid_points)
# Ridimensiona le previsioni nella forma della griglia
predictions = predictions.reshape(xx.shape)
# Plot dei punti
plt.scatter(inputs[:, 0], inputs[:, 1], c=labels, cmap=plt.cm.Spectral)
# Plot della decision boundary
plt.contourf(xx, yy, predictions, cmap=plt.cm.Spectral, alpha=0.8)
# Etichette degli assi
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
# Mostra il plot
plt.show()
Per utilizzare questo metodo implementiamo un nuovo esempio, in cui si crea una serie casuale di coppie di dati in input per poi sottoporla alla nostra SLP.
# Esempio di utilizzo
if __name__ == "__main__":
# Dati di esempio
np.random.seed(42)
inputs = np.random.rand(100, 2)
labels = (inputs[:, 0] + inputs[:, 1] > 1).astype(int) # Semplice decisione di classe
# Creazione e addestramento del perceptron
input_size = 2
perceptron = SingleLayerPerceptron(input_size)
perceptron.plot_decision_boundary(inputs, labels)
Eseguendo il codice di esempio otterremo il seguente grafico che corrisponderà al Decision Boundary del SLP da noi definito:
La funzione plot_decision_boundary
estrae i pesi e il bias, quindi crea una griglia di punti e calcola i valori previsti del perceptron per ogni punto sulla griglia. Infine, traccia la decision boundary usando contourf
di matplotlib
e colora i punti di input in base alle rispettive classi.
- Punti di Input:
plt.scatter(inputs[:, 0], inputs[:, 1], c=labels, cmap=plt.cm.Spectral)
I punti di input sono visualizzati sul grafico. Ogni punto è colorato in base alla sua classe. - Decision Boundary:
plt.contourf(xx, yy, predictions, cmap=plt.cm.Spectral, alpha=0.8)
La decision boundary è tracciata sulla griglia di punti. Le regioni sopra e sotto la decision boundary rappresentano le due classi separate dal perceptron. - Etichette degli Assi:
plt.xlabel('Feature 1') plt.ylabel('Feature 2')
Etichette degli assi per indicare quali feature rappresentano gli assi x e y. - Mostra il Plot:
plt.show()
Questo comando mostra il grafico.
In questo esempio, i punti di input sono generati casualmente, e la decisione di classe è basata sulla somma delle due feature. Il perceptron cerca di separare queste due classi con una linea di decisione. Puoi sperimentare cambiando i dati di input per vedere come il perceptron si adatta alle diverse distribuzioni dei dati.