Cómo escribir canalizaciones de aprendizaje automático ordenadas

Hola, Habr.



El tema de la canalización y la paralelización del aprendizaje automático ha estado en nuestro trabajo durante mucho tiempo. En particular, me pregunto si un libro especializado con énfasis en Python es suficiente para esto, o si se necesita una descripción más general y posiblemente una literatura compleja. Decidimos traducir un artículo introductorio sobre las canalizaciones de aprendizaje automático, que cubra consideraciones tanto arquitectónicas como más aplicadas. Analicemos si las búsquedas en esta dirección son relevantes.





¿Ha escrito alguna vez una canalización de aprendizaje automático que haya tardado mucho en ejecutarse? O peor aún: ¿ha llegado a la etapa en la que necesita guardar partes intermedias de la tubería en el disco para poder estudiar las etapas de la tubería una por una, confiando en los puntos de control? O peor: ¿alguna vez ha intentado refactorizar un código de aprendizaje automático tan repugnante antes de poner ese código en producción y descubrió que le llevó meses? Sí, todos los que han trabajado en canales de aprendizaje automático durante mucho tiempo han tenido que lidiar con esto. Entonces, ¿por qué no crear una buena canalización que nos brinde suficiente flexibilidad y la capacidad de refactorizar fácilmente el código para enviarlo posteriormente a producción?



Primero, definamos una canalización de aprendizaje automático y analicemos la idea de usar puntos de interrupción entre las etapas de la canalización. Luego, veamos cómo podemos implementar tales puntos de interrupción para no dispararnos en el pie al mover la tubería a producción. También discutiremos la transmisión de datos y las compensaciones asociadas con la encapsulación de programación orientada a objetos (OOP) por las que debe pasar en las canalizaciones al especificar hiperparámetros.



¿QUÉ SON TRANSPORTADORES?



TransportadorEs una secuencia de pasos en la transformación de datos. Se crea de acuerdo con el antiguo patrón de diseño de tuberías y filtros (recuerde, por ejemplo, los comandos bash de Unix con tuberías "|" o operadores de redirección ">"). Sin embargo, las canalizaciones son objetos en el código. Por lo tanto, puede tener una clase para cada filtro (es decir, para cada etapa de la canalización), así como otra clase para combinar todas estas etapas en una canalización terminada. Algunas tuberías pueden combinar otras tuberías en serie o en paralelo, tener muchas entradas o salidas, etc. Es conveniente pensar en las canalizaciones de aprendizaje automático como:



  • Canal y filtros . Las etapas de los datos del proceso de la canalización y las etapas gestionan su estado interno, que se puede aprender de los datos.
  • . ; , . , – .
  • (DAG). , . : , , , , (, fit_transform ), , ( RNN). , , .




Métodos de



transporte Los transportadores (o etapas de la tubería) deben tener los siguientes dos métodos:



  • " Ajuste " para entrenar con datos y adquirir un estado (p. Ej., Tal estado es el peso de una red neuronal)
  • Transformar ” (o “predecir”) para procesar realmente los datos y generar una predicción.
  • Nota: Si una etapa de canalización no requiere uno de estos métodos, entonces la etapa puede heredar de NonFittableMixin o NonTransformableMixin, que proporcionará una implementación de uno de estos métodos de forma predeterminada para que no haga nada.




Los siguientes métodos también se pueden definir opcionalmente en etapas de canalización :



  • fit_transform” , , , .
  • " Configuración " que llamará al método de "configuración" en cada una de estas etapas de la tubería. Por ejemplo, si una etapa de canalización contiene una red neuronal TensorFlow, PyTorch o Keras, estas etapas podrían crear sus propios gráficos neuronales y registrarse para trabajar con la GPU en el método de "configuración" antes de ajustar. No se recomienda crear gráficos directamente en los constructores del escenario antes de ajustar; hay varias razones para esto. Por ejemplo, antes de comenzar, los pasos se pueden copiar muchas veces con diferentes hiperparámetros como parte del algoritmo de aprendizaje automático automático, que busca los mejores hiperparámetros para usted.
  • " Desmontaje ", este método es funcionalmente opuesto a "configuración": destruye recursos.




