paint-brush
Introducción al análisis de audio: reconocimiento de sonidos mediante el aprendizaje automáticopor@tyiannak
27,139 lecturas
27,139 lecturas

Introducción al análisis de audio: reconocimiento de sonidos mediante el aprendizaje automático

por Theodoros Giannakopoulos37m2020/09/12
Read on Terminal Reader
Read this story w/o Javascript

Demasiado Largo; Para Leer

El análisis de sonido es una tarea desafiante asociada a varias aplicaciones modernas, como análisis de voz, recuperación de información musical, reconocimiento de locutores, análisis de comportamiento y análisis de escenas auditivas para monitoreo de seguridad, salud y medio ambiente. Este artículo proporciona una breve introducción a los conceptos básicos de extracción de características de audio, clasificación de sonido y clasificación de sonido. Se proporcionan ejemplos de Python en todos los casos, principalmente a través de la biblioteca pyAudioAnalysis. Todos los ejemplos también se proporcionan en este repositorio de GitHub. En este artículo, este artículo se centra en las funciones de audio hechas a mano y los clasificadores estadísticos tradicionales.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Introducción al análisis de audio: reconocimiento de sonidos mediante el aprendizaje automático
Theodoros Giannakopoulos HackerNoon profile picture


Like my articles? Feel free to vote for me
 as ML Writer of the year here
 .

El análisis de sonido es una tarea desafiante, asociada a varias aplicaciones modernas, como análisis de voz, recuperación de información musical, reconocimiento de locutores, análisis de comportamiento y análisis de escenas auditivas para monitoreo de seguridad, salud y medio ambiente. Este artículo proporciona una breve introducción a los conceptos básicos de extracción de características de audio , clasificación y segmentación de sonido, con ejemplos de demostración en aplicaciones como clasificación de géneros musicales, agrupación de altavoces, clasificación de eventos de audio y detección de actividad de voz.

Se proporcionan ejemplos de Python en todos los casos, principalmente a través de la biblioteca pyAudioAnalysis . Todos los ejemplos también se proporcionan en este repositorio de github.

Con respecto a las metodologías de ML involucradas, este artículo se centra en las funciones de audio hechas a mano y los clasificadores estadísticos tradicionales como SVM. Los métodos de audio profundo se seguirán en un artículo futuro, ya que el presente artículo trata más sobre aprender a extraer características de audio que tengan sentido para sus clasificadores, incluso cuando tenga algunas decenas de muestras de entrenamiento.

requisitos previos

Antes de profundizar en el reconocimiento de audio, el lector debe conocer los conceptos básicos del manejo de audio y la representación de señales: definición de sonido, muestreo, cuantificación, frecuencia de muestreo, resolución de muestreo y los conceptos básicos de representación de frecuencia. Estos temas se tratan en este artículo.

Extracción de funciones de audio: a corto plazo y basada en segmentos

Por lo tanto, ya debería saber que una señal de audio está representada por una secuencia de muestras con una "resolución de muestra" dada (generalmente 16 bits = 2 bytes por muestra) y con una frecuencia de muestreo particular (por ejemplo, 16 KHz = 16000 muestras por segundo).

Ahora podemos continuar con el siguiente paso: usar estas muestras para analizar los sonidos correspondientes. Por "analizar" podemos referirnos a cualquier cosa, desde: reconocer entre diferentes tipos de sonidos, segmentar una señal de audio en partes homogéneas (por ejemplo, dividir los segmentos sonoros de los no sonoros en una señal de voz) o agrupar archivos de sonido en función de su similitud de contenido.

En todos los casos, primero debemos encontrar una manera de pasar de las muestras de datos de audio voluminosas y de bajo nivel a una representación de nivel superior del contenido de audio. Este es el propósito de la extracción de características (FE) , la tarea más común e importante en todas las aplicaciones de aprendizaje automático y reconocimiento de patrones.

FE se trata de extraer un conjunto de características que son informativas con respecto a las propiedades deseadas de los datos originales. En nuestro caso, estamos interesados en extraer características de audio que sean capaces de discriminar entre diferentes clases de audio, es decir, diferentes hablantes, eventos, emociones o géneros musicales, dependiendo del subdominio de la aplicación.

El concepto más importante de la extracción de características de audio es el uso de ventanas a corto plazo (o marcos ): esto simplemente significa que la señal de audio se divide en ventanas (o marcos ) a corto plazo. Los marcos pueden superponerse opcionalmente.

La duración de las tramas suele oscilar entre 10 y 100 milisegundos, según la aplicación y los tipos de señales. Para el caso de no superposición, el paso del procedimiento de ventana es igual a la longitud de la ventana (también llamado "tamaño").

Si, por otro lado, paso < tamaño, entonces los marcos se superponen: por ejemplo, un paso de 10 ms para un tamaño de ventana de 40 ms significa una superposición del 75%. Por lo general, también se aplica una función de ventana (como hamming) a cada cuadro.

Para cada fotograma (sea N el número total de fotogramas), extraemos un conjunto de características de audio (a corto plazo). Cuando las características se extraen directamente de los valores de la muestra de audio, se denominan dominio del tiempo. Si las características se calculan sobre los valores de FFT, se denominan características de dominio de frecuencia. Finalmente, las características cepstrales (como los MFCC ) son características que se basan en el cepstrum.

Como ejemplo, supongamos que solo extraemos la energía de la señal (la media de los cuadrados de las muestras de audio) y el centroide espectral (el centroide de la magnitud de la FFT). Esto significa que, durante este procedimiento de trama, la señal se representa mediante una secuencia de vectores de características bidimensionales a corto plazo. (o dos secuencias de funciones de igual duración, si lo desea).

Entonces, ¿cómo podemos usar estas secuencias de tamaño arbitrario para analizar la señal respectiva? Imagina que quieres construir un clasificador para discriminar entre dos clases de audio, por ejemplo, habla y silencio. Tus datos de entrenamiento iniciales son archivos de audio y las etiquetas de clase correspondientes (una etiqueta de clase por archivo de audio completo ). Si estos archivos tienen la misma duración, las secuencias de vectores de características a corto plazo correspondientes tendrán la misma longitud. ¿Qué sucede, sin embargo, en el caso general de duraciones arbitrarias?

¿Cómo representamos segmentos de audio de tamaño arbitrario?

Una solución sería rellenar con cero las secuencias de características hasta la duración máxima del conjunto de datos y luego concatenar las diferentes secuencias de características a corto plazo en un solo vector de características. Pero eso (a) conduciría a una dimensionalidad muy alta (y, por lo tanto, a la necesidad de más muestras de datos para lograr el entrenamiento) y (b) dependería mucho de las posiciones temporales de los valores de las características (ya que cada característica correspondería a una marca de tiempo diferente) .

Un enfoque más común seguido en el análisis de audio tradicional es extraer un conjunto de estadísticas de características por segmento de tamaño fijo . Las estadísticas a nivel de segmento extraídas sobre las secuencias de características a corto plazo son las representaciones para cada segmento de tamaño fijo. La representación final de la señal puede ser el promedio a largo plazo de las estadísticas del segmento.

... las estadísticas de características del segmento son la forma más sencilla de hacerlo

Como ejemplo, considere una señal de audio de 2,5 segundos. Seleccionamos una ventana a corto plazo de 50 ms y un segmento de 1 segundo. De acuerdo con lo anterior, las secuencias de energía y centroide espectral se extraerán para cada segmento de 1 segundo. La longitud de las secuencias N será igual a 1 / 0.050 = 20. Luego, se extraen los μ y σ de cada secuencia para cada segmento de 1 segundo, como las estadísticas de características del segmento. Estos finalmente se promedian a largo plazo, lo que da como resultado la representación final de la señal. (Tenga en cuenta que el último segmento tiene una longitud de 0,5, por lo que las estadísticas se extraen en un segmento más corto)

