Realizziamo un Single Layer Perceptron (SLP) con Python

Single Layer Perceptron SLP header

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.

Single Layer Perceptron SLP

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.

 z = w_1x_1 + w_2x_2 + \ldots + w_nx_n

Funzione di attivazione: La somma ponderata viene poi passata attraverso una funzione di attivazione per produrre l’output del perceptron.

 y = \varphi(z)

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.

  1. Creazione del Perceptron: con __init__() stiamo definendo la classe SingleLayerPerceptron con un costruttore che inizializza casualmente i pesi e il bias del perceptron.
  2. Funzione di Attivazione Sigmoid: con sigmoid() si calcola la sigmoid di un dato valore, che viene utilizzata come funzione di attivazione nel perceptron.
  3. 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]
Single Layer Perceptron with networkx library

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:

Single Layer Perceptron SLP - Decision Boundary

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.

  1. 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.
  2. 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.
  3. Etichette degli Assi:plt.xlabel('Feature 1') plt.ylabel('Feature 2') Etichette degli assi per indicare quali feature rappresentano gli assi x e y.
  4. 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.

Lascia un commento