Dynamic Time Warping explicado usando el conjunto de datos de Python y HAR

Nodo de origen: 1123947

Este artículo fue publicado como parte del Blogatón de ciencia de datos

Introducción a la envoltura de tiempo dinámica

La clasificación de series de tiempo es una tarea muy común en la que tendrá datos de varios dominios como procesamiento de señales, IoT, actividad humana y más, y el objetivo final es entrenar un modelo específico para que pueda predecir la clase de cualquier serie de tiempo con precisión casi perfecta. El conjunto de datos dado debería tener secuencias de tiempo etiquetadas para que nuestro modelo pueda predecir la clase de la serie de tiempo con precisión.

Una solución clásica a este problema es utilizar el método del algoritmo del vecino más cercano K. Aquí, en este artículo, vamos a omitir el enfoque habitual de la distancia euclidiana y usaremos el Deformación de tiempo dinámica or Métrica DTW. Este método tiene en cuenta que cuando comparamos dos series de tiempo diferentes, pueden variar en longitud y velocidad.

Imagen de portada | Envoltura de tiempo dinámica

El enfoque no es tan complejo, pero es bastante exclusivo del algoritmo DTW o Dynamic Time Warping. Algunos errores comunes pueden incluir ajustar los hiperparámetros en el modelo KNN y simplemente pasar por alto la parte DTW, pero no se debe hacer eso. Una de las principales desventajas del algoritmo es que la complejidad del tiempo aumenta casi exponencialmente para grandes conjuntos de datos con grandes secuencias. Es posible que no sea posible entrenar estos modelos en un período de tiempo razonable. Es posible que deba realizar algunos ajustes adicionales en el algoritmo para acelerarlo, lo que discutiremos más adelante.

¿Cómo funciona la deformación dinámica del tiempo?

DTW o Deformación de tiempo dinámica es un tipo dinámico de algoritmo de programación en el que un gran tipo de problema se subdivide en varios subproblemas, si es posible. Luego, la solución de los subproblemas se almacena para su uso posterior en lugar de volver a calcularla. Esto es bastante similar a Programación dinámica que se utiliza en estructuras de datos y algoritmos y, por lo tanto, esa es la definición básica del algoritmo.

Aquellos que olvidan el pasado están condenados a repetirlo ”- George Santayana

En este artículo, nos centraremos en la parte de ciencia de datos del algoritmo y usaremos Python como nuestro lenguaje base para comprender cómo funciona el algoritmo. Usaremos el módulo de biblioteca conocido como "distancia”Que nos ayudará a calcular la distancia entre dos ondas sinusoidales que están desfasadas entre sí.

Para instalar el paquete, use el siguiente código:

! pip install dtaidistance

Para un entorno de portátil o elimine el signo de exclamación si está instalando a través del símbolo del sistema

Código:

de dtaidistance importar dtw de dtaidistance importar dtw_visualisation como dtwvis de una muestra de importación aleatoria importar numpy as np x = np.arange (0, 20, .5) s1 = np.sin (x) s2 = np.sin (x - 1) ruta = dtw.warping_path (s1, s2) dtwvis.plot_warping (s1, s2, path) distancia = dtw.distance (s1, s2)

salida:

Distancia de deformación óptima entre 2 ondas sinusoidales

La figura anterior muestra la distancia de deformación óptima entre 2 ondas sinusoidales

La figura anterior muestra la distancia más óptima entre todos los puntos entre las 2 ondas sinusoidales. La matriz de programación dinámica también se puede trazar o también se conoce como matriz de programación. Esto mostrará todas las rutas de deformación y como se muestra en la imagen a continuación.

Código:

d, rutas = dtw.warping_paths (s1, s2, window = 20) best_path = dtw.best_path (rutas) dtwvis.plot_warpingpaths (s1, s2, rutas, mejor_ruta)

salida:

La matriz de costos | Envoltura de tiempo dinámica

La matriz de costos

