Visualización y transformación de datos
- 15 minutos
Una vez que comprenda la forma de onda sin procesar, el siguiente paso es transformar el audio en una representación que sea útil para la clasificación.
Una forma de onda muestra amplitud a lo largo del tiempo. Un espectrograma muestra el contenido de frecuencia a lo largo del tiempo. Para crear un espectrograma, se calcula la transformada de Fourier de tiempo corto (STFT) en pequeñas ventanas superpuestas de la forma de onda. TensorFlow proporciona tf.signal.stft para esta operación.
En versiones anteriores de este flujo de trabajo, los espectrogramas se guardaron como archivos PNG y, a continuación, se cargaron como imágenes. El enfoque actualizado mantiene los espectrogramas como tensores. Esto evita E/S de archivos adicionales, evita artefactos de redimensionamiento de imágenes y se ajusta al patrón actual del tutorial de audio de TensorFlow.
Preparación del conjunto de datos de audio binario
Comience con la misma descarga de mini comandos de voz de la unidad anterior. El código siguiente descarga el conjunto de datos si aún no está presente, valida el hash del archivo, busca la carpeta extraída para los diseños Keras 2 y Keras 3 y copia solo las carpetas no y yes en un directorio de conjunto de datos binario más pequeño.
import pathlib
import shutil
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
SOURCE_DATASET_PATH = pathlib.Path("data/mini_speech_commands")
ALT_SOURCE_DATASET_PATH = pathlib.Path("data/mini_speech_commands_extracted/mini_speech_commands")
BINARY_DATASET_PATH = pathlib.Path("data/speech_commands_yes_no")
MINI_SPEECH_COMMANDS_SHA256 = "49650f2341b26d886b46b3f4fb8fed59e30300b17550f1ee4a768b3106cf93a0"
if not SOURCE_DATASET_PATH.exists() and not ALT_SOURCE_DATASET_PATH.exists():
tf.keras.utils.get_file(
"mini_speech_commands.zip",
origin="https://storage.googleapis.com/download.tensorflow.org/data/mini_speech_commands.zip",
file_hash=MINI_SPEECH_COMMANDS_SHA256,
hash_algorithm="sha256",
extract=True,
cache_dir=".",
cache_subdir="data",
)
if ALT_SOURCE_DATASET_PATH.exists():
SOURCE_DATASET_PATH = ALT_SOURCE_DATASET_PATH
for label in ("no", "yes"):
target_dir = BINARY_DATASET_PATH / label
target_dir.mkdir(parents=True, exist_ok=True)
for source_file in (SOURCE_DATASET_PATH / label).glob("*.wav"):
target_file = target_dir / source_file.name
if not target_file.exists():
shutil.copy2(source_file, target_file)
Salida esperada: Este código de instalación no imprime la salida. Crea un data/speech_commands_yes_no directorio con un subdirectorio para no y otro para yes. TensorFlow 2.16 y versiones posteriores usan Keras 3, que extrae el archivo en un <archive>_extracted subdirectorio; el asistente controla ambos diseños para que el resto de la unidad funcione en cualquier versión compatible de TensorFlow.
Carga de archivos de audio como conjuntos de datos de TensorFlow
Use tf.keras.utils.audio_dataset_from_directory para crear conjuntos de datos de TensorFlow a partir de la estructura de directorios. Los archivos de comandos de voz en miniatura están muestreados a 16 kHz, por lo que output_sequence_length=16000 selecciona una ventana fija de 16,000 muestras por clip, que es exactamente un segundo de audio a esa velocidad de muestreo. Los archivos que son más cortos que 16 000 muestras se rellenan con ceros, y los archivos que superan las 16 000 muestras se truncan. El output_sequence_length argumento no remuestrea el audio grabado a una frecuencia de muestreo diferente; el remuestreo significa cambiar el número de muestras que representan cada segundo de sonido. Si adapta este flujo de trabajo a archivos grabados a otra velocidad de muestreo, re-muestree a 16 kHz antes de cargarlos, o utilice la opción sampling_rate del cargador con tensorflow-io y compruebe que las formas de los tensores resultantes siguen coincidiendo con la entrada del modelo.
El código establece label_mode="int" explícitamente porque el modelo utiliza etiquetas de clase enteras con entropía cruzada categórica dispersa en la siguiente unidad. Los nombres de clase se deducen de los nombres de carpeta en orden alfanumérico.
SEED = 42
BATCH_SIZE = 64
tf.random.set_seed(SEED)
np.random.seed(SEED)
train_ds, validation_ds = tf.keras.utils.audio_dataset_from_directory(
directory=BINARY_DATASET_PATH,
label_mode="int",
batch_size=BATCH_SIZE,
validation_split=0.2,
subset="both",
seed=SEED,
output_sequence_length=16000,
)
label_names = np.array(train_ds.class_names)
print("Label names:", label_names)
Salida esperada: El cargador busca aproximadamente 2000 archivos WAV en las no clases y yes . Con una división de validación del 20 por ciento, se usan aproximadamente 1600 ejemplos para el entrenamiento y aproximadamente 400 para la validación.
Found 2000 files belonging to 2 classes.
Using 1600 files for training.
Using 400 files for validation.
Label names: ['no' 'yes']
Mantenga un conjunto de pruebas independiente del conjunto de validación. El cargador no crea una división de prueba independiente, por lo que el siguiente código desagrupa los datos de validación y divide los clips individuales en un fragmento de validación y un fragmento de prueba. Deshaga el lote antes de dividir los fragmentos, de modo que la división se realice a nivel de clip en lugar de a nivel de lote. Almacene en caché los datos de validación para que los fragmentos de validación y prueba no tengan que descodificar repetidamente los mismos archivos WAV.
holdout_ds = validation_ds.unbatch().cache()
val_ds = holdout_ds.shard(num_shards=2, index=0).batch(BATCH_SIZE)
test_ds = holdout_ds.shard(num_shards=2, index=1).batch(BATCH_SIZE)
Salida esperada: Este código no imprime la salida. El modelo usa train_ds para el entrenamiento, val_ds para la optimización durante el entrenamiento y test_ds para la evaluación final.
Note
Dataset.shard selecciona elementos del conjunto de datos. Dado que este código llama a unbatch() antes que a shard(), los elementos de los fragmentos son clips de audio individuales en lugar de lotes completos. Con unos 400 clips de validación, val_ds y test_ds contienen cada uno unos 200 clips tras la agrupación en lotes.
Note
Este módulo sigue un patrón de división compacto que se adapta al tutorial de TensorFlow para que el ejemplo sea pequeño. Para una evaluación rigurosa con el conjunto de datos completo de Speech Commands, utilice las divisiones train, validation y test proporcionadas por TensorFlow Datasets, o divida por ID de hablante, de modo que los clips del mismo hablante no aparezcan tanto en los datos de entrenamiento como en los de evaluación.
El conjunto de datos devuelve tensores de audio con una dimensión de canal. Dado que estos archivos son mono, quite la dimensión de canal adicional antes de crear espectrogramas.
def squeeze(audio, labels):
audio = tf.squeeze(audio, axis=-1)
return audio, labels
train_ds = train_ds.map(squeeze, num_parallel_calls=tf.data.AUTOTUNE)
val_ds = val_ds.map(squeeze, num_parallel_calls=tf.data.AUTOTUNE)
test_ds = test_ds.map(squeeze, num_parallel_calls=tf.data.AUTOTUNE)
for example_audio, example_labels in train_ds.take(1):
print("Audio batch shape:", example_audio.shape)
print("Label batch shape:", example_labels.shape)
Salida esperada: Cada lote de audio contiene hasta 64 formas de onda de un segundo.
Audio batch shape: (64, 16000)
Label batch shape: (64,)
Visualización de una forma de onda
El código siguiente grafica una forma de onda de un lote de entrenamiento.
example_waveform = example_audio[0]
example_label = label_names[example_labels[0].numpy()]
plt.figure(figsize=(12, 4))
plt.plot(example_waveform.numpy())
plt.title(f"Waveform for '{example_label}'")
plt.xlabel("Sample")
plt.ylabel("Amplitude")
plt.xlim([0, 16000])
plt.show()
Salida esperada: El gráfico muestra la amplitud a lo largo de 16 000 muestras para un clip de audio no o yes.
Creación de espectrogramas
El STFT convierte la forma de onda del dominio del tiempo en una representación de tiempo-frecuencia. El modelo utiliza únicamente la magnitud de los valores STFT, por lo que el código aplica tf.abs al resultado STFT complejo. La línea final agrega una dimensión de canal para que las capas convolucionales puedan procesar el espectrograma como entrada similar a la imagen.
def get_spectrogram(waveform):
spectrogram = tf.signal.stft(
waveform,
frame_length=255,
frame_step=128,
)
spectrogram = tf.abs(spectrogram)
spectrogram = spectrogram[..., tf.newaxis]
return spectrogram
example_spectrogram = get_spectrogram(example_waveform)
print("Spectrogram shape:", example_spectrogram.shape)
Salida esperada: Con un segundo de audio (16 000 muestras), una longitud de fotograma de 255 y un paso de fotograma de 128, la forma del espectrograma es (124, 129, 1). La primera dimensión es el número de intervalos de tiempo: floor((16000 - 255) / 128) + 1 = 124. Esta fórmula se aplica porque tf.signal.stft deja pad_end establecido en False de forma predeterminada, por lo que no agrega un fotograma parcial adicional al final. La segunda dimensión es el número de intervalos de frecuencia: tf.signal.stft rellena con ceros cada fotograma hasta la siguiente potencia de dos para la FFT (256 en este caso) y, a continuación, devuelve fft_length / 2 + 1 = 129 intervalos no redundantes. La dimensión final es el eje de canal añadido para el modelo convolucional.
Spectrogram shape: (124, 129, 1)
Visualización de un espectrograma
Use una escala logarítmica para mostrar, por lo que los componentes de frecuencia más silenciosos son más fáciles de ver. Agregue un pequeño epsilon antes de llamar a np.log para que los valores cero no se conviertan en infinito negativo.
def plot_spectrogram(spectrogram, ax):
if len(spectrogram.shape) > 2:
spectrogram = np.squeeze(spectrogram, axis=-1)
log_spec = np.log(spectrogram.T + np.finfo(float).eps)
height = log_spec.shape[0]
width = log_spec.shape[1]
time_steps = np.arange(width)
frequency_bins = np.arange(height)
ax.pcolormesh(time_steps, frequency_bins, log_spec)
ax.set_xlabel("Time frame")
ax.set_ylabel("Frequency bin")
fig, axes = plt.subplots(2, figsize=(12, 8))
axes[0].plot(example_waveform.numpy())
axes[0].set_title("Waveform")
axes[0].set_xlim([0, 16000])
plot_spectrogram(example_spectrogram.numpy(), axes[1])
axes[1].set_title("Spectrogram")
plt.suptitle(example_label.title())
plt.show()
Salida esperada: El primer gráfico muestra la forma de onda. En el segundo gráfico se muestra el espectrograma, con intervalos de tiempo en el eje horizontal, los intervalos de frecuencia en el eje vertical y la intensidad del color que representa la magnitud.
Creación de conjuntos de datos de espectrograma
Asigne los conjuntos de datos de forma de onda a conjuntos de datos de espectrograma. Almacena en caché y precarga los conjuntos de datos para reducir la latencia del canal de entrada durante el entrenamiento.
def make_spectrogram_dataset(dataset):
return dataset.map(
map_func=lambda audio, label: (get_spectrogram(audio), label),
num_parallel_calls=tf.data.AUTOTUNE,
)
train_spectrogram_ds = make_spectrogram_dataset(train_ds)
val_spectrogram_ds = make_spectrogram_dataset(val_ds)
test_spectrogram_ds = make_spectrogram_dataset(test_ds)
train_spectrogram_ds = train_spectrogram_ds.cache().shuffle(1000, seed=SEED).prefetch(tf.data.AUTOTUNE)
val_spectrogram_ds = val_spectrogram_ds.cache().prefetch(tf.data.AUTOTUNE)
test_spectrogram_ds = test_spectrogram_ds.cache().prefetch(tf.data.AUTOTUNE)
for spectrograms, labels in train_spectrogram_ds.take(1):
print("Spectrogram batch shape:", spectrograms.shape)
print("Label batch shape:", labels.shape)
Salida esperada: El conjunto de datos listo para el modelo contiene lotes de tensores de espectrograma y etiquetas de enteros.
Spectrogram batch shape: (64, 124, 129, 1)
Label batch shape: (64,)
Comprobación de conocimientos
Comentarios
¿Le ha resultado útil esta página?
No
¿Necesita ayuda con este tema?
¿Desea intentar usar Ask Learn para aclarar o guiarle a través de este tema?