Álgebra Lineal y Cálculo de Tensores

El siguiente documento sacrifica rigurosidad matemática para hacer más accesible su contenido. El objetivo es construir la intuición necesaria para comprender las operaciones que subyacen en todo modelo de Machine Learning.

Un sistema de ecuaciones lineales con incógnitas se expresa formalmente como:

\begin{aligned} a_{11}x_1 + a_{12}x_2 + \cdots + a_{1m}x_m &= b_1 \\ a_{21}x_1 + a_{22}x_2 + \cdots + a_{2m}x_m &= b_2 \\ &\vdots \\ a_{n1}x_1 + a_{n2}x_2 + \cdots + a_{nm}x_m &= b_n \end{aligned}

Donde los coeficientes y los términos independientes son valores conocidos que definen la estructura del problema.

Se denomina "sistema " a esta configuración. Resolverlo implica encontrar un vector que satisfaga simultáneamente cada una de las igualdades. El conjunto de todas estas soluciones posibles constituye el espacio solución.

Por ejemplo, si buscamos predecir el precio de una suscripción basándonos en el número de usuarios ( ) y el almacenamiento extra ( ), nos enfrentaríamos a una composición geométrica como esta:

\begin{cases} 2x_1 + x_2 = 15 \\ x_1 + 3x_2 = 20 \end{cases}

En Elixir, usando la librería Nx, representaríamos los coeficientes y los resultados como tensores independientes:

# Los coeficientes de la izquierda
a = Nx.tensor([[2, 1], [1, 3]])

# Los resultados de la derecha
b = Nx.tensor([15, 20])

Operaciones Elementales

Están permitidas tres tipos de operaciones para transformar un sistema a otro, sin modificar el conjunto solución:

  1. Multiplicar una ecuación por un número real distinto de cero. ii. Multiplicar una ecuación por un número real y sumarla a otra ecuación. iii. Intercambiar la posición de dos ecuaciones.

Dos sistemas de ecuaciones lineales con incógnitas son equivalentes si comparten un conjunto solución.

Para visualizar esto, piensa en el sistema anterior. Si multiplicamos la primera ecuación por 2, obtenemos . Aunque los números "se vean" distintos, la recta que representan en el espacio es exactamente la misma. No hemos perdido ni ganado información, solo hemos cambiado la escala de nuestra observación.

# Multiplicar una fila por un escalar en Nx
# Supongamos que 'a' es nuestro tensor de coeficientes
fila_1 = a[0]
fila_1_escalada = Nx.multiply(fila_1, 2)

La Matriz: Arquitectura del Sistema

Esta arquitectura de coeficientes no es una simple lista; es una entidad rectangular que encapsula la transformación completa:

\begin{pmatrix} a_{11}x_1 + a_{12}x_2 + \cdots + a_{1m}x_m \\ a_{21}x_1 + a_{22}x_2 + \cdots + a_{2m}x_m \\ \vdots \\ a_{n1}x_1 + a_{n2}x_2 + \cdots + a_{nm}x_m \end{pmatrix} = \begin{pmatrix} b_1 \\ b_2 \\ \vdots \\ b_n \end{pmatrix}

Esta estructura rectangular representa una entidad propia, mucho más allá de ser una simple convención de escritura. En matemáticas, la representamos así:

\mathbf{A} = \begin{pmatrix} a_{11} & a_{12} & \dots & a_{1m} \\ a_{21} & a_{22} & \dots & a_{2m} \\ \vdots & \vdots & \ddots & \vdots \\ a_{n1} & a_{n2} & \dots & a_{nm} \end{pmatrix}

Y en el ecosistema de Elixir, la creación de este objeto es casi poética por su simplicidad:

alias Nx
matrix = Nx.tensor([
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
])

Una matriz como esta se dice que es una matriz , donde es el número de filas y el número de columnas.

Resolución de Sistemas

Para resolver un sistema como , no necesitamos hacer cuentas a mano eternamente. Podemos usar la Eliminación de Gauss (llevar la matriz a su forma escalonada) o simplemente dejar que Nx haga el trabajo pesado:

