47. Álgebra lineal aplicada a inteligencia artificial

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.

47.1 Introducción

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.

entrada ⟶ transformaciones ⟶ predicción ⟶ error ⟶ ajuste

El álgebra lineal hace posible procesar muchas características, neuronas y observaciones en paralelo.

47.2 Datos, parámetros y tensores

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.

ObjetoForma posibleEjemplo
Escalar1Tasa de aprendizaje
VectordCaracterísticas de una persona
Matrizn × dLote de n observaciones
Tensorn × h × w × cLote 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]

47.3 Una neurona artificial

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.

z = wTx + b     a = φ(z)
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

47.4 Capa densa como transformación matricial

Una capa con varias neuronas reúne sus pesos en una matriz W y sus sesgos en un vector b:

z = Wx + 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]

47.5 Procesamiento por lotes

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:

Z = XW + b     (n × d)(d × m) = n × 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.

47.6 Por qué hacen falta activaciones no lineales

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.

W2(W1x + b1) + b2 = (W2W1)x + (W2b1 + b2)
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));

47.7 Propagación hacia adelante

El recorrido desde la entrada hasta la predicción se denomina propagación hacia adelante. Cada capa recibe la salida de la anterior.

h = ReLU(W1x + b1)
ŷ = W2h + b2
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));

47.8 Funciones de pérdida

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.

ECM = (1/n)Σ(yi − ŷi)2
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));

47.9 Gradientes

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.

θL = (∂L/∂θ1, ..., ∂L/∂θp)
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.

47.10 Retropropagació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.

si z = Wx + b y llega g = ∂L/∂z:
∂L/∂W = gxT    ∂L/∂b = g    ∂L/∂x = WTg
function gradientePesos(gradSalida, entrada) {
  return gradSalida.map(g => entrada.map(x => g * x));
}

console.log(gradientePesos([2, -1], [3, 4]));
// [[6, 8], [-3, -4]]

47.11 Descenso del gradiente

El optimizador actualiza los parámetros en dirección contraria al gradiente. La tasa de aprendizaje η controla el tamaño del paso.

θ ← θ − η∇θL
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.

47.12 Clasificación y softmax

Para clasificación multiclase, la capa final produce puntuaciones llamadas logits. Softmax las convierte en probabilidades positivas que suman 1.

pi = ezijezj
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.

47.13 Embeddings

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.

identificador i ⟶ fila i de la matriz E
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.

47.14 Similitud entre embeddings

La similitud coseno compara el ángulo entre dos vectores. Es común al buscar textos, imágenes o productos relacionados.

cos(a, b) = (a · b)/(||a|| ||b||)
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.

47.15 El mecanismo de atención

La atención compara consultas Q con claves K y utiliza los resultados para combinar valores V. En atención de producto escalar:

Atención(Q, K, V) = softmax(QKT/√dk)V

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]]));

47.16 Convoluciones y visión artificial

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.

salida(y, x) = Σi,j,c entrada(y+i, x+j, c) · núcleo(i, j, c) + b
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.

47.17 Normalización y regularizació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.

regularización L2: Ltotal = Ldatos + λ||W||2
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.

47.18 Dimensiones, estabilidad y evaluación

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.

47.19 Qué debes recordar de este tema

  • Los datos, activaciones y parámetros se representan con vectores, matrices y tensores.
  • Una capa densa calcula una transformación afín seguida normalmente por una activación.
  • Las activaciones no lineales permiten representar funciones complejas.
  • La pérdida cuantifica el error que se intenta reducir.
  • La retropropagación usa la regla de la cadena para calcular gradientes.
  • El descenso del gradiente actualiza parámetros en dirección opuesta al gradiente.
  • Softmax convierte logits en probabilidades multiclase.
  • Los embeddings representan elementos mediante vectores aprendidos.
  • La atención combina valores según similitudes entre consultas y claves.
  • Las formas de los tensores y la estabilidad numérica deben comprobarse siempre.

47.20 Conclusión

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.