Los modelos de inteligencia artificial representan datos y parámetros mediante vectores, matrices y tensores. Aprender consiste en ajustar esos números para reducir el error de una tarea.
Una red neuronal puede entenderse como una secuencia de transformaciones parametrizadas. Durante la inferencia transforma una entrada en una salida; durante el entrenamiento modifica sus parámetros a partir de ejemplos.
El álgebra lineal hace posible procesar muchas características, neuronas y observaciones en paralelo.
Un escalar puede representar una pérdida; un vector, una observación; una matriz, un lote de observaciones; y un tensor, datos con más ejes como imágenes o secuencias.
| Objeto | Forma posible | Ejemplo |
|---|---|---|
| Escalar | 1 | Tasa de aprendizaje |
| Vector | d | Características de una persona |
| Matriz | n × d | Lote de n observaciones |
| Tensor | n × h × w × c | Lote de imágenes |
const lote = [[1, 2, 3], [4, 5, 6]];
const forma = [lote.length, lote[0].length];
console.log(forma); // [2 observaciones, 3 características]
Una neurona calcula el producto escalar entre la entrada x y sus pesos w, suma un sesgo b y aplica una función de activación.
function neurona(entrada, pesos, sesgo, activacion) {
const z = entrada.reduce((s, x, i) => s + x * pesos[i], sesgo);
return activacion(z);
}
const relu = x => Math.max(0, x);
console.log(neurona([2, -1], [0.5, 3], 1, relu)); // 0
Una capa con varias neuronas reúne sus pesos en una matriz W y sus sesgos en un vector b:
Si la entrada tiene d elementos y la capa posee m neuronas, W tiene dimensión m × d, b tiene m elementos y la salida también.
function capaDensa(W, b, x) {
return W.map((fila, i) =>
fila.reduce((s, peso, j) => s + peso * x[j], b[i])
);
}
const W = [[1, 2], [-1, 0.5], [0, 3]];
console.log(capaDensa(W, [0, 1, -2], [2, 4])); // [10, 1, 10]
Al colocar n entradas como filas de X, una capa completa puede evaluarse con una multiplicación matricial. Si los pesos se almacenan con forma d × m:
function loteDenso(X, W, b) {
return X.map(fila => W[0].map((_, j) =>
fila.reduce((s, x, k) => s + x * W[k][j], b[j])
));
}
const X = [[1, 2], [3, 4]];
const W = [[1, 0, 2], [0, 1, -1]];
console.log(loteDenso(X, W, [0, 0, 1]));
El sesgo se suma a cada fila mediante una operación de difusión o broadcasting.
Componer transformaciones lineales o afines sin activaciones equivale a una sola transformación afín. Una función no lineal permite representar relaciones más complejas.
const relu = x => Math.max(0, x);
const sigmoide = x => 1 / (1 + Math.exp(-x));
const tanh = x => Math.tanh(x);
console.log([-2, 0, 2].map(relu));
console.log([-2, 0, 2].map(sigmoide));
console.log([-2, 0, 2].map(tanh));
El recorrido desde la entrada hasta la predicción se denomina propagación hacia adelante. Cada capa recibe la salida de la anterior.
const densa = (W, b, x) => W.map((fila, i) =>
fila.reduce((s, peso, j) => s + peso * x[j], b[i]));
const W1 = [[1, -1], [0.5, 2]], b1 = [0, 0];
const W2 = [[2, -0.5]], b2 = [1];
const h = densa(W1, b1, [3, 1]).map(x => Math.max(0, x));
console.log(densa(W2, b2, h));
La pérdida cuantifica la diferencia entre la predicción y el objetivo. El error cuadrático medio es habitual en regresión; la entropía cruzada, en clasificación.
function errorCuadraticoMedio(reales, predichos) {
return reales.reduce((s, y, i) => s + (y - predichos[i]) ** 2, 0) /
reales.length;
}
function entropiaCruzadaBinaria(y, p) {
const seguro = Math.max(1e-12, Math.min(1 - 1e-12, p));
return -(y * Math.log(seguro) + (1 - y) * Math.log(1 - seguro));
}
console.log(errorCuadraticoMedio([1, 3], [1.2, 2.7]));
console.log(entropiaCruzadaBinaria(1, 0.9));
El gradiente de la pérdida respecto de un vector de parámetros indica la dirección de máximo crecimiento. Su opuesto señala una dirección local de descenso.
function gradienteNumerico(f, parametros, paso = 1e-5) {
return parametros.map((_, i) => {
const izquierda = [...parametros], derecha = [...parametros];
izquierda[i] -= paso;
derecha[i] += paso;
return (f(derecha) - f(izquierda)) / (2 * paso);
});
}
console.log(gradienteNumerico(([x, y]) => x * x + 3 * y * y, [2, 1]));
// aproximadamente [4, 6]
Las diferencias finitas sirven para comprobar implementaciones, pero el entrenamiento usa diferenciación automática por eficiencia y precisión.
La retropropagación aplica la regla de la cadena desde la pérdida hacia las primeras capas. Reutiliza resultados intermedios para obtener todos los gradientes eficientemente.
function gradientePesos(gradSalida, entrada) {
return gradSalida.map(g => entrada.map(x => g * x));
}
console.log(gradientePesos([2, -1], [3, 4]));
// [[6, 8], [-3, -4]]
El optimizador actualiza los parámetros en dirección contraria al gradiente. La tasa de aprendizaje η controla el tamaño del paso.
function actualizar(parametros, gradientes, tasa) {
return parametros.map((valor, i) => valor - tasa * gradientes[i]);
}
let parametros = [4, -2];
parametros = actualizar(parametros, [8, -4], 0.1);
console.log(parametros); // [3.2, -1.6]
Una tasa excesiva puede volver inestable el entrenamiento y una demasiado pequeña puede hacerlo muy lento. Optimizadores como momentum o Adam adaptan las actualizaciones.
Para clasificación multiclase, la capa final produce puntuaciones llamadas logits. Softmax las convierte en probabilidades positivas que suman 1.
function softmax(logits) {
const maximo = Math.max(...logits);
const exponenciales = logits.map(z => Math.exp(z - maximo));
const suma = exponenciales.reduce((s, x) => s + x, 0);
return exponenciales.map(x => x / suma);
}
console.log(softmax([2, 1, 0]));
Restar el máximo no cambia el resultado y evita desbordamientos numéricos.
Un embedding asigna cada elemento discreto —palabra, producto o usuario— a un vector denso aprendido. Elementos utilizados en contextos similares pueden quedar cerca en el espacio.
const vocabulario = new Map([["gato", 0], ["perro", 1], ["casa", 2]]);
const E = [[0.8, 0.1], [0.75, 0.2], [-0.3, 0.9]];
function embedding(palabra) {
return E[vocabulario.get(palabra)];
}
console.log(embedding("perro")); // [0.75, 0.2]
Un embedding es una representación aprendida, no una descripción completa ni necesariamente neutral del elemento.
La similitud coseno compara el ángulo entre dos vectores. Es común al buscar textos, imágenes o productos relacionados.
function similitudCoseno(a, b) {
const producto = a.reduce((s, x, i) => s + x * b[i], 0);
const denominador = Math.hypot(...a) * Math.hypot(...b);
return denominador === 0 ? 0 : producto / denominador;
}
console.log(similitudCoseno([1, 2, 0], [2, 4, 1]));
La cercanía refleja patrones presentes en los datos y el objetivo de entrenamiento; no garantiza equivalencia semántica.
La atención compara consultas Q con claves K y utiliza los resultados para combinar valores V. En atención de producto escalar:
La escala por √dk ayuda a evitar que productos grandes saturen softmax.
function atencionUnaConsulta(q, claves, valores) {
const escala = Math.sqrt(q.length);
const puntajes = claves.map(k =>
q.reduce((s, x, i) => s + x * k[i], 0) / escala);
const maximo = Math.max(...puntajes);
const exp = puntajes.map(x => Math.exp(x - maximo));
const suma = exp.reduce((s, x) => s + x, 0);
const pesos = exp.map(x => x / suma);
return valores[0].map((_, j) =>
valores.reduce((s, v, i) => s + pesos[i] * v[j], 0));
}
console.log(atencionUnaConsulta([1, 0], [[1, 0], [0, 1]], [[10, 0], [0, 10]]));
Una capa convolucional aplica núcleos aprendidos a vecindarios de una imagen. Los mismos pesos se reutilizan en todas las posiciones, lo que reduce parámetros y detecta patrones locales.
function correlacionValida(imagen, nucleo) {
const alto = imagen.length - nucleo.length + 1;
const ancho = imagen[0].length - nucleo[0].length + 1;
return Array.from({ length: alto }, (_, y) =>
Array.from({ length: ancho }, (_, x) => nucleo.reduce((total, fila, i) =>
total + fila.reduce((s, peso, j) => s + peso * imagen[y + i][x + j], 0), 0))
);
}
console.log(correlacionValida([[1, 2, 3], [4, 5, 6], [7, 8, 9]], [[1, 0], [0, -1]]));
Muchas bibliotecas implementan correlación cruzada sin invertir el núcleo aunque la operación se denomine convolución.
Las técnicas de normalización estabilizan escalas internas. La regularización combate el sobreajuste mediante penalizaciones, abandono aleatorio de activaciones, aumento de datos o detención temprana.
function perdidaConL2(perdidaDatos, matrices, lambda) {
const sumaCuadrados = matrices.flat(2)
.reduce((s, peso) => s + peso * peso, 0);
return perdidaDatos + lambda * sumaCuadrados;
}
console.log(perdidaConL2(0.4, [[[1, -2], [0.5, 0]]], 0.01)); // 0.4525
Las técnicas se comportan de forma distinta durante entrenamiento e inferencia; esa diferencia debe respetarse al desplegar el modelo.
Gran parte de los errores de implementación proviene de dimensiones incompatibles, ejes intercambiados o sesgos difundidos sobre el eje incorrecto.
function verificarProducto(filasA, columnasA, filasB, columnasB) {
if (columnasA !== filasB) throw new Error("Dimensiones incompatibles");
return [filasA, columnasB];
}
console.log(verificarProducto(32, 128, 128, 64)); // [32, 64]
También deben vigilarse gradientes que desaparecen o explotan, divisiones por cero, exponenciales grandes, precisión numérica, consumo de memoria y desbordamientos.
Una pérdida baja no basta: el modelo debe evaluarse con datos no utilizados en el entrenamiento, métricas adecuadas y análisis por grupos. Los datos pueden contener errores y sesgos que el modelo aprenderá; por eso deben revisarse representatividad, privacidad, seguridad y consecuencias de uso.
El álgebra lineal constituye la infraestructura matemática de gran parte de la inteligencia artificial moderna. Las multiplicaciones matriciales construyen predicciones y los gradientes indican cómo modificar sus parámetros para aprender.
En el próximo tema integraremos lo aprendido en el curso mediante un proyecto aplicado que combinará representación, transformación, análisis e interpretación de datos.