# Resolver Ax = b
# a es la matriz de coeficientes, b el vector de resultados
solucion = Nx.LinAlg.solve(a, b)

Rango de una Matriz

El rango de una matriz es el número de filas (o columnas) que son linealmente independientes. Es, de alguna forma, la medida de la "información real" que contiene la matriz. Si una fila se puede obtener sumando las otras, esa fila es redundante y no aumenta el rango.

En computación, solemos estimar el rango observando los valores singulares (SVD). Si un valor es muy cercano a cero, esa dimensión es prácticamente despreciable.

# Una forma de ver la 'salud' del rango es vía SVD
{u, s, vt} = Nx.LinAlg.svd(matrix)

Sistemas Inconsistentes

No todos los caminos tienen destino. A veces, las ecuaciones se contradicen (como decir que y al mismo tiempo). Geométricamente, esto sucede cuando las rectas o planos son paralelos y nunca se cruzan. En estos casos, el sistema es inconsistente.

Transpuesta

Es como girar la matriz sobre su diagonal principal. Las filas se convierten en columnas y viceversa. Es una operación fundamental para reorientar datos antes de un producto.

transpuesta = Nx.transpose(matrix)
# O de forma abreviada
transpuesta = Nx.t(matrix)

Suma y Escalamiento

Las matrices se pueden sumar (si tienen la misma forma) elemento a elemento. También se pueden multiplicar por un escalar, lo que "estira" o "encoge" toda la transformación.

suma = Nx.add(matrix_a, matrix_b)
escalado = Nx.multiply(matrix, 0.5)

Multiplicación Matricial

Multiplicar dos matrices ( ) no es multiplicar elemento a elemento. Es una composición de transformaciones. El elemento en la posición es el producto punto de la fila de por la columna de .

El orden de los factores altera el producto: . En el mundo de las transformaciones, no es equivalente rotar y luego trasladar, que trasladar y luego rotar.

producto = Nx.dot(matrix_a, matrix_b)

Determinante

El determinante de una matriz cuadrada es un escalar que codifica información geométrica fundamental: mide el factor de escala por el cual la transformación estira o comprime el espacio.

Para una matriz :

  • Si : la transformación colapsa una dimensión. La matriz no tiene inversa y el sistema es singular.

  • Si : la transformación es invertible y el sistema tiene solución única.

det = Nx.LinAlg.determinant(matrix)

Linealidad

En matemáticas, la linealidad es una propiedad que garantiza que un sistema sea predecible y fácil de analizar. Se apoya en dos pilares fundamentales:

  • Aditividad: El efecto combinado de dos entradas juntas es igual a la suma de sus efectos individuales.

  • Homogeneidad (Escalamiento): Si multiplicamos la magnitud de una entrada por un factor escalar constante, la salida se multiplicará exactamente por ese mismo factor.

En términos visuales, las transformaciones lineales mantienen las líneas rectas (no las curvan) y mantienen el origen fijo en su lugar. Esta simplicidad nos permite tomar problemas incomprensibles y descomponerlos en millones de operaciones básicas procesables en paralelo.

El Tejido del Espacio: Combinaciones y Bases

Si la linealidad nos dice cómo cambian las cosas, necesitamos entender con qué estamos construyendo ese espacio. Aquí entran los conceptos que dan forma a cualquier sistema multidimensional:

  • Combinación Lineal: Es la operación constructiva por excelencia. Consiste en tomar un conjunto de vectores, "estirarlos" o "encogerlos" (multiplicarlos por un escalar) y sumarlos. Matemáticamente, un vector es una combinación lineal de otros vectores si puede expresarse como:

    \mathbf{w} = c_1\mathbf{v}_1 + c_2\mathbf{v}_2 + \dots + c_n\mathbf{v}_n

    (donde las representan coeficientes escalares).

  • Espacio Generado (Span): Imagina dos vectores que apuntan en direcciones distintas. Si calculas absolutamente todas sus combinaciones lineales posibles, obtendrás un plano infinito. Ese conjunto de todos los destinos posibles a los que puedes llegar combinando tus vectores se llama espacio generado.

  • Independencia Lineal: ¿Aportan todos los vectores información nueva? Si tienes un conjunto de vectores y ninguno puede construirse como una combinación lineal de los demás, son linealmente independientes; no hay redundancia. Si uno de ellos sí puede formarse combinando el resto, es información repetida (linealmente dependiente) y no expande tu espacio generado.

  • Base y Dimensión: Si encuentras un conjunto de vectores que son linealmente independientes (cero redundancia) y que generan todo tu espacio de interés, has encontrado una base. El número estricto de vectores en esa base es lo que define la dimensión del espacio. Es el equivalente a definir los ejes norte-sur, este-oeste y arriba-abajo para poder describir cualquier punto del universo.

