Arquitecturas Multi-Tarea Una Guía Completa

Arquitecturas Multi-Tarea Guía Completa

Modelos Ligeros para Inferencia Multi-Tarea en Tiempo Real

Foto de Julien Duduoglu en Unsplash

Introducción

¿Alguna vez te has preguntado cómo entrenar una red neuronal profunda para realizar muchas tareas? A este tipo de modelo se le conoce como una Arquitectura Multi-Tarea y puede tener ventajas sobre un enfoque tradicional que utiliza modelos individuales para cada tarea. Una Arquitectura Multi-Tarea es un subconjunto del Aprendizaje Multi-Tarea, que es un enfoque general para entrenar un modelo o conjunto de modelos para realizar múltiples tareas simultáneamente.

En esta publicación aprenderemos cómo entrenar un solo modelo para realizar simultáneamente tareas de clasificación y regresión. El código de esta publicación se puede encontrar en GitHub. Aquí tienes un resumen:

  • Motivación — ¿Por qué haríamos esto?
  • Enfoque — ¿Cómo lo vamos a hacer?
  • Arquitectura del Modelo
  • Enfoque de Entrenamiento
  • Inferencia — Verificar el rendimiento y aprender de un fallo interesante
  • Conclusión

Motivación

¿Por qué querríamos utilizar un modelo ligero? ¿No disminuiría el rendimiento? Si no estamos desplegando en dispositivos con recursos limitados, ¿no deberíamos utilizar el modelo más grande posible?

Las aplicaciones en dispositivos con recursos limitados necesitan modelos ligeros para realizar inferencias en tiempo real con bajo consumo de energía. Otras aplicaciones también pueden beneficiarse de ellos, pero ¿cómo? Un beneficio pasado por alto de los modelos ligeros es su menor requerimiento de cómputo. En general, esto puede reducir el uso del servidor y, por lo tanto, disminuir el consumo de energía. Esto tiene el efecto general de reducir los costos y disminuir las emisiones de carbono, lo cual podría convertirse en un problema importante en el futuro de la IA.

Los modelos ligeros pueden ayudar a reducir costos y disminuir las emisiones de carbono mediante un menor consumo de energía

Dicho esto, una arquitectura multi-tarea es simplemente una herramienta en el arsenal, y se deben considerar todos los requisitos del proyecto antes de decidir qué herramientas utilizar. ¡Ahora adentrémonos en un ejemplo de cómo entrenar uno de estos!

Enfoque

Para construir nuestra Arquitectura Multi-Tarea, cubriremos de manera general el enfoque de este artículo, donde se entrenó un solo modelo para la segmentación y estimación de profundidad simultáneas. El objetivo subyacente era realizar estas tareas de manera rápida y eficiente, con el compromiso de una pérdida aceptable de rendimiento. En el Aprendizaje Multi-Tarea, generalmente agrupamos tareas similares. Durante el entrenamiento, también podemos agregar una tarea auxiliar que puede ayudar al aprendizaje de nuestro modelo, pero podemos decidir no usarla durante la inferencia [1, 2]. Para simplificar, no utilizaremos ninguna tarea auxiliar durante el entrenamiento.

La Profundidad y la Segmentación son ambas tareas de predicción densa y tienen similitudes. Por ejemplo, la profundidad de un objeto individual probablemente sea consistente en todas las áreas del objeto, formando una distribución muy estrecha. La idea principal es que cada objeto individual tenga su propio valor de profundidad, y deberíamos poder reconocer los objetos individuales simplemente mirando un mapa de profundidad. De la misma manera, deberíamos poder reconocer los mismos objetos individuales mirando un mapa de segmentación. Si bien es probable que haya algunos valores atípicos, asumiremos que esta relación se cumple.

Conjunto de Datos

Utilizaremos el conjunto de datos CityScapes para proporcionar imágenes de entrada (cámara izquierda), máscaras de segmentación y mapas de profundidad. Para los mapas de segmentación, elegimos usar las etiquetas de entrenamiento estándar, con 19 clases + 1 categoría no etiquetada.

Preparación de Mapas de Profundidad — disparidad predeterminada

Los mapas de disparidad creados con SteroSGBM están disponibles en el sitio web de CityScapes. La disparidad describe la diferencia de píxeles entre objetos vistos desde la perspectiva de cada cámara estéreo, y es inversamente proporcional a la profundidad, que se puede calcular con:

Cálculo de profundidad en CityScapes, las unidades están en paréntesis. Fuente: Autor.

