Las Matemáticas Detrás de la IA: Comencemos Simple
¿Cómo escribirías normalmente un programa para sumar dos números menores de 100? Fácil, ¿verdad? Normalmente escribirías algo sencillo como:
#include <iostream>
using namespace std;
int main() {
int a, b, sum;
cout << "Ingresa el primer número: ";
cin >> a;
cout << "Ingresa el segundo número: ";
cin >> b;
sum = a + b;
cout << "La suma es: " << sum << endl;
return 0;
}
Pero tomemos un camino más emocionante—enseñemos a una máquina a aprender a sumar por sí sola, usando una red neuronal. Las redes neuronales están en el corazón de la IA moderna, impulsando todo, desde el reconocimiento de imágenes y la traducción de idiomas hasta sistemas avanzados como ChatGPT.
En este artículo, mostraremos cómo una red neuronal puede "aprender" a sumar dos números a través del entrenamiento, no siguiendo una regla codificada. Verás cómo una red neuronal aprende a "sumar" al detectar patrones. Incluso revelaremos los números reales (la magia detrás de la cortina) que hacen que esto suceda.
Aquí hay un vistazo a la estructura interna de una red neuronal entrenada:
// Pesos y sesgos de la capa 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 y sesgo de la capa de salida
double weight_out_node1 = 1.93108;
double weight_out_node2 = -0.718584;
double weight_out_node3 = 0.589741;
double bias_out = -0.467899;
A primera vista, estos valores pueden parecer aleatorios, pero representan un pequeño cerebro artificial capaz de aprender a sumar dos números.
Usa esta fórmula para calcular la salida. No te preocupes si parece complicado ahora—solo sigue el ritmo:
Aquí, x1
y x2
son los dos números que quieres sumar (normalizados dividiendo por 100):
double x1 = 0.3; // 30 / 100
double x2 = 0.5; // 50 / 100
// Capa 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);
// Capa de salida
double sum = (h1 * weight_out_node1 + h2 * weight_out_node2 + h3 * weight_out_node3 + bias_out) * 200.0f;
Desglosemos por qué multiplicamos por 200: inicialmente escalamos nuestras entradas y salidas para que se ajusten entre 0 y 1 para un entrenamiento más fácil. Dado que la suma máxima de dos números menores de 100 es 200, escalamos la salida de nuevo multiplicándola por 200.
Función sigmoide:
double sigmoid(double x) {
return 1.0 / (1.0 + exp(-x));
}
Probemos con algunos números reales:
Usaremos x1 = 30
y x2 = 50
. Primero los normalizamos:
x1 = 30 / 100 = 0.3
x2 = 50 / 100 = 0.5
Ahora pasamos por las matemáticas:
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
// Capa de salida final
output = (h1 × 1.93108) + (h2 × -0.718584) + (h3 × 0.589741) - 0.467899
≈ (0.834389) + (-0.294320) + (0.328013) - 0.467899
≈ 0.400183
Suma predicha = 0.400183 × 200 ≈ 80.0366
// ¿Por qué multiplicar por 200? Porque durante el entrenamiento, normalizamos todas las entradas y salidas para que se mantuvieran entre 0 y 1 para una mejor estabilidad de aprendizaje. Dado que la suma máxima de dos entradas es 100 + 100 = 200, escalamos la salida de la red multiplicándola por 200.
¡Boom! El resultado es aproximadamente 80, que es la suma correcta de 30 y 50.
Si no crees que esta pequeña red realmente puede sumar, probémoslo de nuevo con diferentes números: 20 y 30.
Primero normalizamos las entradas:
x1 = 20 / 100 = 0.2
x2 = 30 / 100 = 0.3
Luego las 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
// Capa de salida final
output = (h1 × 1.93108) + (h2 × -0.718584) + (h3 × 0.589741) - 0.467899
≈ (0.724688) + (-0.320095) + (0.311644) - 0.467899
≈ 0.248338
Suma predicha = 0.248338 × 200 ≈ 49.6676
Predijo aproximadamente 49.67. ¡Eso está muy cerca de la suma real: 50!
Bastante genial, ¿no?
Entonces... ¿Qué es una Red Neuronal de Todos Modos?
Introducción rápida: Una red neuronal es como una versión matemática de tu cerebro. Consiste en unidades llamadas neuronas que procesan entradas multiplicándolas por pesos, sumando un sesgo y luego aplicando una función de activación. Una función de activación común es la sigmoide:
sigmoid(x) = 1 / (1 + e^(-x))
Esto aplana cualquier entrada en un valor entre 0 y 1, permitiendo que la red capture patrones complejos.
Este es el gráfico de la función sigmoide, mostrando claramente cómo las entradas se transforman suavemente en valores entre 0 y 1:

También hay otras funciones de activación, como ReLU (Unidad Lineal Rectificada), tanh y softmax, cada una con sus propios casos de uso y comportamientos. Pero la sigmoide es un gran lugar para comenzar porque es simple y suave—perfecta para redes neuronales pequeñas como la nuestra.
La Configuración: Enseñando a Nuestra Red a Sumar
Para este experimento, creamos una pequeña red con:
- 2 nodos de entrada (para los dos números a sumar)
- 3 nodos ocultos (haciendo el trabajo pesado con la magia de la sigmoide)
- 1 nodo de salida (dándonos la suma)
Aquí hay un diagrama que muestra la estructura de la red neuronal que estamos usando para sumar dos números. Puedes ver cómo cada nodo de entrada se conecta a todos los nodos de la capa oculta, cómo se aplican los pesos y sesgos, y cómo todo fluye hacia la salida final:

Y sí, escalamos todo. Dado que nuestros números son menores de 100, dividimos las entradas por 100 y escalamos la salida de nuevo multiplicándola por 200 al final.
Este es el código que escribimos para sumar dos números usando la red neuronal:
#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 objetivo al rango 0-1
// Paso hacia adelante para la capa 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);
// Capa de salida con activación lineal
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;
// Paso hacia atrás (capa de salida - lineal)
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;
// Paso hacia atrás (capa 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;
// Actualizar 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 << "[Resumen] Época " << epoch + 1 << ": Pérdida = " << total_loss << endl;
}
// Probar el modelo
float a, b;
cout << "\nPredicción de suma de prueba (a + b)\nIngresa a: ";
cin >> a;
cout << "Ingresa 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 << "Suma predicha: " << predicted_sum << "\nSuma real: " << (a + b) << endl;
return 0;
}
Entonces... ¿Cómo Aprendió?
Aquí está la parte genial: no codificamos las reglas para la suma. Solo le dimos a la red un montón de pares de números y sus sumas, y poco a poco ajustó esos pesos y sesgos hasta que las predicciones mejoraron.
Es un poco como prueba y error pero más inteligente—con un toque de cálculo.
¿Realmente Aprendió a Sumar?
Eh, no exactamente como nosotros. No entiende lo que significa “más”. Pero sí aprende una aproximación muy buena. Ve patrones en los números y predice sumas que a menudo son precisas.
Es más como aprender sintiendo patrones en lugar de conocer reglas. Si tienes curiosidad, puedes cambiar los números de entrada y probarlo. Cuanto más juegues, más entenderás cómo funcionan estos pequeños cerebros.
Cerrando
Enseñar a una red neuronal a sumar dos números puede parecer tonto al principio, pero es una gran manera de echar un vistazo dentro de la mente de la IA. Detrás de todo el bombo, solo hay pesos, sesgos y matemáticas inteligentes.
En la próxima parte, profundizaremos en cómo funcionan las redes neuronales: exploraremos capas, funciones de activación, derivadas y lo que realmente sucede durante el entrenamiento.