Compartir a través de


Reconocimiento de imágenes de laboratorios prácticos

Tenga en cuenta que este tutorial requiere la versión maestra más reciente o la próxima CNTK 1.7 que se publicará pronto. Puede encontrar una descarga binaria intermedia en las instrucciones del tutorial de KDD CNTK Hands-On para el que este tutorial se diseñó originalmente.

Hands-On Lab: reconocimiento de imágenes con redes convolucionales, normalización por lotes y redes residuales

En este laboratorio práctico se muestra cómo implementar el reconocimiento de imágenes basado en convolución con CNTK. Comenzaremos con una arquitectura común de reconocimiento de imágenes convolucionales, agregaremos normalización de Batch y, a continuación, la extenderemos a una red residual (ResNet-20).

Las técnicas que practicará incluyen:

  • modificar una definición de red CNTK para agregar una operación predefinida (lista desplegable)
  • creación de funciones definidas por el usuario para extraer elementos repetitivos en una red en un módulo reutilizable
  • implementación de estructuras de red personalizadas (resNet skip connection)
  • crear muchas capas a la vez mediante bucles recursivos
  • entrenamiento paralelo
  • redes convolucionales
  • normalización por lotes

Prerrequisitos

Se supone que ya ha instalado CNTK y puede ejecutar el comando CNTK. Este tutorial se celebró en KDD 2016 y requiere una compilación reciente, consulte aquí para obtener instrucciones de configuración. Solo puede seguir las instrucciones para descargar un paquete de instalación binario desde esa página. Para las tareas relacionadas con imágenes, debe hacerlo en una máquina con una GPU compatible con CUDA compatible.

A continuación, descargue un archivo ZIP (aproximadamente 12 MB): haga clic en este vínculo y, a continuación, en el botón Descargar. El archivo contiene los archivos de este tutorial. Archive y establezca el directorio de trabajo en ImageHandsOn. Trabajará con los siguientes archivos:

  • ImageHandsOn.cntk: el archivo de configuración de CNTK introduciremos a continuación y trabajaremos con él.
  • cifar10.pretrained.cmf: modelo resultante de la configuración con la que comenzaremos.
  • cifar10.ResNet.cmf: modelo resultante de la versión de ResNet que crearemos a continuación.

Por último, debemos descargar y convertir el conjunto de datos CIFAR-10. El paso de conversión tardará aproximadamente 10 minutos. Ejecute los dos scripts de Python siguientes que también encontrará en el directorio de trabajo:

wget -rc http://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
tar xvf www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
python CifarConverter.py cifar-10-batches-py

Esto convierte las imágenes en archivos PNG, 50000 para el entrenamiento y 10000 para las pruebas, que se colocarán en los dos directorios siguientes, respectivamente: cifar-10-batches-py/data/train y cifar-10-batches-py/data/test.

Estructura del modelo

Comenzaremos este tutorial con un modelo convolucional sencillo. Consta de 3 capas de convoluciones 5x5 + no linealidad + reducción de dimensión 2x por agrupación máxima de 3x3, que luego van seguidas de una capa oculta densa y una transformación densa para formar la entrada a un clasificador softmax de 10 vías.

O bien, como descripción de red de CNTK. Tenga un aspecto rápido y házlo coincidir con la descripción anterior:

featNorm = features - Constant (128)
l1 = ConvolutionalLayer {32, (5:5), pad = true, activation = ReLU,
                         init = "gaussian", initValueScale = 0.0043} (featNorm)
p1 = MaxPoolingLayer {(3:3), stride = (2:2)} (l1)
l2 = ConvolutionalLayer {32, (5:5), pad = true, activation = ReLU,
                         init = "gaussian", initValueScale = 1.414} (p1)
p2 = MaxPoolingLayer {(3:3), stride = (2:2)} (l2)
l3 = ConvolutionalLayer {64, (5:5), pad = true, activation = ReLU,
                         init = "gaussian", initValueScale = 1.414} (p2)