Notas:

  1. Los tamaños de trama a corto plazo suelen oscilar entre 10 y 100 mseg. Los cuadros más largos significan mejores representaciones de frecuencia (más muestras para calcular FFT y, por lo tanto, cada contenedor de FFT corresponde a menos Hz). Pero los cuadros más largos también significan una pérdida de resolución temporal, ya que las señales de audio no son estacionarias (considere un cuadro tan largo que encapsule dos eventos de audio diferentes: la resolución de frecuencia sería muy alta, pero ¿qué representarían las características respectivas?)
  2. El paso de promedio a largo plazo de las estadísticas de las características del segmento de una señal (descrito anteriormente) es opcional y generalmente se adopta durante el entrenamiento/prueba de un clasificador de audio. Se utiliza para asignar toda la señal a un solo vector de características. Esto no podría desearse en otra configuración, por ejemplo, cuando estamos interesados en segmentar la señal inicial.

Extracción de características de audio: ejemplos de código

El ejemplo 1 usa pyAudioAnalysis para leer un archivo de audio WAV y extraer secuencias de funciones a corto plazo y trazar la secuencia de energía (solo una de las funciones). Consulte los comentarios en línea para obtener una explicación, junto con estas dos notas:

  1.  read_audio_file()
    devuelve la frecuencia de muestreo (Fs) del archivo de audio y una matriz NumPy de las muestras de audio sin procesar. Para obtener la duración en segundos, simplemente hay que dividir el número de muestras por Fs
  2.  ShortTermFeatures.feature_extraction()
    La función devuelve (a) una matriz de características a corto plazo de 68 x 20, donde 68 es la cantidad de características a corto plazo implementadas en la biblioteca y 20 es la cantidad de fotogramas que caben en los segmentos de 1 segundo (se usa 1 segundo como ventana intermedia en el ejemplo) (b) una lista de 68 cadenas de caracteres que contienen los nombres de cada función implementada en la biblioteca.
 # Example 1: short-term feature extraction from pyAudioAnalysis import ShortTermFeatures as aF from pyAudioAnalysis import audioBasicIO as aIO import numpy as np import plotly.graph_objs as go import plotly import IPython # read audio data from file # (returns sampling freq and signal as a numpy array) fs, s = aIO.read_audio_file( "data/object.wav" ) # play the initial and the generated files in notebook: IPython.display.display(IPython.display.Audio( "data/object.wav" )) # print duration in seconds: duration = len(s) / float(fs) print( f'duration = {duration} seconds' ) # extract short-term features using a 50msec non-overlapping windows win, step = 0.050 , 0.050 [f, fn] = aF.feature_extraction(s, fs, int(fs * win), int(fs * step)) print( f' {f.shape[ 1 ]} frames, {f.shape[ 0 ]} short-term features' ) print( 'Feature names:' ) for i, nam in enumerate(fn): print( f' {i} : {nam} ' ) # plot short-term energy # create time axis in seconds time = np.arange( 0 , duration - step, win) # get the feature whose name is 'energy' energy = f[fn.index( 'energy' ), :] mylayout = go.Layout(yaxis=dict(title= "frame energy value" ), xaxis=dict(title= "time (sec)" )) plotly.offline.iplot(go.Figure(data=[go.Scatter(x=time, y=energy)], layout=mylayout))

da como resultado (recortado debido a la longitud):

 duration = 1.03 seconds 20 frames, 68 short-term features Feature names: 0 :zcr 1 :energy 2 :energy_entropy 3 :spectral_centroid 4 :spectral_spread 5 :spectral_entropy 6 :spectral_flux 7 :spectral_rolloff 8 :mfcc_1 ... 31 :chroma_11 32 :chroma_12 33 :chroma_std 34 :delta zcr 35 :delta energy ... 66 :delta chroma_12 67 :delta chroma_std

y el siguiente gráfico:

El ejemplo 2 demuestra la característica a corto plazo del centroide espectral. El centroide espectral es simplemente el centroide de la magnitud FFT, normalizado en el rango de frecuencia [0, Fs/2] (por ejemplo, si el centroide espectral = 0,5, esto es igual a Fs/4 medido en Hz).

 # Example 2: short-term feature extraction: # spectral centroid of two speakers from pyAudioAnalysis import ShortTermFeatures as aF from pyAudioAnalysis import audioBasicIO as aIO import numpy as np import plotly.graph_objs as go import plotly import IPython # read audio data from file # (returns sampling freq and signal as a numpy array) fs, s = aIO.read_audio_file( "data/trump_bugs.wav" ) # play the initial and the generated files in notebook: IPython.display.display(IPython.display.Audio( "data/trump_bugs.wav" )) # print duration in seconds: duration = len(s) / float(fs) print( f'duration = {duration} seconds' ) # extract short-term features using a 50msec non-overlapping windows win, step = 0.050 , 0.050 [f, fn] = aF.feature_extraction(s, fs, int(fs * win), int(fs * step)) print( f' {f.shape[ 1 ]} frames, {f.shape[ 0 ]} short-term features' ) # plot short-term energy # create time axis in seconds time = np.arange( 0 , duration - step, win) # get the feature whose name is 'energy' energy = f[fn.index( 'spectral_centroid' ), :] mylayout = go.Layout(yaxis=dict(title= "spectral_centroid value" ), xaxis=dict(title= "time (sec)" )) plotly.offline.iplot(go.Figure(data=[go.Scatter(x=time, y=energy)], layout=mylayout))

En este ejemplo, la secuencia del centroide espectral se calcula para una grabación que contiene una muestra del discurso de Donald Trump de 2 segundos, seguida de "¿Qué pasa, doc?" de Bugs Bunny. frase. Es obvio que la secuencia del centroide espectral se puede utilizar para discriminar estos dos altavoces en este ejemplo particular (los valores más altos del centroide espectral corresponden a frecuencias más altas y, por lo tanto, a sonidos "más brillantes").

En total, se extraen 34 funciones a corto plazo en pyAudioAnalysis , para cada cuadro, y ShortTermFeatures.feature_extraction() La función también (opcionalmente) extrae las características delta respectivas. En ese caso, el número total de características extraídas para cada cuadro a corto plazo es 68. La lista completa y la descripción de las características a corto plazo se pueden encontrar en el wiki de la biblioteca y en esta publicación .

Los dos primeros ejemplos utilizaron la función

 ShortTermFeatures.feature_extraction()
para extraer 68 características por cuadro a corto plazo. Como se describe en la sección anterior, en muchos casos, como la clasificación a nivel de segmento, también extraemos estadísticas a nivel de segmento. Esto se logra a través de la
 MidTermFeatures.mid_feature_extraction()
función, como se muestra en el Ejemplo 3 :

 # Example 3: segment-level feature extraction from pyAudioAnalysis import MidTermFeatures as aF from pyAudioAnalysis import audioBasicIO as aIO # read audio data from file # (returns sampling freq and signal as a numpy array) fs, s = aIO.read_audio_file( "data/trump_bugs.wav" ) # get mid-term (segment) feature statistics # and respective short-term features: mt, st, mt_n = aF.mid_feature_extraction(s, fs, 1 * fs, 1 * fs, 0.05 * fs, 0.05 * fs) print( f'signal duration {len(s)/fs} seconds' ) print( f' {st.shape[ 1 ]} {st.shape[ 0 ]} -D short-term feature vectors extracted' ) print( f' {mt.shape[ 1 ]} {mt.shape[ 0 ]} -D segment feature statistic vectors extracted' ) print( 'mid-term feature names' ) for i, mi in enumerate(mt_n): print( f' {i} : {mi} ' )