Sin embargo, los mapas de disparidad predeterminados son ruidosos, con muchos agujeros correspondientes a profundidad infinita y una porción donde siempre se muestra el vehículo ego. Un enfoque común para limpiar estos mapas de disparidad implica:

  1. Recortar el 20% inferior junto con partes de los bordes izquierdo y superior
  2. Redimensionar a escala original
  3. Aplicar un filtro de suavizado
  4. Realizar el relleno

Una vez que limpiamos la disparidad, podemos calcular la profundidad, lo que resulta en:

Figura 1. Datos de profundidad de City Scapes. Fuente del autor.

Los detalles específicos de este enfoque están fuera del alcance de esta publicación, pero si estás interesado, aquí tienes una explicación en video en YouTube.

El paso de recorte y redimensionamiento significa que el mapa de disparidad (así como la profundidad) no se alineará exactamente con la imagen de entrada. Aunque podríamos hacer el mismo recorte y redimensionamiento con la imagen de entrada para corregir esto, optamos por explorar un nuevo enfoque.

Preparación del mapa de profundidad – disparidad de CreStereo

Exploramos el uso de CreStereo para producir mapas de disparidad de alta calidad a partir de imágenes estéreo izquierda y derecha. CreStereo es un modelo avanzado que puede predecir mapas de disparidad suaves a partir de pares de imágenes estéreo. Este enfoque introduce un paradigma conocido como destilación del conocimiento, donde CreStereo es una red maestra y nuestro modelo será la red de estudiante (al menos para la estimación de la profundidad). Los detalles de este enfoque están fuera del alcance de esta publicación, pero aquí tienes un enlace de YouTube si estás interesado.

En general, los mapas de profundidad de CreStereo tienen un ruido mínimo, por lo que no es necesario recortar ni redimensionar. Sin embargo, el vehículo ego presente en las máscaras de segmentación podría causar problemas con la generalización, por lo que se eliminó el 20% inferior en todas las imágenes de entrenamiento. Se muestra un ejemplo de entrenamiento a continuación:

Figura 2. Muestra de entrenamiento. Fuente del autor.

Ahora que tenemos nuestros datos, veamos la arquitectura.

Arquitectura del modelo

Según [1], la arquitectura consistirá en un encoder MobileNet, un decodificador LightWeight RefineNet y Heads para cada tarea individual. La arquitectura general se muestra a continuación en la figura 3.

Figura 3. Arquitectura del modelo. Fuente.

Para el encoder/backbone, utilizaremos un MobileNetV3 y pasaremos conexiones de omisión en resoluciones de 1/4, 1/8, 1/16 y 1/32 al Light Weight Refine Net. Finalmente, la salida se pasa a cada head que es responsable de una tarea diferente. Observa cómo incluso podríamos agregar más tareas a esta arquitectura si quisiéramos.

Figura 4. (Izquierda) Arquitectura detallada de encoder-decoder multi-tarea. (Derecha) detalles de los bloques LightWeight RefineNet. Modificado de la fuente.

Para implementar el encoder, utilizamos un encoder MobileNetV3 pre-entrenado, donde pasaremos el encoder MobileNetV3 a un módulo personalizado de PyTorch. La salida de su función forward es un ParameterDict de conexiones de omisión para la entrada al LightWeight Refine Net. El fragmento de código a continuación muestra cómo hacer esto.

class MobileNetV3Backbone(nn.Module):    def __init__(self, backbone):        super().__init__()        self.backbone = backbone        def forward(self, x):        """ Pasa la entrada a través de las capas de extracción de características del encoder MobileNetV3            capas para agregar conexiones a                - 1:  1/4 res                - 3:  1/8 res                - 7, 8:  1/16 res                - 10, 11: 1/32 res           """        skips = nn.ParameterDict()        for i in range(len(self.backbone) - 1):            x = self.backbone[i](x)            # agregar salidas de conexión de omisión            if i in [1, 3, 7, 8, 10, 11]:                skips.update({f"l{i}_out" : x})        return skips

El decodificador LightWeight RefineNet es muy similar al implementado en [1], excepto con algunas modificaciones para hacerlo compatible con MobileNetV3 en lugar de MobileNetV2. También señalamos que la parte del decodificador consta de las cabezas de segmentación y profundidad. El código completo del modelo está disponible en GitHub. Podemos armar el modelo de la siguiente manera:

from torchvision.models import mobilenet_v3_small    mobilenet = mobilenet_v3_small(weights='IMAGENET1K_V1')encoder = MobileNetV3Backbone(mobilenet.features)decoder = LightWeightRefineNet(num_seg_classes)model = MultiTaskNetwork(encoder, freeze_encoder=False).to(device)