Pensar en términos de combinaciones y bases es crucial. Cuando entrenamos una red neuronal o comprimimos un archivo de audio, los algoritmos están, en el fondo, buscando la "base" más eficiente para representar esa información, descartando todo lo que sea "linealmente dependiente" o redundante.

La Jerarquía de los Tensores

Ahora que entendemos cómo se estructuran y combinan los elementos dentro de un espacio, podemos organizar las "colecciones de información" que lo habitan. Un tensor es, en su forma más pura, un objeto matemático multilineal definido por su rango.

Rango (NDim) Nombre Significado Geométrico

0

Escalar ( )

Magnitud pura en un punto sin dirección.

1

Vector ( )

Dirección y magnitud en el espacio.

2

Matriz ( )

Transformación lineal entre espacios.

Tensor de orden

Generalización multidimensional de datos.

En computación y ciencia de datos, el rango de un tensor se suele llamar su número de ejes o dimensiones (conocido en librerías de código como rank o ndim).

Espacios, Kernel e Imagen

Dada una matriz , esta define dos subespacios fundamentales:

  • Imagen o Espacio Columna ( ): El conjunto de todos los vectores que pueden ser alcanzados por la transformación . Su dimensión es el rango de la matriz.

  • Kernel o Espacio Nulo ( ): El conjunto de vectores que la matriz "colapsa" hacia el origen ( ).

Si el Kernel contiene vectores distintos de cero, la transformación no es inyectiva; es decir, hay pérdida de información irreversible.

Valores y Vectores Propios (Eigenvalues y Eigenvectors)

Entre todas las direcciones del espacio, existen algunas privilegiadas: aquellas que una transformación lineal no desvía, sino que solo estira o comprime. Si un vector cumple:

entonces es un vector propio (eigenvector) de , y es su valor propio (eigenvalue) asociado.

¿Por qué importan?

Los eigenvalues y eigenvectors revelan la estructura interna de una transformación:

  • PCA (Análisis de Componentes Principales): Los eigenvectors de la matriz de covarianza son las direcciones de máxima varianza en los datos. Los eigenvalues nos dicen cuánta varianza captura cada dirección.

  • Estabilidad de sistemas: En sistemas dinámicos y redes recurrentes, los eigenvalues determinan si el sistema converge, diverge o oscila.

  • PageRank: El algoritmo fundacional de Google calcula el eigenvector dominante de la matriz de transiciones de la web.

Cálculo en Elixir

# Descomposición en eigenvalues/eigenvectors
matrix = Nx.tensor([[4.0, 2.0], [1.0, 3.0]])
{eigenvalues, eigenvectors} = Nx.LinAlg.eigh(matrix)

# eigenvalues: los factores de escala en cada dirección propia
# eigenvectors: las direcciones que la transformación no desvía

Nx.LinAlg.eigh/1 calcula eigenvalues para matrices simétricas (Hermitianas), que es el caso más común en ML (matrices de covarianza, kernels, etc.). Para matrices generales, se usaría una descomposición más elaborada.

Broadcasting (Difusión de Tensores)

El Broadcasting es una regla matemática y computacional que permite realizar operaciones elemento a elemento entre tensores de distintas formas (shapes).

Dos dimensiones son compatibles para el broadcasting si: 1. Son iguales. 2. Una de ellas es .

Tensor A (Shape: 3 x 4)
Tensor B (Shape:     4)
-----------------------
A + B (Resultado: 3 x 4)

