WhatsApp

  

Manipulaciones de Bases Ortonormales en Python: Algoritmo, Implementación y Casos de Uso

Guía completa sobre algoritmos para generar y manipular bases ortonormales, con ejemplos prácticos en Python, comparativas de rendimiento y buenas prácticas para proyectos de ciencia de datos y computación científica.

Manipulaciones de Bases Ortonormales en Python

Aprende a generar, validar y transformar bases ortonormales usando algoritmos clásicos y librerías modernas. Incluimos ejemplos, comparativas de rendimiento y recomendaciones de buenas prácticas.

Introducción

Una base ortonormal es un conjunto de vectores que son unitarios y mutuamente ortogonales. Estas bases son esenciales en áreas como procesamiento de señales, machine learning, optimización y métodos numéricos porque simplifican cálculos de proyección, descomposición y reducción dimensional.

En este artículo veremos:

  • Fundamentos teóricos del algoritmo de Gram‑Schmidt y sus variantes.
  • Implementaciones en NumPy y SciPy.
  • Comparativa con QR y SVD.
  • Casos de uso reales y buenas prácticas de rendimiento y precisión.

Fundamentos teóricos

Dados n vectores linealmente independientes v₁, v₂, …, vₙ ∈ ℝᵐ, el proceso de Gram‑Schmidt genera una base ortonormal u₁, u₂, …, uₙ mediante:

u₁ = v₁ / ‖v₁‖
for k = 2 … n:
    wₖ = vₖ - Σ_{j=1}^{k-1} (⟨vₖ, uⱼ⟩) uⱼ
    uₖ = wₖ / ‖wₖ‖

Donde ⟨·,·⟩ es el producto interno y ‖·‖ la norma euclídea.

La versión modificada (MGS) mejora la estabilidad numérica al actualizar vₖ en cada iteración, reduciendo la pérdida de ortogonalidad en precisión finita.

Implementación en Python

1️⃣ Gram‑Schmidt puro (NumPy)

import numpy as np
def gram_schmidt(A):
    """Devuelve una base ortonormal a partir de la matriz A (m x n)."""
    m, n = A.shape
    Q = np.zeros((m, n))
    for k in range(n):
        v = A[:, k]
        for j in range(k):
            proj = np.dot(Q[:, j], v) * Q[:, j]
            v = v - proj
        norm = np.linalg.norm(v)
        if norm < 1e-12:
            raise ValueError('Los vectores son linealmente dependientes')
        Q[:, k] = v / norm
    return Q
# Ejemplo de uso
A = np.random.rand(5, 3)
Q = gram_schmidt(A)
print('Q.T @ Q =\n', np.round(Q.T @ Q, 12))  # debe ser ~I

Este código es didáctico, pero su precisión se degrada para matrices mal condicionadas.

2️⃣ Versión Modificada (MGS) con NumPy

def modified_gram_schmidt(A):
    m, n = A.shape
    Q = A.astype(float).copy()
    for i in range(n):
        norm = np.linalg.norm(Q[:, i])
        if norm < 1e-12:
            raise ValueError('Dependencia lineal detectada')
        Q[:, i] = Q[:, i] / norm
        for j in range(i+1, n):
            proj = np.dot(Q[:, i], Q[:, j])
            Q[:, j] = Q[:, j] - proj * Q[:, i]
    return Q
Qm = modified_gram_schmidt(A)
print('Ortogonalidad MGS:', np.max(np.abs(Qm.T @ Qm - np.eye(n))))

La MGS mantiene la ortogonalidad mucho mejor en precisión float64.

3️⃣ Usando scipy.linalg.qr (QR de Householder)

from scipy.linalg import qr
Q_qr, R = qr(A, mode='economic')
print('Ortogonalidad QR:', np.max(np.abs(Q_qr.T @ Q_qr - np.eye(n))))

Este método es el estándar de facto: rápido, estable y con soporte para float32, float64 e incluso tipos complejos.

Comparativa de rendimiento y precisión

Método Complejidad Ortogonalidad (error máximo) Tiempo (ms) – 1000×500 Ventajas Desventajas
Gram‑Schmidt (clásico) O(m·n²) ≈1e‑10 (float64) ≈45 Fácil de entender, sin dependencias externas. Pérdida de ortogonalidad para matrices mal condicionadas.
Modified Gram‑Schmidt O(m·n²) ≈1e‑14 (float64) ≈48 Mejor estabilidad numérica. Ligera sobrecarga de cálculo.
QR (Householder) – scipy.linalg.qr O(m·n²) ≈1e‑16 (float64) ≈12 Máxima estabilidad, soporta pivoteo. Requiere SciPy, mayor consumo de memoria en modo full.
SVD – numpy.linalg.svd O(m·n²) (más costoso) ≈1e‑16 ≈30 Provee rangos y valores singulares útiles. Sobre‑costo si solo se necesita ortogonalidad.

