Backpropagation costuma aparecer por trás de bibliotecas de alto nível, mas sua mecânica essencial pode ser observada com poucas operações: multiplicar matrizes, aplicar uma função não linear, medir o erro, propagá-lo no sentido inverso e ajustar os pesos. O projeto Python-backpropagation apresenta esse fluxo em Python, usando NumPy e o problema XOR como exemplo supervisionado.
Esta publicação explica o que aparece na demonstração: os dados, a arquitetura, as duas funções de ativação e o laço de treinamento. As capturas disponíveis mostram o código, mas não exibem saídas numéricas; por isso, não serão feitas afirmações sobre convergência, acurácia ou desempenho.
O problema XOR
XOR, ou “ou exclusivo”, retorna 1 quando suas duas entradas binárias são diferentes e retorna 0 quando elas são iguais. O código usa as quatro combinações possíveis como conjunto de treinamento completo.
| Entrada A | Entrada B | Saída esperada |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
O interesse didático do XOR está no fato de que uma única separação linear não resolve sua tabela-verdade. Uma camada intermediária acompanhada de uma ativação não linear permite representar relações que não cabem em uma fronteira linear simples.
X = np.array([[0, 0],
[0, 1],
[1, 0],
[1, 1]])
y = np.array([[0], [1], [1], [0]])
Arquitetura 2–5–1 e dimensões
A rede mostrada tem duas entradas, cinco neurônios ocultos e uma saída. Os quatro exemplos são processados juntos em cada iteração, de modo que entradas, ativações e saídas são representadas por matrizes.
| Elemento | Dimensão | Papel |
|---|---|---|
X | 4 × 2 | Quatro exemplos com duas características |
pesos_entrada_oculta | 2 × 5 | Conecta as entradas à camada oculta |
camada_oculta | 4 × 5 | Ativações ocultas para cada exemplo |
pesos_oculta_saida | 5 × 1 | Conecta a camada oculta à saída |
camada_saida | 4 × 1 | Uma previsão para cada combinação |
X (4 × 2)
→ pesos_entrada_oculta (2 × 5)
→ camada_oculta (4 × 5)
→ pesos_oculta_saida (5 × 1)
→ camada_saida (4 × 1)
As matrizes contêm dez pesos entre entrada e camada oculta e cinco entre camada oculta e saída, totalizando 15 pesos treináveis. Essa contagem deriva diretamente das dimensões. No código demonstrado não aparecem vetores de bias.
Forward pass: da entrada à previsão
No forward pass, a entrada é multiplicada pela primeira matriz de pesos. A função de ativação transforma o resultado e produz a camada oculta. A mesma sequência se repete entre a camada oculta e a saída.
camada_oculta = f(np.dot(X, pesos_entrada_oculta))
camada_saida = f(np.dot(camada_oculta, pesos_oculta_saida))
Em notação compacta, o fluxo é H = f(X · W_ih) e Ŷ = f(H · W_ho). A função f é sigmoide em uma variante e tangente hiperbólica na outra.
Por que as dimensões importam
O produto (4 × 2) · (2 × 5) produz (4 × 5). Em seguida, (4 × 5) · (5 × 1) produz (4 × 1), exatamente uma saída para cada linha de XOR.
Erro e backpropagation
Depois do forward pass, o script calcula o residual com erro = y - camada_saida. Esse sinal forma o delta da saída em conjunto com a derivada da ativação. O erro é então projetado de volta para a camada oculta pela transposta da matriz de pesos da saída.
erro = y - camada_saida
d_saida = erro * derivada(camada_saida)
erro_oculta = d_saida.dot(pesos_oculta_saida.T)
d_oculta = erro_oculta * derivada(camada_oculta)
pesos_oculta_saida += camada_oculta.T.dot(d_saida) * learning_rate
pesos_entrada_oculta += X.T.dot(d_oculta) * learning_rate
O delta da saída combina o residual com a sensibilidade local da ativação. Depois, pesos_oculta_saida.T transporta essa informação para trás. Por fim, os produtos com camada_oculta.T e X.T reúnem as contribuições dos quatro exemplos e atualizam as duas matrizes.
Sigmoide e tangente hiperbólica
Variante com sigmoide
A sigmoide é implementada como 1 / (1 + np.exp(-x)) e produz valores no intervalo aberto entre zero e um. Sua derivada é calculada a partir do valor já ativado com x * (1 - x).
Variante com tangente hiperbólica
A segunda versão usa np.tanh(x) e 1 - x**2 como derivada sobre o valor ativado. Dados, dimensões, inicialização, taxa de aprendizado e número de iterações permanecem iguais na captura.
A tanh não é uma função exclusiva de regressão. Nesta demonstração, os alvos continuam codificados como 0 e 1; comparar essa escolha com uma codificação centrada em -1 e 1 seria uma análise adicional, não um resultado apresentado pelo projeto.
Inicialização e treinamento
O código define np.random.seed(42), inicializa os pesos com valores uniformes entre -1 e 1, usa taxa de aprendizado 0.3 e executa 20.000 iterações.
| Configuração | Valor exibido |
|---|---|
| Semente pseudoaleatória | 42 |
| Faixa inicial dos pesos | -1 a 1 |
| Taxa de aprendizado | 0.3 |
| Iterações | 20.000 |
| Monitoramento | A cada 1.000 iterações |
np.random.seed(42)
learning_rate = 0.3
for epoch in range(20000):
# forward pass, erro, backpropagation e atualização
if epoch % 1000 == 0:
print(np.mean(np.abs(erro)))
O valor monitorado é a média do erro absoluto naquele estado do treinamento. Ele ajuda a acompanhar a magnitude média do residual, mas, isoladamente, não é uma declaração de acurácia. O script também imprime a última camada_saida após o laço, embora os valores produzidos não apareçam nas imagens analisadas.
O que a demonstração comprova
As duas capturas comprovam que o exemplo contém os dados de XOR, uma rede 2–5–1, forward pass, cálculo do residual, retropropagação, atualização dos pesos e duas opções de ativação. Também mostram que o código foi preparado para imprimir o erro periodicamente e a saída final.
Limite factual da demonstração
As imagens não mostram terminal, previsões finais, curva de erro, acurácia, tempo de execução ou comparação quantitativa entre as ativações. Portanto, esta publicação não afirma que uma variante convergiu ou foi superior à outra.
Análise: limitações e evoluções possíveis
As observações a seguir são uma análise técnica do código exibido, não funcionalidades ou resultados declarados pelo projeto.
- Adicionar vetores de bias às duas camadas.
- Definir um critério de parada baseado no erro, em vez de sempre executar 20.000 iterações.
- Registrar o histórico do erro para produzir uma curva de treinamento.
- Executar as duas ativações sob o mesmo protocolo e documentar os resultados antes de compará-las.
- Separar dados, forward pass, treinamento e avaliação em funções testáveis.
- Avaliar uma formulação numericamente estável da sigmoide para valores extremos.
Não há separação entre treino e teste no trecho mostrado: as quatro linhas de XOR são usadas juntas. Para esta demonstração, elas também representam todas as combinações possíveis de duas entradas binárias, mas isso não constitui evidência de generalização para outros conjuntos de dados.