Los siguientes métodos se proporcionan de forma predeterminada y proporcionan control de hiperparámetros:







Reajuste de canalizaciones, mini lotes y aprendizaje en línea



Para algoritmos que utilizan mini lotes, como el entrenamiento de redes neuronales profundas (DNN) o algoritmos que aprenden en línea, como aprendizaje por refuerzo (RL), para conductos o sus etapas ideales es conveniente encadenar varias llamadas para que sigan exactamente una tras otra, y sobre la marcha se ajustan al tamaño de los mini lotes. Esta característica se admite en algunas tuberías y en algunas etapas de las tuberías, pero en algún momento el ajuste logrado puede restablecerse debido al hecho de que se volverá a llamar al método "ajuste". Todo depende de cómo haya programado la etapa de su canalización. Idealmente, una etapa de canalización solo debería vaciarse después de llamar al método "teardown" y luego llamar al "setup”Hasta el próximo ajuste, y los datos no se vaciaron entre adaptaciones o durante la conversión.



USO DE PUNTOS DE VERIFICACIÓN EN TRANSPORTADORES



Es una buena práctica usar puntos de interrupción en tuberías hasta que necesite usar este código para otros fines y cambiar los datos. Si no utiliza las abstracciones correctas en su código, es posible que se esté disparando en el pie.



Pros y contras de usar puntos de control en tuberías:



  • Los puntos de interrupción pueden acelerar el flujo de trabajo si los pasos de programación y depuración están en el medio o al final del proceso. Esto elimina la necesidad de volver a calcular las primeras etapas de la tubería cada vez.
  • ( , ), , . , , – . , , , , , .
  • Quizás tenga recursos informáticos limitados y la única opción viable para usted es ejecutar un paso a la vez en el hardware disponible. Puede usar un punto de interrupción, luego agregar algunos pasos más después de él, y luego los datos se usarán desde donde los dejó si desea volver a ejecutar toda la estructura.




Desventajas de usar puntos de interrupción en canalizaciones:



  • Esto usa discos, por lo que si lo hace mal, su código puede ralentizarse. Para acelerar las cosas, al menos puede usar el disco RAM o montar la carpeta de caché en su RAM.
  • Esto puede ocupar mucho espacio en disco. O mucho espacio RAM cuando se usa un directorio montado en RAM.
  • El estado almacenado en el disco es más difícil de administrar: su programa tiene la complejidad adicional necesaria para que el código se ejecute más rápido. Tenga en cuenta que desde una perspectiva de programación funcional, sus funciones y código ya no estarán limpios, ya que necesita administrar los efectos secundarios asociados con el uso del disco. Los efectos secundarios asociados con la administración del estado del disco (su caché) pueden ser el caldo de cultivo para todo tipo de errores extraños
...



Se sabe que algunos de los errores de programación más difíciles surgen de problemas de invalidación de caché.



En Ciencias de la Computación, solo hay dos cosas realmente complicadas: invalidación de caché y denominación de entidades. - Phil Carlton




Asesoramiento sobre cómo gestionar correctamente el estado y la caché en canalizaciones.



Se sabe que los marcos de programación y los patrones de diseño pueden ser un factor limitante , por la sencilla razón de que gobiernan ciertas reglas. Con suerte, esto se hace para mantener sus tareas de administración de código lo más simples posible, para que usted mismo evite errores y su código no termine desordenado. Aquí están mis cinco centavos sobre el diseño en el contexto de las tuberías y la gestión estatal:



LAS ETAPAS DEL TRANSPORTADOR NO DEBEN CONTROLAR LOS AJUSTES DEL PUNTO DE PRUEBA EN LOS DATOS EMITIDOS POR




