Skip to content

English · Español

Lab 02 — Compresión SVD de la matriz de conteos de conjugaciones §A13

Objetivo: comprimir una matriz de conteos de conjugaciones de verbos con SVD de rango-k; ver Eckart-Young en acción sobre la propia estructura de datos que motiva los word embeddings.

Tiempo estimado: 60–90 minutos.

Prerrequisito: theory/03-svd-and-rank.md y theory/04-norms-and-conditioning.md leídas.


Lo que produces

Un directorio experiments/03-svd-compression/ que contiene:

  • build_matrix.py — sintetiza la matriz de conteos de conjugaciones C a partir de los verbos §A13 (Borja escribe).
  • compress.py — ejecuta SVD y reconstrucciones de rango-k.
  • spectrum.png — valores singulares vs índice, log-y.
  • reconstructions.png — heatmaps de C, C_1, C_3, C_5, C_10, C_15 lado a lado.
  • error_curve.png||C - C_k||_F / ||C||_F vs k.
  • results.json — error, energía capturada, mediciones de almacenamiento por k.
  • manifest.json.
  • README.md — interpretación, 2-3 párrafos.

La matriz §A13

Construye C de shape (20, 15):

  • Filas: los 20 verbos del §A13 — 12 regulares (work, play, walk, talk, listen, watch, study, finish, start, look, want, like) + 8 irregulares (be, have, do, go, come, see, eat, write).
  • Columnas: las 15 celdas de conjugación = 5 tiempos × 3 personas. Orden: (infinitive, I) (infinitive, you) (infinitive, he) (present, I) (present, you) (present, he) (past, I) (past, you) (past, he) (pastPart, I) (pastPart, you) (pastPart, he) (future, I) (future, you) (future, he).
  • Entrada C[i, j]: el conteo de cuántas veces el verbo i aparece en la conjugación j en un corpus sintético de 10,000 frases.

Sintetizar conteos

Aún no tienes un corpus real (eso es la Fase 13). Para la Fase 3, falséalo con un generador estructurado para que el espectro sea interesante:

  • Muestrea una "frecuencia" por verbo f_v ~ Zipf(s=1.2) sobre 20 verbos, normalizada para que el verbo más común aparezca ≈10× más que el más raro.
  • Muestrea una "frecuencia" por tiempo t_τ de una distribución discreta aproximadamente [0.05, 0.45, 0.20, 0.05, 0.25] sobre [infinitive, present, past, pastPart, future].
  • Muestrea una "frecuencia" por persona p_π aproximadamente [0.35, 0.25, 0.40] sobre [I, you, he/she/it].
  • Por celda, el conteo esperado es 10_000 · f_v · t_τ · p_π. Añade ruido Poisson.
  • Para verbos irregulares, perturba ligeramente la fila para romper la estructura estricta de producto externo rango-1 — si no, C tendría rango exactamente 1 y el lab es trivial.

Documenta el generador en build_matrix.py y siémbralo (np.random.default_rng(42)).

TODOs

Bloque A — construir la matriz

  • build_matrix.py escribe C.npy (shape (20, 15), dtype fp32) y verb_order.json + cell_order.json (arrays de etiquetas).
  • Imprime C por stdout al ejecutar — observa con vista que parece sensato (los verbos comunes tienen filas mayores; las columnas de futuro y participio pasado son más dispersas).
  • Reproducibilidad: RNG sembrado, una sola entrada manifest.json por ejecución.

Bloque B — SVD + reconstrucciones

  • compress.py carga C.npy, calcula U, S, Vt = np.linalg.svd(C, full_matrices=False).
  • S.shape == (15,), U.shape == (20, 15), Vt.shape == (15, 15).
  • Para cada k ∈ {1, 2, 3, 5, 8, 10, 12, 15}:
  • Reconstruye C_k = U[:, :k] @ diag(S[:k]) @ Vt[:k, :] (o usa la forma broadcast U[:, :k] * S[:k] @ Vt[:k, :]).
  • Mide frobenius_error = np.linalg.norm(C - C_k, 'fro').
  • Mide relative_error = frobenius_error / np.linalg.norm(C, 'fro').
  • Mide captured_energy = sum(S[:k]**2) / sum(S**2).
  • Mide op_norm_error = S[k] if k < 15 else 0.0 (Eckart-Young en norma de operador).
  • Verifica frobenius_error == sqrt(sum(S[k:]**2)) con tolerancia fp32 (la identidad de Eckart-Young).

Bloque C — visualizar

  • spectrum.png: valores singulares σ_k vs k en escala log-y. Espera una caída brusca tras los primeros 3-5 valores.
  • reconstructions.png: grid de heatmaps de C, C_1, C_3, C_5, C_10, C_15. Usa la misma escala de color entre paneles. Etiqueta cada uno con k y el error relativo.
  • error_curve.png: relative_error vs k (log-y). Anota el "codo" — el menor k donde el error relativo cae por debajo del 5%.

Bloque D — análisis de almacenamiento

En README.md, calcula:

  • Almacenamiento original: 20 × 15 = 300 números fp32 = 1200 bytes.
  • Almacenamiento SVD rango-k: k × (20 + 1 + 15) = 36k números fp32 = 144k bytes.
  • Cruce: en k = 8, el almacenamiento son 288 números — menos que el original. En k ≥ 9, la forma SVD ya no es una ganancia (para esta matriz minúscula). Indica el cruce.