resultados en:

 signal duration 3.812625 seconds 76 68 -D short-term feature vectors extracted 4 136 -D segment feature statistic vectors extracted mid-term feature names 0 :zcr_mean 1 :energy_mean 2 :energy_entropy_mean 3 :spectral_centroid_mean 4 :spectral_spread_mean 5 :spectral_entropy_mean 6 :spectral_flux_mean 7 :spectral_rolloff_mean 8 :mfcc_1_mean ... 131 :delta chroma_9_std 132 :delta chroma_10_std 133 :delta chroma_11_std 134 :delta chroma_12_std 135 :delta chroma_std_std

 MidTermFeatures.mid_feature_extraction()
extrae 2 estadísticas, a saber, la media y la estándar de cada secuencia de características a corto plazo, utilizando el tamaño de ventana de "mediano plazo" (segmento) proporcionado de 1 segundo para el ejemplo anterior. Dado que la duración de la señal es de 3,8 segundos, y el paso y el tamaño de la ventana a medio plazo es de 1 segundo, esperamos que se creen 4 segmentos a medio plazo y para cada uno de ellos se calculará un vector de estadísticas de características. Además, estas estadísticas de segmento se calculan sobre las secuencias de características a corto plazo de 3,8/0,05 = 76 fotogramas a corto plazo. Además, tenga en cuenta que los nombres de características a mediano plazo también contienen la estadística del segmento, por ejemplo, zcr_mean es la media de la característica a corto plazo con tasa de cruce por cero.

Los primeros 3 ejemplos mostraron cómo podemos extraer características a corto plazo y estadísticas de características a mediano plazo (segmento). Función

 MidTermFeatures.directory_feature_extraction()
extrae características de audio para todos los archivos en la carpeta proporcionada, de modo que estos datos se puedan usar para entrenar un clasificador, etc.

Así que en realidad llama

 MidTermFeatures.mid_feature_extraction()
para cada archivo WAV y realiza un promedio a largo plazo para pasar de vectores estadísticos de características de segmento a un solo vector de características. Además, esta función es capaz de extraer dos características relacionadas con el ritmo de la música que se adjuntan en las estadísticas del segmento promediado. Como ejemplo, supongamos que queremos analizar una canción de 120 segundos, con una ventana (y paso) a corto plazo de 50 mseg y una ventana (segmento) a medio plazo y paso de 1 segundo.

Los siguientes pasos ocurrirán durante el

 MidTermFeatures.directory_feature_extraction()
llamar:

  1. 120 / 0.05 = 2400 Se extraen vectores de características a corto plazo 68-D
  2. Se calculan las estadísticas de características 120 136-D (media y estándar de las secuencias vectoriales 68-D)
  3. los 120 136-D se promedian a largo plazo para toda la canción y (opcionalmente) se agregan dos características de tiempo (el tiempo debe calcularse en un nivel de archivo ya que necesita información a largo plazo), lo que lleva a un vector de característica final de 138 valores.

El ejemplo 4 demuestra el uso de

 MidTermFeatures.directory_feature_extraction()
para extraer características a nivel de archivo (promedios de estadísticas de características de segmento) para 20 muestras de música de 2 segundos (archivos WAV separados) de dos categorías de géneros musicales, a saber, música clásica y heavy metal. Para cada uno de los segmentos de canciones de 2 segundos
 MidTermFeatures.directory_feature_extraction()
extrae el vector de características 138-D, como se describe anteriormente. Luego seleccionamos trazar 2 de estas características, usando diferentes colores para las dos clases de audio (clásico y metal):

 # Example4: plot 2 features for 10 2-second samples # from classical and 10 from metal music from pyAudioAnalysis import MidTermFeatures as aF import os import numpy as np import plotly.graph_objs as go import plotly dirs = [ "data/music/classical" , "data/music/metal" ] class_names = [os.path.basename(d) for d in dirs] m_win, m_step, s_win, s_step = 1 , 1 , 0.1 , 0.05 # segment-level feature extraction: features = [] for d in dirs: # get feature matrix for each directory (class) f, files, fn = aF.directory_feature_extraction(d, m_win, m_step, s_win, s_step) features.append(f) # (each element of the features list contains a # (samples x segment features) = (10 x 138) feature matrix) print(features[ 0 ].shape, features[ 1 ].shape) # select 2 features and create feature matrices for the two classes: f1 = np.array([features[ 0 ][:, fn.index( 'spectral_centroid_mean' )], features[ 0 ][:, fn.index( 'energy_entropy_mean' )]]) f2 = np.array([features[ 1 ][:, fn.index( 'spectral_centroid_mean' )], features[ 1 ][:, fn.index( 'energy_entropy_mean' )]]) # plot 2D features plots = [go.Scatter(x=f1[ 0 , :], y=f1[ 1 , :], name=class_names[ 0 ], mode= 'markers' ), go.Scatter(x=f2[ 0 , :], y=f2[ 1 , :], name=class_names[ 1 ], mode= 'markers' )] mylayout = go.Layout(xaxis=dict(title= "spectral_centroid_mean" ), yaxis=dict(title= "energy_entropy_mean" )) plotly.offline.iplot(go.Figure(data=plots, layout=mylayout))

Este ejemplo traza el tamaño de las matrices de características para las dos clases (10 x 138 como se explicó anteriormente) y devuelve la siguiente gráfica de las distribuciones para las dos características seleccionadas (media del centroide espectral y media de la entropía de energía):

Se puede ver que las dos características pueden discriminar entre las dos clases con una precisión muy alta (solo una muestra de canción clásica se clasificará incorrectamente con un clasificador lineal simple). En particular, la media de los valores del centroide espectral tiene valores más altos para las muestras de metal, mientras que la media de la entropía de energía tiene valores más altos para las muestras de entropía de energía. Por supuesto, esto es solo una pequeña demostración de una tarea muy simple y con pocas muestras. Como veremos en la siguiente Sección, la clasificación basada en características de audio no siempre es fácil y requiere más de dos características...

Clasificación de audio: entrenar el clasificador de audio

Habiendo visto cómo extraer vectores de características de audio por cuadro a corto plazo, segmento y para grabaciones completas, ahora podemos proceder a construir modelos supervisados para tareas de clasificación particulares. Todo lo que necesitamos es un conjunto de archivos de audio y las respectivas etiquetas de clase. pyAudioAnalysis asume que los archivos de audio están organizados en carpetas y cada carpeta representa una clase de audio diferente.