Para gestionar esto, se debe utilizar una biblioteca de canalización especial que pueda hacer todo esto por usted.



¿Por qué?



¿Por qué las etapas de la canalización no deberían controlar la ubicación de los puntos de control en los datos que producen? Por las mismas buenas razones por las que utiliza una biblioteca o marco cuando trabaja, y no reproduce la funcionalidad correspondiente usted mismo:



  • Tendrá un interruptor de palanca simple que facilitará la activación o desactivación completa de los puntos de interrupción antes de la implementación en producción.
  • , , , : , , . , .
  • / (I/O) . , . : , . ?
  • , , – . , , .
  • , , , , , , . , . .
  • , , (, , ) . , ( , ) . , , , , , , . , . , .




Eso es genial. Con la abstracción correcta, ahora puede programar pipelines de aprendizaje automático para acelerar drásticamente la fase de ajuste de hiperparámetros; Para hacer esto, necesita almacenar en caché el resultado intermedio de cada prueba, omitiendo las etapas de la canalización una y otra vez, cuando los hiperparámetros de las etapas intermedias de la canalización permanecen sin cambios. Además, cuando esté listo para lanzar el código a producción, puede desactivar inmediatamente el almacenamiento en caché por completo, en lugar de refactorizar el código para esto durante todo un mes.



No golpees esta pared.



TRANSMISIÓN DE DATOS EN TRANSPORTADORES DE APRENDIZAJE DE MÁQUINAS



La teoría del procesamiento paralelo establece que las tuberías son una herramienta de transmisión de datos que le permite paralelizar las etapas de las tuberías. Ejemplo de lavanderíailustra bien tanto este problema como su solución. Por ejemplo, una segunda etapa de la canalización podría comenzar a procesar información parcial de la primera etapa de la canalización, mientras que la primera etapa continúa calculando nuevos datos. Además, para que funcione la segunda etapa del transportador, no es necesario que la primera etapa complete completamente su etapa de procesamiento de todos los datos. Llamemos a estas canalizaciones especiales transmisión (ver aquí y aquí ).



No me malinterpretes, trabajar con canalizaciones de scikit-learn es muy divertido. Pero no están clasificados para transmisión. No solo scikit-learn, sino que la mayoría de las bibliotecas canalizadas existentes no aprovechan las capacidades de transmisión cuando pueden. Hay problemas de subprocesos múltiples en todo el ecosistema de Python. En la mayoría de las bibliotecas canalizadas, cada etapa se bloquea por completo y requiere la transformación de todos los datos a la vez. Solo hay unas pocas bibliotecas de transmisión disponibles.



Activar la transmisión puede ser tan simple como usar una clase en StreamingPipelinelugar dePipelinepara vincular las etapas una a una. Al mismo tiempo, se indica el tamaño del mini-lote y el tamaño de la cola (para evitar un consumo excesivo de RAM, esto asegura un trabajo más estable en producción). Idealmente, una estructura de este tipo también requeriría colas multiproceso con semáforos, como se describe en el problema del proveedor y el consumidor : para organizar la transferencia de información de una etapa de la tubería a otra.



En nuestra empresa, Neuraxle ya logra hacer algo mejor que scikit-learn: se trata de pipelines secuenciales que se pueden usar usando la clase MiniBatchSequentialPipeline.... Hasta ahora, esto no es multiproceso (pero esto está en los planes). Como mínimo, ya pasa datos a la tubería en forma de mini lotes durante el proceso de ajuste o transformación, antes de recopilar los resultados, lo que permite trabajar con tuberías grandes como en scikit-learn , pero esta vez utilizando mini lotes, así como muchas otras posibilidades, que incluyen: espacios de hiperparámetros, métodos de instalación, aprendizaje automático automático, etc.



