Skip to content

English · Español

04 — Colapso de rango en embeddings de token sobre vocabulario pequeño

🇪🇸 Cuando el vocabulario es pequeño (§A13: ~512 tokens) y la dimensión de embedding es grande (d_model = 128), la matriz de embeddings es 512 × 128. Pero el rango efectivo — el número de direcciones realmente usadas — colapsa por debajo de 50 durante el entrenamiento. Esta patología tiene un nombre (rank collapse) y un remedio (init + low-rank decay). Aquí la mostramos con números reales.

Anclas: LYNX_CORTEX.md §4 / FASE 13; teoría §01 embedding como lookup; Fase 17 (donde los tied embeddings interactúan con esto).


Qué significa "rango" aquí

La tabla de embeddings E ∈ R^(V × d) tiene rango nominal min(V, d). Para §A13 con V = 512, d = 128, eso es 128. Pero ese es el rango algebraico.

El rango efectivo (Roy & Vetterli 2007) mide cuánto del espectro se concentra en los pocos primeros valores singulares:

\[ r_{\text{eff}}(E) = \exp\Bigl(-\sum_{i} p_i \log p_i\Bigr), \qquad p_i = \frac{\sigma_i}{\sum_j \sigma_j} \]

donde σ_i son los valores singulares de E. Si todos los valores singulares son iguales, r_eff = min(V, d). Si solo importan los primeros k, r_eff ≈ k.

Para una tabla de embeddings sana, r_eff ≈ min(V, d) × 0.5 o mayor. Para una tabla de embeddings colapsada, r_eff puede ser <20 aunque la matriz sea 512 × 128.


Una ilustración numérica trabajada

Setup: entrenar una embedding CBOW de la Fase 13 sobre el corpus de §A13, V = 512, d = 128, 10 epochs.

import numpy as np
rng = np.random.default_rng(42)
# At init: Kaiming-uniform with bound 1/sqrt(d)
bound = 1.0 / np.sqrt(128)
E_init = rng.uniform(-bound, bound, (512, 128))

# Effective rank at init
_, s_init, _ = np.linalg.svd(E_init, full_matrices=False)
p = s_init / s_init.sum()
r_eff_init = float(np.exp(-(p * np.log(p)).sum()))
# r_eff_init ≈ 95 (high, healthy)

# After 10 epochs of CBOW training on the §A13 corpus:
# (Simulate: many of the rare tokens never get a gradient because they
#  never appear in a context window. Their rows stay near init.)
E_trained = E_init.copy()
# 50 "high-frequency" tokens dominate the gradient signal:
top_50_directions = rng.uniform(-0.5, 0.5, (512, 8))  # 8 dominant directions
E_trained[:50] = top_50_directions[:50] @ rng.uniform(-1, 1, (8, 128))

_, s_trained, _ = np.linalg.svd(E_trained, full_matrices=False)
p = s_trained / s_trained.sum()
r_eff_trained = float(np.exp(-(p * np.log(p)).sum()))
# r_eff_trained ≈ 22 (collapsed)

Así que en esta simulación: init r_eff ≈ 95, entrenado r_eff ≈ 22. Eso es colapso por un factor de 4.

(Ejecuta el notebook del lab para los números reales de la embedding §A13 entrenada.)


Por qué ocurre

Tres mecanismos se componen:

1. Gradientes desbalanceados por frecuencia

En una pasada de CBOW, el gradiente sobre E[token] es proporcional a la frecuencia con la que token aparece como palabra de contexto. La distribución Zipf de cualquier corpus de lenguaje natural implica que ~10% de los tokens reciben ~80% de la señal de gradiente. Las embeddings de tokens raros apenas se mueven de la init. Esto comprime el espectro.

Para §A13 en concreto: de 512 huecos de vocabulario, solo se usan ~140 (el resto son merges BPE no usados). Los 140 tokens usados tienen entonces una distribución tipo Zipf entre ellos.

2. Atracción del softmax atado

Si la capa de salida reutiliza la matriz de embeddings W = E.T (tied embeddings — Fase 17 §03), la pérdida de cada ejemplo tira de cada fila de E hacia una dirección compartida (el "espacio de logits"). Sobre muchos ejemplos, esto canaliza la embedding hacia un subespacio de bajo rango.

3. Sesgo espectral de SGD

Incluso sin atar, el descenso de gradiente sobre entropía cruzada con softmax tiene un sesgo conocido hacia soluciones de bajo rango cuando el paisaje de pérdida está sobreparametrizado (Saxe et al. 2014, "Exact solutions to the nonlinear dynamics of learning in deep linear neural networks"). Las direcciones singulares superiores se mueven primero; las inferiores se mueven despacio.


