Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Aprenda Implementação do Backpropagation | Rede Neural do Zero
Introdução às Redes Neurais

bookImplementação do Backpropagation

Abordagem Geral

Na propagação direta, cada camada ll recebe as saídas da camada anterior, al1a^{l-1}, como entradas e calcula suas próprias saídas. Portanto, o método forward() da classe Layer recebe o vetor de saídas anteriores como seu único parâmetro, enquanto o restante das informações necessárias é armazenado dentro da classe.

Na propagação reversa, cada camada ll precisa apenas de dalda^l para calcular os respectivos gradientes e retornar dal1da^{l-1}, então o método backward() recebe o vetor dalda^l como parâmetro. O restante das informações necessárias já está armazenado na classe Layer.

Derivadas das Funções de Ativação

Como as derivadas das funções de ativação são necessárias para a propagação reversa, funções de ativação como ReLU e sigmoide devem ser implementadas como classes em vez de funções isoladas. Essa estrutura permite definir claramente ambos os componentes:

  1. A própria função de ativação — implementada usando o método __call__(), para que possa ser aplicada diretamente na classe Layer com self.activation(z);
  2. Sua derivada — implementada usando o método derivative(), permitindo o cálculo eficiente durante a propagação reversa via self.activation.derivative(z).

Representar funções de ativação como objetos facilita o repasse para diferentes camadas e a aplicação dinâmica durante a propagação direta e reversa.

ReLu

A derivada da função de ativação ReLU é a seguinte, onde ziz_i é um elemento do vetor de pré-ativações 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)

Sigmoid

A derivada da função de ativação sigmoid é a seguinte:

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)

Para ambas as funções de ativação, a operação é aplicada a todo o vetor zz, assim como à sua derivada. O NumPy realiza automaticamente o cálculo elemento a elemento, ou seja, cada elemento do vetor é processado de forma independente.

Por exemplo, se o vetor zz contém três elementos, a derivada é calculada como:

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}

O método backward()

O método backward() é responsável por calcular os gradientes utilizando as fórmulas abaixo:

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 são armazenados como os atributos inputs e outputs na classe Layer, respectivamente. A função de ativação ff é armazenada como o atributo activation.

Após todos os gradientes necessários serem calculados, os pesos e biases podem ser atualizados, pois não são mais necessários para cálculos posteriores:

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}

Portanto, learning_rate (α\alpha) é outro parâmetro deste método.

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

O operador * realiza a multiplicação elemento a elemento, enquanto a função np.dot() executa o produto escalar no NumPy. O atributo .T transpõe um array.

question mark

Qual das alternativas a seguir melhor descreve o papel do método backward() na classe Layer durante a retropropagação?

Select the correct answer

Tudo estava claro?

Como podemos melhorá-lo?

Obrigado pelo seu feedback!

Seção 2. Capítulo 8

Pergunte à IA

expand

Pergunte à IA

ChatGPT

Pergunte o que quiser ou experimente uma das perguntas sugeridas para iniciar nosso bate-papo

Suggested prompts:

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

What is the purpose of the derivative() method in the activation function classes?

Could you provide an example of how forward and backward propagation work together in a simple neural network?

Awesome!

Completion rate improved to 4

bookImplementação do Backpropagation

Deslize para mostrar o menu

Abordagem Geral

Na propagação direta, cada camada ll recebe as saídas da camada anterior, al1a^{l-1}, como entradas e calcula suas próprias saídas. Portanto, o método forward() da classe Layer recebe o vetor de saídas anteriores como seu único parâmetro, enquanto o restante das informações necessárias é armazenado dentro da classe.

Na propagação reversa, cada camada ll precisa apenas de dalda^l para calcular os respectivos gradientes e retornar dal1da^{l-1}, então o método backward() recebe o vetor dalda^l como parâmetro. O restante das informações necessárias já está armazenado na classe Layer.

Derivadas das Funções de Ativação

Como as derivadas das funções de ativação são necessárias para a propagação reversa, funções de ativação como ReLU e sigmoide devem ser implementadas como classes em vez de funções isoladas. Essa estrutura permite definir claramente ambos os componentes:

  1. A própria função de ativação — implementada usando o método __call__(), para que possa ser aplicada diretamente na classe Layer com self.activation(z);
  2. Sua derivada — implementada usando o método derivative(), permitindo o cálculo eficiente durante a propagação reversa via self.activation.derivative(z).

Representar funções de ativação como objetos facilita o repasse para diferentes camadas e a aplicação dinâmica durante a propagação direta e reversa.

ReLu

A derivada da função de ativação ReLU é a seguinte, onde ziz_i é um elemento do vetor de pré-ativações 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)

Sigmoid

A derivada da função de ativação sigmoid é a seguinte:

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)

Para ambas as funções de ativação, a operação é aplicada a todo o vetor zz, assim como à sua derivada. O NumPy realiza automaticamente o cálculo elemento a elemento, ou seja, cada elemento do vetor é processado de forma independente.

Por exemplo, se o vetor zz contém três elementos, a derivada é calculada como:

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}

O método backward()

O método backward() é responsável por calcular os gradientes utilizando as fórmulas abaixo:

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 são armazenados como os atributos inputs e outputs na classe Layer, respectivamente. A função de ativação ff é armazenada como o atributo activation.

Após todos os gradientes necessários serem calculados, os pesos e biases podem ser atualizados, pois não são mais necessários para cálculos posteriores:

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}

Portanto, learning_rate (α\alpha) é outro parâmetro deste método.

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

O operador * realiza a multiplicação elemento a elemento, enquanto a função np.dot() executa o produto escalar no NumPy. O atributo .T transpõe um array.

question mark

Qual das alternativas a seguir melhor descreve o papel do método backward() na classe Layer durante a retropropagação?

Select the correct answer

Tudo estava claro?

Como podemos melhorá-lo?

Obrigado pelo seu feedback!

Seção 2. Capítulo 8
some-alt