En el ejemplo de la Sección anterior, hemos visto cómo se diferencian dos características para dos clases de géneros musicales, a partir de respectivos archivos WAV organizados en dos carpetas. El ejemplo 5 muestra cómo se pueden usar las mismas características para entrenar un clasificador SVM simple: cada punto de una cuadrícula en el espacio de características 2D se clasifica luego en cualquiera de las dos clases. Esta es una forma de visualizar la superficie de decisión del clasificador.

 # Example5: plot 2 features for 10 2-second samples # from classical and 10 from metal music. # also train an SVM classifier and draw the respective # decision surfaces from pyAudioAnalysis import MidTermFeatures as aF import os import numpy as np from sklearn.svm import SVC import plotly.graph_objs as go import plotly dirs = [ "data/music/classical" , "data/music/metal" ] class_names = [os.path.basename(d) for d in dirs] m_win, m_step, s_win, s_step = 1 , 1 , 0.1 , 0.05 # segment-level feature extraction: features = [] for d in dirs: # get feature matrix for each directory (class) f, files, fn = aF.directory_feature_extraction(d, m_win, m_step, s_win, s_step) features.append(f) # select 2 features and create feature matrices for the two classes: f1 = np.array([features[ 0 ][:, fn.index( 'spectral_centroid_mean' )], features[ 0 ][:, fn.index( 'energy_entropy_mean' )]]) f2 = np.array([features[ 1 ][:, fn.index( 'spectral_centroid_mean' )], features[ 1 ][:, fn.index( 'energy_entropy_mean' )]]) # plot 2D features p1 = go.Scatter(x=f1[ 0 , :], y=f1[ 1 , :], name=class_names[ 0 ], marker=dict(size= 10 ,color= 'rgba(255, 182, 193, .9)' ), mode= 'markers' ) p2 = go.Scatter(x=f2[ 0 , :], y=f2[ 1 , :], name=class_names[ 1 ], marker=dict(size= 10 ,color= 'rgba(100, 100, 220, .9)' ), mode= 'markers' ) mylayout = go.Layout(xaxis=dict(title= "spectral_centroid_mean" ), yaxis=dict(title= "energy_entropy_mean" )) y = np.concatenate((np.zeros(f1.shape[ 1 ]), np.ones(f2.shape[ 1 ]))) f = np.concatenate((f1.T, f2.T), axis = 0 ) # train the svm classifier cl = SVC(kernel= 'rbf' , C= 20 ) cl.fit(f, y) # apply the trained model on the points of a grid x_ = np.arange(f[:, 0 ].min(), f[:, 0 ].max(), 0.002 ) y_ = np.arange(f[:, 1 ].min(), f[:, 1 ].max(), 0.002 ) xx, yy = np.meshgrid(x_, y_) Z = cl.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape) / 2 # and visualize the grid on the same plot (decision surfaces) cs = go.Heatmap(x=x_, y=y_, z=Z, showscale= False , colorscale= [[ 0 , 'rgba(255, 182, 193, .3)' ], [ 1 , 'rgba(100, 100, 220, .3)' ]]) mylayout = go.Layout(xaxis=dict(title= "spectral_centroid_mean" ), yaxis=dict(title= "energy_entropy_mean" )) plotly.offline.iplot(go.Figure(data=[p1, p2, cs], layout=mylayout))

resultados en:

En los dos ejemplos anteriores (4 y 5), las matrices f1 y f2 se pueden usar para nuestra tarea de clasificación, como en cualquier otro caso cuando usamos scikit-learn y la mayoría de las bibliotecas similares de ML: se requiere una matriz X de vectores de características (esto se puede generar fusionando f1 y f2 como se muestra en el Ejemplo 5), junto con una matriz y del mismo número de filas con X, correspondientes a los valores objetivo (las etiquetas de clase).

Luego, como con cualquier otra tarea de clasificación, X se puede dividir en entrenamiento y prueba (usando submuestreo aleatorio, validación cruzada o dejar uno fuera) y para cada división de entrenamiento/prueba una métrica de evaluación (como F1, Recall , Precisión, Exactitud o incluso toda la matriz de confusión). Finalmente, informamos la métrica de evaluación general, como el promedio entre todas las divisiones de entrenamiento/prueba (según el método). Este procedimiento se puede seguir como se describe en muchos tutoriales (en mi opinión, se muestra mejor en la página web de scikit-learn), tan pronto como tengamos las funciones de audio, como se describe en los ejemplos anteriores.

Alternativamente, pyAudioAnalysis proporciona una funcionalidad envuelta que incluye tanto la extracción de características como el entrenamiento del clasificador. esto se hace en

 audioTrainTest.extract_features_and_train()
función, que

(a) función de primeras llamadas

 MidTermFeatures.multi_directory_feature_extraction()
, que llama
MidTermFeatures.directory_feature_extraction()
, para extraer matrices de características para todas las carpetas dadas de archivos de audio (asumiendo que cada carpeta corresponde a una clase)

(b) genera matrices X e y, también conocidas como matriz de características para la tarea de clasificación y las respectivas etiquetas de clase.

(c) evalúa el clasificador para diferentes parámetros (por ejemplo, C si se seleccionan clasificadores SVM)

(d) devuelve los resultados de evaluación impresos y guarda el mejor modelo en un archivo binario (para que lo use otra función para realizar pruebas, como se muestra más adelante)

El siguiente ejemplo entrena un clasificador SVM para la tarea de clasificación de música clásica/metal:

 # Example6: use pyAudioAnalysis wrapper # to extract feature and train SVM classifier # for 20 music (10 classical/10 metal) song samples from pyAudioAnalysis.audioTrainTest import extract_features_and_train mt, st = 1.0 , 0.05 dirs = [ "data/music/classical" , "data/music/metal" ] extract_features_and_train(dirs, mt, mt, st, st, "svm_rbf" , "svm_classical_metal" )

Los resultados finales están aquí:

 classical metal OVERALL C PRE REC f1 PRE REC f1 ACC f1 0.001 79.4 81.0 80.2 80.6 79.0 79.8 80.0 80.0 0.010 77.2 78.0 77.6 77.8 77.0 77.4 77.5 77.5 0.500 76.5 75.0 75.8 75.5 77.0 76.2 76.0 76.0 1.000 88.2 75.0 81.1 78.3 90.0 83.7 82.5 82.4 5.000 100.0 83.0 90.7 85.5 100.0 92.2 91.5 91.4 best f1 best Acc 10.000 100.0 78.0 87.6 82.0 100.0 90.1 89.0 88.9 20.000 100.0 75.0 85.7 80.0 100.0 88.9 87.5 87.3 Confusion Matrix: cla met cla 41.50 8.50 met 0.00 50.00 Selected params: 5.00000

La matriz de confusión general para el mejor parámetro C (en ese caso C=5), indica que existe (en promedio para todos los experimentos de submuestreo) casi un 9% de probabilidad de que un segmento clásico se clasifique como metal (que, dicho sea de paso, , tiene sentido si recordamos los diagramas de distribución de características que hemos visto anteriormente).

audioTrainTest.extract_features_and_train() de pyAudioAnalysis lib, es un contenedor que (a) lee todos los archivos de audio organizados en una lista de carpetas y extrae estadísticas de características promediadas a largo plazo (b) luego entrena un clasificador asumiendo que los nombres de las carpetas representan clases de audio.

El modelo entrenado se guardará en

 svm_classical_metal
(último argumento de la función), junto con los parámetros de extracción de características (tamaños y pasos de ventana a corto plazo y de segmento). Tenga en cuenta que también se crea otro archivo llamado
 svm_classical_metalMEANS
, que almacena los parámetros de normalización, es decir, la media y el estándar utilizados para normalizar las funciones de audio antes del entrenamiento y la prueba. Finalmente, aparte de las SVM, el contenedor es compatible con la mayoría de los clasificadores de scikit-learn, como árboles de decisión y aumento de gradiente.

Clasificación de audio: aplicar el clasificador de audio

Por lo tanto, hemos entrenado un clasificador de audio para distinguir entre dos clases de audio (clásico y metal) en función de los promedios de las estadísticas de características, como se describió anteriormente. Ahora veamos cómo podemos usar el modelo entrenado para predecir la clase de un archivo de audio desconocido . Con este fin, vamos a utilizar pyAudioAnalysis'

 audioTrainTest.file_classification()
como se muestra en el Ejemplo 7 :

 # Example7: use trained model from Example6 # to classify an unknown sample (song) from pyAudioAnalysis import audioTrainTest as aT files_to_test = [ "data/music/test/classical.00095.au.wav" , "data/music/test/metal.00004.au.wav" , "data/music/test/rock.00037.au.wav" ] for f in files_to_test: print( f' {f} :' ) c, p, p_nam = aT.file_classification(f, "svm_classical_metal" , "svm_rbf" ) print( f'P( {p_nam[ 0 ]} = {p[ 0 ]} )' ) print( f'P( {p_nam[ 1 ]} = {p[ 1 ]} )' ) print()

