A Matemática por trás da IA: Vamos Começar Simples
Como você normalmente escreveria um programa para somar dois números abaixo de 100? Fácil, certo? Você normalmente escreveria algo simples como:
#include <iostream>
using namespace std;
int main() {
int a, b, sum;
cout << "Digite o primeiro número: ";
cin >> a;
cout << "Digite o segundo número: ";
cin >> b;
sum = a + b;
cout << "A soma é: " << sum << endl;
return 0;
}
Mas vamos seguir um caminho mais empolgante—vamos ensinar uma máquina a aprender a adição sozinha, usando uma rede neural. Redes neurais estão no coração da IA moderna, impulsionando tudo, desde reconhecimento de imagem e tradução de idiomas até sistemas avançados como o ChatGPT.
Neste artigo, vamos mostrar como uma rede neural pode "aprender" a somar dois números através de treinamento, não seguindo uma regra codificada. Você verá como uma rede neural aprende a "somar" identificando padrões. Vamos até revelar os números reais (a mágica por trás da cortina) que fazem isso acontecer.
Aqui está uma espiada na estrutura interna de uma rede neural treinada:
// Pesos e viéses da camada oculta
double weight1_node1 = 0.658136;
double weight2_node1 = 0.840666;
double bias_node1 = -0.893218;
double weight1_node2 = -0.720667;
double weight2_node2 = -0.369172;
double bias_node2 = 0.036762;
double weight1_node3 = 0.512252;
double weight2_node3 = 0.292342;
double bias_node3 = -0.0745917;
// Pesos e viés da camada de saída
double weight_out_node1 = 1.93108;
double weight_out_node2 = -0.718584;
double weight_out_node3 = 0.589741;
double bias_out = -0.467899;
À primeira vista, esses valores podem parecer aleatórios, mas eles representam um pequeno cérebro artificial capaz de aprender como somar dois números.
Use esta fórmula para calcular a saída. Não se preocupe se parecer complicado agora—apenas siga o fluxo:
Aqui, x1
e x2
são os dois números que você deseja somar (normalizados dividindo por 100):
double x1 = 0.3; // 30 / 100
double x2 = 0.5; // 50 / 100
// Camada oculta
double z1 = x1 * weight1_node1 + x2 * weight2_node1 + bias_node1;
double h1 = sigmoid(z1);
double z2 = x1 * weight1_node2 + x2 * weight2_node2 + bias_node2;
double h2 = sigmoid(z2);
double z3 = x1 * weight1_node3 + x2 * weight2_node3 + bias_node3;
double h3 = sigmoid(z3);
// Camada de saída
double sum = (h1 * weight_out_node1 + h2 * weight_out_node2 + h3 * weight_out_node3 + bias_out) * 200.0f;
Vamos analisar por que multiplicamos por 200: inicialmente escalamos nossas entradas e saídas para se encaixar entre 0 e 1 para um treinamento mais fácil. Como a soma máxima de dois números abaixo de 100 é 200, escalamos a saída de volta multiplicando por 200.
Função sigmoide:
double sigmoid(double x) {
return 1.0 / (1.0 + exp(-x));
}
Vamos testar com alguns números reais:
Vamos usar x1 = 30
e x2 = 50
. Primeiro, normalizamos:
x1 = 30 / 100 = 0.3
x2 = 50 / 100 = 0.5
Agora vamos passar pela matemática:
z1 = (0.3 × 0.658136) + (0.5 × 0.840666) - 0.893218
≈ 0.1974408 + 0.420333 - 0.893218
≈ -0.2754442
h1 = sigmoid(z1) ≈ 0.431844
z2 = (0.3 × -0.720667) + (0.5 × -0.369172) + 0.036762
≈ -0.2162001 - 0.184586 + 0.036762
≈ -0.3640241
h2 = sigmoid(z2) ≈ 0.409872
z3 = (0.3 × 0.512252) + (0.5 × 0.292342) - 0.0745917
≈ 0.1536756 + 0.146171 - 0.0745917
≈ 0.2252549
h3 = sigmoid(z3) ≈ 0.556078
// Camada de saída
output = (h1 × 1.93108) + (h2 × -0.718584) + (h3 × 0.589741) - 0.467899
≈ (0.834389) + (-0.294320) + (0.328013) - 0.467899
≈ 0.400183
Soma prevista = 0.400183 × 200 ≈ 80.0366
// Por que multiplicar por 200? Porque durante o treinamento, normalizamos todas as entradas e saídas para ficarem entre 0 e 1 para melhor estabilidade de aprendizado. Como a soma máxima de duas entradas é 100 + 100 = 200, escalamos a saída da rede de volta multiplicando por 200.
Boom! O resultado é aproximadamente 80, que é a soma correta de 30 e 50.
Se você não acredita que essa pequena rede realmente pode somar, vamos testá-la novamente com números diferentes: 20 e 30.
Primeiro, normalizamos as entradas:
x1 = 20 / 100 = 0.2
x2 = 30 / 100 = 0.3
Então, as cálculos:
z1 = (0.2 × 0.658136) + (0.3 × 0.840666) - 0.893218
≈ 0.1316272 + 0.2521998 - 0.893218
≈ -0.509391
h1 = sigmoid(z1) ≈ 0.375241
z2 = (0.2 × -0.720667) + (0.3 × -0.369172) + 0.036762
≈ -0.1441334 - 0.1107516 + 0.036762
≈ -0.218123
h2 = sigmoid(z2) ≈ 0.445714
z3 = (0.2 × 0.512252) + (0.3 × 0.292342) - 0.0745917
≈ 0.1024504 + 0.0877026 - 0.0745917
≈ 0.1155613
h3 = sigmoid(z3) ≈ 0.528860
// Camada de saída
output = (h1 × 1.93108) + (h2 × -0.718584) + (h3 × 0.589741) - 0.467899
≈ (0.724688) + (-0.320095) + (0.311644) - 0.467899
≈ 0.248338
Soma prevista = 0.248338 × 200 ≈ 49.6676
Ele previu cerca de 49.67. Isso está super próximo da soma real: 50!
Bem legal, né?
Então... O que é uma Rede Neural, afinal?
Introdução rápida: Uma rede neural é como uma versão matemática do seu cérebro. Ela consiste em unidades chamadas neurônios que processam entradas multiplicando-as por pesos, adicionando um viés e, em seguida, aplicando uma função de ativação. Uma função de ativação comum é a sigmoide:
sigmoid(x) = 1 / (1 + e^(-x))
Isso comprime qualquer entrada em um valor entre 0 e 1, permitindo que a rede capture padrões complexos.
Este é o gráfico da função sigmoide, mostrando claramente como as entradas são suavemente transformadas em valores entre 0 e 1:

Existem outras funções de ativação também, como ReLU (Rectified Linear Unit), tanh e softmax, cada uma com seus próprios casos de uso e comportamentos. Mas a sigmoide é um ótimo lugar para começar porque é simples e suave—perfeita para redes neurais pequenas como a nossa.
A Configuração: Ensinando Nossa Rede a Somar
Para este experimento, criamos uma pequena rede com:
- 2 nós de entrada (para os dois números a serem somados)
- 3 nós ocultos (fazendo o trabalho pesado com a mágica da sigmoide)
- 1 nó de saída (nos dando a soma)
Aqui está um diagrama mostrando a estrutura da rede neural que estamos usando para somar dois números. Você pode ver como cada nó de entrada se conecta a todos os nós da camada oculta, como os pesos e viéses são aplicados, e como tudo flui em direção à saída final:

E sim, escalamos tudo. Como nossos números estão abaixo de 100, dividimos as entradas por 100 e escalamos a saída de volta por 200 no final.
Este é o código que escrevemos para somar dois números usando a rede neural:
#include <iostream>
#include <cmath>
using namespace std;
float sigmoid(float x) {
return 1.0f / (1.0f + exp(-x));
}
float sigmoid_derivative(float y) {
return y * (1 - y); // y = sigmoid(x)
}
int main() {
float weight1_node1 = 0.5f, weight2_node1 = 0.5f, bias_node1 = 0.0f;
float weight1_node2 = -0.5f, weight2_node2 = -0.5f, bias_node2 = 0.0f;
float weight1_node3 = 0.25f, weight2_node3 = -0.25f, bias_node3 = 0.0f;
float weight_out_node1 = 0.5f, weight_out_node2 = 0.5f, weight_out_node3 = 0.5f, bias_out = 0.0f;
float learning_rate = 0.01f;
int epochs = 1000;
for (int epoch = 0; epoch < epochs; ++epoch) {
float total_loss = 0.0f;
for (int a = 0; a < 100; ++a) {
for (int b = 0; b < 100; ++b) {
float x1 = a / 100.0f;
float x2 = b / 100.0f;
float target = (a + b) / 200.0f; // Normalizar alvo para a faixa de 0-1
// Passagem para frente para a camada oculta
float z1 = x1 * weight1_node1 + x2 * weight2_node1 + bias_node1;
float h1 = sigmoid(z1);
float z2 = x1 * weight1_node2 + x2 * weight2_node2 + bias_node2;
float h2 = sigmoid(z2);
float z3 = x1 * weight1_node3 + x2 * weight2_node3 + bias_node3;
float h3 = sigmoid(z3);
// Camada de saída com ativação linear
float y_pred = h1 * weight_out_node1 + h2 * weight_out_node2 + h3 * weight_out_node3 + bias_out;
float error = target - y_pred;
total_loss += error * error;
// Passagem para trás (camada de saída - linear)
float delta_out = error;
float grad_out_w1 = delta_out * h1;
float grad_out_w2 = delta_out * h2;
float grad_out_w3 = delta_out * h3;
float grad_out_b = delta_out;
// Passagem para trás (camada oculta)
float delta_h1 = delta_out * weight_out_node1 * sigmoid_derivative(h1);
float delta_h2 = delta_out * weight_out_node2 * sigmoid_derivative(h2);
float delta_h3 = delta_out * weight_out_node3 * sigmoid_derivative(h3);
float grad_w1_node1 = delta_h1 * x1;
float grad_w2_node1 = delta_h1 * x2;
float grad_b_node1 = delta_h1;
float grad_w1_node2 = delta_h2 * x1;
float grad_w2_node2 = delta_h2 * x2;
float grad_b_node2 = delta_h2;
float grad_w1_node3 = delta_h3 * x1;
float grad_w2_node3 = delta_h3 * x2;
float grad_b_node3 = delta_h3;
// Atualizar pesos
weight_out_node1 += learning_rate * grad_out_w1;
weight_out_node2 += learning_rate * grad_out_w2;
weight_out_node3 += learning_rate * grad_out_w3;
bias_out += learning_rate * grad_out_b;
weight1_node1 += learning_rate * grad_w1_node1;
weight2_node1 += learning_rate * grad_w2_node1;
bias_node1 += learning_rate * grad_b_node1;
weight1_node2 += learning_rate * grad_w1_node2;
weight2_node2 += learning_rate * grad_w2_node2;
bias_node2 += learning_rate * grad_b_node2;
weight1_node3 += learning_rate * grad_w1_node3;
weight2_node3 += learning_rate * grad_w2_node3;
bias_node3 += learning_rate * grad_b_node3;
}
}
if ((epoch + 1) % 100 == 0 || epoch == 0)
cout << "[Resumo] Época " << epoch + 1 << ": Perda = " << total_loss << endl;
}
// Testar o modelo
float a, b;
cout << "\nTeste de previsão de soma (a + b)\nDigite a: ";
cin >> a;
cout << "Digite b: ";
cin >> b;
float x1 = a / 100.0f;
float x2 = b / 100.0f;
float h1 = sigmoid(x1 * weight1_node1 + x2 * weight2_node1 + bias_node1);
float h2 = sigmoid(x1 * weight1_node2 + x2 * weight2_node2 + bias_node2);
float h3 = sigmoid(x1 * weight1_node3 + x2 * weight2_node3 + bias_node3);
float y_pred = h1 * weight_out_node1 + h2 * weight_out_node2 + h3 * weight_out_node3 + bias_out;
float predicted_sum = y_pred * 200.0f;
cout << "w1_node1: " << weight1_node1 << ", w2_node1: " << weight2_node1
<< ", w1_node2: " << weight1_node2 << ", w2_node2: " << weight2_node2
<< ", w1_node3: " << weight1_node3 << ", w2_node3: " << weight2_node3
<< ", out_w1: " << weight_out_node1 << ", out_w2: " << weight_out_node2
<< ", out_w3: " << weight_out_node3 << ", bias_out: " << bias_out
<< ", bias_node1: " << bias_node1 << ", bias_node2: " << bias_node2 << ", bias_node3: " << bias_node3 << endl;
cout << "Soma prevista: " << predicted_sum << "\nSoma real: " << (a + b) << endl;
return 0;
}
Então... Como Ele Aprendeu?
Aqui está a parte legal: não codificamos as regras para a adição. Apenas fornecemos à rede um monte de pares de números e suas somas, e ela ajustou lentamente aqueles pesos e viéses até que as previsões melhorassem cada vez mais.
É meio que como tentativa e erro, mas mais inteligente—com uma pitada de cálculo.
Ele Realmente Aprendeu a Somar?
Eh, não exatamente como nós. Ele não entende o que “mais” significa. Mas ele aprende uma aproximação muito boa. Ele vê padrões nos números e prevê somas que muitas vezes estão corretas.
É mais como aprender sentindo padrões em vez de conhecer regras. Se você estiver curioso, pode mudar os números de entrada e testá-lo. Quanto mais você brincar, mais entenderá como esses pequenos cérebros funcionam.
Concluindo
Ensinar uma rede neural a somar dois números pode parecer bobo à primeira vista, mas é uma ótima maneira de espiar a mente da IA. Por trás de todo o hype, são apenas pesos, viéses e um pouco de matemática inteligente.
Na próxima parte, vamos nos aprofundar em como as redes neurais funcionam: vamos explorar camadas, funções de ativação, derivadas e o que realmente acontece durante o treinamento.