Nuestra solución de transmisión de datos en paralelo en Python



  • El método de ajuste y / o transformación se puede llamar muchas veces seguidas para mejorar el ajuste con nuevos mini lotes.
  • , -. , , .
  • , . , setup. , , . , TensorFlow, , , , C++, Python, GPU. joblib . .
  • , . , – , , , , .
  • . , , ; , . , , , , ( Joiner). , . , , , .




Además, queremos asegurarnos de que cualquier objeto en Python se pueda compartir entre subprocesos para que sea serializable y recargable. En este caso, el código puede enviarse dinámicamente para su procesamiento en cualquier trabajador (puede ser otra computadora o proceso), incluso si el código necesario en sí no está en este trabajador. Esto se hace mediante una cadena de serializadores específicos para cada clase que encarna la etapa de canalización. De forma predeterminada, cada uno de estos pasos tiene un serializador que le permite procesar código Python normal y, para un código más complejo, utilice la GPU e importe el código en otros idiomas. Los modelos simplemente se serializan usando sus protectoresy luego se vuelve a cargar en el trabajador. Si el trabajador es local, los objetos se pueden serializar en un disco ubicado en la RAM o en un directorio montado en la RAM.



COMPROMISOS PARA LA INCAPSULACIÓN



Hay una cosa más molesta inherente a la mayoría de las bibliotecas para el aprendizaje automático en cadena. Se trata de cómo se manejan los hiperparámetros. Tome scikit-learn, por ejemplo. Los espacios de hiperparámetros (también conocidos como distribuciones estadísticas de valores de hiperparámetros ) a menudo deben especificarse fuera de la tubería, con guiones bajos como separadores entre las etapas de la (s) tubería (s). Considerando que la búsqueda aleatoria y la búsqueda de cuadrículale permite explorar cuadrículas de hiperparámetros o espacios de probabilidad de hiperparámetros como se define en las distribuciones scipy, scikit-learn en sí no proporciona espacios de hiperparámetros predeterminados para cada clasificador y transformador. La responsabilidad de realizar estas funciones se puede asignar a cada uno de los objetos de la canalización. Por tanto, el objeto será autosuficiente y contendrá sus propios hiperparámetros. Esto no viola el principio de responsabilidad única, el principio de apertura / cierre y los principios de la programación orientada a objetos SOLID.



COMPATIBILIDAD E INTEGRACIÓN Al



programar canalizaciones de aprendizaje automático, es útil tener en cuenta que deben ser compatibles con muchas otras herramientas, en particular scikit-learn., TensorFlow , Keras , PyTorch y muchas otras bibliotecas de aprendizaje profundo y de máquina.

Por ejemplo, escribimos un método .tosklearn()que nos permite convertir las etapas de una canalización o una canalización completa en BaseEstimatorun objeto base de la biblioteca scikit-learn. En cuanto a otras bibliotecas de aprendizaje automático, la tarea se reduce a escribir una nueva clase que herede de la nuestra BaseStepy anular en un código específico las operaciones de ajuste y transformación, así como, posiblemente, el ajuste y la demolición. También necesita definir un protector que guardará y cargará su modelo. Aquí está la documentación de la clase BaseStepy ejemplos.



CONCLUSIÓN



Para resumir, observamos que el código de las canalizaciones de aprendizaje automático, listo para entrar en producción, debe cumplir con muchos criterios de calidad, que son bastante alcanzables si se adhiere a los patrones de diseño correctos y estructura bien el código. Tenga en cuenta lo siguiente:



  • En el código de aprendizaje automático, tiene sentido usar canalizaciones y definir cada etapa de la canalización como una instancia de una clase.
  • Luego, toda la estructura se puede optimizar con puntos de interrupción para ayudar a encontrar los mejores hiperparámetros y ejecutar repetidamente el código en los mismos datos (pero posiblemente con diferentes hiperparámetros o código fuente modificado).
  • , RAM. , .
  • , – BaseStep, , .



All Articles