Enfoque de entrenamiento

Dividimos el entrenamiento en tres fases, la primera a 1/4 de resolución, la segunda a 1/2 de resolución y la última a resolución completa. Todos los pesos se actualizaron, ya que congelar los pesos del codificador no parecía producir buenos resultados.

Transformaciones

Durante cada fase, realizamos recorte y redimensionamiento aleatorio, ajuste de color, volteos aleatorios y normalización. La imagen de entrada izquierda se normaliza con la media y la desviación estándar de ImageNet.

Transformación de profundidad

En general, los mapas de profundidad contienen principalmente valores más pequeños, ya que la mayor parte de la información contenida en un mapa de profundidad consiste en objetos y superficies cercanas a la cámara. Dado que el mapa de profundidad tiene la mayor parte de su profundidad concentrada en valores más bajos (ver a la izquierda de la figura 4 a continuación), deberá transformarse para ser aprendido de manera efectiva por una red neuronal. El mapa de profundidad se recorta entre 0 y 250, esto se debe a que los datos de disparidad/profundidad estéreo a grandes distancias suelen ser poco confiables y en este caso queremos una forma de descartarlos. Luego tomamos el logaritmo natural y lo dividimos por 5 para condensar la distribución alrededor de un rango más pequeño de números. Consulte este cuaderno para obtener más detalles.

Figura 4. Izquierda — Distribución de profundidad recortada. Derecha — Distribución de profundidad transformada. Se muestreó la profundidad de 64 máscaras de profundidad de entrenamiento a tamaño completo aleatorias. Fuente Autor.

Seré honesto, no estaba seguro de la mejor manera de transformar los datos de profundidad. Si hay una mejor manera o si lo harías de manera diferente, estaría interesado en aprender más en los comentarios :).

Funciones de pérdida

Mantenemos las funciones de pérdida simples, Pérdida de entropía cruzada para la segmentación y Error cuadrático medio para la estimación de profundidad. Las sumamos sin ponderación y las optimizamos en conjunto.

Tasa de aprendizaje

Utilizamos una tasa de aprendizaje de coseno anélica de un ciclo con un máximo de 5e-4 y entrenamos durante 150 épocas a 1/4 de resolución. El cuaderno utilizado para el entrenamiento se encuentra aquí.

Figura 5. Tasa de aprendizaje anélica de un ciclo. Fuente Autor.

Ajustamos finamente a 1/2 de resolución durante 25 épocas y nuevamente a resolución completa durante otras 25 épocas, ambas con una tasa de aprendizaje de 5e-6. Tenga en cuenta que necesitamos reducir el tamaño del lote cada vez que ajustamos finamente a una resolución aumentada.

Inferencia

Para la inferencia, normalizamos la imagen de entrada y ejecutamos un pase hacia adelante a través del modelo. La Figura 6 muestra los resultados del entrenamiento tanto de los datos de validación como de prueba

Figura 6. Resultados de inferencia (los 2 superiores son del conjunto de pruebas y los 2 inferiores son del conjunto de validación). Fuente Autor.

En general, parece que el modelo es capaz de segmentar y estimar la profundidad cuando hay objetos más grandes en una imagen. Cuando hay objetos más detallados, como los peatones, presentes, el modelo tiende a tener dificultades para segmentarlos por completo. El modelo es capaz de estimar su profundidad con cierto grado de precisión.

Un fallo interesante

La parte inferior de la figura 6 muestra un caso interesante de fallo al segmentar completamente el poste de luz en el lado izquierdo de la imagen. La segmentación solo cubre la mitad inferior del poste de luz, mientras que la profundidad muestra que la mitad inferior del poste de luz está mucho más cerca que la mitad superior. El fallo de profundidad podría deberse al sesgo de que los píxeles inferiores generalmente corresponden a una profundidad más cercana; observe la línea del horizonte alrededor del píxel 500, hay una clara división entre los píxeles más cercanos y los más lejanos. Parece que este sesgo podría haber afectado la tarea de segmentación del modelo. Este tipo de fuga de tareas debe tenerse en cuenta al entrenar modelos de múltiples tareas.

En el aprendizaje multi-tarea, los datos de entrenamiento de una tarea pueden afectar el rendimiento en otra tarea

Distribuciones de profundidad

Veamos cómo se distribuye la profundidad predicha en comparación con la verdad. Para simplificar, utilizaremos una muestra de 94 pares de mapas de profundidad completos verdaderos/predichos.