resultados en:

 data/music/test/classical .00095 .au.wav: P(classical= 0.6365171558566964 ) P(metal= 0.3634828441433037 ) data/music/test/metal .00004 .au.wav: P(classical= 0.1743387880715576 ) P(metal= 0.8256612119284426 ) data/music/test/rock .00037 .au.wav: P(classical= 0.2757302369241449 ) P(metal= 0.7242697630758552 )

Podemos ver que le hemos pedido al clasificador que prediga para tres archivos. El primero fue un segmento de música clásica (obviamente, no se usó en el conjunto de datos de entrenamiento) y, de hecho, la parte posterior estimada de la música clásica fue más alta que la del metal. De igual manera, probamos contra un segmento de metal y se clasificó como metal con un posterior del 86%. Finalmente, el tercer archivo de prueba no pertenece a las dos clases (es un segmento de una canción de rock), sin embargo, el resultado tiene sentido ya que se clasifica en la clase más cercana.

audioTrainTest.file_classification() obtiene el modelo entrenado y la ruta de un archivo de audio desconocido y presenta extracción y predicción del clasificador para el archivo desconocido, devolviendo la clase ganadora (prevista), las clases posteriores y los nombres de las clases respectivas.

El ejemplo anterior mostró cómo podemos aplicar el clasificador de audio entrenado a un archivo de audio desconocido para predecir su etiqueta de audio. Además de eso, pyAudioAnalysis proporciona la función

 audioTrainTest.evaluate_model_for_folders()
, que acepta una lista de carpetas, asumiendo que sus nombres base son nombres de clase (como hacemos durante el entrenamiento), y aplica de forma repetitiva un clasificador previamente entrenado en los archivos de audio de cada carpeta. Al final, genera métricas de rendimiento como la matriz de confusión y las curvas ROC:

 # Example8: use trained model from Example6 # to classify audio files organized in folders # and evaluate the predictions, assuming that # foldernames = classes names as during training from pyAudioAnalysis import audioTrainTest as aT aT.evaluate_model_for_folders([ "data/music/test/classical" , "data/music/test/metal" ], "svm_classical_metal" , "svm_rbf" , "classical" )

da como resultado las siguientes cifras de la matriz de confusión, precisión/recuperación/f1 por clase, y curva de precisión/recuperación y curva ROC para una "clase de interés" (aquí hemos proporcionado clásica). Tenga en cuenta que las subparcelas 3 y 4 evalúan el clasificador como un detector de la clase "clásica" (último argumento). Por ejemplo, el último gráfico muestra la tasa de verdaderos positivos frente a la tasa de falsos positivos, y esto se logra simulando el umbral de la parte posterior de la clase de interés (clásica): a medida que aumenta el umbral de probabilidad, aumentan las tasas de verdaderos positivos y falsos negativos. , la pregunta es: ¿qué tan "empinado" es el aumento de la tasa positiva verdadera? Puede encontrar más información sobre la curva ROC aquí . La curva de precisión/recuperación es equivalente a ROC, pero muestra ambas métricas en el mismo gráfico en el eje y para diferentes umbrales de la parte posterior que se muestra en el eje x (más información aquí ).

En nuestro ejemplo, podemos ver que para un umbral de probabilidad de, digamos, 0.6 podemos tener una Precisión del 100 % con alrededor del 80 % de Recall para clásico: esto significa que todos los archivos detectados serán de hecho clásicos, mientras que estaremos "perdiendo" casi 1 de cada 5 canciones "clásicas" como metal. Otra nota importante aquí es que no hay un "mejor" punto de operación del clasificador, eso depende de la aplicación individual .

Regresión de audio: predicción de valores objetivo continuos

La regresión es la tarea de entrenar una función de mapeo desde un espacio de características a una variable objetivo continua (en lugar de una clase discreta). Algunas aplicaciones de regresión de señales de audio incluyen: reconocimiento de emociones de voz y/o música usando clases no discretas (emoción y excitación) y estimación de atributos suaves de la música (por ejemplo, para detectar la "bailabilidad" de Spotify).

En este artículo, demostramos cómo se puede usar la regresión para detectar el tono de un segmento de canto coral, sin usar ningún enfoque de procesamiento de señales (por ejemplo, el método de autocorrelación). Con este fin, hemos utilizado parte del Choral Signing Dataset , que es un conjunto de grabaciones acapella con las respectivas anotaciones de tono . Aquí, hemos seleccionado usar este conjunto de datos para producir anotaciones de tono a nivel de segmento: dividimos las grabaciones de canto en segmentos pequeños (0,5 segundos) y para cada segmento, calculamos la media y la desviación estándar del tono (que proporciona el conjunto de datos). ). Estas dos métricas son f0_mean y f0_std respectivamente y son los dos valores de regresión objetivo que se muestran en el siguiente código. A continuación puede escuchar una muestra de 0,5 segundos con una f0 baja y una desviación de f0 baja:


y una muestra de 0,5 segundos con una f0 alta y una desviación de f0 alta:

Obtener segmentos de 0,5 segundos del Conjunto de datos de señas corales conduce a miles de muestras, pero para fines de demostración de este artículo, hemos utilizado alrededor de 120 muestras de entrenamiento y 120 de prueba, disponibles en

 data/regression/f0/segments_train
y
 data/regression/f0/segments_train folders
del repositorio de github. Nuevamente, para demostrar el entrenamiento y las pruebas del modelo de regresión, estamos usando pyAudioAnalysis, que trata la segmentación de audio de la siguiente manera:

  1. dada una ruta que contiene archivos de audio y
  2. un conjunto de archivos <tarea_regresión>.csv con el formato <nombre_archivo_audio>, <valor>
  3. pyAudioAnalysis entrena un modelo de regresión para cada archivo <regression_task>.csv

En nuestro ejemplo,

 data/regression/f0/segments_train
contiene 120 archivos WAV y dos archivos CSV de 120 líneas llamados f0.csv y f0_std.csv. Cada CSV corresponde a una tarea de regresión separada y cada línea del CSV corresponde a la verdad básica del archivo de audio respectivo. Para entrenar, evaluar y guardar los dos modelos de regresión para nuestro ejemplo, se usa el siguiente código:

 # Example9: # Train two linear SVM regression models # that map song segments to pitch and pitch deviation # The following function searches for .csv files in the # input folder. For each csv of the format <filename>,<value> # a separate regresion model is trained from pyAudioAnalysis import audioTrainTest as aT aT.feature_extraction_train_regression( "data/regression/f0/segments_train" , 0.5 , 0.5 , 0.05 , 0.05 , "svm" , "singing" , False )

Ya que

 data/regression/f0/segments_train
contiene dos CSV, a saber
 f0.csv
y
 f0_std.csv
, el código anterior da como resultado dos modelos:
 singing_f0
y
 singing_f0_std
(
 singing
El prefijo se proporciona como un séptimo argumento en la función anterior y se usa para todos los modelos entrenados).

Asimismo, este es el resultado del proceso de evaluación ejecutado internamente por la

 audioTrainTest.feature_extraction_train_regression()
