Compartir a través de


Crear su propio clasificador de imágenes mediante transfer Learning

Tabla de contenido

Resumen

imageimageimageimage

Las imágenes anteriores son imágenes de prueba usadas en la segunda parte de este tutorial. La tarea es entrenar un clasificador que pueda distinguir diferentes categorías de imágenes (en nuestro ejemplo ovejas y lobos) modificando un modelo clasificador existente, el modelo base. Aquí se usa un modelo de ResNet_18 que se entrenó en el corpus de ImageNet. Entrenamos solo en 15 imágenes por clase en unos segundos y predicemos correctamente todas las 10 imágenes de prueba (tenga en cuenta los pocos granos de sal).

A continuación se muestran los recursos principales para el tutorial de aprendizaje de transferencia:

Receta TransferLearning.py y TransferLearning_Extended.py (consulte Examples/Image/TransferLearning).
modelos entrenados previamente Como modelo base para el aprendizaje de transferencia, usamos un modelo de ResNet_18 previamente entrenado.
data El conjunto de datos Flowers con 102 categorías y imágenes de ejemplo de ovejas y lobos (consulte Configuración).
Ejecución Siga la descripción siguiente.

Configurar

Para ejecutar el código en este ejemplo, necesita un entorno de Python de CNTK (consulte aquí para obtener ayuda de configuración).

Para descargar los datos necesarios y el modelo entrenado previamente, ejecute el siguiente comando en la carpeta Examples/Image/TransferLearning :

python install_data_and_model.py

Ejecutar el ejemplo

imageimageimageimage

En esta sección, crearemos un clasificador para el conjunto de datos Flowers. El conjunto de datos fue creado por el grupo Visual Geometry en la Universidad de Oxford para tareas de clasificación de imágenes. Consta de 102 categorías diferentes de flores comunes al Reino Unido y contiene aproximadamente 8000 imágenes que se dividen en tres conjuntos de una vez 6000 y dos veces 1000 imágenes. Para más información, consulte la página principal de VGG.

Para entrenar y evaluar un modelo de aprendizaje de transferencia en la ejecución del conjunto de datos Flowers

python TransferLearning.py

El modelo logra una precisión del 93 % en el conjunto de datos Flowers después del entrenamiento durante 20 épocas.

El concepto básico de aprendizaje de transferencia

Cuando se usa un modelo base para el aprendizaje de transferencia, básicamente se basa en las características y el concepto que se han aprendido durante el entrenamiento del modelo base. Para un DNN convolucional, ResNet_18 en nuestro caso, esto significa, por ejemplo, que cortamos la capa densa final que es responsable de predecir las etiquetas de clase del modelo base original y reemplazarla por una nueva capa densa que predecirá las etiquetas de clase de nuestra nueva tarea a mano. La entrada a la capa de predicción antigua y nueva es la misma, simplemente reutilizamos las características entrenadas. A continuación, entrenamos esta red modificada, ya sea solo los nuevos pesos de la nueva capa de predicción o todos los pesos de toda la red.

El código siguiente es la parte de TransferLearning.py que crea el nuevo modelo a partir del modelo base:

    # Load the pretrained classification net and find nodes
    base_model   = load_model(base_model_file)
    feature_node = find_by_name(base_model, feature_node_name)
    last_node    = find_by_name(base_model, last_hidden_node_name)

    # Clone the desired layers with fixed weights
    cloned_layers = combine([last_node.owner]).clone(
        CloneMethod.freeze if freeze else CloneMethod.clone,
        {feature_node: Placeholder(name='features')})

    # Add new dense layer for class prediction
    feat_norm  = input_features - Constant(114)
    cloned_out = cloned_layers(feat_norm)
    z          = Dense(num_classes, activation=None, name=new_output_node_name) (cloned_out)

Compilación de su propio clasificador de imágenes personalizado

En la sección anterior hemos entrenado un clasificador que distingue 102 categorías diferentes de flores usando aproximadamente 6000 imágenes para el entrenamiento. En esta sección solo usaremos 15 imágenes por categoría para crear un clasificador que pueda indicar a un lobo de una oveja. Usamos el mismo ResNet_18 modelo base para el aprendizaje de transferencia. Para entrenar y evaluar la ejecución del modelo