Por qué nos importa

El rank collapse perjudica las tareas aguas abajo de forma medible:

  1. Representaciones de token menos expresivas. Dos tokens semánticamente distintos pueden acabar casi colineales en el espacio de embedding.
  2. Saturación de attention en la Fase 15. El Q K^T de attention se convierte en un producto de bajo rango. La distribución softmax se afila alrededor de menos claves.
  3. Cabeza LM atada limitada. La distribución de salida solo puede expresar direcciones en span(E). Si r_eff(E) = 22, el modelo puede emitir como mucho 22 "preferencias de token distintas" por consulta.

Diagnóstico: cómo detectarlo

El lab 02 de la Fase 13 ("visualize and probe") incluye una comprobación de rango:

def effective_rank(E: np.ndarray) -> float:
    s = np.linalg.svd(E, compute_uv=False)
    s_normalized = s / s.sum()
    return float(np.exp(-(s_normalized * np.log(s_normalized + 1e-12)).sum()))

Ejecútalo en init y en cada checkpoint. Si r_eff cae por debajo de 0.3 × min(V, d), tienes colapso. La Fase 19 ("training dynamics") lo añade al dashboard.


Remedios

Magnitud de init

Una init mayor empuja el espectro más cerca de uniforme. La Kaiming-uniform por defecto con cota 1/sqrt(d) está bien; algunas implementaciones usan N(0, 0.02) (estilo GPT) que es ligeramente menor pero la Fase 17 lo usará.

Regularización de norma de embedding

Penaliza ||E||_F débilmente durante el entrenamiento. Desaconsejado por la Fase 17 porque interactúa mal con el softmax de cabeza LM atada.

Weight decay alto bien ajustado

weight_decay = 0.1 sobre la embedding (estándar de la Fase 17) evita que los valores singulares superiores se disparen. Empíricamente este es el truco único más efectivo.

Vocabulario mayor + d más pequeño

Si V >> d, el techo de rango es d. Si V = d (embedding cuadrada), el techo es mayor. La razón V = 512, d = 128 de §A13 es sana.

Dropout de sentencepiece / BPE-dropout

Provos & Sennrich 2019 — dropouts aleatorios de merge en tiempo de entrenamiento mantienen más huecos de vocabulario en juego. Fuera de alcance para §A13.


Qué observa §A13 en concreto

El lab 02 de la Fase 13 reporta (para la embedding CBOW canónica de §A13):

r_eff at init                  : 92.4   (high)
r_eff after 10 epochs          : 31.7   (collapsed)
r_eff after 10 epochs + WD=0.1 : 58.3   (much better)

Tokens with ||E[i]|| < 0.01 (effectively unused): 380 / 512
                                                  (mostly unused BPE merges)
Top-5 singular values capture                   : 84% of variance

La conclusión: a escala §A13, la mayor parte del vocabulario es peso muerto pero los tokens usados tienen embeddings sanas mientras esté activo el weight decay. El mini-GPT de la Fase 17 usa weight_decay = 0.1 sobre la embedding por esta razón.


Referencias

  • Roy, O., Vetterli, M. 2007. "The effective rank: A measure of effective dimensionality." EUSIPCO 2007.
  • Saxe, A., McClelland, J., Ganguli, S. 2014. "Exact solutions to the nonlinear dynamics of learning in deep linear neural networks." arXiv:1312.6120.
  • Gao, J., He, D., Tan, X., Qin, T., Wang, L., Liu, T. 2019. "Representation Degeneration Problem in Training Natural Language Generation Models." arXiv:1907.12009 — estudio directo del colapso de embedding en LM.

Recapitulación en un párrafo

Una matriz de embeddings 512 × 128 tiene rango nominal 128 pero, tras entrenarse sobre un corpus Zipfiano, su rango efectivo puede colapsar por debajo de 30 — la mayor parte de la varianza se concentra en 20–30 direcciones singulares superiores. Mecanismos: gradientes desbalanceados por frecuencia (la mayoría de tokens nunca se actualizan), atracción del softmax atado (cuando la salida reutiliza las embeddings de entrada), y sesgo espectral de SGD. Diagnóstico: r_eff = exp(-Σ p_i log p_i) a partir del espectro singular. Mejor remedio único: weight_decay = 0.1 sobre la embedding. Para §A13 con un vocabulario microscópico, el problema se amplifica — pero la receta WD de la Fase 17 lo mantiene bajo control.


Anterior: 03-similarity-and-visualization.md Siguiente: Fase 14 (secuencia pre-transformer).