función:

 Analyzing file 1 of 120 : data/regression/f0/segments_train/CSD_ER_alto_1.wav_segments_263 .1595 .wav Analyzing file 2 of 120 : data/regression/f0/segments_train/CSD_ER_alto_1.wav_segments_264 .957 .wav Analyzing file 3 of 120 : data/regression/f0/segments_train/CSD_ER_alto_1.wav_segments_301 .632 .wav Analyzing file 4 of 120 : data/regression/f0/segments_train/CSD_ER_alto_1.wav_segments_328 .748 .wav Analyzing file 5 of 120 : data/regression/f0/segments_train/CSD_ER_alto_1.wav_segments_331 .2835 .wav ... ... ... Analyzing file 119 of 120 : data/regression/f0/segments_train/CSD_ND_alto_4.wav_segments_383 .483 .wav Analyzing file 120 of 120 : data/regression/f0/segments_train/CSD_ND_alto_4.wav_segments_394 .315 .wav Feature extraction complexity ratio: 44.7 x realtime Regression task f0_std Param MSE T-MSE R-MSE 0.0010 736.98 10.46 661.43 0.0050 585.38 9.64 573.52 0.0100 522.73 9.17 539.87 0.0500 529.10 7.41 657.36 0.1000 379.13 6.73 541.03 0.2500 361.75 5.09 585.60 0.5000 323.20 3.88 522.12 best 1.0000 386.30 2.58 590.08 5.0000 782.14 0.99 548.65 10.0000 1140.95 0.47 529.20 Selected params: 0.50000 Regression task f0 Param MSE T-MSE R-MSE 0.0010 3103.83 44.65 3121.97 0.0050 2772.07 41.38 3098.40 0.0100 2293.79 37.57 2935.42 0.0500 1206.49 19.69 2999.49 0.1000 1012.29 13.94 3115.49 0.2500 839.82 8.64 3147.30 0.5000 758.04 5.62 2917.62 1.0000 689.12 3.53 3087.71 best 5.0000 892.52 1.07 3061.10 10.0000 1158.60 0.47 2889.27 Selected params: 1.00000

La primera columna de los resultados anteriores representa el parámetro del clasificador evaluado durante el experimento. La segunda columna es el error cuadrático medio (MSE) del parámetro estimado (f0_std y f0 para los dos modelos), medido en los datos de la prueba interna (validación) de cada experimento. La 3.ª columna muestra el MSE de entrenamiento y la 4.ª columna muestra una estimación del modelo aleatorio (que se utilizará como métrica de referencia). Podemos ver que el objetivo f0_std es mucho más difícil de estimar a través de un modelo de regresión: el mejor MSE logrado (320) es ligeramente mejor que el MSE aleatorio promedio (alrededor de 550). Por otro lado, para el objetivo f0, el modelo de regresión entrenado logra un MSE de 680 con un error de referencia de alrededor de 3000. En otras palabras, el modelo logra un aumento del rendimiento de x5 en relación con el modelo aleatorio, mientras que para el f0_std este aumento es alrededor de x1.5.

Ahora, una vez que los dos modelos de regresión se entrenan, evalúan y guardan, podemos usarlos para asignar cualquier segmento de audio a f0 o f0_std. Example10 demuestra cómo hacer esto usando

 audioTrainTest.file_regression()
:

 # Example10 # load trained regression model for f0 and apply it to a folder # of WAV files and evaluate (use csv file with ground truths) import glob import csv import os import numpy as np import plotly.graph_objs as go import plotly from pyAudioAnalysis import audioTrainTest as aT # read all files in testing folder: wav_files_to_test = glob.glob( "data/regression/f0/segments_test/*.wav" ) ground_truths = {} with open( 'data/regression/f0/segments_test/f0.csv' , 'r' ) as file: reader = csv.reader(file, delimiter = ',' ) for row in reader: ground_truths[row[ 0 ]] = float(row[ 1 ]) estimated_val, gt_val = [], [] for w in wav_files_to_test: # for each audio file # get the estimates for all regression models starting with "singing" values, tasks = aT.file_regression(w, "singing" , "svm" ) # check if there is ground truth available for the current file if os.path.basename(w) in ground_truths: # ... and append ground truth and estimated values # for the f0 task estimated_val.append(values[tasks.index( 'f0' )]) gt_val.append(ground_truths[os.path.basename(w)]) # compute mean square error: mse = ((np.array(estimated_val) - np.array(gt_val))** 2 ).mean() print( f'Testing MSE= {mse} ' ) # plot real vs predicted results p = go.Scatter(x=gt_val, y=estimated_val, mode= 'markers' ) mylayout = go.Layout(xaxis=dict(title= "f0 real" ), yaxis=dict(title= "f0 predicted" ), showlegend= False ) plotly.offline.iplot(go.Figure(data=[p, go.Scatter(x=[min(gt_val+ estimated_val), max(gt_val+ estimated_val)], y=[min(gt_val+ estimated_val), max(gt_val+ estimated_val)])], layout=mylayout))

En este ejemplo, demostramos cómo

 audioTrainTest.file_regression()
se puede utilizar para un conjunto de archivos de un conjunto de datos de prueba. Tenga en cuenta que esta función devuelve decisiones y nombres de tareas para todas las tareas de regresión disponibles que comienzan con el prefijo proporcionado (en nuestro caso, "cantando"). Los resultados se muestran a continuación: podemos ver que los valores real y predicho están bastante cerca para la tarea f0.

 Testing MSE=492.7141430351564
audioTrainTest.feature_extraction_train_regression() lee una carpeta de archivos WAV y asume que cada CSV de formato (<ruta>,<valor>) es un archivo de regresión real. Luego extrae características de audio, entrena y guarda el número respectivo de modelos usando un prefijo (proporcionado también como argumento). audioTrainTest.file_regression() lee los modelos guardados y devuelve resultados de regresión previstos para todas las tareas.

Acerca de la segmentación de audio

Hasta ahora, hemos visto cómo entrenar modelos supervisados que asignan estadísticas de características de audio a nivel de segmento a etiquetas de clase (clasificación de audio) o objetivos de valor real (regresión de audio). Además, hemos visto cómo utilizar estos modelos para predecir la etiqueta de un archivo de audio desconocido, por ejemplo, una expresión verbal o una canción completa o un segmento de una canción. En todos estos casos, la suposición seguida fue que las señales de audio desconocidas pertenecían a una sola etiqueta . Por ejemplo, una canción pertenece a un género particular, un segmento de canto tiene un valor de tono particular y una expresión verbal tiene una emoción particular. Sin embargo, en las aplicaciones del mundo real, hay muchos casos en los que las señales de audio no son segmentos de contenido homogéneo, sino flujos de audio complejos que contienen muchos segmentos sucesivos de diferentes etiquetas de contenido. Una grabación de un diálogo del mundo real, por ejemplo, es una secuencia de etiquetas de identidades o emociones del hablante.

Las grabaciones del mundo real no son segmentos de contenido homogéneo sino secuencias de segmentos de diferentes etiquetas.

Por esa razón, la segmentación de audio es un paso importante del análisis de audio y se trata de segmentar una grabación de audio larga en una secuencia de segmentos que son de contenido homogéneo. La definición de homogeneidad es relativa al dominio de la aplicación: si, por ejemplo, estamos interesados en el reconocimiento del hablante, un segmento se considera homogéneo si pertenece al mismo hablante.

Segmentación de audio: utilizando modelos preentrenados (supervisados)

Los algoritmos de segmentación de hTAudio se pueden dividir en dos categorías: (a) supervisados y (b) no supervisados o semisupervisados. La segmentación supervisada se basa en un modelo de segmento preentrenado que puede clasificar segmentos homogéneos. En esta sección, mostraremos cómo lograr la segmentación utilizando un segmento de tamaño fijo simple y un modelo previamente entrenado. Supongamos que ha entrenado un modelo de segmento para distinguir entre las clases S y M. El método de segmentación que se presenta en esta sección es tan simple como eso: divida la grabación de audio en segmentos de tamaño fijo que no se superpongan con la misma longitud que el utilizado para entrenar el modelo. Luego clasifique cada segmento de tamaño fijo de la transmisión de audio usando el modelo entrenado y finalmente combine los segmentos sucesivos que contienen la misma etiqueta de clase. Este proceso se ilustra en el siguiente diagrama.