También se construye este tipo de matriz que implementa otro algoritmo conocido como Algoritmo de Needleman-Wunsch. Este algoritmo también se utiliza para alinear proteínas y secuencias de nucleótidos en bioinformática. Junto con esto, se utiliza otro algoritmo que es Distancia Levenshtein. Ambos de estos dos algoritmos pertenecen a la clasificación de los algoritmos de la familia de Programación dinámica.

En la segunda figura, cada llamada es un número que representa la distancia de dos puntos de datos respectivos que se comparan. Un punto de datos para cada secuencia. Un color más oscuro significa una distancia menor y podemos recuperar el camino más óptimo que se extrae en forma de línea roja como se muestra en la imagen. La complejidad temporal de esto es exactamente O (m, n) donde myn son las secuencias respectivas y tienen un costo cuadrático. Es común en el mundo real que las secuencias sean grandes y que el modelo KNN no esté completamente optimizado, el modelo general puede tomar bastante tiempo para entrenarse.

En nuestro caso, somos plenamente conscientes del contexto de nuestro problema y de cómo funcionará nuestro algoritmo de deformación dinámica del tiempo y, por lo tanto, también optimizaremos el modelo para reducir el tiempo total necesario para entrenarlo. Para la mayoría de los casos, encontramos que la ruta óptima es a través de la diagonal de la matriz, como también es el caso de nuestro ejemplo. En el mundo real, el cambio de fase masivo o enorme también es poco común y, por lo tanto, se espera que el cambio de fase esté cerca uno del otro o, de lo contrario, el costo será demasiado alto. Todo esto se puede inferir de la fase de exploración del conjunto de datos y las frecuencias importantes también se pueden extraer con la Transformación de Fourier para asegurarse de que no haya problemas en los datos.

En el último código, mostramos la matriz usando la función 'warping_paths ()' que por defecto usa un argumento 'use_pruning = True' y, por lo tanto, las esquinas superior derecha e inferior izquierda se podan para optimización. Esto significa que después de un cierto costo, los valores no se calculan, ya que sería un uso intensivo de la CPU y ya hemos encontrado el resultado deseado. En versiones anteriores del módulo o la versión anterior a la 2.3.2, tenía que pasar explícitamente este parámetro a la función. Ahora pasaremos a un escenario de caso real para usar nuestro modelo.

Estudio de caso real para comprender el envasado de tiempo dinámico

El reconocimiento de actividad humana o el conjunto de datos HAR es lo que usaremos aquí y se puede descargar desde el Biblioteca UCL que contiene datos de series de tiempo preetiquetados. Estos datos se han capturado con la ayuda de un teléfono inteligente que lleva un ser humano y la acción se puede clasificar en algunas actividades específicas como sentarse, caminar, pararse, acostarse, caminar abajo o caminar arriba. Todo esto se puede inferir de la velocidad angular y la aceleración lineal que se pueden obtener del hardware del teléfono inteligente. Cada observación son datos que constan de 561 características vectoriales con la variable de dominio de tiempo y frecuencia y una etiqueta específica que denota la actividad de una persona en ese momento. Nuestro objetivo aquí es construir un modelo que pueda predecir cualquier actividad de un ser humano utilizando los datos del teléfono inteligente.

Código:

x_train_file = open (r'UCI HAR Dataset / train / X_train.txt ',' r ') y_train_file = open (r'UCI HAR Dataset / train / y_train.txt', 'r') x_test_file = open (r'UCI HAR Dataset / test / X_test.txt ',' r ') y_test_file = open (r'UCI HAR Dataset / test / y_test.txt', 'r') x_train = [] y_train = [] x_test = [] y_test = [] etiquetas = {1: 'CAMINANDO', 2: 'CAMINANDO ARRIBA', 3: 'CAMINANDO BAJANDO', 4: 'SENTADO', 5: 'DE PIE', 6: 'ACOSADO'} para x en x_train_file: x_train.append ( [float (ts) para ts en x.split ()]) para y en y_train_file: y_train.append (int (y.rstrip ('n'))) para x en x_test_file: x_test.append ([float (ts) para ts en x.split ()]) para y en y_test_file: y_test.append (int (y.rstrip ('n'))) x_train = np.array (x_train) y_train = np.array (y_train) x_test = np .array (x_test) y_test = np.array (y_test) colors = ['# D62728', '# 2C9F2C', '# FD7F23', '# 1F77B4', '# 9467BD', '# 8C564A', '# 7F7F7F' , '# 1FBECF', '# E377C2', '# BCBD27']
idx = 0