Figura 8. Distribuciones de mapas de profundidad verdaderos (izquierda) y predichos (derecha), cada uno con 1000 bins. Fuente: Autor.

Parece que el modelo ha aprendido dos distribuciones, una con un pico alrededor de 4 y otra con un pico alrededor de 30. Observe que el artefacto de recorte no parece haber hecho ninguna diferencia. La distribución general contiene una cola larga, que es característica del hecho de que solo una pequeña parte de una imagen contendrá datos de profundidad lejanos.

La distribución de profundidad predicha es mucho más suave que la verdad. La rugosidad de la distribución de verdad podría deberse al hecho de que cada objeto contiene valores de profundidad similares. Es posible utilizar esta información para aplicar algún tipo de regularización que obligue al modelo a seguir este paradigma, pero eso será para otra ocasión.

Extra: Velocidad de inferencia

Dado que este es un modelo ligero pensado para la velocidad, veamos qué tan rápido realiza la inferencia en la GPU. El código a continuación ha sido modificado a partir de este artículo. En esta prueba, la imagen de entrada se ha escalado a 400×1024.

# encontrar el backend óptimo para realizar convoluciones torch.backends.cudnn.benchmark = True # redimensionar a la mitad del tamaño muestra_redimensionada = Redimensionar(400, 1024)(muestra)izquierda_redimensionada = muestra_redimensionada['izquierda'].to(DEVICE)# INICIALIZAR REGISTRADORESiniciador, finalizador = torch.cuda.Event(enable_timing=True), torch.cuda.Event(enable_timing=True)repeticiones = 300tiempos = np.zeros((repeticiones,1))#CALENTAMIENTO DE GPUfor _ in range(10):    _, _ = modelo(izquierda_redimensionada.unsqueeze(0))# MEDIR EL RENDIMIENTOwith torch.no_grad():    for rep in range(repeticiones):        iniciador.record()        _, _ = modelo(izquierda_redimensionada.unsqueeze(0))        finalizador.record()        # ESPERAR LA SINCRONIZACIÓN DE LA GPU        torch.cuda.synchronize()        tiempo_actual = iniciador.elapsed_time(finalizador)        tiempos[rep] = tiempo_actualmedia_sincronizada = np.sum(tiempos) / repeticionesstd_sincronizada = np.std(tiempos)print(media_sincronizada, std_sincronizada)

La prueba de inferencia muestra que este modelo puede ejecutarse en 18.69+/-0.44 ms, aproximadamente 55Hz. Es importante tener en cuenta que esto es solo un prototipo de Python ejecutado en una computadora portátil con una GPU NVIDIA RTX 3060, diferentes hardware cambiarán la velocidad de inferencia. También debemos tener en cuenta que un SDK como Torch-TensorRt podría proporcionar una mejora significativa de velocidad si se implementa en una GPU NVIDIA.

Conclusión

En esta publicación aprendimos cómo el aprendizaje multi-tarea puede ahorrar costos y reducir las emisiones de carbono. Aprendimos cómo construir una arquitectura multi-tarea ligera capaz de realizar clasificación y regresión simultáneamente en el conjunto de datos de CityScapes. También aprovechamos CreStereo y Knowledge Distillation para ayudar a nuestro modelo a aprender a predecir mejores mapas de profundidad.

Este modelo ligero presenta un compromiso donde sacrificamos algo de rendimiento a cambio de velocidad y eficiencia. A pesar de este compromiso, el modelo entrenado fue capaz de predecir resultados razonables de profundidad y segmentación en los datos de prueba. Además, fue capaz de aprender a predecir una distribución de profundidad similar a la de los mapas de profundidad reales.

Referencias

[1] Nekrasov, Vladimir, et al. ‘Segmentación Semántica Conjunta y Estimación de Profundidad en Tiempo Real Utilizando Anotaciones Asimétricas’. CoRR, vol. abs/1809.04766, 2018, http://arxiv.org/abs/1809.04766

[2] Standley, Trevor, et al. ‘¿Qué tareas se deberían aprender juntas en el Aprendizaje Multi-Tarea?’ CoRR, vol. abs/1905.07553, 2019, http://arxiv.org/abs/1905.07553

[3] Cordts, M., Omran, M., Ramos, S., Rehfeld, T., Enzweiler, M., Benenson, R., Franke, U., Roth, S., & Schiele, B. (2016). El conjunto de datos de Cityscapes para la comprensión semántica de escenas urbanas. 2016 IEEE Conferencia sobre Visión por Computadora y Reconocimiento de Patrones (CVPR). https://doi.org/10.1109/cvpr.2016.350