Para matrices más grandes la ganancia es enorme — vista previa de LoRA en la Fase 28: un adaptador (D, D) con D = 4096, r = 8 da 260× de compresión. Menciónalo en el README.

Bloque E — preguntas de interpretación

En README.md, responde:

  1. ¿Dónde está el codo? ¿Cuál es el menor k tal que ||C - C_k||_F / ||C||_F < 5%?
  2. ¿Qué captura el patrón dominante? Grafica U[:, 0] (el primer vector singular izquierdo, indexado por verbo) y Vt[0, :] (el primer vector singular derecho, indexado por celda de conjugación). ¿A qué ejes §A13 corresponden? (Pista: el producto externo de σ_1 σ_1 · u_1 ⊗ v_1 es la mejor aproximación rango-1; para lenguaje natural suele capturar el eje de "frecuencia general" — verbos comunes × formas comunes.)
  3. ¿Qué capturan los patrones secundarios? Repítelo para U[:, 1], Vt[1, :] y U[:, 2], Vt[2, :]. Busca: split regular-vs-irregular, splits persona-vs-tiempo, etc.
  4. Rango efectivo. Con un umbral de ruido de 0.01 × σ_1, ¿cuántos valores singulares están por encima del umbral? Este es el rango efectivo de C. Compáralo con el rango completo de la matriz (≤ 15).
  5. Adelanto de la Fase 13 (embeddings). Si tratas la factorización rango-k C ≈ (U[:, :k] · sqrt(S[:k])) · (sqrt(S[:k]) · Vt[:k, :]) como una representación aprendida, el factor izquierdo es un embedding de verbo en R^k y el factor derecho es un embedding de conjugación en R^k. ¿Qué k escogerías? ¿Por qué importa esto cuando construyamos la matriz de embedding real en la Fase 13?

Bloque F — manifest

Manifest estándar. Incluye el seed del generador de matrices + el conteo de (verbos, celdas) en config.

Restricciones

  • np.linalg.svd únicamente. No implementes SVD a mano — eso es una optativa de álgebra lineal numérica. Siempre full_matrices=False.
  • fp32 en todo. Castea C a fp32 antes de la SVD. (La matriz tiene conteos enteros pequeños; el dtype importa más para los productos internos de vectores singulares que para los propios valores.)
  • Sin sklearn.decomposition.TruncatedSVD. Usa la SVD densa completa; la matriz es minúscula.
  • Reproducibilidad. Un solo seed de RNG; manifest.json lo registra.

Escollos

  • Olvidar full_matrices=False. Sin eso, U.shape == (20, 20) y Vt.shape == (15, 15); el lab sigue funcionando pero desperdicia 5 columnas de U.
  • Off-by-one en la comprobación de Eckart-Young. ||C - C_k||_F² = Σ_{i ≥ k} σ_i² — la suma empieza en el índice k (con índice base cero, S[k] es el primer σ no conservado).
  • Ambigüedad de signo de vectores singulares. Al graficar U[:, 0], los signos son arbitrarios; para interpretación, invierte para que la entrada de mayor valor absoluto sea positiva. Documenta la convención.
  • Tratar el almacenamiento como solo k · (M + N) sin el +1 para S[:k]. Pequeño en sentido absoluto; importa en el cálculo del cruce.
  • Generar C con estructura demasiado limpia (por ejemplo, producto externo rango-1 puro). El lab necesita algo de estructura más allá del rango-1 para que puedas ver valores singulares secundarios — para eso están el ruido Poisson + la perturbación de verbos irregulares.

Recapitulación del anclaje §A13

La matriz de conteos de conjugaciones C es el ejemplo más pequeño y limpio de una matriz de co-ocurrencia. La Fase 13 (word embeddings) explicará cómo Word2Vec / GloVe extraen representaciones vectoriales densas de la estadística de co-ocurrencia — lo que hacen es aproximadamente este lab, sobre un vocabulario mucho mayor con un kernel de ventana de contexto. La SVD sobre C es el algoritmo original de embedding (Análisis Semántico Latente, años 90). LoRA de la Fase 28 es la misma idea aplicada a actualizaciones de pesos en lugar de co-ocurrencias.

Condiciones de parada

  • Tres PNGs commiteados (spectrum.png, reconstructions.png, error_curve.png).
  • results.json tiene filas para al menos k ∈ {1, 2, 3, 5, 8, 10, 12, 15}.
  • Eckart-Young verificado numéricamente: ||C - C_k||_F == sqrt(sum(S[k:]**2)) con tolerancia fp32 para al menos un k de rango medio.
  • README responde a las cinco preguntas de interpretación.
  • Los tres primeros vectores singulares están interpretados en términos §A13.

Cuándo consultar solutions/

Tras commitear los tres PNGs + interpretación. solutions/02-svd-compression-ref.md (en la apertura de fase) discute cómo el espectro §A13 se compara con un espectro real de co-ocurrencia en corpus español y adelanta la conexión con la Fase 13.


Siguiente lab: lab/03-norms-by-experiment.md.