p3 = MaxPoolingLayer {(3:3), stride = (2:2)} (l3)
d1 = DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} (p3)
z  = LinearLayer {10, init = "gaussian", initValueScale = 1.5} (d1)

Puede encontrar más información sobre estos operadores aquí: ConvolutionalLayer{}, MaxPoolingLayer{}, DenseLayer{}, LinearLayer{}.

Configuración de CNTK

Archivo de configuración

Para entrenar y probar un modelo en CNTK, es necesario proporcionar un archivo de configuración que indique CNTK las operaciones que desea ejecutar (commandvariable) y una sección de parámetros para cada comando.

Para el comando de entrenamiento, se debe indicar CNTK:

  • cómo leer los datos (reader sección)
  • la función del modelo y sus entradas y salidas en el gráfico de cálculo (BrainScriptNetworkBuilder sección)
  • hiperparámetres para el aprendiz (SGD sección)

Para el comando de evaluación, CNTK debe saber lo siguiente:

  • cómo leer los datos de prueba (reader sección)
  • qué métricas se van a evaluar (evalNodeNames parámetro)

A continuación se muestra el archivo de configuración con el que empezaremos. Como ve, un archivo de configuración de CNTK es un archivo de texto que consta de definiciones de parámetros, que se organizan en una jerarquía de registros. También puede ver cómo CNTK admite el reemplazo de parámetros básicos mediante la $parameterName$ sintaxis . El archivo real contiene solo algunos parámetros más de los mencionados anteriormente, pero búsquelo y busque los elementos de configuración que acaba de mencionar:

# CNTK Configuration File for training a simple CIFAR-10 convnet.
# During the hands-on tutorial, this will be fleshed out into a ResNet-20 model.

command = TrainConvNet:Eval

makeMode = false ; traceLevel = 0 ; deviceId = "auto"

rootDir = "." ; dataDir  = "$rootDir$" ; modelDir = "$rootDir$/Models"

modelPath = "$modelDir$/cifar10.cmf"

# Training action for a convolutional network
TrainConvNet = {
    action = "train"

    BrainScriptNetworkBuilder = {
        imageShape = 32:32:3
        labelDim = 10

        model (features) = {
            featNorm = features - Constant (128)
            l1 = ConvolutionalLayer {32, (5:5), pad=true, activation=ReLU,
                                     init="gaussian", initValueScale=0.0043} (featNorm)
            p1 = MaxPoolingLayer {(3:3), stride=(2:2)} (l1)
            l2 = ConvolutionalLayer {32, (5:5), pad=true, activation=ReLU,
                                     init="gaussian", initValueScale=1.414} (p1)
            p2 = MaxPoolingLayer {(3:3), stride=(2:2)} (l2)
            l3 = ConvolutionalLayer {64, (5:5), pad=true, activation=ReLU,
                                     init="gaussian", initValueScale=1.414} (p2)
            p3 = MaxPoolingLayer {(3:3), stride=(2:2)} (l3)
            d1 = DenseLayer {64, activation=ReLU, init="gaussian", initValueScale=12} (p3)
            z  = LinearLayer {10, init="gaussian", initValueScale=1.5} (d1)
        }.z

        # inputs
        features = Input {imageShape}
        labels   = Input {labelDim}

        # apply model to features
        z = model (features)

        # connect to system
        ce       = CrossEntropyWithSoftmax (labels, z)
        errs     = ErrorPrediction         (labels, z)

        featureNodes    = (features)
        labelNodes      = (labels)
        criterionNodes  = (ce)
        evaluationNodes = (errs)
        outputNodes     = (z)
    }

    SGD = {
        epochSize = 50000

        maxEpochs = 30 ; minibatchSize = 64
        learningRatesPerSample = 0.00015625*10:0.000046875*10:0.000015625
        momentumAsTimeConstant = 600*20:6400
        L2RegWeight = 0.03

        firstMBsToShowResult = 10 ; numMBsToShowResult = 100
    }

    reader = {
        verbosity = 0 ; randomize = true
        deserializers = ({
            type = "ImageDeserializer" ; module = "ImageReader"
            file = "$dataDir$/cifar-10-batches-py/train_map.txt"
            input = {
                features = { transforms = (
                    { type = "Crop" ; cropType = "RandomSide" ; sideRatio = 0.8 ; jitterType = "UniRatio" } :
                    { type = "Scale" ; width = 32 ; height = 32 ; channels = 3 ; interpolations = "linear" } :
                    { type = "Transpose" }
                )}
                labels = { labelDim = 10 }
            }
        })
    }
}

