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

Escalera de forma de onda a STFT a espectrograma mel a MFCC

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

  1. 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.
  2. Medio. Vuelve a ejecutar con n_mels en {40, 80, 128} y frame_len en {200, 400, 800}. Mide el ancho de banda del pico agudo a lo largo del eje temporal. ¿Qué combinación resuelve mejor el chirp?
  3. Difícil. Implementa power_to_db y compara la precisión de ASR de un pequeño clasificador CNN sobre AudioMNIST usando (a) log-mel crudo, (b) dB-mel con ref=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

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).