python TransferLearning_Extended.py

El modelo se prueba en cinco imágenes de ovejas y lobos cada una y predice todas las etiquetas correctamente. El archivo de salida contiene por línea una representación JSON de los resultados de la predicción:

[{"class": "Sheep", "predictions": {"Sheep":1.000, "Wolf":0.000}, "image": "..."}]
[{"class": "Sheep", "predictions": {"Sheep":1.000, "Wolf":0.000}, "image": "..."}]
[{"class": "Sheep", "predictions": {"Sheep":1.000, "Wolf":0.000}, "image": "..."}]
[{"class": "Sheep", "predictions": {"Sheep":0.997, "Wolf":0.003}, "image": "..."}]
[{"class": "Sheep", "predictions": {"Sheep":1.000, "Wolf":0.000}, "image": "..."}]
[{"class": "Wolf", "predictions": {"Wolf":1.000, "Sheep":0.000}, "image": "..."}]
[{"class": "Wolf", "predictions": {"Wolf":1.000, "Sheep":0.000}, "image": "..."}]
[{"class": "Wolf", "predictions": {"Wolf":1.000, "Sheep":0.000}, "image": "..."}]
[{"class": "Wolf", "predictions": {"Wolf":1.000, "Sheep":0.000}, "image": "..."}]
[{"class": "Wolf", "predictions": {"Wolf":1.000, "Sheep":0.000}, "image": "..."}]
[{"class": "unknown", "predictions": {"Sheep":0.994, "Wolf":0.006}, "image": "..."}]
[{"class": "unknown", "predictions": {"Sheep":0.614, "Wolf":0.386}, "image": "..."}]
[{"class": "unknown", "predictions": {"Wolf":0.980, "Sheep":0.020}, "image": "..."}]

Tenga en cuenta que las tres últimas imágenes no tienen asignada una clase de verdad básica, que es, por supuesto, un escenario válido, por ejemplo, para puntuar imágenes no vista en un servicio web. Las imágenes reales son las tres imágenes de pájaro que se muestran a continuación. La clase de verdad básica para estas en la salida JSON se establece unknownen . Tenga en cuenta que las predicciones de los conceptos en los que se entrenó el clasificador son bastante buenas a pesar de las pocas imágenes de entrenamiento. Esto está en grandes partes debido al modelo base entrenado previamente. Las predicciones de conceptos no vistos, por ejemplo, imágenes de aves, no son muy significativas, ya que el clasificador solo conoce ovejas y lobos. Más adelante.

imageimageimage

Estructura de carpetas para conjuntos de imágenes personalizadas

Puede usar el TransferLearning_Extended.py script con sus propias imágenes. Esto es lo que necesita:

  1. class_mapping - Matriz que contiene los nombres de las categorías, por ejemplo, ['wolf', 'sheep']
  2. train_map_file - Un archivo de texto que contiene por línea primero una dirección URL de imagen y tabulación separa el índice de categoría correspondiente, por ejemplo 0 , para lobo o 1 para ovejas:
  3. test_map_file - Un archivo de texto que asigna imágenes de prueba a su categoría correspondiente. Para las categorías desconocidas de las imágenes de prueba, use -1 como índice de categoría.

El script puede generar los tres elementos anteriores si estructura las imágenes de la siguiente manera:

<image root folder>
    Train
        Sheep
        Wolf
    Test
        Sheep
        Wolf
        <optional: image files with unknown label directly here>

Vea <cntk root>/Examples/Image/DataSets/Animals/ como ejemplo. Cada subcarpeta de la Train carpeta se considerará como una categoría (solo nivel único, sin recursividad). Para entrenar imágenes individuales en la Train carpeta raíz se omiten, ya que no tienen una categoría asignada. Se omiten las subcarpetas adicionales de la Test carpeta que no se producen en la Train carpeta. Para probar imágenes individuales sin categoría también se usan para puntuar, es decir, imágenes que se almacenan directamente en la Test carpeta, como las tres imágenes de pájaro en nuestro ejemplo.