# Eval action
Eval = {
    action = "eval"
    minibatchSize = 16
    evalNodeNames = errs
    reader = {
        verbosity = 0 ; randomize = true
        deserializers = ({
            type = "ImageDeserializer" ; module = "ImageReader"
            file = "$dataDir$/cifar-10-batches-py/test_map.txt"
            input = {
                features = { transforms = (
                   { type = "Scale" ; width = 32 ; height = 32 ; channels = 3 ; interpolations = "linear" } :
                   { type = "Transpose" }
                )}
                labels = { labelDim = 10 }
            }
        })
    }
}

Lectura de datos y datos

Después de haber descargado los datos CIFAR-10 y ejecutar el CifarConverter.py script como se solicitó al principio de este tutorial, encontrará un directorio denominado cifar-10-batches-py/data, que contiene dos subdirectorios y traintest, lleno de archivos PNG. El CNTK ImageDeserializer consume formatos de imagen estándar.

También encontrará dos archivos train_map.txt y test_map.txt. Mirando esto último,

% more cifar-10-batches-py/test_map.txt
cifar-10-batches-py/data/test/00000.png 3
cifar-10-batches-py/data/test/00001.png 8
cifar-10-batches-py/data/test/00002.png 8
...

Ambos archivos constan de dos columnas, donde la primera contiene la ruta de acceso al archivo de imagen y la segunda etiqueta de clase, como índice numérico. Estas columnas corresponden a las entradas features del lector y labels que se definieron como:

 features = { transforms = (
     { type = "Crop" ; cropType = "RandomSide" ; sideRatio = 0.8 ; jitterType = "UniRatio" } :
     { type = "Scale" ; width = 32 ; height = 32 ; channels = 3 ; interpolations = "linear" } :
     { type = "Transpose" }
 )}
 labels = { labelDim = 10 }

En la sección adicional transforms se indica a que ImageDeserializer aplique una secuencia de transformaciones (comunes) a las imágenes a medida que se leen. Para obtener más información, consulte aquí.

Ejecutándolo

Puede encontrar el archivo de configuración anterior bajo el nombre ImageHandsOn.cntk en la carpeta de trabajo. Para ejecutarla, ejecute la configuración anterior mediante este comando:

cntk  configFile=ImageHandsOn.cntk

La pantalla estará activa con una gran oleada de mensajes de registro (CNTK puede ser conversativo en ocasiones), pero si todo salió bien, pronto verá esto:

Training 116906 parameters in 10 out of 10 parameter tensors and 28 nodes with gradient

seguido de una salida similar a la siguiente:

Finished Epoch[ 1 of 10]: [Training] ce = 1.66950797 * 50000; errs = 61.228% * 50000
Finished Epoch[ 2 of 10]: [Training] ce = 1.32699016 * 50000; errs = 47.394% * 50000
Finished Epoch[ 3 of 10]: [Training] ce = 1.17140398 * 50000; errs = 41.168% * 50000

Esto le indica que está aprendiendo. Cada época representa un paso a través de las imágenes de entrenamiento de 50000. También indica que después de la segunda época, el criterio de entrenamiento, que la configuración denominada ce, ha alcanzado el 1,33 como se mide en las muestras de 50000 de esta época, y que la tasa de errores es del 47 % en esas mismas muestras de entrenamiento de 50000.

Nota Las máquinas solo de CPU son aproximadamente 20 veces más lentas. Tardará muchos minutos hasta que incluso vea la primera salida del registro. Para asegurarse de que el sistema progresa, puede habilitar el seguimiento para ver resultados parciales, lo que debería aparecer razonablemente rápidamente:

cntk  configFile=ImageHandsOn.cntk  traceLevel=1

Epoch[ 1 of 10]-Minibatch[-498-   1, 0.13%]: ce = 2.30260658 * 64; errs = 90.625% * 64
...
Epoch[ 1 of 10]-Minibatch[   1- 100, 12.80%]: ce = 2.10434176 * 5760; errs = 78.472% * 5760
Epoch[ 1 of 10]-Minibatch[ 101- 200, 25.60%]: ce = 1.82372971 * 6400; errs = 68.172% * 6400
Epoch[ 1 of 10]-Minibatch[ 201- 300, 38.40%]: ce = 1.69708496 * 6400; errs = 62.469% * 6400

Cuando se haya completado el entrenamiento (que tarda unos 3 minutos en un Surface Book y en una máquina de escritorio con una GPU Titan-X), el mensaje final será similar al siguiente:

Finished Epoch[10 of 10]: [Training] ce = 0.74679766 * 50000; errs = 25.486% * 50000

que muestra que la red redujo correctamente la ce pérdida y alcanzó un error de clasificación del 25,5 % en el conjunto de entrenamiento. Dado que nuestra command variable especifica un segundo comando Eval, CNTK continuará con esa acción. Mide la tasa de errores de clasificación en las 10000 imágenes del conjunto de pruebas.

Final Results: Minibatch[1-625]: errs = 24.180% * 10000

La tasa de errores de prueba está cerca del entrenamiento. Dado que CIFAR-10 es un conjunto de datos bastante pequeño, este es un indicador de que nuestro modelo aún no ha convergedo completamente (y, de hecho, ejecutarlo durante 30 épocas le llevará a unos 20%).

Si no desea esperar hasta que finalice, puede ejecutar un modelo intermedio, por ejemplo.

cntk  configFile=ImageHandsOn.cntk  command=Eval  modelPath=Models/cifar10.cmf.5
Final Results: Minibatch[1-625]: errs = 31.710% * 10000

o ejecute también nuestro modelo entrenado previamente:

cntk  configFile=ImageHandsOn.cntk  command=Eval  modelPath=cifar10.pretrained.cmf
Final Results: Minibatch[1-625]: errs = 24.180% * 10000

Modificación del modelo

A continuación, se le proporcionarán tareas para practicar la modificación de CNTK configuraciones. Las soluciones se proporcionan al final de este documento... pero por favor pruebe sin!

Tarea 1: Agregar lista desplegable

Una técnica común para mejorar la generalización de los modelos es la eliminación. Para agregar la lista desplegable a un modelo de CNTK, necesita

  • agregue una llamada a la función Dropout() CNTK donde desea insertar la operación de eliminación.
  • agregue un parámetro dropoutRate a la SGD sección llamada para definir la probabilidad de la lista desplegable.

En esta tarea específica, especifique ninguna lista desplegable para la primera época, seguida de una tasa de abandono del 50 %. Consulte la Dropout() documentación para ver cómo hacerlo.

Si todo salió bien, no observará ningún cambio en la primera época, pero una mejora mucho menor de ce una vez que el abandono se inicia con la segunda época. Se espera que esto sea así. (Para esta configuración específica, la precisión del reconocimiento no mejora, en realidad). El resultado final cuando el entrenamiento solo 10 épocas es aproximadamente el 32 %. 10 épocas no son suficientes para el dropout.

Consulte la solución aquí.

Tarea 2: Simplificar la definición del modelo mediante la extracción de elementos repetitivos en una función

En el ejemplo, la secuencia (agrupación convolución >> reLU >> ) se repite tres veces. La tarea consiste en escribir una función brainScript que agrupa estas tres operaciones en un módulo reutilizable. Tenga en cuenta que los tres usos de esta secuencia usan parámetros diferentes (dimensiones de salida, peso de inicialización). Por lo tanto, la función que escriba debe tomar estos dos como parámetros, además de los datos de entrada. Por ejemplo, podría definirse como

