Skip to content

English · Español

Laboratorio 03 — Normas por experimento: verificando ||Ax|| ≤ ||A||·||x||

Objetivo: verificar empíricamente la desigualdad submultiplicativa y encontrar el caso de igualdad (vector singular superior).

Tiempo estimado: 30–45 minutos.

Prerrequisito: leído theory/04-norms-and-conditioning.md.


Lo que produces

Un directorio experiments/03-norms-by-experiment/ que contiene:

  • norms.py — script (lo escribe Borja).
  • results.json — mediciones.
  • equality_case.png — visualización de cuándo ||Ax|| / ||A||·||x|| se aproxima a 1.
  • manifest.json.
  • README.md — 1-2 párrafos.

E implementaciones en src/minigrad/linalg.py: frobenius_norm, l1_norm, l2_norm, linf_norm, op_norm.

Parte A — Implementa las normas en src/minigrad/linalg.py

  • l1_norm(x), l2_norm(x), linf_norm(x) para vectores 1-D. NumPy puro.
  • frobenius_norm(A) para matrices.
  • op_norm(A) que devuelve la norma operador L2 = σ_max(A). Usa np.linalg.svd(A, full_matrices=False, compute_uv=False).

Restricciones para las implementaciones

  • Estabilidad numérica. Para entradas muy grandes o muy pequeñas, el ingenuo sqrt(sum(x**2)) puede desbordar / subdesbordar. Usar np.linalg.norm por debajo está bien — pero para una de ellas, implementa a mano la forma estable max(|x|) * sqrt(sum((x / max(|x|))**2)) y comprueba que sobrevive a entradas con magnitud 1e30.
  • Sin scipy.linalg.norm. NumPy puro.
  • Anotaciones de tipo + docstrings. Cada docstring expone la fórmula de la norma y enlaza a theory/04.

Tests en tests/test_linalg.py

Añade:

  • test_norms_match_numpy — verifica contra np.linalg.norm para entradas aleatorias.
  • test_frobenius_equals_l2_of_singular_values — para A aleatoria, verifica que frobenius_norm(A)² ≈ sum(σ²).
  • test_op_norm_is_sigma_max — para A aleatoria, verifica que op_norm(A) == max(np.linalg.svd(A, compute_uv=False)).
  • test_submultiplicative_inequality — para A, x aleatorios, verifica que l2_norm(A @ x) ≤ op_norm(A) * l2_norm(x) con atol=1e-5.
  • test_stable_l2_handles_huge_valuesx = [1e30, 1e30], se espera sqrt(2) * 1e30, sin desbordamiento.

Parte B — El experimento

En norms.py:

  • Genera A = np.random.randn(M, N) con M=64, N=64. Calcula su SVD: U, S, V = np.linalg.svd(A, full_matrices=False).
  • Muestrea 1000 vectores unitarios aleatorios x_i ∈ R^N (gaussianos y luego normalizados). Calcula r_i = ||A x_i||_2 / (||A||_2 · ||x_i||_2).
  • Dibuja un histograma de r_i. Todos los valores deben ser ≤ 1.
  • Caso de igualdad: define x_top = V.T[:, 0] (vector singular derecho superior). Calcula r_top = ||A x_top||_2 / (||A||_2 · ||x_top||_2). Debería ser ≈ 1 (dentro del ulp de fp32).
  • Dibuja el histograma con r_top marcado. Anótalo.

Bloque C — qué registrar

En results.json:

{
  "matrix_shape": [64, 64],
  "matrix_seed": 42,
  "n_random_samples": 1000,
  "sigma_max": ...,
  "ratios": {
    "min": ...,
    "max": ...,
    "mean": ...,
    "ratio_at_top_singular_vector": ...
  }
}

Bloque D — interpretar

En README.md, responde:

  1. ¿Cuál es el máximo de las razones? Debería ser muy cercano a 1 (el caso del vector singular superior).
  2. ¿Cuál es la media? Para un vector unitario aleatorio, E[r] ≈ ||A||_F / (sqrt(N) · ||A||_2). Calcula esto y compara.
  3. Verifica la ortogonalidad de la SVD: comprueba que U.T @ U ≈ I y V @ V.T ≈ I con tolerancia fp32.
  4. ¿Qué pasa si reemplazas ||·||_2 por ||·||_F? ¿Se cumple ||Ax||_F ≤ ||A||_F · ||x||_F? (Pista: sí, pero es una cota más débil.)

Bloque E — manifest

Manifest estándar. Documenta la semilla de la matriz para reproducibilidad.

Restricciones

  • fp32 en todo. Coincide con el resto del currículo.
  • Sin torch.linalg.norm. NumPy puro.
  • Hilo único. Añade OPENBLAS_NUM_THREADS=1 al manifest.

Escollos

  • r_top ligeramente > 1. Posible debido a que np.linalg.svd en fp32 devuelve un S[0] ligeramente demasiado pequeño. Si supera 1 por más de 1e-5, tu op_norm está mal. Si es por menos, es ruido de fp32.
  • Vectores unitarios aleatorios: np.random.randn(N) / np.linalg.norm(...) es la forma estándar. No uses un vector uniforme en [0, 1] — está sesgado hacia la esquina todo-positiva.
  • Histograma con muy pocas barras. Usa ~30 bins para 1000 muestras.

Condiciones de parada

  • Todas las normas implementadas; todos los tests pasan.
  • El histograma muestra razones en [0, 1] con el caso de igualdad en ≈ 1.
  • El README responde las cuatro preguntas de interpretación.

Cuándo consultar solutions/

Tras hacer commit del histograma + tests. solutions/03-norms-by-experiment-ref.md (en la apertura de la fase) discute la concentración de la medida (por qué la mayoría de las razones aleatorias se agrupan muy por debajo de 1 en alta dimensión) y enlaza hacia adelante con la intuición de optimización en pérdidas anisotrópicas de la Fase 4.


Fin del laboratorio de la Fase 3. Próxima fase: docs/phase-04-calculus-optimization/.