De ahora en adelante vamos a probar los dos métodos y esta parte es importante ya que una parte mostrará el tiempo de ejecución sin optimización y la otra estará con él.

Código sin optimización:

para r en rango (len (x_test)): distancia = dtw.distance (x_train [idx], x_test [r])

Tiempo de ejecución de la celda:

Tiempo de pared: 25min 16s

Código con optimización:

para r en el rango (len (x_test)): distancia = dtw.distance (x_train [idx], x_test [r], ventana = 20, use_pruning = 'True')

Tiempo de ejecución de la celda:

Tiempo de pared: 1min 42s

Como puede ver, la diferencia significativa en mi computadora portátil que ejecuta la computadora portátil Jupyter de forma nativa en un chip i5 de octava generación. Por lo tanto, utilizaremos un algoritmo KNN con k = 8 y un tamaño de ventana de 20 para la función Dynamic Time Warping. La variable 'idx' ayuda a almacenar el índice de la serie de tiempo para nuestro conjunto de prueba.

Ahora definiremos una función para nuestro modelo y devolveremos el resultado en función del número de vecinos del KNN proporcionado y también el índice de la serie de tiempo respectiva de nuestro conjunto de prueba y luego devolveremos una de las etiquetas que puede ser:

Etiquetas = {CAMINAR, CAMINAR_ESTERIORES, CAMINAR_BAJAS, SENTARSE, DE PIE, ACOSTARSE}

Código funcional:

def classifyNN (k: int, idx: int) -> str: idxs = range (0, x_train.shape [0]) n = x_train.shape [0] distancias = [] contadores = {} c = 1; max_value = 0 para r en rango (n): distance.append (dtw.distance (x_test [idx], x_train [idxs [r]], window = 10, use_pruning = True)) NN = sorted (range (len (distancias )), key = lambda i: distancias [i], reverse = False) [: k] para l en etiquetas.valores (): contadores [l] = 0 para r en NN: l = etiquetas [y_train [r]] contadores [l] + = 1 if (contadores [l])> max_value: max_value = contadores [l] #print ('NN (% d) tiene etiqueta% s'% (c, l)) c + = 1 # Etiqueta con claves de frecuencia máxima = [k para k en contadores si contadores [k] == valor_máx] # devolviendo una muestra aleatoria en caso de que exista un empate entre varias etiquetas return (muestra (claves, 1) [0]) 

Casos de prueba

Aquí demostraremos algunos de los casos de prueba de muestra, pero puede experimentar con cualquier valor 'idx' y encontrar la etiqueta correspondiente para ese caso de prueba.

1. Primer caso de prueba: de pie

Código para mostrar los datos:

k = 20 idx = 3 plt.plot (x_test [idx], label = labels [y_test [idx]], color = colors [y_test [idx] -1], linewidth = 2) plt.xlabel ('Samples @ 50Hz' ) plt.legend (loc = 'superior izquierda') plt.tight_layout ()

salida:

Visualización de la variable de pie

Código para encontrar la etiqueta de prueba:

%% tiempo classifyNN (k, idx)

salida:

Tiempo de pared: 3min 13s 'STANDING' 

2. Segundo caso de prueba: sentado

Código para visualizar los datos:

k = 20 idx = 200 plt.plot (x_test [idx], label = labels [y_test [idx]], color = colors [y_test [idx] -1], linewidth = 2) plt.xlabel ('Samples @ 50Hz' ) plt.legend (loc = 'superior izquierda') plt.tight_layout ()