MyLayer (x, depth, initValueScale)

Al ejecutar esto, se espera que los valores y errs resultantes ce sean los mismos. Sin embargo, si se ejecuta en la GPU, el no determinismo en la implementación de propagación inversa de cuDNN provocará pequeñas variaciones.

Consulte la solución aquí.

Tarea 3: Agregar BatchNormalization

(Esta tarea requiere una GPU, ya que la implementación de CNTK de normalización por lotes se basa en cuDNN).

La normalización por lotes es una técnica popular para acelerar y mejorar la convergencia. En CNTK, la normalización por lotes se implementa como BatchNormalizationLayer{}.

El formato espacial (donde todas las posiciones de píxeles se normalizan con parámetros compartidos) se invoca mediante un parámetro opcional: BatchNormalizationLayer{spatialRank=2}.

Agregue la normalización por lotes a las tres capas de convolución y entre las dos capas densas. Tenga en cuenta que la normalización por lotes debe insertarse justo antes de la no linealidad. Por lo tanto, debe quitar el activation parámetro y, en su lugar, insertar llamadas explícitas a la función ReLU()CNTK .

Además, la normalización por lotes cambia la velocidad de convergencia. Así que vamos a aumentar las tasas de aprendizaje de las primeras 7 épocas de 3 plegamientos y deshabilitar la regularización de impulso y L2 estableciendo sus parámetros en 0.

Al ejecutarse, verá parámetros adicionales que se pueden aprender en el registro de entrenamiento. El resultado final será alrededor del 28 %, que es 4 puntos mejor que sin normalización por lotes después del mismo número de iteraciones. La convergencia se acelera de hecho.

Consulte la solución aquí.

Tarea 4: Convertir en neto residual