Para usar las carpetas de imágenes personalizadas, debe establecer train_image_folder y test_image_folder en la parte superior del TransferLearning_Extended.py script:

# define data location and characteristics
train_image_folder = "<your_image_root_folder>/Train"
test_image_folder = "<your_image_root_folder>/Test"

A continuación, simplemente ejecute python TransferLearning_Extended.py. A continuación se describe cómo usar un modelo base diferente.

Para puntuar no es necesario usar , test_map_filepor ejemplo, si desea puntuar imágenes únicas una por una. Basta con cargar el modelo de aprendizaje de transferencia entrenada una vez y, a continuación, llamar eval_single_image a cada vez que quiera obtener predicciones para una nueva imagen:

    # once:
    # load the trained transfer learning model
    trained_model = load_model(new_model_file)

    # for every new image:
    # get predictions for a single image
    probs = eval_single_image(trained_model, img_file, image_width, image_height)

Algunos granos de sal

Las imágenes de entrenamiento deben cubrir lo suficientemente los escenarios que desea puntuar más adelante. Si el clasificador ve conceptos o contextos totalmente nuevos, es probable que funcione mal. Solo algunos ejemplos:

  • Solo se entrena en imágenes de un entorno de restricción (por ejemplo, interior) e intenta puntuar imágenes de un entorno diferente (al aire libre).
  • Solo entrenas en imágenes de una marca determinada e intentas puntuar a otros.
  • Las imágenes de prueba tienen características muy diferentes, por ejemplo, con respecto a la iluminación, el fondo, el color, el tamaño, la posición, etc.
  • Las imágenes de prueba contienen conceptos completamente nuevos.

Agregar una categoría catch-all puede ser una buena idea, pero solo si los datos de entrenamiento de esa categoría contienen imágenes que de nuevo son suficientemente similares a las imágenes que espera en tiempo de puntuación. Como en el ejemplo anterior, si entrenamos un clasificador con imágenes de ovejas y lobos y lo usamos para puntuar una imagen de un pájaro, el clasificador todavía puede asignar una etiqueta de oveja o lobo, ya que no conoce ninguna otra categoría. Si fueramos a agregar una categoría catch-all y agregar imágenes de entrenamiento de aves a él, el clasificador podría predecir la clase correctamente para la imagen de pájaro. Sin embargo, si lo presentamos, por ejemplo, una imagen de un coche, se enfrenta al mismo problema que antes, ya que solo conoce ovejas, lobos y aves (que acabamos de llamar llamado catch-all). Por lo tanto, los datos de entrenamiento, también para detectar todo, deben cubrir suficientemente esos conceptos e imágenes que espera más adelante en el momento de la puntuación.

Otro aspecto que se debe tener en cuenta es que un modelo base determinado puede funcionar muy bien para algunas tareas de aprendizaje de transferencia y no tan buena para otros. Por ejemplo, el modelo anterior ResNet_18 se ha entrenado previamente en el corpus de ImageNet, que contiene muchas imágenes de animales, personas, coches y muchos otros objetos todos los días. El uso de este modelo base en el aprendizaje de transferencia para crear un clasificador para objetos similares cada día puede funcionar bien. El uso del mismo modelo que un modelo base para crear un clasificador para imágenes de microorganismos o dibujos de lápiz solo puede producir resultados mediocres.

Uso de un modelo base diferente

Para usar un modelo diferente como modelo base, debe adaptar los parámetros siguientes en TransferLearning.py (igual para TransferLearning_Extended.py):

# define base model location and characteristics
_base_model_file = os.path.join(base_folder, "..", "..", "..", "PretrainedModels", "ResNet_18.model")
_feature_node_name = "features"
_last_hidden_node_name = "z.x"
_image_height = 224
_image_width = 224
_num_channels = 3

Para investigar cuáles son los nombres de nodo en el modelo y cuál elegir, puede last_hidden_node imprimir todos los nombres de nodo y las formas de nodo mediante las siguientes líneas (vea __main__ el método en TransferLearning.py):

    # You can use the following to inspect the base model and determine the desired node names
    node_outputs = get_node_outputs(load_model(_base_model_file))
    for out in node_outputs: print("{0} {1}".format(out.name, out.shape))