salida:

Visualización de la variable sentada

Código para encontrar la etiqueta de prueba:

%% tiempo classifyNN (k, idx)

salida:

Tiempo de pared: 3min 15s 'SENTADO' 

3. Tercer caso de prueba: caminar

Código para visualizar los datos:

k = 20 idx = 401 plt.plot (x_test [idx], label = labels [y_test [idx]], color = colors [y_test [idx] -1], linewidth = 2) plt.xlabel ('Samples @ 50Hz' ) plt.legend (loc = 'superior izquierda') plt.tight_layout ()

salida:

Visualización de la variable que camina | Deformación de tiempo dinámica

Código para encontrar la etiqueta de prueba:

%% tiempo classifyNN (k, idx)

salida:

Tiempo de pared: 3 min 9 s 'CAMINANDO'

Entonces, en todos estos ejemplos, podemos ver que el algoritmo pudo etiquetar correctamente la serie de tiempo y tiene una gran precisión. La funcionalidad de ventanas también reduce en gran medida el tiempo que tarda el modelo en entrenarse y, si no me cree, puede cambiar manualmente el argumento de la ventana y ver la diferencia en el tiempo requerido para entrenar el modelo.

¿Es la paralelización una posibilidad para la optimización del envolvente de tiempo dinámico?

Bueno, la siguiente pregunta general aquí es sobre la paralelización, ya que cuando NumPy y el marco de datos de pandas estaban trabajando en el enorme conjunto de datos y era necesario hacer algo en un tiempo más corto, Spark se creó entre otras razones para usar el mejor escenario de tener múltiples procesadores en una máquina.

De manera similar, aquí podemos ver que en esta forma actual de Dynamic Time Warping, la paralelización es muy difícil y la actualizaré en el próximo artículo si encuentro algún método, pero puede proporcionar algunas otras optimizaciones al algoritmo para reducir la complejidad, pero eso también afectan la precisión del modelo. Por tanto, el desafío sigue siendo reducir la complejidad del tiempo sin reducir mucho la precisión del modelo.

Una posibilidad es acelerar el proceso si calcula las distancias de los pares DTW de todos los datos de series de tiempo en el conjunto de datos y eso logra un nivel de paralelismo por el concepto. Esto nos ayudará a construir la matriz de distancia más rápido y también aumentar la opción de poda permitirá que el entrenamiento pase por alto más puntos de datos, pero esa podría no ser la solución óptima en todos los casos.

Conclusión

La clasificación de series de tiempo se puede hacer muy efectiva usando la combinación de algoritmos KNN y DTW y aquí el conjunto de datos usado es uno de los muchos escenarios posibles donde puede usar este modelo para clasificar los datos en varias etiquetas y cambiando la fuente de entrada a una datos en vivo o un flujo de datos de entrada, puede determinar qué actividad está haciendo un usuario en tiempo real. Hay muchas aplicaciones que usan esto en tiempo real como PasoEstablecerIr es una aplicación que te paga en su moneda por caminar. Entonces, si usa este modelo y recupera los datos del usuario directamente de los sensores del teléfono inteligente, puede determinar la actividad del usuario en ese momento.

Gracias por leer hasta el final. Nos vemos en mi próximo artículo.

Arnab Mondal

Desarrollador e ingeniero de datos de Python | Escritor técnico independiente

Enlace a mis otros artículos

Enlace al cuaderno de collab

Fuentes de imagen:

  1. Imagen 1: https://unsplash.com/photos/BXOXnQ26B7o
  2. Imagen 2: https://unsplash.com/photos/ePAL0f6jjr0

Los medios que se muestran en este artículo no son propiedad de Analytics Vidhya y se utilizan a discreción del autor.

Fuente: https://www.analyticsvidhya.com/blog/2021/10/dynamic-time-warping-explained-using-python-and-har-dataset/

Sello de tiempo:

Mas de Analítica Vidhya