La configuración anterior es un ejemplo de "toy" para que las manos se ensucien y modifiquen las configuraciones de CNTK, y no hemos ejecutado intencionadamente para lograr una convergencia completa para mantener los tiempos de respuesta para usted bajo. Así que ahora avancemos a una configuración más real: una red residual. La red residual (https://arxiv.org/pdf/1512.03385v1.pdf) es una estructura de red profunda modificada donde las capas, en lugar de aprender una asignación de entrada a salida, aprenden un término de corrección.

(Esta tarea también requiere una GPU para la operación de normalización por lotes, aunque si tiene mucho tiempo, puede intentar ejecutarla en una CPU editando la normalización por lotes de llamadas, con cierta pérdida de precisión).

Para empezar, modifique la configuración anterior. En primer lugar, reemplace la función model(features) del modelo por esta:

        MySubSampleBN (x, depth, stride) =
        {
            s = Splice ((MaxPoolingLayer {(1:1), stride = (stride:stride)} (x) : ConstantTensor (0, (1:1:depth/stride))), axis = 3)  # sub-sample and pad: [W x H x depth/2] --> [W/2 x H/2 x depth]
            b = BatchNormalizationLayer {spatialRank = 2, normalizationTimeConstant = 4096} (s)
        }.b
        MyConvBN (x, depth, initValueScale, stride) =
        {
            c = ConvolutionalLayer {depth, (3:3), pad = true, stride = (stride:stride), bias = false,
                                    init = "gaussian", initValueScale = initValueScale} (x)
            b = BatchNormalizationLayer {spatialRank = 2, normalizationTimeConstant = 4096} (c)
        }.b
        ResNetNode (x, depth) =
        {
            c1 = MyConvBN (x,  depth, 7.07, 1)
            r1 = ReLU (c1)
            c2 = MyConvBN (r1, depth, 7.07, 1)
            r  = ReLU (c2)
        }.r
        ResNetIncNode (x, depth) =
        {
            c1 = MyConvBN (x,  depth, 7.07, 2)  # note the 2
            r1 = ReLU (c1)
            c2 = MyConvBN (r1, depth, 7.07, 1)
            r  = ReLU (c2)
        }.r
        model (features) =
        {
            conv1 = ReLU (MyConvBN (features, 16, 0.26, 1))
            rn1   = ResNetNode (ResNetNode (ResNetNode (conv1, 16), 16), 16)

            rn2_1 = ResNetIncNode (rn1, 32)
            rn2   = ResNetNode (ResNetNode (rn2_1, 32), 32)

            rn3_1 = ResNetIncNode (rn2, 64)
            rn3   = ResNetNode (ResNetNode (rn3_1, 64), 64)

            pool = AveragePoolingLayer {(8:8)} (rn3)

            z = LinearLayer {labelDim, init = "gaussian", initValueScale = 0.4} (pool)
        }.z

y cambie la configuración de SGD a:

SGD = {
    epochSize = 50000

    maxEpochs = 160 ; minibatchSize = 128
    learningRatesPerSample = 0.0078125*80:0.00078125*40:0.000078125
    momentumAsTimeConstant = 1200
    L2RegWeight = 0.0001

    firstMBsToShowResult = 10 ; numMBsToShowResult = 500
}

La tarea es modificar ResNetNode() y ResNetNodeInc() para que implementen la estructura que se ha establecido en el siguiente arte ASCII digno de premio:

            ResNetNode                   ResNetNodeInc

                |                              |
         +------+------+             +---------+----------+
         |             |             |                    |
         V             |             V                    V
    +----------+       |      +--------------+   +----------------+
    | Conv, BN |       |      | Conv x 2, BN |   | SubSample, BN  |
    +----------+       |      +--------------+   +----------------+
         |             |             |                    |
         V             |             V                    |
     +-------+         |         +-------+                |
     | ReLU  |         |         | ReLU  |                |
     +-------+         |         +-------+                |
         |             |             |                    |
         V             |             V                    |
    +----------+       |        +----------+              |
    | Conv, BN |       |        | Conv, BN |              |
    +----------+       |        +----------+              |
         |             |             |                    |
         |    +---+    |             |       +---+        |
         +--->| + |<---+             +------>+ + +<-------+
              +---+                          +---+
                |                              |
                V                              V
            +-------+                      +-------+
            | ReLU  |                      | ReLU  |
            +-------+                      +-------+
                |                              |
                V                              V

Confirme con la salida de validación en el registro que lo hizo correctamente.

Esto tardará mucho tiempo en completarse. La salida esperada será similar a la siguiente:

Finished Epoch[ 1 of 160]: [Training] ce = 1.57037109 * 50000; errs = 58.940% * 50000
Finished Epoch[ 2 of 160]: [Training] ce = 1.06968234 * 50000; errs = 38.166% * 50000
Finished Epoch[ 3 of 160]: [Training] ce = 0.85858969 * 50000; errs = 30.316% * 50000

mientras que el modelo incorrecto sin las conexiones de omisión tiene un aspecto más similar al siguiente:

Finished Epoch[ 1 of 160]: [Training] ce = 1.72901219 * 50000; errs = 66.232% * 50000
Finished Epoch[ 2 of 160]: [Training] ce = 1.30180430 * 50000; errs = 47.424% * 50000
Finished Epoch[ 3 of 160]: [Training] ce = 1.04641961 * 50000; errs = 37.568% * 50000

Consulte la solución aquí.

Tarea 5: Generar automáticamente muchas capas

Por último, el rendimiento óptimo de ResNet tiene 152 capas. Dado que sería muy tedioso y propenso a errores escribir 152 expresiones individuales, ahora modificaremos la definición para generar automáticamente una pila de ResNetNode().

La tarea consiste en escribir una función con esta firma:

ResNetNodeStack (x, depth, L)

donde L indica cuántos ResNetNodes se deben apilar, de modo que podamos reemplazar la expresión de rn1 arriba por una llamada con parámetros:

rn1   = ResNetNodeStack (conv1, 16, 3)  # 3 means 3 such nodes

y del mismo modo para rn2 y rn3. Las herramientas que necesitará son la expresión condicional :

z = if cond then x else y

y recursividad.

Este entrenamiento se ejecutará durante aproximadamente la mitad de nuestro en un Titan-X. Si lo hizo correctamente, al principio del registro contendrá este mensaje:

Training 200410 parameters in 51 out of 51 parameter tensors and 112 nodes with gradient:

Como referencia, se incluye una versión previamente entrenada de este modelo. Puede medir la tasa de errores con este comando:

cntk  configFile=ImageHandsOn.ResNet.cntk  command=Eval

y deberían ver un resultado similar al siguiente:

Final Results: Minibatch[1-625]: errs = 8.400% * 10000; top5Errs = 0.290% * 10000

Esta tasa de errores es muy cercana a la notificada en el documento original de ResNet (https://arxiv.org/pdf/1512.03385v1.pdf, tabla 6).

Consulte la solución aquí.

Tarea 6: Entrenamiento paralelo

Por último, si tiene varias GPU, CNTK le permite paralelizar el entrenamiento mediante MPI (interfaz de paso de mensajes). Este modelo es demasiado pequeño para esperar mucha velocidad sin ajustar aún más, por ejemplo, de tamaños de minibatch (la configuración actual de tamaño de minibatch es demasiado pequeña para obtener el uso completo de los núcleos de GPU disponibles). Sin embargo, pasemos por los movimientos, de modo que sepa cómo hacerlo una vez que pase a cargas de trabajo reales.

Agregue la siguiente línea al SGD bloque :

SGD = {
    ...
    parallelTrain = {
        parallelizationMethod = "DataParallelSGD"
        parallelizationStartEpoch = 2
        distributedMBReading = true
        dataParallelSGD = { gradientBits = 1 }
    }
}

y, a continuación, ejecute este comando:

mpiexec -np 4 cntk  configFile=ImageHandsOn.cntk  stderr=Models/log  parallelTrain=true

Pasos siguientes

En este tutorial se ha práctico tomar una configuración existente y modificarla de maneras específicas:

  • agregar una operación predefinida (lista desplegable)
  • extracción de elementos repetitivos en módulos reutilizables (funciones)
  • refactorización (para insertar la normalización por lotes)
  • estructuras de red personalizadas (resNet skip connection)
  • parametrización de estructuras repetitivas mediante recursividad

y hemos visto cómo acelerar el entrenamiento por paralelización.

Entonces, ¿adónde vamos desde aquí? Es posible que ya haya descubierto que el patrón usado en estos ejemplos, que llamamos estilo de creación de grafos , puede ser bastante propenso a errores. ¿Detecta el error?

model (features) =
{
    l1 = ConvolutionalLayer {32, (5:5), pad = true, activation = ReLU,
                             init = "gaussian", initValueScale = 0.0043} (featNorm)
    p1 = MaxPoolingLayer {(3:3), stride = (2:2)} (l1)
    l2 = ConvolutionalLayer {64, (5:5), pad = true, activation = ReLU,
                             init = "gaussian", initValueScale = 1.414} (p1)
    p2 = MaxPoolingLayer {(3:3), stride = (2:2)} (l1)
    d1 = DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} (p2)
    z  = LinearLayer {10, init = "gaussian", initValueScale = 1.5} (d1)
}.z

Una manera de evitar este error es usar la composición de funciones. A continuación se muestra una manera alternativa y más concisa de escribir lo mismo:

model = Sequential (
    ConvolutionalLayer {32, (5:5), pad = true, activation = ReLU,
                        init = "gaussian", initValueScale = 0.0043} :
    MaxPoolingLayer {(3:3), stride = (2:2)} :
    ConvolutionalLayer {64, (5:5), pad = true, activation = ReLU,
                        init = "gaussian", initValueScale = 1.414} :
    MaxPoolingLayer {(3:3), stride = (2:2)} :
    DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} :
    LinearLayer {10, init = "gaussian", initValueScale = 1.5}
)