En el Ejemplo 6, habíamos entrenado un modelo que clasifica segmentos de música desconocidos en "metal" y "clásico" (el modelo se guardó en el archivo

 svm_classical_metal
). Usemos este modelo para segmentar una grabación de 30 segundos que contiene partes de metal y clásicas (no superpuestas). Esta grabación se almacena en
 data/music/metal_classical_mix.wav
del código del artículo. También,
 data/music/metal_classical_mix.segment
contiene el respectivo archivo de anotación de verdad en tierra con el formato <start_segment_sec>\t<end_segment_sec>\t<segment_label>. Este es el archivo de verdad del terreno:

 0 7.5 classical 7.5 15 metal 15 19 classical 19 29 metal

La funcionalidad de segmentación supervisada de ventana fija se implementa en función

 audioSegmentation.mid_term_file_classification()
, como se muestra en el Ejemplo 11 :

 # Example 11 # Supervised audio segmentation example: # - Apply model "svm_classical_metal" to achieve fix-sized, supervised audio segmentation # on file data/music/metal_classical_mix.wav # - Function audioSegmentation.mid_term_file_classification() uses pretrained model and applies # the mid-term step that has been used when training the model (1 sec in our case as shown in Example6) # - data/music/metal_classical_mix.segments contains the ground truth of the audio file from pyAudioAnalysis.audioSegmentation import mid_term_file_classification, labels_to_segments from pyAudioAnalysis.audioTrainTest import load_model labels, class_names, _, _ = mid_term_file_classification( "data/music/metal_classical_mix.wav" , "svm_classical_metal" , "svm_rbf" , True , "data/music/metal_classical_mix.segments" ) print( "\nFix-sized segments:" ) for il, l in enumerate(labels): print( f'fix-sized segment {il} : {class_names[int(l)]} ' ) # load the parameters of the model (actually we just want the mt_step here): cl, m, s, m_classes, mt_win, mt_step, s_win, s_step, c_beat = load_model( "svm_classical_metal" ) # print "merged" segments (use labels_to_segments()) print( "\nSegments:" ) segs, c = labels_to_segments(labels, mt_step) for iS, seg in enumerate(segs): print( f'segment {iS} {seg[ 0 ]} sec - {seg[ 1 ]} sec: {class_names[int(c[iS])]} ' )

 audioSegmentation.mid_term_file_classification()
devuelve una lista de identificadores de etiqueta (uno para cada ventana de segmento de tamaño fijo), una lista de nombres de clase y la matriz de precisión y confusión (si también se proporciona la verdad básica, como en el ejemplo anterior). los
 labels
list corresponde a segmentos de tamaño fijo de longitud igual al paso de segmento utilizado durante el entrenamiento del modelo (1 segundo en el ejemplo anterior, según el Ejemplo 6). Por eso usamos
 audioTrainTest.load_model()
, para cargar la ventana del segmento directamente desde el archivo del modelo. También, usamos
 audioSegmentation.labels_to_segments()
para generar la lista de segmentos finales, basada en la ruta de fusión simple (es decir, concatenar segmentos sucesivos de 1 segundo que tienen la misma etiqueta).

El resultado del código anterior es el siguiente (el rojo corresponde a la realidad del terreno y el azul a las etiquetas de los segmentos previstos):

 Overall Accuracy: 0.79 Fix-sized segments: fix-sized segment 0 : classical fix-sized segment 1 : classical fix-sized segment 2 : classical fix-sized segment 3 : classical fix-sized segment 4 : classical fix-sized segment 5 : classical fix-sized segment 6 : classical fix-sized segment 7 : metal fix-sized segment 8 : metal fix-sized segment 9 : metal fix-sized segment 10 : metal fix-sized segment 11 : metal fix-sized segment 12 : classical fix-sized segment 13 : metal fix-sized segment 14 : metal fix-sized segment 15 : classical fix-sized segment 16 : classical fix-sized segment 17 : classical fix-sized segment 18 : metal fix-sized segment 19 : metal fix-sized segment 20 : classical fix-sized segment 21 : metal fix-sized segment 22 : classical fix-sized segment 23 : classical fix-sized segment 24 : metal fix-sized segment 25 : metal fix-sized segment 26 : metal fix-sized segment 27 : metal fix-sized segment 28 : metal fix-sized segment 29 : metal Segments: segment 0 0.0 sec - 7.0 sec: classical segment 1 7.0 sec - 12.0 sec: metal segment 2 12.0 sec - 13.0 sec: classical segment 3 13.0 sec - 15.0 sec: metal segment 4 15.0 sec - 18.0 sec: classical segment 5 18.0 sec - 20.0 sec: metal segment 6 20.0 sec - 21.0 sec: classical segment 7 21.0 sec - 22.0 sec: metal segment 8 22.0 sec - 24.0 sec: classical segment 9 24.0 sec - 29.0 sec: metal

 audioSegmentation.labels_to_segments()
devuelve la información más "compacta" y útil para el "usuario" final. Además, tenga en cuenta que los errores de segmentación son:

(a) debido a errores de clasificación del clasificador de segmentos (p. ej., los segmentos 22 y 23 se clasifican erróneamente como clásicos cuando su verdadera etiqueta es metal o

(b) debido a problemas de resolución de tiempo : por ejemplo, de acuerdo con la realidad básica, el primer segmento de música clásica termina en 7,5 segundos, mientras que nuestro modelo se aplica cada 1 segundo, por lo que lo mejor que logrará esta metodología de ventana fija es reconocer música clásica hasta 7 u 8 seg. Obviamente, esto se puede manejar a través de un paso más pequeño en la ventana del segmento (es decir, introduciendo una superposición de segmento), sin embargo, esto será con un aumento significativo en las demandas computacionales (se realizarán más predicciones a nivel de segmento).

Segmentación de audio: sin supervisión

Hemos visto cómo, dado un modelo de segmento de audio preentrenado, podemos dividir una grabación de audio en segmentos de contenido homogéneo. Sin embargo, en muchos casos, no somos conscientes del problema de clasificación exacto, o no tenemos los datos para entrenar dicho clasificador. Dichos casos requieren soluciones no supervisadas o semisupervisadas, como se muestra en los casos de uso a continuación:

Segmentación musical

La extracción de partes estructurales de una pista de música es un caso de uso típico en el que se puede utilizar el análisis de audio no supervisado. Dado que obviamente es bastante difícil tener un clasificador que distinga entre partes de canciones, podemos responder a la pregunta: ¿puedes agrupar segmentos de canciones para que los segmentos del mismo grupo suenen como si pertenecieran a la misma parte de una canción? En el siguiente ejemplo, "Billie Jean" de M. Jackson se utiliza como entrada para el proceso de extracción de características a nivel de segmento descrito anteriormente y se aplica un agrupamiento simple de k-medias en las secuencias de vectores de características resultantes. Luego, los segmentos de cada grupo se concatenan en una grabación artificial y se guardan en archivos de audio. Cada "grabación de grupo" artificial muestra cómo se pueden agrupar las partes de la canción y si esta agrupación tiene algún sentido en términos de estructura musical. El código se muestra en el Ejemplo 12 :

 # Example 12: Unsupervised Music Segmentation # # This example groups of song segments to clusters of similar content import os, sklearn.cluster from pyAudioAnalysis.MidTermFeatures import mid_feature_extraction as mT from pyAudioAnalysis.audioBasicIO import read_audio_file, stereo_to_mono from pyAudioAnalysis.audioSegmentation import labels_to_segments from pyAudioAnalysis.audioTrainTest import normalize_features import numpy as np import scipy.io.wavfile as wavfile import IPython # read signal and get normalized segment feature statistics: input_file = "data/music/billie_jean.wav" fs, x = read_audio_file(input_file) mt_size, mt_step, st_win = 5 , 0.5 , 0.1 [mt_feats, st_feats, _] = mT(x, fs, mt_size * fs, mt_step * fs, round(fs * st_win), round(fs * st_win * 0.5 )) (mt_feats_norm, MEAN, STD) = normalize_features([mt_feats.T]) mt_feats_norm = mt_feats_norm[ 0 ].T # perform clustering n_clusters = 5 x_clusters = [np.zeros((fs, )) for i in range(n_clusters)] k_means = sklearn.cluster.KMeans(n_clusters=n_clusters) k_means.fit(mt_feats_norm.T) cls = k_means.labels_ # save clusters to concatenated wav files segs, c = labels_to_segments(cls, mt_step) # convert flags to segment limits for sp in range(n_clusters): count_cl = 0 for i in range(len(c)): # for each segment in each cluster (>2 secs long) if c[i] == sp and segs[i, 1 ]-segs[i, 0 ] > 2 : count_cl += 1 # get the signal and append it to the cluster's signal (followed by some silence) cur_x = x[int(segs[i, 0 ] * fs): int(segs[i, 1 ] * fs)] x_clusters[sp] = np.append(x_clusters[sp], cur_x) x_clusters[sp] = np.append(x_clusters[sp], np.zeros((fs,))) # write cluster's signal into a WAV file print( f'cluster {sp} : {count_cl} segments {len(x_clusters[sp])/float(fs)} sec total dur' ) wavfile.write( f'cluster_ {sp} .wav' , fs, np.int16(x_clusters[sp])) IPython.display.display(IPython.display.Audio( f'cluster_ {sp} .wav' ))

El código anterior guarda los sonidos de grupos artificiales en archivos WAV y también los muestra en un mini reproductor en el propio portátil, pero también he subido los sonidos de grupos a YouTube (no se me ocurrió una manera obvia de insertarlos en el artículo) . Entonces, escuchemos los grupos resultantes y veamos si corresponden a partes de canciones homogéneas:

Este es claramente el estribillo de la canción, repetido dos veces (aunque la segunda vez es mucho más larga ya que incluye más repeticiones sucesivas y un pequeño solo)

El grupo 2 tiene un solo segmento que corresponde a la introducción de la canción.

Cluster 3 es el pre-estribillo de la canción.

El cuarto grupo contiene segmentos de los versos de la canción (si excluye el segmento pequeño al principio). El quinto grupo no se muestra ya que solo incluía un segmento muy corto casi silencioso al comienzo de la canción. En todos los casos, los grupos representaban (con algunos errores, por supuesto) componentes estructurales de la canción, incluso usando este enfoque muy simple y sin hacer uso de ningún conocimiento supervisado "externo", aparte de características similares, pueden significar contenido musical similar.

Los grupos de segmentos de canciones pueden corresponder a elementos estructurales de canciones si se utilizan las funciones de audio apropiadas.

Por último, tenga en cuenta que ejecutar el código anterior puede dar como resultado el mismo agrupamiento pero con un orden diferente de ID de clúster (y, por lo tanto, orden en los archivos de audio resultantes). Esto probablemente se deba a la semilla aleatoria k-means.

Diarización del hablante

Esta es la tarea que, dada una grabación de voz desconocida, responde a la pregunta: "¿quién habla cuándo?". En aras de la simplicidad, supongamos que ya conocemos el número de hablantes en la grabación. ¿Cuál es la forma más sencilla de resolver esta tarea? Obviamente, primero extraiga características de audio a nivel de segmento y luego realice algún tipo de agrupación, con la esperanza de que las agrupaciones resultantes correspondan a las identificaciones de los altavoces. En el siguiente ejemplo (13), usamos exactamente la misma canalización que la que se siguió en el Ejemplo 12, donde agrupamos una canción en sus partes estructurales. Sólo hemos cambiado el tamaño de la ventana del segmento a 2 seg con un paso de 0,1 seg y una ventana de corto plazo más pequeña (50 mseg), ya que las señales de voz se caracterizan, en general, por cambios más rápidos en sus atributos principales, debido a la existencia de fonemas muy diferentes, algunos de los cuales duran apenas unos segundos (en cambio, la nota musical dura varios mseg, incluso en las músicas más rápidas).

Entonces, el Ejemplo 13 usa la misma lógica de agrupamiento de vectores de funciones de audio. Esta vez, la señal de entrada es una señal de voz con 4 altavoces (esto se sabe de antemano), por lo que establecemos nuestro tamaño de grupo de kmeans en 4:

 import os, sklearn.cluster from pyAudioAnalysis.MidTermFeatures import mid_feature_extraction as mT from pyAudioAnalysis.audioBasicIO import read_audio_file, stereo_to_mono from pyAudioAnalysis.audioSegmentation import labels_to_segments from pyAudioAnalysis.audioTrainTest import normalize_features import numpy as np import scipy.io.wavfile as wavfile import IPython # read signal and get normalized segment feature statistics: input_file = "data/diarization_example.wav" fs, x = read_audio_file(input_file) mt_size, mt_step, st_win = 2 , 0.1 , 0.05 [mt_feats, st_feats, _] = mT(x, fs, mt_size * fs, mt_step * fs, round(fs * st_win), round(fs * st_win * 0.5 )) (mt_feats_norm, MEAN, STD) = normalize_features([mt_feats.T]) mt_feats_norm = mt_feats_norm[ 0 ].T # perform clustering n_clusters = 4 x_clusters = [np.zeros((fs, )) for i in range(n_clusters)] k_means = sklearn.cluster.KMeans(n_clusters=n_clusters) k_means.fit(mt_feats_norm.T) cls = k_means.labels_ # save clusters to concatenated wav files segs, c = labels_to_segments(cls, mt_step) # convert flags to segment limits for sp in range(n_clusters): count_cl = 0 for i in range(len(c)): # for each segment in each cluster (>2 secs long) if c[i] == sp and segs[i, 1 ]-segs[i, 0 ] > 2 : count_cl += 1 # get the signal and append it to the cluster's signal (followed by some silence) cur_x = x[int(segs[i, 0 ] * fs): int(segs[i, 1 ] * fs)] x_clusters[sp] = np.append(x_clusters[sp], cur_x) x_clusters[sp] = np.append(x_clusters[sp], np.zeros((fs,))) # write cluster's signal into a WAV file print( f'speaker {sp} : {count_cl} segments {len(x_clusters[sp])/float(fs)} sec total dur' ) wavfile.write( f'diarization_cluster_ {sp} .wav' , fs, np.int16(x_clusters[sp])) IPython.display.display(IPython.display.Audio( f'diarization_cluster_ {sp} .wav' ))

Esta es la grabación inicial.

Y estos son los 4 grupos resultantes (los resultados también se escriben en clips de audio en línea en el cuaderno de jupiter nuevamente):

En el ejemplo anterior, la agrupación de altavoces (o diarización de altavoces, como solemos llamarla) tuvo bastante éxito con algunos errores al comienzo de los segmentos, principalmente debido a limitaciones de resolución de tiempo (se ha utilizado una ventana de 2 segundos). Por supuesto, este no es siempre el caso: la diarización de los altavoces es una tarea difícil, especialmente si (a) hay mucho ruido de fondo (b) el número de altavoces es desconocido de antemano (c) los altavoces no están equilibrados (por ejemplo, un altavoz habla el 60% del tiempo y otro hablante solo el 0,5% del tiempo).

El código de este artículo se proporciona como un cuaderno de notas de Júpiter en este repositorio de GitHub .