Explicación: La dimensión de se "difunde" virtualmente a lo largo de las filas de para que la operación sea posible.

Cálculo de Matrices: Gradientes y Jacobianos

Cuando trabajamos con funciones que toman vectores como entrada o salida, las reglas ordinarias de derivación se expanden.

El Gradiente ( )

Si tenemos una función escalar que depende de un vector , el gradiente es el vector de todas sus derivadas parciales:

\nabla f(\mathbf{x}) = \begin{pmatrix} \frac{\partial f}{\partial x_1} \\ \frac{\partial f}{\partial x_2} \\ \vdots \\ \frac{\partial f}{\partial x_n} \end{pmatrix}

El Jacobiano ( )

Si tenemos una función vectorial (una función que toma un vector y devuelve otro), el Jacobiano es la matriz que contiene todas las derivadas parciales posibles:

\mathbf{J} = \frac{\partial \mathbf{f}}{\partial \mathbf{x}} = \begin{pmatrix} \frac{\partial f_1}{\partial x_1} & \dots & \frac{\partial f_1}{\partial x_n} \\ \vdots & \ddots & \vdots \\ \frac{\partial f_m}{\partial x_1} & \dots & \frac{\partial f_m}{\partial x_n} \end{pmatrix}

El Jacobiano es fundamental en el entrenamiento de redes neuronales, ya que mide cómo una pequeña perturbación en la entrada afecta a cada componente de la salida.

De la Teoría a la Práctica: Nx

En Nx, la diferenciación automática y el broadcasting son ciudadanos de primera clase.

import Nx
alias Nx.LinAlg

# Ejemplo de Broadcasting
a = Nx.tensor([[1, 2], [3, 4]])
b = Nx.tensor([10, 20])
Nx.add(a, b) # [[11, 22], [13, 24]]

# Diferenciación Automática (Gradiente)
f = fn x -> Nx.sum(Nx.pow(x, 2)) end
gradiente_f = Nx.grad(f)

# Evaluar el gradiente en un punto
x = Nx.tensor([1.0, 2.0, 3.0])
gradiente_f.(x) # [2.0, 4.0, 6.0]

Nx utiliza modos de compilación para calcular estos gradientes de forma extremadamente eficiente, permitiendo que el compilador optimice la cadena de derivadas (regla de la cadena) antes de la ejecución.

Compendio: Álgebra Lineal en el Ecosistema Elixir (Nx)

Como hemos visto, Nx funciona como un lenguaje para describir transformaciones en el espacio, superando la función de una librería de cálculo convencional. Aquí tienes un resumen organizado de las herramientas fundamentales que hemos introducido:

Categoría Operación Función en Elixir (Nx / Nx.LinAlg)

Manipulación de Tensores

Creación de Matriz

Nx.tensor([[…​], […​]])

Transpuesta (Espejo)

Nx.transpose/1 o Nx.t/1

Difusión (Broadcasting)

Nx.broadcast/2

Remodelar (Reshape)

Nx.reshape/2

Concatenación (Stack)

Nx.stack/2 o Nx.concatenate/2

Aritmética de Transformación

Suma / Resta

Nx.add/2, Nx.subtract/2

Escalamiento

Nx.multiply/2

Multiplicación Matricial

Nx.dot/2

Producto Exterior

Nx.outer/2

Inversa de Matriz

Nx.LinAlg.invert/1

Análisis y Geometría

Resolución ( )

Nx.LinAlg.solve/2

Norma (Magnitud)

Nx.LinAlg.norm/2

Determinante

Nx.LinAlg.determinant/1

Rango e Información

Nx.LinAlg.svd/1

Eigenvalues / Eigenvectors

Nx.LinAlg.eigh/1

Cálculo Avanzado

Gradientes ( )

Nx.grad/1

Diferenciación Auto

Nx.ValueWithGrad

Potencia de Matriz

Nx.LinAlg.matrix_power/2

Descomposiciones

QR, Cholesky, LU

Recuerda que para operaciones de alta complejidad como SVD o Inversión de matrices grandes, es altamente recomendable usar el controlador EXLA para aprovechar la aceleración por hardware.