9.9 KiB
Documentazione della Libreria percettroni.h
Questa libreria implementa una rete neurale feedforward completamente connessa (fully-connected) in linguaggio C puro, progettata per la classificazione di immagini sui dataset MNIST e CIFAR-10. L'implementazione segue i principi fondamentali del deep learning con backpropagation e discesa del gradiente stocastica.
Struttura dei Dati
Percettrone
Il percettrone è l'unità fondamentale di calcolo nella rete neurale, ispirato al modello biologico del neurone.
typedef struct {
float *pesi; // Vettore dei pesi sinaptici
float bias; // Bias (soglia di attivazione)
int size; // Numero di input (dimensione del vettore pesi)
} Percettrone;
Proprietà matematiche:
- Ogni percettrone calcola una combinazione lineare degli input:
z = Σ(w_i * x_i) + b - La funzione di attivazione trasforma questo valore in un output non lineare
- I pesi rappresentano la "forza" delle connessioni sinaptiche
- Il bias permette di spostare la soglia di attivazione
Layer
Un layer (strato) è un insieme di percettroni che operano in parallelo sugli stessi input.
typedef struct {
Percettrone *percettroni; // Array di percettroni
int size; // Numero di percettroni nello strato
} Layer;
ReteNeurale
La rete neurale è composta da una sequenza di layer interconnessi.
typedef struct {
Layer *layers; // Array di layer
int size; // Numero totale di layer
} ReteNeurale;
Costanti Fondamentali
LRE = 0.01: Learning Rate (tasso di apprendimento) - controlla la dimensione dei passi nella discesa del gradientesoglia_sigmoide = 0.5: Soglia per la classificazione binaria con funzione sigmoideTOLLERANZA = 99.5: Percentuale di accuratezza richiesta per l'arresto anticipato dell'addestramentoFUNZIONE_ATTIVAZIONE = 1: Tipo di funzione di attivazione (0=sigmoide, 1=ReLU, 2=gradino)
Funzioni di Inizializzazione
inizializza_percettrone(int n_pesi)
Descrizione: Inizializza un singolo percettrone con pesi casuali secondo la strategia di inizializzazione He.
Implementazione matematica:
- Utilizza il Teorema Centrale del Limite per generare pesi distribuiti normalmente
- Deviazione standard:
σ = √(2/n)dovenè il numero di input - Formula:
peso = (Σ₁₂(rand()) - 6) × √(2/n) - Il bias viene inizializzato a 0
Proprietà: Questa inizializzazione previene il problema del vanishing/exploding gradient nelle reti profonde con ReLU.
inizializza_layer(int n_percettroni, int n_pesi)
Descrizione: Crea un layer completo con un numero specificato di percettroni, ognuno con lo stesso numero di pesi.
Architettura dinamica: La libreria supporta architetture con riduzione progressiva del numero di neuroni:
- Layer intermedi:
neuroni = neuroni_iniziali × (layer_rimanenti / layer_totali) - Questo crea una struttura a imbuto che estrae caratteristiche sempre più astratte
inizializza_rete_neurale(int numero_input, int numero_layers, int numero_percettroni_iniziali, int numero_percettroni_finali)
Descrizione: Costruisce l'intera rete neurale con architettura personalizzabile.
Caratteristiche:
- Supporta reti profonde con qualsiasi numero di layer
- Configurazione flessibile del numero di neuroni per layer
- Calcolo automatico del numero totale di parametri (pesi + bias)
Funzioni di Attivazione e Previsione
attivazione(Percettrone p, float *valori)
Descrizione: Calcola l'output di un percettrone applicando la funzione di attivazione scelta.
Formule matematiche implementate:
-
Sigmoide (FUNZIONE_ATTIVAZIONE = 0):
σ(z) = 1 / (1 + e^(-z))- Output nell'intervallo (0, 1)
- Derivata:
σ'(z) = σ(z) × (1 - σ(z))
-
ReLU (FUNZIONE_ATTIVAZIONE = 1):
ReLU(z) = max(0, z)- Output nell'intervallo [0, ∞)
- Derivata:
ReLU'(z) = 1 se z > 0, altrimenti 0
-
Gradino (FUNZIONE_ATTIVAZIONE = 2):
step(z) = 1 se z > 0, altrimenti 0- Output binario {0, 1}
- Derivata: sempre 0 (non utilizzabile per backpropagation)
derivata_attivazione(float valore)
Descrizione: Calcola la derivata della funzione di attivazione rispetto all'input.
Importanza nel backpropagation: La derivata è essenziale per calcolare i gradienti durante l'addestramento, permettendo alla rete di apprendere attraverso la regola della catena.
softmax(float *input, int size)
Descrizione: Applica la funzione softmax al layer di output per la classificazione multi-classe.
Formula matematica:
softmax(z_i) = e^(z_i) / Σⱼ(e^(z_j))
Proprietà:
- Trasforma gli output in probabilità che sommano a 1
- È differenziabile, permettendo l'uso con backpropagation
- Implementazione numerica stabile con sottrazione del massimo per evitare overflow
previsione_softmax(float *livello_percettroni, int size)
Descrizione: Esegue la classificazione finale applicando softmax e restituendo l'indice della classe con probabilità massima.
Forward Propagation
elabora_attivazioni(ReteNeurale *rete, Istanza istanza)
Descrizione: Esegue il forward propagation attraverso l'intera rete, calcolando le attivazioni di tutti i neuroni.
Processo:
- Normalizza gli input pixel (0-255 → 0.0-1.0)
- Propaga sequenzialmente attraverso ogni layer
- Restituisce una matrice 2D con tutte le attivazioni intermedie
Importanza: Le attivazioni intermedie sono necessarie per il calcolo dei gradienti durante il backpropagation.
Backpropagation e Discesa del Gradiente
elabora_gradienti(ReteNeurale *rete_neurale, byte output, float **attivazioni)
Descrizione: Calcola i gradienti dell'errore rispetto ai pesi utilizzando l'algoritmo di backpropagation.
Algoritmo matematico:
- Layer di output:
δ_output = y_true - y_pred(per perdita cross-entropy con softmax) - Layer nascosti:
δ_hidden = (W^T × δ_next) ⊙ f'(z)- Dove
⊙è il prodotto elemento-per-elemento (Hadamard product) f'(z)è la derivata della funzione di attivazione
- Dove
discesa_gradiente(ReteNeurale *rete, float **attivazioni, float **gradienti)
Descrizione: Propaga i gradienti all'indietro attraverso i layer nascosti.
Regola della catena: Applica ricorsivamente la regola della catena per calcolare i gradienti nei layer precedenti.
calcola_gradiente_disceso(ReteNeurale *rete, int livello, int indice_peso, float **gradienti)
Descrizione: Calcola il contributo aggregato di un peso specifico al gradiente del layer successivo.
Formula: gradiente_disceso = Σ(δ_j × w_ji) dove j indica i neuroni del layer successivo.
Aggiornamento dei Pesi
aggiorna_pesi(ReteNeurale *rete_neurale, float **attivazioni, float **gradienti, Istanza istanza)
Descrizione: Aggiorna tutti i pesi e bias della rete utilizzando la discesa del gradiente stocastica.
Regola di aggiornamento:
w_new = w_old + η × δ × input
b_new = b_old + η × δ
Dove η è il learning rate (LRE) e δ è il gradiente dell'errore.
correggi_pesi_percettrone_float() e correggi_pesi_percettrone_byte()
Descrizione: Funzioni specializzate per l'aggiornamento dei pesi nel primo layer (che riceve input grezzi come byte) e nei layer successivi (che ricevono attivazioni normalizzate come float).
Addestramento
addestra(ReteNeurale *rete_neurale, Dataset set)
Descrizione: Esegue un'epoca completa di addestramento sul dataset fornito.
Processo completo:
- Per ogni istanza nel dataset:
- Forward propagation per ottenere le predizioni
- Calcolo dell'accuratezza corrente
- Backpropagation per calcolare i gradienti
- Aggiornamento dei pesi
- Calcolo della percentuale di risposte corrette
- Arresto anticipato se l'accuratezza supera la
TOLLERANZA
Caratteristiche:
- Implementa la discesa del gradiente stocastica (SGD)
- Supporta l'arresto anticipato per prevenire overfitting
- Fornisce feedback sull'accuratezza durante l'addestramento
Persistenza
salvaReteNeurale(const char *filename, ReteNeurale *rete)
Descrizione: Salva l'intera rete su file binario in formato proprietario.
Formato: Struttura annidata che preserva esattamente la topologia e i valori dei pesi.
caricaReteNeurale(const char *filename)
Descrizione: Carica una rete precedentemente salvata da file.
Utilizzo: Permette di riprendere l'addestramento o di utilizzare modelli pre-addestrati.
Debugging e Analisi
stampa_pesi_rete(ReteNeurale *rete)
Descrizione: Stampa tutti i pesi e bias della rete in formato leggibile.
Utilizzo: Essenziale per il debugging e l'analisi del comportamento della rete durante l'addestramento.
Proprietà Matematiche della Rete
Architettura Feedforward
- Informazione che fluisce solo in avanti (input → output)
- Nessuna connessione ricorrente o laterale
- Computazione deterministica e parallela all'interno di ogni layer
Universal Approximation Theorem
Questa implementazione, essendo una rete feedforward con almeno uno strato nascosto e funzioni di attivazione non lineari, può approssimare qualsiasi funzione continua con precisione arbitraria, dato un numero sufficiente di neuroni.
Ottimizzazione
- Funzione di perdita: Implicitamente cross-entropy (grazie all'uso di softmax e backpropagation)
- Algoritmo di ottimizzazione: Discesa del gradiente stocastica
- Regularizzazione: Nessuna esplicita, ma l'inizializzazione He e l'arresto anticipato aiutano a prevenire overfitting
Complessità Computazionale
- Forward propagation: O(N×M) dove N è il numero totale di neuroni e M il numero di connessioni
- Backpropagation: O(N×M) - stessa complessità del forward pass
- Memoria: O(N×M) per memorizzare pesi, gradienti e attivazioni intermedie
Questa libreria rappresenta un'implementazione educativa completa di una rete neurale moderna, dimostrando i principi fondamentali del deep learning in modo trasparente e accessibile.