# 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. ```c 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. ```c 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. ```c 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 gradiente - **`soglia_sigmoide = 0.5`**: Soglia per la classificazione binaria con funzione sigmoide - **`TOLLERANZA = 99.5`**: Percentuale di accuratezza richiesta per l'arresto anticipato dell'addestramento - **`FUNZIONE_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)` dove `n` è 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:** 1. **Sigmoide (FUNZIONE_ATTIVAZIONE = 0):** ``` σ(z) = 1 / (1 + e^(-z)) ``` - Output nell'intervallo (0, 1) - Derivata: `σ'(z) = σ(z) × (1 - σ(z))` 2. **ReLU (FUNZIONE_ATTIVAZIONE = 1):** ``` ReLU(z) = max(0, z) ``` - Output nell'intervallo [0, ∞) - Derivata: `ReLU'(z) = 1 se z > 0, altrimenti 0` 3. **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:** 1. Normalizza gli input pixel (0-255 → 0.0-1.0) 2. Propaga sequenzialmente attraverso ogni layer 3. 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 ### `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:** 1. Per ogni istanza nel dataset: - Forward propagation per ottenere le predizioni - Calcolo dell'accuratezza corrente - Backpropagation per calcolare i gradienti - Aggiornamento dei pesi 2. Calcolo della percentuale di risposte corrette 3. 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.