Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Impara Implementazione della Retropropagazione | Rete Neurale da Zero
Introduzione alle Reti Neurali

bookImplementazione della Retropropagazione

Approccio Generale

Nella propagazione in avanti, ogni livello ll prende come input le uscite del livello precedente, al1a^{l-1}, e calcola le proprie uscite. Pertanto, il metodo forward() della classe Layer accetta come unico parametro il vettore delle uscite precedenti, mentre le altre informazioni necessarie sono memorizzate all'interno della classe.

Nella propagazione all'indietro, ogni livello ll necessita solo di dalda^l per calcolare i rispettivi gradienti e restituire dal1da^{l-1}, quindi il metodo backward() prende come parametro il vettore dalda^l. Il resto delle informazioni richieste è già memorizzato nella classe Layer.

Derivate delle Funzioni di Attivazione

Poiché le derivate delle funzioni di attivazione sono necessarie per la retropropagazione, funzioni di attivazione come ReLU e sigmoide dovrebbero essere implementate come classi invece che come funzioni standalone. Questa struttura consente di definire chiaramente entrambe le componenti:

  1. La funzione di attivazione stessa — implementata tramite il metodo __call__(), così da poter essere applicata direttamente nella classe Layer con self.activation(z);
  2. La sua derivata — implementata tramite il metodo derivative(), permettendo un calcolo efficiente durante la retropropagazione tramite self.activation.derivative(z).

Rappresentare le funzioni di attivazione come oggetti facilita il loro passaggio ai diversi livelli e la loro applicazione dinamica sia durante la propagazione in avanti che all'indietro.

ReLU

La derivata della funzione di attivazione ReLU è la seguente, dove ziz_i è un elemento del vettore delle pre-attivazioni zz:

f(zi)={1,zi>00,zi0f'(z_i) = \begin{cases} 1, z_i > 0\\ 0, z_i \le 0 \end{cases}
class ReLU:
    def __call__(self, z):
        return np.maximum(0, z)

    def derivative(self, z):
        return (z > 0).astype(float)

Sigmoide

La derivata della funzione di attivazione sigmoide è la seguente:

f(zi)=f(zi)(1f(zi))f'(z_i) = f(z_i) \cdot (1 - f(z_i))
class Sigmoid:
    def __call__(self, x):
        return 1 / (1 + np.exp(-z))

    def derivative(self, z):
        sig = self(z)
        return sig * (1 - sig)

Per entrambe le funzioni di attivazione, l'operazione viene applicata all'intero vettore zz, così come alla sua derivata. NumPy esegue automaticamente il calcolo element-wise, ovvero ogni elemento del vettore viene elaborato indipendentemente.

Ad esempio, se il vettore zz contiene tre elementi, la derivata viene calcolata come:

f(z)=f([z1z2z3])=[f(z1)f(z2)f(z3)]f'(z) = f'\left( \begin{bmatrix} z_1\\ z_2\\ z_3 \end{bmatrix} \right) = \begin{bmatrix} f'(z_1)\\ f'(z_2)\\ f'(z_3) \end{bmatrix}

Il metodo backward()

Il metodo backward() è responsabile del calcolo dei gradienti utilizzando le seguenti formule:

dzl=dalfl(zl)dWl=dzl(al1)Tdbl=dzldal1=(Wl)Tdzl\begin{aligned} dz^l &= da^l \odot f'^l(z^l)\\ dW^l &= dz^l \cdot (a^{l-1})^T\\ db^l &= dz^l\\ da^{l-1} &= (W^l)^T \cdot dz^l \end{aligned}

a^{l-1} e zlz^l sono memorizzati rispettivamente come attributi inputs e outputs nella classe Layer. La funzione di attivazione ff è memorizzata come attributo activation.

Una volta calcolati tutti i gradienti necessari, pesi e bias possono essere aggiornati poiché non sono più necessari per ulteriori calcoli:

Wl=WlαdWlbl=blαdbl\begin{aligned} W^l &= W^l - \alpha \cdot dW^l\\ b^l &= b^l - \alpha \cdot db^l \end{aligned}

Pertanto, learning_rate (α\alpha) è un altro parametro di questo metodo.

def backward(self, da, learning_rate):
    dz = ...
    d_weights = ...
    d_biases = ...
    da_prev = ...

    self.weights -= learning_rate * d_weights
    self.biases -= learning_rate * d_biases

    return da_prev
Note
Nota

L'operatore * esegue la moltiplicazione elemento per elemento, mentre la funzione np.dot() esegue il prodotto scalare in NumPy. L'attributo .T trasporta (traspone) un array.

question mark

Quale delle seguenti opzioni descrive meglio il ruolo del metodo backward() nella classe Layer durante la retropropagazione?

Select the correct answer

Tutto è chiaro?

Come possiamo migliorarlo?

Grazie per i tuoi commenti!

Sezione 2. Capitolo 8

Chieda ad AI

expand

Chieda ad AI

ChatGPT

Chieda pure quello che desidera o provi una delle domande suggerite per iniziare la nostra conversazione

Awesome!

Completion rate improved to 4

bookImplementazione della Retropropagazione

Scorri per mostrare il menu

Approccio Generale

Nella propagazione in avanti, ogni livello ll prende come input le uscite del livello precedente, al1a^{l-1}, e calcola le proprie uscite. Pertanto, il metodo forward() della classe Layer accetta come unico parametro il vettore delle uscite precedenti, mentre le altre informazioni necessarie sono memorizzate all'interno della classe.

Nella propagazione all'indietro, ogni livello ll necessita solo di dalda^l per calcolare i rispettivi gradienti e restituire dal1da^{l-1}, quindi il metodo backward() prende come parametro il vettore dalda^l. Il resto delle informazioni richieste è già memorizzato nella classe Layer.

Derivate delle Funzioni di Attivazione

Poiché le derivate delle funzioni di attivazione sono necessarie per la retropropagazione, funzioni di attivazione come ReLU e sigmoide dovrebbero essere implementate come classi invece che come funzioni standalone. Questa struttura consente di definire chiaramente entrambe le componenti:

  1. La funzione di attivazione stessa — implementata tramite il metodo __call__(), così da poter essere applicata direttamente nella classe Layer con self.activation(z);
  2. La sua derivata — implementata tramite il metodo derivative(), permettendo un calcolo efficiente durante la retropropagazione tramite self.activation.derivative(z).

Rappresentare le funzioni di attivazione come oggetti facilita il loro passaggio ai diversi livelli e la loro applicazione dinamica sia durante la propagazione in avanti che all'indietro.

ReLU

La derivata della funzione di attivazione ReLU è la seguente, dove ziz_i è un elemento del vettore delle pre-attivazioni zz:

f(zi)={1,zi>00,zi0f'(z_i) = \begin{cases} 1, z_i > 0\\ 0, z_i \le 0 \end{cases}
class ReLU:
    def __call__(self, z):
        return np.maximum(0, z)

    def derivative(self, z):
        return (z > 0).astype(float)

Sigmoide

La derivata della funzione di attivazione sigmoide è la seguente:

f(zi)=f(zi)(1f(zi))f'(z_i) = f(z_i) \cdot (1 - f(z_i))
class Sigmoid:
    def __call__(self, x):
        return 1 / (1 + np.exp(-z))

    def derivative(self, z):
        sig = self(z)
        return sig * (1 - sig)

Per entrambe le funzioni di attivazione, l'operazione viene applicata all'intero vettore zz, così come alla sua derivata. NumPy esegue automaticamente il calcolo element-wise, ovvero ogni elemento del vettore viene elaborato indipendentemente.

Ad esempio, se il vettore zz contiene tre elementi, la derivata viene calcolata come:

f(z)=f([z1z2z3])=[f(z1)f(z2)f(z3)]f'(z) = f'\left( \begin{bmatrix} z_1\\ z_2\\ z_3 \end{bmatrix} \right) = \begin{bmatrix} f'(z_1)\\ f'(z_2)\\ f'(z_3) \end{bmatrix}

Il metodo backward()

Il metodo backward() è responsabile del calcolo dei gradienti utilizzando le seguenti formule:

dzl=dalfl(zl)dWl=dzl(al1)Tdbl=dzldal1=(Wl)Tdzl\begin{aligned} dz^l &= da^l \odot f'^l(z^l)\\ dW^l &= dz^l \cdot (a^{l-1})^T\\ db^l &= dz^l\\ da^{l-1} &= (W^l)^T \cdot dz^l \end{aligned}

a^{l-1} e zlz^l sono memorizzati rispettivamente come attributi inputs e outputs nella classe Layer. La funzione di attivazione ff è memorizzata come attributo activation.

Una volta calcolati tutti i gradienti necessari, pesi e bias possono essere aggiornati poiché non sono più necessari per ulteriori calcoli:

Wl=WlαdWlbl=blαdbl\begin{aligned} W^l &= W^l - \alpha \cdot dW^l\\ b^l &= b^l - \alpha \cdot db^l \end{aligned}

Pertanto, learning_rate (α\alpha) è un altro parametro di questo metodo.

def backward(self, da, learning_rate):
    dz = ...
    d_weights = ...
    d_biases = ...
    da_prev = ...

    self.weights -= learning_rate * d_weights
    self.biases -= learning_rate * d_biases

    return da_prev
Note
Nota

L'operatore * esegue la moltiplicazione elemento per elemento, mentre la funzione np.dot() esegue il prodotto scalare in NumPy. L'attributo .T trasporta (traspone) un array.

question mark

Quale delle seguenti opzioni descrive meglio il ruolo del metodo backward() nella classe Layer durante la retropropagazione?

Select the correct answer

Tutto è chiaro?

Come possiamo migliorarlo?

Grazie per i tuoi commenti!

Sezione 2. Capitolo 8
some-alt