Este estilo se introducirá y usará en el siguiente tutorial práctico, Text Understanding with Recurr Networks.

Soluciones

Solución 1: Agregar lista desplegable

Modifique la definición del modelo de la siguiente manera:

p3 = MaxPoolingLayer {(3:3), stride = (2:2)} (l3)
d1 = DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} (p3)
d1_d = Dropout (d1)    ##### added
z  = LinearLayer {10, init = "gaussian", initValueScale = 1.5} (d1_d)  ##### d1 -> d1_d

y la sección SGD:

SGD = {
    ...
    dropoutRate = 0*5:0.5   ##### added
    ...
}

Solución 2: Simplificar la definición del modelo mediante la extracción de elementos repetitivos en una función

Agregue una definición de función de la siguiente manera:

MyLayer (x, depth, initValueScale) =
{
    c = ConvolutionalLayer {depth, (5:5), pad = true, activation = ReLU,
                            init = "gaussian", initValueScale = initValueScale} (x)
    p = MaxPoolingLayer {(3:3), stride = (2:2)} (c)
}.p

y actualice la definición del modelo para usarla.

featNorm = features - Constant (128)
p1 = MyLayer (featNorm, 32, 0.0043)  ##### replaced
p2 = MyLayer (p1,       32, 1.414)   ##### replaced
p3 = MyLayer (p2,       64, 1.414)   ##### replaced
d1 = DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} (p3)

