Laboratorio 03

Tipi Base e Funzioni di Attivazione

Docente

Soluzione del Laboratorio 02

#include <iostream>

int main() {
    std::cout << "Inizio progetto BASIC_NEURAL++!" << std::endl;

    int epochs = 100;
    double learning_rate = 0.01;
    double weight = 0.55;

    std::cout << "Epoche: " << epochs << std::endl;
    std::cout << "Learning Rate: " << learning_rate << std::endl;

    double updated_weight = weight - (learning_rate * 0.1);
    std::cout << "Peso Aggiornato: " << updated_weight << std::endl;

    // Il punto centrale del laboratorio
    int a = 5, b = 2;
    std::cout << "5 / 2 = " << (a / b) << " (taglia i decimali!)" << std::endl;
    std::cout << "con cast = " << (static_cast<double>(a) / b) << std::endl;
}

5 / 2 vale 2, non 2.5: se entrambi gli operandi sono int, C++ fa la divisione intera. È il bug più insidioso del calcolo scientifico, perché non dà nessun errore — dà solo il risultato sbagliato. Basta un static_cast<double> su uno dei due operandi.

Note

static_cast<double>(a) non modifica a: produce una copia temporanea di tipo double da usare in quell’espressione. Basta convertire uno dei due operandi, perché a quel punto C++ promuove automaticamente anche l’altro. È lo stesso static_cast della Lezione 4 (divisione intera): qui lo usate per la prima volta in un programma vero. Le altre forme di cast arrivano nella Lezione 16.

Obiettivo di Oggi

Iniziare a scrivere la logica matematica fondamentale che dà “intelligenza” a un neurone artificiale. Impareremo a usare il Control Flow del C++ (if, for), definiremo le Funzioni di Attivazione con le loro derivate, e — da fisici — verificheremo numericamente che le derivate siano giuste. Nel Laboratorio 13 queste derivate diventeranno il cuore della backpropagation: un errore qui si pagherebbe lì, e sarebbe molto più difficile da trovare.

Task 1: La funzione ReLU

La Rectified Linear Unit (ReLU) è la funzione di attivazione più usata nel Deep Learning moderno. Formula: \(f(x) = \max(0, x)\).

  1. Nel tuo main.cpp, scrivi una funzione double relu(double x).
  2. Implementala usando un classico costrutto if/else.
  3. Testala passandole un numero negativo e uno positivo, stampando i risultati a schermo con std::cout.

Task 2: La derivata della ReLU

Durante la Backpropagation, la rete neurale dovrà calcolare la pendenza (derivata) della funzione di attivazione. Derivata di ReLU: \(f'(x) = 1\) se \(x > 0\), altrimenti \(0\).

  1. Scrivi una funzione double relu_derivative(double x).
  2. Implementala e testala.

Task 3: La Sigmoide e <cmath>

La Sigmoide “schiaccia” qualsiasi numero reale nell’intervallo \((0, 1)\). Formula: \(\sigma(x) = \frac{1}{1 + e^{-x}}\).

  1. Includi l’header della libreria matematica standard: #include <cmath>
  2. Scrivi una funzione double sigmoid(double x). Usa la funzione esponenziale std::exp().
  3. Scrivi la sua derivata: \(\sigma'(x) = \sigma(x) \cdot (1 - \sigma(x))\).

Task 4: Validazione con il Ciclo For

Scrivi un ciclo for nel main() che iteri una variabile x partendo da -5.0 fino a +5.0, avanzando a step di 1.0.

Ad ogni iterazione, stampa a schermo una tabella rudimentale usando \t (tabulazione): - Il valore x - relu(x) e relu_derivative(x) - sigmoid(x) e sigmoid_derivative(x)

Controlla che i numeri stampati abbiano senso logico!

Task 5: La Tangente Iperbolica

La terza attivazione classica, che schiaccia in \((-1, 1)\) ed è centrata sullo zero: \(f(x) = \tanh(x)\), con derivata \(f'(x) = 1 - \tanh^2(x)\).

  1. Scrivi double tanh_act(double x) usando std::tanh di <cmath> (il nome tanh da solo farebbe a pugni con quello della libreria).
  2. Scrivi double tanh_derivative(double x): calcola std::tanh(x) una volta sola in una variabile e riusala.
  3. Aggiungi le due colonne alla tabella, o stampa una seconda tabella.

Task 6: Le Derivate sono Giuste? Verifica Numerica

Una derivata analitica sbagliata di un segno o di un fattore 2 non produce nessun errore di compilazione: produce una rete che non impara. Il fisico ha un’arma: la derivata numerica con le differenze centrali, \(f'(x) \approx \dfrac{f(x+h) - f(x-h)}{2h}\).

  1. Scrivi double sigmoid_numerical_derivative(double x, double h) e l’analoga per la tanh. (Una funzione per attivazione: passare una funzione come argomento lo vedremo più avanti.)
  2. Nel main, con h = 1e-5, scorri x da \(-5\) a \(5\) a passi di \(0.5\) e tieni il massimo errore assoluto std::fabs(analitica - numerica) per ciascuna funzione.
  3. Stampa i due massimi (in notazione scientifica: std::scientific) e un [PASS] se entrambi sono sotto 1e-6, [FAIL] altrimenti.
  4. Perché proprio 1e-5? Ripeti il confronto in x = 1 per h = \(10^{-1}, 10^{-3}, \dots, 10^{-13}\) e stampa l’errore. Scende, poi risale: troncamento da una parte, cancellazione numerica fra f(x+h) e f(x-h) dall’altra. È il primo incontro con i limiti del double, e non sarà l’ultimo.

Buon lavoro! La soluzione di riferimento verrà discussa all’inizio della prossima lezione.