El reconocimiento de voz es un problema complejo en varias industrias. Conocer algunos de los conceptos básicos sobre el manejo de datos de audio y cómo clasificar muestras de sonido es algo bueno para tener en su caja de herramientas de ciencia de datos.
Vamos a ver un ejemplo de cómo clasificar algunos clips de sonido usando Tensorflow . Para cuando supere esto, sabrá lo suficiente como para poder construir sus propios modelos de reconocimiento de voz. Con investigación adicional, puede tomar estos conceptos y aplicarlos a archivos de audio más grandes y complejos.
Puede encontrar el código completo en este repositorio de Github .
La recopilación de datos es uno de los problemas difíciles en la ciencia de datos. Hay tantos datos disponibles, pero no todos son fáciles de usar en problemas de aprendizaje automático. Debe asegurarse de que los datos estén limpios, etiquetados y completos.
Para hacer nuestro ejemplo, vamos a utilizar algunos archivos de audio publicados por Google .
Primero, crearemos una nueva canalización de Conducto . Aquí es donde podrá construir, entrenar y probar su modelo y compartir un enlace con cualquier otra persona interesada:
### # Main Pipeline ### def main () -> co.Serial: path = "/conducto/data/pipeline" root = co.Serial(image = get_image()) # Get data from keras for testing and training root[ "Get Data" ] = co.Exec(run_whole_thing, f" {path} /raw" ) return root
Entonces empezaremos a escribir el
run_whole_thing
función: def run_whole_thing (out_dir) : os.makedirs(out_dir, exist_ok= True ) # Set seed for experiment reproducibility seed = 55 tf.random.set_seed(seed) np.random.seed(seed) data_dir = pathlib.Path( "data/mini_speech_commands" )
A continuación, debemos configurar el directorio para almacenar los archivos de audio:
if not data_dir.exists(): # Get the files from external source and put them in an accessible directory tf.keras.utils.get_file( 'mini_speech_commands.zip' , origin= "http://storage.googleapis.com/download.tensorflow.org/data/mini_speech_commands.zip" , extract= True )
Ahora que tenemos nuestros datos en el directorio correcto, podemos dividirlos en conjuntos de datos de entrenamiento, prueba y validación.
Primero, necesitamos escribir algunas funciones para ayudar a preprocesar los datos para que funcionen en nuestro modelo.
Necesitamos los datos en un formato que nuestro algoritmo pueda entender. Usaremos una red neuronal convolucional, por lo que los datos deben transformarse en imágenes.
Esta primera función convertirá el archivo de audio binario en un tensor:
# Convert the binary audio file to a tensor def decode_audio (audio_binary) : audio, _ = tf.audio.decode_wav(audio_binary) return tf.squeeze(audio, axis= -1 )
Como tenemos un tensor con el que podemos trabajar que tiene los datos sin procesar, necesitamos obtener las etiquetas para que coincidan. Eso es lo que hace la siguiente función al obtener la etiqueta de un archivo de audio de la ruta del archivo:
# Get the label (yes, no, up, down, etc) for an audio file. def get_label (file_path) : parts = tf.strings.split(file_path, os.path.sep) return parts[ -2 ]
A continuación, debemos asociar los archivos de audio con las etiquetas correctas. Estamos haciendo esto y devolviendo una tupla con la que Tensorflow puede trabajar:
# Create a tuple that has the labeled audio files def get_waveform_and_label (file_path) : label = get_label(file_path) audio_binary = tf.io.read_file(file_path) waveform = decode_audio(audio_binary) return waveform, label
Mencionamos brevemente el uso del algoritmo de red neuronal convolucional (CNN) anteriormente. Esta es una de las formas en que podemos manejar un modelo de reconocimiento de voz como este. Por lo general, las CNN funcionan muy bien con datos de imágenes y ayudan a reducir el tiempo de preprocesamiento.
Vamos a aprovechar eso convirtiendo nuestros archivos de audio en espectrogramas. Un espectrograma es una imagen de un espectro de frecuencias. Si observa un archivo de audio, verá que solo son datos de frecuencia. Así que vamos a escribir una función que convierta nuestros datos de audio en imágenes:
# Convert audio files to images def get_spectrogram (waveform) : # Padding for files with less than 16000 samples zero_padding = tf.zeros([ 16000 ] - tf.shape(waveform), dtype=tf.float32) # Concatenate audio with padding so that all audio clips will be of the same length waveform = tf.cast(waveform, tf.float32) equal_length = tf.concat([waveform, zero_padding], 0 ) spectrogram = tf.signal.stft( equal_length, frame_length= 255 , frame_step= 128 ) spectrogram = tf.abs(spectrogram) return spectrogram
Ahora que hemos formateado nuestros datos como imágenes, debemos aplicar las etiquetas correctas a esas imágenes. Esto es similar a lo que hicimos con los archivos de audio originales:
# Label the images created from the audio files and return a tuple def get_spectrogram_and_label_id (audio, label) : spectrogram = get_spectrogram(audio) spectrogram = tf.expand_dims(spectrogram, -1 ) label_id = tf.argmax(label == commands) return spectrogram, label_id
La última función auxiliar que necesitamos es la que manejará todas las operaciones anteriores para cualquier conjunto de archivos de audio que le pasemos:
# Preprocess any audio files def preprocess_dataset (files, autotune, commands) : # Creates the dataset files_ds = tf.data.Dataset.from_tensor_slices(files) # Matches audio files with correct labels output_ds = files_ds.map(get_waveform_and_label, num_parallel_calls=autotune) # Matches audio file images to the correct labels output_ds = output_ds.map( get_spectrogram_and_label_id, num_parallel_calls=autotune) return output_ds
Ahora que tenemos todas estas funciones auxiliares, podemos dividir los datos.
La conversión de archivos de audio a imágenes ayuda a que los datos sean más fáciles de procesar con una CNN y es por eso que escribimos todas esas funciones auxiliares. Haremos un par de cosas para simplificar la división de datos.
Primero, obtendremos una lista de todos los comandos potenciales para los archivos de audio que usaremos en algunos otros lugares del código:
# Get all of the commands for the audio files commands = np.array(tf.io.gfile.listdir(str(data_dir))) commands = commands[commands != 'README.md' ]
Luego obtendremos una lista de todos los archivos en el directorio de datos y los mezclaremos para que podamos asignar valores aleatorios a cada uno de los conjuntos de datos que necesitamos:
# Get a list of all the files in the directory filenames = tf.io.gfile.glob(str(data_dir) + '/*/*' ) # Shuffle the file names so that random bunches can be used as the training, testing, and validation sets filenames = tf.random.shuffle(filenames) # Create the list of files for training data train_files = filenames[: 6400 ] # Create the list of files for validation data validation_files = filenames[ 6400 : 6400 + 800 ] # Create the list of files for test data test_files = filenames[ -800 :]
Ahora tenemos nuestros archivos de entrenamiento, validación y prueba claramente separados para que podamos continuar y preprocesar estos archivos para prepararlos para construir y probar nuestro modelo. Estamos usando autoajuste aquí para ajustar el valor de nuestros parámetros dinámicamente en tiempo de ejecución:
autotune = tf.data.AUTOTUNE
Este primer ejemplo es solo para mostrar cómo funciona el preprocesamiento y nos da la
spectrogram_ds
valor que necesitaremos en un momento: # Get the converted audio files for training the model files_ds = tf.data.Dataset.from_tensor_slices(train_files) waveform_ds = files_ds.map( get_waveform_and_label, num_parallel_calls=autotune) spectrogram_ds = waveform_ds.map( get_spectrogram_and_label_id, num_parallel_calls=autotune)
Como ha visto lo que es pasar por los pasos de preprocesamiento, podemos continuar y usar la función de ayuda para manejar esto para todos los conjuntos de datos:
# Preprocess the training, test, and validation datasets train_ds = preprocess_dataset(train_files, autotune, commands) validation_ds = preprocess_dataset( validation_files, autotune, commands) test_ds = preprocess_dataset(test_files, autotune, commands)
Queremos establecer una cantidad de ejemplos de entrenamiento que se ejecuten en cada iteración de las épocas, por lo que estableceremos un tamaño de lote:
# Batch datasets for training and validation batch_size = 64 train_ds = train_ds.batch(batch_size) validation_ds = validation_ds.batch(batch_size)
Por último, podemos reducir la cantidad de latencia en el entrenamiento de nuestro modelo aprovechando el almacenamiento en caché:
# Reduce latency while training train_ds = train_ds.cache().prefetch(autotune) validation_ds = validation_ds.cache().prefetch(autotune)
Nuestros conjuntos de datos finalmente están en una forma con la que podemos entrenar el modelo.
Dado que nuestros conjuntos de datos están claramente definidos, podemos seguir adelante y construir el modelo. Usaremos una CNN para crear nuestro modelo, por lo que necesitaremos obtener la forma de los datos para obtener la forma correcta para nuestras capas. Luego seguimos construyendo el modelo secuencialmente:
# Build model for spectrogram, _ in spectrogram_ds.take( 1 ): input_shape = spectrogram.shape num_labels = len(commands) norm_layer = preprocessing.Normalization() norm_layer.adapt(spectrogram_ds.map( lambda x, _: x)) model = models.Sequential([ layers.Input(shape=input_shape), preprocessing.Resizing( 32 , 32 ), norm_layer, layers.Conv2D( 32 , 3 , activation= 'relu' ), layers.Conv2D( 64 , 3 , activation= 'relu' ), layers.MaxPooling2D(), layers.Dropout( 0.25 ), layers.Flatten(), layers.Dense( 128 , activation= 'relu' ), layers.Dropout( 0.5 ), layers.Dense(num_labels), ]) model.summary()
Hacemos una configuración en el modelo para que nos dé la mayor precisión posible:
# Configure built model with losses and metrics model.compile( optimizer=tf.keras.optimizers.Adam(), loss=tf.keras.losses.SparseCategoricalCrossentropy( from_logits= True ), metrics=[ 'accuracy' ], )
El modelo está construido, así que ahora todo lo que queda es entrenarlo.
Después de todo el trabajo de preprocesamiento de los datos y construcción del modelo, el entrenamiento es relativamente simple. Determinamos cuántas épocas queremos ejecutar con nuestros conjuntos de datos de entrenamiento y validación:
# Finally train the model and return info about each epoch EPOCHS = 10 model.fit( train_ds, validation_data=validation_ds, epochs=EPOCHS, callbacks=tf.keras.callbacks.EarlyStopping(verbose= 1 , patience= 2 ), )
¡Eso es todo! El modelo ha sido entrenado y ahora solo falta probarlo.
Ahora que tenemos un modelo con una precisión de aproximadamente el 83 %, es hora de que probemos qué tan bien funciona con nuevos datos. Así que tomamos nuestro conjunto de datos de prueba y separamos los archivos de audio de las etiquetas:
# Test the model test_audio = [] test_labels = [] for audio, label in test_ds: test_audio.append(audio.numpy()) test_labels.append(label.numpy()) test_audio = np.array(test_audio) test_labels = np.array(test_labels)
Luego tomamos los datos de audio y los usamos en nuestro modelo para ver si predice la etiqueta correcta:
# See how accurate the model is when making predictions on the test dataset y_pred = np.argmax(model.predict(test_audio), axis= 1 ) y_true = test_labels test_acc = sum(y_pred == y_true) / len(y_true) print( f'Test set accuracy: {test_acc: .0 %} ' )
Solo hay un pequeño fragmento de código que necesitará para terminar su canalización y poder compartirlo con cualquiera. Esto define la imagen que se usará en esta canalización de Conducto y maneja la ejecución del archivo:
### # Pipeline Helper functions ### def get_image () : return co.Image( "python:3.8-slim" , copy_dir= "." , reqs_py=[ "conducto" , "tensorflow" , "keras" ], ) if __name__ == "__main__" : co.main(default=main)
Ahora puedes correr
python pipeline.py --local
en su terminal y debería generar un enlace a una nueva tubería de Conducto. Si no tienes una cuenta, puedes crear una gratis aquí.Esta es una de las formas en que puede resolver un problema de procesamiento de audio, pero puede ser mucho más complejo según los datos que intente analizar. Construirlo en una canalización hace que sea fácil compartirlo con compañeros de trabajo y obtener ayuda o comentarios si se encuentra con errores.
Publicado anteriormente aquí .