Solución 3: Agregar BatchNormalization

Modifique MyLayer():

MyLayer (x, depth, initValueScale) =
{
    c = ConvolutionalLayer {depth, (5:5), pad = true,  ##### no activation=ReLU
                            init = "gaussian", initValueScale = initValueScale} (x)
    b = BatchNormalizationLayer {spatialRank = 2} (c)
    r = ReLU (b)   ##### now called explicitly
    p = MaxPoolingLayer {(3:3), stride = (2:2)} (r)
}.p

y úselo. Inserte también la normalización por lotes antes de z:

d1 = DenseLayer {64, init = "gaussian", initValueScale = 12} (p3)
d1_bnr = ReLU (BatchNormalizationLayer {} (d1))  ##### added BN and explicit ReLU
d1_d = Dropout (d1_bnr)                          ##### d1 -> d1_bnr
z  = LinearLayer {10, init = "gaussian", initValueScale = 1.5} (d1_d)

Y actualice estos parámetros en la sección SGD:

SGD = {
    ....
    learningRatesPerSample = 0.00046875*7:0.00015625*10:0.000046875*10:0.000015625
    momentumAsTimeConstant = 0
    L2RegWeight = 0
    ...
}

Solución 4: Convertir en red residual

Las implementaciones correctas para ResNetNode() y ResNetNodeInc() son:

    ResNetNode (x, depth) =
    {
        c1 = MyConvBN (x,  depth, 7.07, 1)
        r1 = ReLU (c1)
        c2 = MyConvBN (r1, depth, 7.07, 1)
        r  = ReLU (x + c2)   ##### added skip connection
    }.r
    ResNetIncNode (x, depth) =
    {
        c1 = MyConvBN (x,  depth, 7.07, 2)  # note the 2
        r1 = ReLU (c1)
        c2 = MyConvBN (r1, depth, 7.07, 1)

        xs = MySubSampleBN (x, depth, 2)

        r  = ReLU (xs + c2)   ##### added skip connection
    }.r

Solución 5: Generación automática de muchas capas

Esta es la implementación:

    ResNetNodeStack (x, depth, L) =
    {
        r = if L == 0
            then x
            else ResNetNode (ResNetNodeStack (x, depth, L-1), depth)
    }.r

o, más corto:

    ResNetNodeStack (x, depth, L) =
        if L == 0
        then x
        else ResNetNode (ResNetNodeStack (x, depth, L-1), depth)

También debe modificar la función del modelo:

        conv1 = ReLU (MyConvBN (features, 16, 0.26, 1))
        rn1   = ResNetNodeStack (conv1, 16, 3)  ##### replaced

        rn2_1 = ResNetIncNode (rn1, 32)
        rn2   = ResNetNodeStack (rn2_1, 32, 2)  ##### replaced

        rn3_1 = ResNetIncNode (rn2, 64)
        rn3   = ResNetNodeStack (rn3_1, 64, 2)  ##### replaced