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, le funzioni di attivazione come ReLU e sigmoide dovrebbero essere strutturate come classi invece che come funzioni standalone. Questo consente di definire sia:

  1. La funzione di attivazione stessa (implementata tramite il metodo __call__()), permettendo di applicarla direttamente nella classe Layer usando self.activation(z);
  2. La sua derivata (implementata tramite il metodo derivative()), abilitando una retropropagazione efficiente e utilizzata nella classe Layer come self.activation.derivative(z).

Strutturando le funzioni di attivazione come oggetti, è possibile passarle facilmente alla classe Layer e utilizzarle in modo dinamico.

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 queste funzioni di attivazione, l'applicazione avviene sull'intero vettore zz, così come per le loro derivate. NumPy applica internamente l'operazione a ciascun elemento del vettore. Ad esempio, se il vettore zz contiene 3 elementi, la derivazione è la seguente:

f(z)=f([z1z2z3])=[f(z1)f(z2)f(z3)]f'(z) = f'( \begin{bmatrix} z_1\\ z_2\\ z_3 \end{bmatrix} ) = \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

Suggested prompts:

Can you explain how the backward() method uses the stored attributes in the Layer class?

What is the purpose of the activation function's derivative in backpropagation?

Could you provide an example of how to use these activation function classes in a neural network layer?

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, le funzioni di attivazione come ReLU e sigmoide dovrebbero essere strutturate come classi invece che come funzioni standalone. Questo consente di definire sia:

  1. La funzione di attivazione stessa (implementata tramite il metodo __call__()), permettendo di applicarla direttamente nella classe Layer usando self.activation(z);
  2. La sua derivata (implementata tramite il metodo derivative()), abilitando una retropropagazione efficiente e utilizzata nella classe Layer come self.activation.derivative(z).

Strutturando le funzioni di attivazione come oggetti, è possibile passarle facilmente alla classe Layer e utilizzarle in modo dinamico.

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 queste funzioni di attivazione, l'applicazione avviene sull'intero vettore zz, così come per le loro derivate. NumPy applica internamente l'operazione a ciascun elemento del vettore. Ad esempio, se il vettore zz contiene 3 elementi, la derivazione è la seguente:

f(z)=f([z1z2z3])=[f(z1)f(z2)f(z3)]f'(z) = f'( \begin{bmatrix} z_1\\ z_2\\ z_3 \end{bmatrix} ) = \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