Phase 06 - Lesson 02
Espectrogramas, escala mel y features de audio
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
Las redes neuronales no consumen bien las formas de onda crudas. Consumen espectrogramas. Consumen espectrogramas mel aún mejor. Todo ASR, TTS y clasificador de audio en 2026 vive o muere por esta única decisión de preprocesamiento.
Type: Build Languages: Python Prerequisites: Phase 6 · 01 (Audio Fundamentals) Time: ~45 minutes
El problema
Toma un clip de 10 segundos a 16 kHz. Son 160.000 floats, todos en [-1, 1], casi perfectamente descorrelacionados de la etiqueta "perro ladrando" o "la palabra gato". La forma de onda cruda tiene la información, pero en un formato que el modelo no puede extraer con facilidad. Dos fonemas idénticos pronunciados con 100 ms de diferencia tienen muestras crudas completamente distintas.
Un espectrograma resuelve esto. Descarta el detalle temporal donde la percepción humana lo ignora (jitter de microsegundos) y preserva la estructura donde la percepción presta atención (qué frecuencias son energéticas, a lo largo de ventanas de tiempo de ~10–25 ms).
Los espectrogramas mel van más allá. Los humanos perciben el tono (pitch) de forma logarítmica: 100 Hz vs 200 Hz suena "a la misma distancia" que 1000 Hz vs 2000 Hz. La escala mel deforma el eje de frecuencia para que coincida. Un espectrograma en escala mel es la feature más importante en ML de habla desde 2010 hasta 2026.
El concepto
STFT (Short-Time Fourier Transform). Divide la forma de onda en marcos (frames) superpuestos (típico: ventana de 25 ms, salto de 10 ms = 400 muestras / 160 muestras a 16 kHz). Multiplica cada marco por una función de ventana (Hann es la predeterminada; Hamming tiene un tradeoff ligeramente distinto). Aplica FFT a cada marco. Apila los espectros de magnitud en una matriz de forma (n_frames, n_freq_bins). Ese es tu espectrograma.
Log-magnitud. Las magnitudes crudas abarcan 5-6 órdenes de magnitud. Aplica log(|X| + 1e-6) o 20 * log10(|X|) para comprimir el rango dinámico. Todo pipeline de producción usa log-magnitud, no magnitud cruda.
Escala mel. La frecuencia f en Hz mapea a mel m mediante m = 2595 * log10(1 + f / 700). El mapeo es aproximadamente lineal por debajo de 1 kHz y aproximadamente logarítmico por encima. 80 bins mel cubriendo 0–8 kHz es la entrada estándar de ASR.
Banco de filtros mel (mel filterbank). Un conjunto de filtros triangulares espaciados de forma equidistante en la escala mel. Cada filtro es una suma ponderada de bins adyacentes de la FFT. Multiplicar la magnitud de la STFT por la matriz del banco de filtros da el espectrograma mel en una sola multiplicación de matrices (matmul).
Espectrograma log-mel. log(mel_spec + 1e-10). La entrada de Whisper. La entrada de Parakeet. La entrada de SeamlessM4T. El frontend de audio universal de 2026.
MFCCs. Toma el espectrograma log-mel, aplica una DCT (tipo II), conserva los primeros 13 coeficientes. Descorrelaciona las features y comprime aún más. Feature dominante hasta alrededor de 2015, cuando las CNN/Transformers sobre log-mels crudos la alcanzaron. Todavía se usa en reconocimiento de hablante (x-vectors, ECAPA).
Tradeoff de resolución. Una FFT más grande = mejor resolución de frecuencia, pero peor resolución temporal. 25 ms / 10 ms es el predeterminado de ML de audio; 50 ms / 12,5 ms para música; 5 ms / 2 ms para detección de transitorios (golpes de batería, plosivas).
Constrúyelo
Paso 1: dividir la forma de onda en marcos
def frame(signal, frame_len, hop):
n = 1 + (len(signal) - frame_len) // hop
return [signal[i * hop : i * hop + frame_len] for i in range(n)]
Un clip de 10 segundos a 16 kHz con frame_len=400, hop=160 produce 998 marcos.
Paso 2: ventana de Hann
import math
def hann(N):
return [0.5 * (1 - math.cos(2 * math.pi * n / (N - 1))) for n in range(N)]
Multiplica elemento a elemento antes de la FFT. Elimina la fuga espectral (spectral leakage) causada por truncar en extremos distintos de cero.
Paso 3: magnitud de la STFT
def stft_magnitude(signal, frame_len=400, hop=160):
win = hann(frame_len)
frames = frame(signal, frame_len, hop)
return [magnitudes(dft([w * s for w, s in zip(win, f)])) for f in frames]
La producción usa torch.stft o librosa.stft (respaldados por FFT, vectorizados). El bucle aquí es pedagógico; se ejecuta sobre clips cortos en code/main.py.
Paso 4: banco de filtros mel
def hz_to_mel(f):
return 2595.0 * math.log10(1.0 + f / 700.0)
def mel_to_hz(m):
return 700.0 * (10 ** (m / 2595.0) - 1)
def mel_filterbank(n_mels, n_fft, sr, fmin=0, fmax=None):
fmax = fmax or sr / 2
mels = [hz_to_mel(fmin) + (hz_to_mel(fmax) - hz_to_mel(fmin)) * i / (n_mels + 1)
for i in range(n_mels + 2)]
hzs = [mel_to_hz(m) for m in mels]
bins = [int(h * n_fft / sr) for h in hzs]
fb = [[0.0] * (n_fft // 2 + 1) for _ in range(n_mels)]
for m in range(n_mels):
for k in range(bins[m], bins[m + 1]):
fb[m][k] = (k - bins[m]) / max(1, bins[m + 1] - bins[m])
for k in range(bins[m + 1], bins[m + 2]):
fb[m][k] = (bins[m + 2] - k) / max(1, bins[m + 2] - bins[m + 1])
return fb
80 mels cubriendo 0–8 kHz con n_fft=400 da una matriz (80, 201). Multiplica la magnitud de la STFT (n_frames, 201) por la transpuesta para obtener el espectrograma mel (n_frames, 80).
Paso 5: log-mel
def log_mel(mel_spec, eps=1e-10):
return [[math.log(max(v, eps)) for v in frame] for frame in mel_spec]
Alternativas comunes: librosa.power_to_db (dB normalizado por referencia), 10 * log10(power + eps). Whisper usa una rutina de clip + normalización más elaborada (consulta el log_mel_spectrogram de Whisper).
Paso 6: MFCCs
def dct_ii(x, n_coeffs):
N = len(x)
return [
sum(x[n] * math.cos(math.pi * k * (2 * n + 1) / (2 * N)) for n in range(N))
for k in range(n_coeffs)
]
Aplica la DCT a cada marco log-mel, conserva los primeros 13 coeficientes. Esa es tu matriz MFCC. El primer coeficiente suele descartarse (codifica la energía global).
Úsalo
El stack de 2026:
| Tarea | Features |
|---|---|
| ASR (Whisper, Parakeet, SeamlessM4T) | 80 log-mels, salto de 10 ms, ventana de 25 ms |
| Modelo acústico de TTS (VITS, F5-TTS, Kokoro) | 80 mels, salto de 5–12 ms para control temporal fino |
| Clasificación de audio (AST, PANNs, BEATs) | 128 log-mels, salto de 10 ms |
| Embedding de hablante (ECAPA-TDNN, WavLM) | 80 log-mels o SSL sobre forma de onda cruda |
| Música (MusicGen, Stable Audio 2) | Tokens discretos de EnCodec (no mels) |
| Detección de palabras clave (keyword spotting) | 40 MFCCs para dispositivos diminutos |
Regla general: si no estás trabajando con música, empieza con 80 log-mels. La carga de la prueba recae sobre cualquier desviación.
Trampas que todavía llegan a producción en 2026
- Discrepancia en la cantidad de mels. Entrenar con 80 mels, inferir con 128 mels. Fallo silencioso. Registra la forma (shape) de la feature en ambos extremos.
- Discrepancia de sample rate upstream. Los mels calculados a 22,05 kHz lucen distintos de los de 16 kHz. Corrige el SR antes de la extracción de features.
- dB vs log. Whisper espera log-mel, no dB-mel. Algunos pipelines de HF autodetectan; tu código personalizado no lo hará.
- Deriva de normalización. Normalización por enunciado durante el entrenamiento, normalización global durante la inferencia. Bug de producción que duplica el WER.
- Fuga por padding. Rellenar con ceros el final de un clip produce un espectro plano en los marcos finales. Aplica padding simétrico o replica.
Entrégalo (Ship It)
Guárdalo como outputs/skill-feature-extractor.md. La skill elige el tipo de feature, la cantidad de mels, el frame/hop y la normalización para un modelo objetivo dado.
Ejercicios
- Fácil. Ejecuta
code/main.py. Sintetiza un chirp (frecuencia barrida de 200 → 4000 Hz) e imprime el argmax del bin mel por marco. Grafica (opcional) y confirma que coincide con el barrido. - Medio. Vuelve a ejecutar con
n_melsen{40, 80, 128}yframe_lenen{200, 400, 800}. Mide el ancho de banda del pico agudo a lo largo del eje temporal. ¿Qué combinación resuelve mejor el chirp? - Difícil. Implementa
power_to_dby compara la precisión de ASR de un pequeño clasificador CNN sobre AudioMNIST usando (a) log-mel crudo, (b) dB-mel conref=max, (c) MFCC-13 + delta + delta-delta. Reporta la precisión top-1.
Términos clave
| Término | Lo que dice la gente | Lo que realmente significa |
|---|---|---|
| Frame | Una rebanada | Trozo de 25 ms de la forma de onda que se alimenta a una FFT. |
| Hop | Stride | Muestras entre marcos consecutivos; 10 ms es el predeterminado de ASR. |
| Window | Cosa Hann/Hamming | Multiplicador punto a punto que atenúa los bordes del marco hasta cero. |
| STFT | Generador de espectrograma | FFT enmarcada + con ventana; produce una matriz tiempo × frecuencia. |
| Mel | Frecuencia deformada | Escala de log-percepción; m = 2595·log10(1 + f/700). |
| Filterbank | La matriz | Filtros triangulares que proyectan la STFT sobre bins mel. |
| Log-mel | La entrada de Whisper | log(mel_spec + eps); estandarizada en 2026. |
| MFCC | Feature de la vieja escuela | DCT del log-mel; 13 coeficientes, descorrelacionados. |
Lectura adicional
- Davis, Mermelstein (1980). Comparison of parametric representations for monosyllabic word recognition — el artículo del MFCC.
- Stevens, Volkmann, Newman (1937). A Scale for the Measurement of the Psychological Magnitude Pitch — la escala mel original.
- OpenAI — Whisper source, log_mel_spectrogram — lee la implementación de referencia.
- librosa feature extraction docs — referencia para
mfcc,melspectrogramy hop/window. - NVIDIA NeMo — audio preprocessing — pipeline a escala de producción para los modelos Parakeet + Canary.