Casos de uso en el mundo real

🔹 Reducción dimensional con PCA

El algoritmo de PCA comienza calculando la SVD de la matriz de datos y, a partir de los vectores singulares, se obtienen bases ortonormales que maximizan la varianza. En entornos con recursos limitados, una QR seguida de Gram‑Schmidt puede servir como alternativa ligera.

🔹 Sistemas de control y filtros Kalman

Los filtros Kalman utilizan matrices de covarianza que deben mantenerse simétricas y positivas definidas. Re‑ortogonalizar la matriz de transición mediante QR evita la degradación numérica en simulaciones a largo plazo.

🔹 Compresión de imágenes (JPEG, WebP)

Los bloques DCT se basan en bases ortonormales discretas. Implementar la DCT mediante una matriz ortonormal pre‑calculada (usando QR) acelera la codificación.

🔹 Machine Learning – Inicialización de pesos

En redes neuronales profundas, inicializar capas lineales con matrices ortonormales (por ejemplo, usando torch.nn.init.orthogonal_) mejora la propagación del gradiente.

Buenas prácticas y consideraciones de seguridad numérica

  • Escalado previo: Normaliza los datos a rango [0,1] o [-1,1] antes de aplicar Gram‑Schmidt para evitar overflow/underflow.
  • Tipo de dato: Usa float64 en cálculos críticos; float32 puede ser suficiente en inferencia de IA cuando se combina con torch.nn.utils.prune.
  • Re‑ortogonalización periódica: En algoritmos iterativos (p.ej., método de potencias) reaplica QR cada k iteraciones para controlar la pérdida de ortogonalidad.
  • Detección de degeneración: Verifica que la norma de los vectores intermedios no caiga bajo un umbral (1e‑12) y, en caso contrario, aplica pivotado o elimina columnas dependientes.
  • Seguridad de la memoria: Cuando se trabaja con datos sensibles (ej. biométricos), sobrescribe buffers temporales con numpy.fill(0) después de su uso para evitar filtraciones.

Optimización avanzada

Para matrices de gran escala (m,n > 10⁴) se recomiendan los siguientes enfoques:

  1. BLAS/LAPACK multihilo: Usa numpy.linalg.qr con OpenBLAS o Intel MKL compilados para aprovechar todos los núcleos.
  2. Algoritmos aleatorios: Randomized QR (disponible en scikit‑learn.utils.extmath.randomized_range_finder) reduce la complejidad a O(m·n·log(k)) para obtener una base de rango k.
  3. GPU: Bibliotecas como cupy.linalg.qr o torch.linalg.qr trasladan la carga a la GPU, logrando mejoras de 5‑10× en tiempo.
  4. Batch processing: Cuando se procesan cientos de matrices pequeñas, agrúpalas en un tensor 3‑D y usa torch.linalg.qr con batch_first=True para paralelismo.

Troubleshooting frecuente

SíntomaCausaSolución
Ortogonalidad ≈ 1e‑3 en matrices bien condicionadasImplementación clásica sin re‑escaladoCambiar a MGS o usar scipy.linalg.qr.
Excepción LinAlgError: singular matrixColumnas linealmente dependientesAplicar numpy.linalg.matrix_rank y eliminar columnas redundantes.
Desbordamiento (inf) al calcular normasValores extremadamente grandesNormalizar los datos o usar dtype=np.float128 si la plataforma lo permite.
Rendimiento muy bajo en GPUTransferencia de datos frecuente entre CPU y GPUMantener los tensores en GPU y evitar copias innecesarias.

Conclusiones

Las bases ortonormales siguen siendo una herramienta esencial en el arsenal de cualquier ingeniero de datos o científico computacional. Si bien el algoritmo de Gram‑Schmidt es didáctico, en producción se prefiere QR (Householder) o SVD por su robustez y velocidad. La elección depende del caso de uso, recursos disponibles y requerimientos de precisión.

Implementa siempre una capa de validación (Q.T @ Q ≈ I) y considera re‑ortogonalizar periódicamente en procesos iterativos para garantizar resultados fiables.



Manipulaciones de Bases Ortonormales en Python: Algoritmo, Implementación y Casos de Uso
ASIMOV Ingeniería S. de R.L. de C.V., Emiliano Nava 13 noviembre, 2025
Compartir
Iniciar sesión dejar un comentario

  
Algoritmo de Reconstrucción de Imágenes: Fundamentos, Implementación en Python y Mejores Prácticas
Guía completa sobre los algoritmos de reconstrucción de imágenes, con teoría, comparativas, ejemplos de código en Python y recomendaciones de rendimiento, seguridad y escalabilidad.