Enero de 2017

Volumen 32, número 1

Machine Learning: exploración de la herramienta de aprendizaje automático Microsoft CNTK

Por James McCaffrey

Microsoft Computational Network Toolkit (CNTK) es un sistema de línea de comandos altamente eficaz, que puede crear sistemas de predicción de redes neuronales. Dado que CNTK originalmente se desarrolló para el uso interno en Microsoft, la documentación es un poco intimidatoria. En este artículo, le guiaré en el proceso de instalación de CNTK, con la configuración de un problema de predicción de demostración, la creación de un modelo de red neuronal, la elaboración de una predicción y la interpretación de los resultados.

En la Figura 1 se ofrece una idea de qué es CNTK y una vista previa sobre la dirección de este artículo. Aunque no es visible en la imagen, para ejecutar la herramienta CNTK introduje el comando:

> cntk.exe configFile=MakeModel.cntk makeMode=false

CNTK en acción
Figura 1 CNTK en acción

En la imagen se muestra solo la última parte de los mensajes de salida. El archivo de configuración con una extensión .cntk contiene información sobre los archivos de entrada y sobre el diseño de la red neuronal que se va a usar. Se creó un modelo de predicción y se guardó en el disco.

Después de crear el modelo de predicción, lo usé para realizar una predicción. Los valores de entrada se encuentran en el archivo NewData.txt y son (4.0, 7.0). La herramienta CNTK se usó con un segundo archivo de configuración denominado MakePrediction.cntk para calcular una predicción. Los resultados de la predicción se guardaron en el archivo Prediction_txt.pn y son (0.271139, 0.728821, 0.000040), lo que significa que el resultado precedido es el segundo de los tres valores de salida posibles.

En este artículo, se supone que está acostumbrado a trabajar con programas de línea de comandos y que tiene una idea aproximada de qué son las redes neuronales, pero no se supone que sea un experto en Machine Learning (ML) ni que sepa nada de CNTK. También puede obtener el código y los datos de la descarga complementaria.

Configuración del problema

Imagine un escenario donde quiere predecir la inclinación política de una persona (conservadora, moderada o liberal) a partir de su edad y sus ingresos anuales. Esto se denomina un problema de clasificación. La idea es tomar algunos datos con valores conocidos y crear un modelo de predicción. Aquí, puede pensar en el modelo como un tipo de función matemática compleja que acepta dos valores de entrada numéricos y, luego, emite un valor que indica una de tres clases.

Ahora, eche un vistazo al gráfico de la Figura 2. Contiene 24 puntos de datos. Las dos variables de entrada, denominadas características en la terminología de ML, son x0 y x1. Los tres colores indican tres clases distintas, a menudo denominadas etiquetas en la terminología de ML. Aunque un ser humano puede ver rápidamente un patrón, intentar crear un modelo de predicción incluso para este simple conjunto de datos supone un gran reto para un sistema informático.

Datos de aprendizaje
Figura 2 Datos de aprendizaje

Después de que CNTK use los datos que se muestran en la Figura 2 para crear un modelo de predicción de redes neuronales, el modelo se aplicará a los nueve puntos de datos de prueba que se muestran en la Figura 3. Como observará, CNTK predice correctamente ocho de los nueve casos de prueba. El elemento de prueba en (8.0, 8.0), que es en realidad la clase "red", se predecirá incorrectamente como la clase "blue".

Datos de prueba (círculos abiertos)
Figura 3 Datos de prueba (círculos abiertos)

Instalación de CNTK

La instalación de CNTK consiste solamente en descargar una carpeta comprimida de GitHub y extraer los archivos. El sitio del portal de CNTK principal se encuentra en github.com/Microsoft/CNTK. En esa página encontrará un vínculo a las versiones actuales (github.com/Microsoft/CNTK/releases). Una de las claves de la eficacia de CNTK es que, de manera opcional, puede usar una GPU en lugar de la CPU de la máquina. La página de versiones le ofrece la opción de descargar binarios para una versión de solo CPU o una versión de GPU + CPU.

Con fines de demostración, recomiendo que seleccione la versión de solo CPU, aunque puede indicar a la versión de GPU + CPU que use solo la CPU. Al hacer clic en el vínculo asociado en la página de versiones, se muestra una página en la que deberá aceptar algunos términos de licencia. Después de hacer clic en el botón Aceptar, se mostrará un cuadro de diálogo donde puede guardar un archivo con un nombre, como CNTK-1-6-Windows-64bit-CPU-Only.zip (el número de versión puede ser diferente, por supuesto), en su máquina.

Descargue el archivo .zip en el escritorio o en el directorio que le convenga. A continuación, extraiga todos los archivos directamente en la unidad C: (más habitual) o en el directorio C:\Archivos de programa.

La descarga extraída tendrá un solo directorio raíz denominado cntk. Ese directorio raíz contendrá varios directorios, incluido otro directorio también denominado cntk que contiene todos los binarios, incluido el archivo de clave cntk.exe. Para finalizar el proceso de instalación, agregue la ruta de acceso al archivo cntk.exe a la variable de entorno PATH del sistema (por lo general, C:\cntk\cntk).

Creación de archivos de datos

Para crear y ejecutar un proyecto de CNTK, necesita un archivo de configuración con la extensión .cntk y, como mínimo, un archivo que contenga datos de aprendizaje. La mayoría de los proyectos de CNTK tendrá también un archivo de datos de prueba. Además, la herramienta CNTK creará varios archivos de salida cuando se ejecute un proyecto.

Existen muchas maneras de organizar los archivos con un proyecto de CNTK. Es recomendable que cree un directorio raíz del proyecto que contenga sus archivos de datos y el archivo de configuración .cntk, y que ejecute CNTK desde dicho directorio.

Ya tenía un directorio C:\Data existente en mi máquina. Para la demo, creé en dicho directorio un nuevo subdirectorio denominado CNTK_Projects. Dentro de ese directorio creé un subdirectorio denominado SimpleNeuralNet destinado a actuar como directorio raíz del proyecto de demostración y a contener mi archivo .cntk, un archivo de datos de aprendizaje y un archivo de datos de prueba.

El sistema CNTK puede trabajar con varios tipos de archivos de datos diferentes. La demo usa archivos de texto simples. Abra una instancia del Bloc de notas y use los 24 elementos de datos de la Figura 4, o bien cree los datos manualmente con la información de la Figura 2. Luego, guarde el archivo como TrainData.txt en el directorio SimpleNeuralNet.

Figura 4 Datos de aprendizaje

|features 1.0 5.0 |labels 1 0 0
|features 1.0 2.0 |labels 1 0 0
|features 3.0 8.0 |labels 1 0 0
|features 4.0 1.0 |labels 1 0 0
|features 5.0 8.0 |labels 1 0 0
|features 6.0 3.0 |labels 1 0 0
|features 7.0 5.0 |labels 1 0 0
|features 7.0 6.0 |labels 1 0 0
|features 1.0 4.0 |labels 1 0 0
|features 2.0 7.0 |labels 1 0 0
|features 2.0 1.0 |labels 1 0 0
|features 3.0 1.0 |labels 1 0 0
|features 5.0 2.0 |labels 1 0 0
|features 6.0 7.0 |labels 1 0 0
|features 7.0 4.0 |labels 1 0 0
|features 3.0 5.0 |labels 0 1 0
|features 4.0 4.0 |labels 0 1 0
|features 5.0 5.0 |labels 0 1 0
|features 4.0 6.0 |labels 0 1 0
|features 4.0 5.0 |labels 0 1 0
|features 6.0 1.0 |labels 0 0 1
|features 7.0 1.0 |labels 0 0 1
|features 8.0 2.0 |labels 0 0 1
|features 7.0 2.0 |labels 0 0 1
The training data looks like:
|features 1.0 5.0 |labels 1 0 0
|features 1.0 2.0 |labels 1 0 0
. . .
|features 7.0 2.0 |labels 0 0 1

La etiqueta "|features" indica los valores de entrada y la etiqueta "|labels" indica los valores de salida. Ni "features" ni "labels" son palabras reservadas, de modo que puede usar algo como "|predictors" y "|predicteds" si lo prefiere. Los valores pueden delimitarse con un espacio en blanco o un carácter de tabulación (en los datos de la demo se usan espacios en blanco). Las redes neuronales solo entienden valores numéricos, por lo que las etiquetas de clase, como "red" y "blue" deben codificarse como números. Los modelos clasificadores de redes neuronales usan lo que se conoce como codificación 1-de-N. Para las tres etiquetas de clase posibles, usaría 1 0 0 para primera clase ("red" en la demo), 0 1 0 para la segunda ("blue") y 0 0 1 para la tercera ("green"). Es su decisión realizar el seguimiento de cómo se codifica el valor de cada etiqueta.

En un escenario que no sea de demostración, podría tener que preocuparse por la normalización de los datos de entrada. En situaciones en que los valores de entrada presentan magnitudes muy diferentes, se obtiene resultados mejores si se escalan los datos para que todas las magnitudes sean aproximadamente las mismas. Por ejemplo, supongamos que los datos de entrada son la edad de una persona (por ejemplo, 32.0) y sus ingresos anuales (por ejemplo, $48,500.00). Para preprocesar los datos, podría dividir todos los valores de edad por 10 y todos los valores de ingresos por 10.000, lo que produciría valores de entrada normalizados como (3.20, 4.85). Las tres formas más comunes de normalización de datos de entrada se denominan normalización de puntuación z, normalización mínima-máxima y normalización de orden de magnitud.

Después de crear y guardar el archivo TrainData.txt, cree y guarde los siguientes nueve elementos de datos de prueba como el archivo TestData.txt en el directorio SimpleNeuralNet:

|features 1.0 1.0 |labels 1 0 0
|features 3.0 9.0 |labels 1 0 0
|features 8.0 8.0 |labels 1 0 0
|features 3.0 4.0 |labels 0 1 0
|features 5.0 6.0 |labels 0 1 0
|features 3.0 6.0 |labels 0 1 0
|features 8.0 3.0 |labels 0 0 1
|features 8.0 1.0 |labels 0 0 1
|features 9.0 2.0 |labels 0 0 1

Descripción de la entrada y la salida de las redes neuronales

Para comprender cómo usar CNTK, necesita un conocimiento básico del concepto de red neuronal, y saber cómo calcula los valores de salida y cómo interpretar dichos valores. Eche un vistazo a la Figura 5. El diagrama muestra la red neuronal que corresponde al problema de la demostración.

Entrada y salida de las redes neuronales
Figura 5 Entrada y salida de las redes neuronales

Existen dos nodos de entrada que contienen los valores (8.0, 3.0) y tres nodos de salida con los valores (0.3090, 0.0055, 0.6854). La red también tiene cinco nodos de procesamiento ocultos.

Cada nodo de entrada tiene líneas que lo conectan a todos los nodos ocultos. Y cada nodo oculto tiene líneas que lo conectan a todos los nodos de salida. Estas líneas representan constantes numéricas denominadas ponderaciones. Los nodos se identifican con la indización de base 0, de modo que los nodos de nivel máximo son [0]. Por tanto, la ponderación de input[0] a hidden[0] es 2.41 y la ponderación de hidden[4] a output[2] es -0.85.

Cada nodo oculto y cada nodo de salida tienen una flecha adicional. Se denominan valores de sesgo. El sesgo de hidden[0] es -1.42 y el sesgo de output[2] es -1.03.

Los cálculos de entrada-salida se explican mejor con un ejemplo. En primer lugar, se calculan los valores de los nodos ocultos. El nodo oculto medio, hidden[2] tiene el valor 0.1054. Para calcularlo, se suman los productos de todas las entradas conectadas y sus ponderaciones asociadas más el valor de sesgo y, luego, se toma la tangente hiperbólica (tanh) de esa suma:

hidden[2] = tanh( (8.0)(-0.49) + (3.0)(0.99) + 1.04) )
          = tanh( -3.92 + 2.98 + 1.04 )
          = tanh( 0.1058 )
          = 0.1054

La función tanh se denomina función de activación de capa oculta. Las redes neuronales pueden usar una de las distintas funciones de activación disponibles. Además de tanh, las otras dos más comunes son sigmoide logística (a menudo abreviada como "sigmoide") y lineal rectificada.

Una vez calculados todos los valores de los nodos ocultos, el siguiente paso consiste en calcular los nodos de salida. En primer lugar, se calcula la suma de los productos de los nodos ocultos conectados y sus ponderaciones asociadas, más el valor de sesgo. Por ejemplo, output[0] para este paso es 1.0653, calculado como:

output[0] = (1.0000)(-2.24) + (-0.1253)(1.18) +
            (0.1054)(0.55) + (-0.1905)(1.83) +
            (-1.0000)(-1.23) + 2.51
          = (-2.2400) + (-0.1478) +
            (0.0580) + (-0.3486) +
            (1.2300) + 2.51
          = 1.0653

Del mismo modo, output[1] se calcula para que sea -2.9547 y output[2] es 1.8619.

A continuación, los tres valores de salida preliminares se escalan para sumar 1.0, usando lo que se conoce como función softmax:

output[0] = e^1.0653 / (e^1.0653 + e^-2.9547 + e^1.8619)
          = 0.3090
output[1] = e^-2.9547 / (e^1.0653 + e^-2.9547 + e^1.8619)
          = 0.0055
output[2] = e^1.8619 / (e^1.0653 + e^-2.9547 + e^1.8619)
          = 0.6854

Estos tres valores se interpretan como probabilidades. Por tanto, para las entradas de (8.0, 3.0) y las ponderaciones y los valores de sesgo proporcionados, las salidas son (0.3090, 0.0055, 0.6854). La probabilidad más alta es el tercer valor, de modo que la predicción es la tercera clase, "green" en este caso.

Otra manera de interpretar los valores de salida es asignarlos para que la probabilidad más alta sea uno y todas las demás sean cero. Para este ejemplo, obtendría (0, 0, 1), que se asigna al valor codificado de "green".

El proceso de determinación de los valores de las ponderaciones y los sesgos se denomina entrenamiento de la red, y es lo que hace CNTK.

Creación del archivo de configuración

La Figura 1 le proporciona una idea de cómo se usa CNTK. En un shell de comandos ordinario, navegué al directorio raíz del proyecto en C:\Data\SimpleNeuralNet. El directorio raíz del proyecto contiene los archivos TrainData.txt y TestData.txt, así como un archivo de configuración MakeModel.cntk. La herramienta CNTK se invocó con la ejecución del siguiente comando:

> cntk.exe configFile=MakeModel.cntk makeMode=false

Recuerde que la variable PATH del sistema conoce la ubicación del programa cntk.exe, de modo que no tiene que estar completa. El archivo de configuración .cntk contiene una gran cantidad de información. El parámetro makeMode=false sirve para ejecutar el programa y sobrescribir los resultados anteriores. Los argumentos de línea de comandos de CNTK no distinguen mayúsculas de minúsculas.

En la Figura 6 se muestra la estructura general del archivo de configuración. El listado completo del archivo de configuración se presenta en la Figura 7.

Figura 6 Estructura del archivo de configuración

# MakeModel.cntk
command=Train:WriteProbs:DumpWeights:Test
# system parameters go here
Train = [
  action="train"
  BrainScriptNetworkBuilder = [
    # define network here
  ]
]
Test = [
  # training commands here
]
WriteProbs = [
  # output commands here
]
DumpWeights = [
  # output commands here
]

Figura 7 Archivo de configuración de aprendizaje

# MakeModel.cntk
command=Train:WriteProbs:DumpWeights:Test
modelPath = "Model\SimpleNet.snn"
deviceId = -1
dimension = 2
labelDimension = 3
precision = "float"
# =====
Train = [
  action="train"
  # network description
  BrainScriptNetworkBuilder = [
    FDim = $dimension$
    HDim = 5
    LDim = $labelDimension$
    # define the neural network
    neuralDef (ftrs) = [
      W0 = Parameter (HDim, FDim)
      b0 = Parameter (HDim, 1) 
      W1 = Parameter (LDim, HDim)
      b1 = Parameter (LDim, 1)
      hn = Tanh (W0 * ftrs + b0)
      zn = W1 * hn + b1
    ].zn
    # specify inputs
    features = Input (FDim)
    labels   = Input (LDim)
    # create network
    myNet = neuralDef (features)
    # define training criteria and output(s)
    ce   = CrossEntropyWithSoftmax (labels, myNet)
    err  = ErrorPrediction (labels, myNet)
    pn   = Softmax (myNet)
    # connect to the NDL system.
    featureNodes    = (features)
    inputNodes      = (labels)
    criterionNodes  = (ce)
    evaluationNodes = (err)
    outputNodes     = (pn)
  ]
  # stochastic gradient descent
  SGD = [
    epochSize = 0
    minibatchSize = 1
    learningRatesPerSample = 0.04
    maxEpochs = 500
    momentumPerMB = 0.90
  ]
  # configuration for reading data
  reader = [
    readerType = "CNTKTextFormatReader"
    file = "TrainData.txt"
    input = [
      features = [
        dim = $dimension$
        format = "dense"
      ]
      labels = [
        dim = $labelDimension$
        format = "dense"
      ]
    ]
  ]
]
# test
Test = [
  action = "test"
  reader = [
    readerType = "CNTKTextFormatReader"
    file="TestData.txt"
    randomize = "false"
    input = [
      features = [
        dim = $dimension$
        format = "dense"
      ]
      labels = [
        dim = $labelDimension$
        format = "dense"
      ]
    ]
  ]
]
# log the output node values
WriteProbs = [
  action="write"
  reader=[
    readerType="CNTKTextFormatReader"
    file="TestData.txt"       
    input = [
      features = [
        dim = $dimension$
        format = "dense"
      ]
      labels = [
        dim = $labelDimension$
        format = "dense"
      ]
    ]
  ]
  outputPath = "TestProbs_txt"
]
# dump weight and bias values
DumpWeights = [
  action = "dumpNode"
  printValues = "true"
]

Puede asignar el nombre que desee a un archivo de configuración de CNTK, pero la práctica estándar es usar la extensión .cntk. Puede usar el carácter # o el token // para los comentarios, que no se distribuyen en líneas. En la parte superior del archivo de configuración, se incluye una lista delimitada por dos puntos de los módulos que se van a ejecutar, que son cuatro en este caso:

command=Train:WriteProbs:DumpWeights:Test

Observe que el orden de ejecución de los módulos no tiene que coincidir con el orden en que están definidos. Los nombres de los módulos (en este ejemplo: Train, WriteProbs, DumpWeights y Test) no son palabras clave, por lo que puede asignarles los nombres que desee. Observe que el módulo Train tiene una instrucción action="train". Aquí, ambos términos son palabras clave.

El módulo Train usa el archivo de datos de aprendizaje para crear el modelo de predicción, de modo que la mayoría de los archivos de configuración de CNTK tendrán un módulo, por lo general denominado Train, que contendrá un comando action="train". El módulo Train escribirá la información del modelo resultante en el disco en formato binario.

El módulo Test es opcional, pero suele incluirse al crear un modelo. El módulo Test usará el modelo de predicción recién creado para evaluar la precisión general de la predicción y el error de predicción sobre los datos de aprendizaje.

El módulo WriteProbs es opcional. El módulo escribirá los valores de predicción reales para los elementos de datos de prueba en un archivo de texto en el directorio raíz del proyecto. Esto permite ver exactamente qué casos de prueba se predijeron correctamente y cuáles no.

El módulo DumpWeights escribirá un archivo de texto que contendrá las ponderaciones y los sesgos de las redes neuronales que definen el modelo de predicción. Puede usar esta información para revelar puntos problemáticos, así como realizar predicciones sobre datos nuevos que no se han visto previamente.

Parámetros del sistema

El archivo de configuración MakeModel.cntk configura cinco parámetros del sistema:

modelPath = "Model\SimpleNet.snn"
deviceId = -1
dimension = 2
labelDimension = 3
precision = "float"

La variable modelPath especifica dónde se debe colocar el modelo binario resultante y qué nombre asignar al modelo. Aquí, "snn" significa rede neuronal simple, pero puede usar cualquier extensión. La variable deviceId indica a CNTK si debe usar la CPU (-1) o la GPU (0).

La variable dimension especifica el número de valores en un vector de entrada. La variable labelDimension especifica el número de valores de salida posibles. La variable precision puede tomar los valores float o double. En la mayoría de los casos, se prefiere float porque permite un aprendizaje mucho más rápido que double.

Módulo de aprendizaje

El módulo Train de demostración del archivo de configuración presenta tres subsecciones principales: BrainScriptNetworkBuilder, SGD y reader. Estas subsecciones definen la arquitectura de las redes neuronales, cómo entrenar la red y cómo leer los datos de aprendizaje.

La definición del módulo de aprendizaje comienza de la siguiente manera:

Train = [
  action="train"
  BrainScriptNetworkBuilder = [
    FDim = $dimension$
    HDim = 5
    LDim = $labelDimension$
...

La sección BrainScriptNetworkBuilder del módulo Train usa un lenguaje de scripting especial denominado BrainScript. Las variables FDim, HDim y LDim contienen el número de características, los nodos ocultos y los nodos de etiquetas de la red neuronal. Estos nombres no son obligatorios, de modo que puede usar nombres como NumInput, NumHidden y NumOutput si lo desea. El número de nodos de entrada y salida viene determinado por los datos del problema, pero el número de nodos ocultos es un parámetro libre que debe determinarse mediante prueba y error. El token $ es un operador de sustitución. La definición del módulo Train continúa con:

neuralDef (ftrs) = [
  W0 = Parameter (HDim, FDim)
  b0 = Parameter (HDim, 1) 
  W1 = Parameter (LDim, HDim)
  b1 = Parameter (LDim, 1)
  hn = Tanh (W0 * ftrs + b0)
  zn = W1 * hn + b1
].zn
...

Esta es una función BrainScript. La variable W0 es una matriz que contiene las ponderaciones de entrada a oculta. La función Parameter significa "construcción de una matriz". La variable b0 contiene los valores de sesgo de los nodos ocultos. Todos los cálculos de BrainScript se realizan en matrices, de modo que b0 es una matriz con una columna en lugar de una matriz.

Las variables W1 y b1 contienen los ponderaciones de entrada a oculta y los valores de sesgo de los nodos de salida. Los valores de los nodos ocultos se calculan en una variable denominada hn con una suma de productos y la función tanh, como he explicado anteriormente. La variable zn contiene los valores de salida previos a ­softmax. La notación paréntesis de cierre-punto-variable es el modo en que una función BrainScript devuelve un valor. La definición de Train continúa con:

features = Input (FDim)
labels   = Input (LDim)
myNet = neuralDef (features)
...

Aquí se definen las características de entrada y las etiquetas de salida. Los nombres de variable "features" y "labels" no son palabras clave, pero deben coincidir con las cadenas usadas en los archivos de datos de prueba y aprendizaje. La red neuronal se crea llamando a la función neuralDef. A continuación, el módulo define la información que se usará durante el aprendizaje:

ce   = CrossEntropyWithSoftmax (labels, myNet)
err  = ErrorPrediction (labels, myNet)
pn   = Softmax (myNet)
...

La función CrossEntropyWithSoftmax especifica que el error de entropía cruzada debe usarse al calcular la proximidad de los valores de salida calculados con los valores de salida reales en los datos de aprendizaje. El error de entropía cruzada es la métrica estándar, pero el error cuadrático es una alternativa.

La función ErrorPrediction indica a CNTK que calcule y muestre la precisión del modelo de predicción (porcentaje de predicciones correctas en los datos de aprendizaje), así como la perplejidad y el error de entropía cruzada, que son medidas de error entre las salidas calculadas y las reales.

La función Softmax indica a CNTK que normalice los valores de salida calculados para que sumen hasta 1.0 y puedan interpretarse como probabilidades. Softmax se usa para los clasificadores de redes neuronales, excepto en situaciones muy concretas. La definición del módulo de aprendizaje concluye de la siguiente manera:

...
  featureNodes    = (features)
  inputNodes      = (labels)
  criterionNodes  = (ce)
  evaluationNodes = (err)
  outputNodes     = (pn)
]

Aquí, las variables del sistema necesarias de featureNodes, inputNodes, criterionNodes y outputNodes, así como las propiedades evaluationNodes opcionales, están asociadas a las variables definidas por el usuario.

En la subsección sobre el descenso por gradiente estocástico (stochastic gradient descent, SGD) se define cómo CNTK entrenará a la red neuronal. En el contexto de una red neuronal, SGD suele denominarse propagación inversa. La definición de la sub­sección es:

SGD = [
  epochSize = 0
  minibatchSize = 1
  learningRatesPerSample = 0.04
  maxEpochs = 500
  momentumPerMB = 0.90
]

La variable epochSize especifica la cantidad de datos de aprendizaje que se usará. Un valor especial de cero significa que se usarán todos los datos de aprendizaje disponibles. La variable minibatchSize especifica la cantidad de datos de aprendizaje que se procesará en cada iteración de aprendizaje. Un valor de uno indica la actualización de las ponderaciones y los sesgos tras el procesamiento de cada elemento de aprendizaje. Suele denominarse aprendizaje "en línea" o "estocástico".

Si el valor de minibatchSize está establecido en el número de elementos de aprendizaje (24 en el caso de la demo), se procesarán los 24 elementos y se agregarán los resultados. Solo entonces se actualizarán las ponderaciones y los sesgos. En ocasiones, esto se conoce como aprendizaje por "lotes" o "lotes completos". El uso de un valor entre uno y el tamaño establecido del aprendizaje se denomina aprendizaje por "minilotes".

La variable learningRatesPerSample especifica cuánto deben ajustarse las ponderaciones y los sesgos en cada iteración. El valor de velocidad de aprendizaje, junto con los demás parámetros de la subsección SGD, debe determinarse mediante prueba y error. Las redes neuronales suelen ser extremadamente sensibles al valor de velocidad de aprendizaje; por ejemplo, el valor 0.04 podría proporcionar un sistema de predicción muy preciso, pero el uso de 0.039 o 0.041 podría ofrecer un sistema muy deficiente.

La variable maxEpochs especifica cuántas iteraciones se realizarán para el aprendizaje. Un valor demasiado bajo dará como resultado un modelo deficiente ("modelo infraentrenado"), pero un exceso de iteraciones sobreentrenará los datos de aprendizaje. Esto dará lugar a un modelo que predice muy bien los datos de aprendizaje, pero muy mal los datos nuevos.

momentumPerMB (momento por minilote, no por megabyte como podría suponer) es un factor que aumenta o reduce la cantidad con la cual se actualizan las ponderaciones o los sesgos. Como sucede con la velocidad de aprendizaje, un valor de momento debe determinarse mediante prueba y error. Además, el aprendizaje de una red neuronal suele ser extremadamente sensible al valor de momento. El valor de 0.90 que usa la demo es el predeterminado, de modo que el parámetro momentumPerMB podría haberse omitido.

El módulo de aprendizaje del archivo de configuración de la demo concluye con la configuración de los valores de los parámetros de la subsección reader:

...
  reader = [
    readerType = "CNTKTextFormatReader"
    file = "TrainData.txt"
    input = [
      features = [ dim = $dimension$; format = "dense" ]
      labels = [ dim = $labelDimension$; format = "dense" ]
    ]
  ]
] # end Train

La herramienta CNTK cuenta con numerosos tipos diferentes de lectores para usar con los distintos tipos de datos de entrada. El significado de los parámetros de la subsección reader de la demo debe quedar claro. Observe que coloco varias instrucciones en una sola línea usando el delimitador de punto y coma.

Módulo Test

Para resumir lo visto hasta ahora, el archivo de configuración MakeModel.cntk tiene algunos parámetros del sistema global (como modelPath), más cuatro módulos: Train, Test, WriteProbs y DumpWeights. El módulo Train presenta tres subsecciones: BrainScriptNetworkBuilder, SGD y reader.

Afortunadamente, el módulo Test es muy simple, como se puede apreciar en la Figura 8.

Figura 8 Módulo Test

Test = [
  action = "test"
  reader = [
    readerType="CNTKTextFormatReader"
    file = "TestData.txt"
    randomize = "false"
    input = [
      features = [ dim = $dimension$
        format = "dense" ]
      labels = [ dim = $labelDimension$
        format = "dense" ]
    ]
  ]
]

La subsección reader del módulo de prueba debe coincidir con la subsección reader el módulo de aprendizaje, excepto en el valor del parámetro file y la adición del parámetro randomize. En el aprendizaje con SGD, es de extrema importancia que los elementos de datos se procesen en orden aleatorio y que true sea el valor predeterminado de randomize. No obstante, al revisar los datos de prueba, no es necesario aleatorizar el orden de los elementos de datos.

El módulo de prueba emite una métrica de precisión y dos métricas de error para el shell. Si vuelve a consultar la Figura 1, justo antes del mensaje "Action test complete", verá lo siguiente:

err = 0.11111111 * 9
ce = 0.33729280 * 9
perplexity = 1.40114927

err = 0.1111 * 9 significa que un 11 % de los nueve elementos de datos de prueba se predijeron incorrectamente con el modelo. En otras palabras, ocho de cada nueve elementos de prueba se predijeron incorrectamente. Sin embargo, la salida del aprendizaje no indica qué elementos de datos se predijeron de manera correcta o incorrecta.

ce = 0.3372 * 9 significa que el error de entropía cruzada medio es 0.3372. Para esta introducción a CNTK, solo tiene que pensar en la entropía cruzada como un término de error, de modo que los valores más bajos son mejores.

La métrica perplexity = 1.4011 es secundaria. Puede pensar en la perplejidad como una medida de la fiabilidad de las predicciones, donde los valores más bajos son mejores. Por ejemplo, para tres valores de salida posibles como en la demo, si la predicción es (0.33, 0.33, 0.33), la predicción no será fiable en absoluto. La perplejidad en este caso seria 3.0, que es el máximo para tres valores de salida.

Módulo WriteProbs

El tercer módulo del archivo de configuración de CNTK de la demo es Write­Probs. Este módulo es opcional, pero resulta muy útil porque proporciona información adicional sobre las predicciones realizadas sobre los datos de prueba. El módulo se define en la Figura 9.

Figura 9 Módulo WriteProbs

WriteProbs = [
  action="write"
  reader=[
    readerType="CNTKTextFormatReader"
    file="TestData.txt"       
    input = [
      features = [  dim = $dimension$
        format = "dense" ]
      labels = [ dim = $labelDimension$
        format = "dense"  ]
    ]
  ]
  outputPath = "TestProbs_txt"
]

El módulo WriteProbs es igual que el módulo Test, excepto en tres aspectos. Primero, el parámetro action está establecido en "write" en lugar de en "test". Segundo, el parámetro randomize se ha quitado (porque false es el valor predeterminado). Tercero, se ha agregado un parámetro outputPath.

Cuando se ejecute el módulo WriteProbs, escribirá los valores de salida exactos para los datos de prueba en el archivo especificado. En este caso, el nombre del archivo tendrá anexado ".pn" porque ese era el nombre de variable usado para los nodos de salida en el módulo de aprendizaje.

Para los nueve elementos de prueba de la demo, el contenido del archivo TestProbs_txt.pn es el siguiente:

0.837386 0.162606 0.000008
0.990331 0.009669 0.000000
0.275697 0.724260 0.000042
0.271172 0.728788 0.000040
0.264680 0.735279 0.000041
0.427661 0.572313 0.000026
0.309024 0.005548 0.685428
0.000134 0.000006 0.999860
0.000190 0.000008 0.999801

Los primeros tres vectores de probabilidad van con los tres primeros elementos de prueba, que se asignan a la salida correcta (1, 0, 0), de modo que los dos primeros elementos de prueba se predijeron correctamente. No obstante, el tercer vector de probabilidad de (0.27, 0.74, 0.00) se asigna a (0, 1, 0), de modo que la predicción es incorrecta.

Los siguientes tres vectores de probabilidad van con los elementos de prueba con la salida (0, 1, 0), de modo que las tres predicciones son correctas. De manera similar, los últimos tres vectores de probabilidad van con (0, 0, 1), de modo que también son predicciones correctas.

En resumen, el módulo Test emitirá métricas de precisión y error al shell, pero no le indicará qué elementos de prueba individuales son correctos ni el error que generan. El módulo WriteProbs escribe valores de salida exactos en el archivo, que puede usar para determinar qué elementos de prueba se predijeron de manera incorrecta.

Módulo DumpWeights

El último de los cuatro módulos del archivo de configuración de la demo es DumpWeights, que se define de la siguiente manera:

DumpWeights = [
  action = "dumpNode"
  printValues = "true"
]

Al ejecutarse, este módulo guardará los valores de las ponderaciones y los sesgos del modelo entrenado en un archivo. De manera predeterminada, el nombre de archivo coincidirá con el modelo binario (SimpleNet.snn en la demo), con ".__AllNodes__.txt" anexado, y se guardará en el directorio que especifica el parámetro modelPath ("Model" en la demo).

Después de ejecutar la demo MakeModel.cntk, si abre un explorador de archivos y señala el directorio \SimpleNeuralNet\Model, verá 503 archivos:

SimpleNet.snn
SimpleNet.snn.__AllNodes__.txt
SimpleNet.snn.0
...
SimpleNet.snn.499
SimpleNet.ckp

SimpleNet.snn es el modelo entrenado guardado en formato binario para que lo use CNTK. Los 500 archivos que tienen nombres terminados con un dígito y el archivo que termina con una extensión ".ckp" son archivos de punto de control binarios. La idea es que el aprendizaje de una red neuronal compleja puede requerir horas o incluso días. Recuerde que la demo estableció un parámetro maxEpochs en 500. La herramienta CNTK guarda la información de aprendizaje periódicamente para que, en caso de fallo del sistema, no tenga que volver a comenzar el aprendizaje desde el principio.

La primera mitad del contenido del archivo AllNodes__.txt de la demo (con algunas líneas eliminadas) es:

myNet.b0=LearnableParameter [5,1]
-1.42185283
 1.84464693
 1.04422486
 2.57946277
 1.65035748
 ################################################
myNet.b1=LearnableParameter [3,1]
 2.51937032
-1.5136646
-1.03768802

Estos son los valores de los sesgos de los nodos ocultos (b0) y los sesgos de los nodos de salida (b1). Si vuelve a consultar el diagrama de la red neuronal de la Figura 4, verá donde se truncan estos valores en dos decimales. La segunda mitad del archivo AllNodes__.txt tiene el siguiente aspecto:

myNet.W0=LearnableParameter [5,2]
 2.41520381 -0.806418538
-0.561291218 0.839902222
-0.490522146 0.995252371
-0.740959883 1.05180109
-2.72802472 2.81985259
 #################################################
myNet.W1=LearnableParameter [3,5]
-2.246624  1.186315  0.557211  1.837152 -1.232379
 0.739416  0.814771  1.095480  0.386835  2.120146
 1.549207 -1.959648 -1.627967 -2.235190 -0.850726

Recuerde que la red de la demo tiene dos valores de entrada, cinco nodos ocultos y tres nodos de salida. Por lo tanto, existen 2 * 5 = 10 ponderaciones de entrada a oculta en W0 y 5 * 3 = 15 ponderaciones de oculta a salida en W1.

Realización de una predicción

Una vez dispone de un modelo entrenado, puede usarlo para realizar una predicción. Una manera de hacerlo es usar la herramienta CNTK con un módulo de acción "eval". La demo adopta este enfoque. En primer lugar, se crea un nuevo conjunto de datos con un solo elemento y se guarda como el archivo NewData.txt:

|features 4.0 7.0 |labels -1 -1 -1

Dado que son datos nuevos, las etiquetas de salida usan valores -1 ficticios. A continuación, creé un archivo de configuración denominado MakePrediction.cntk con dos módulos denominados Predict y WriteProbs. El archivo completo se presenta en la Figura 10.

Figura 10 Realización de una predicción

# MakePrediction.cntk
stderr = "Log"   # write all messages to file
command=Predict:WriteProbs
modelPath = "Model\SimpleNet.snn" # where to find model
deviceId = -1 
dimension = 2 
labelDimension = 3 
precision = "float"
Predict = [
  action = "eval"
  reader = [
    readerType="CNTKTextFormatReader"
    file="NewData.txt"
    input = [
      features = [ dim = $dimension$; format = "dense" ]
      labels = [ dim = $labelDimension$; format = "dense" ]
    ]
  ]
]
WriteProbs = [
  action="write"
  reader=[
    readerType="CNTKTextFormatReader"
    file="NewData.txt"       
    input = [
      features = [ dim = $dimension$; format = "dense" ]
      labels = [ dim = $labelDimension$; format = "dense" ]
    ]
  ]
  outputPath = "Prediction_txt"  # dump with .pn extension
]

Cuando se ejecuta, las probabilidades de salida se guardan en un archivo denominado Prediction_txt.pn, que contiene lo siguiente:

0.271139 0.728821 0.000040

Esto se asigna a la salida (0, 1, 0), que es "blue". Si observa los datos de aprendizaje de la Figura 2, puede ver que (4.0, 7.0) podría ser fácilmente "red" (1, 0, 0) o "blue" (0, 1, 0).

Dos técnicas alternativas para usar un modelo entrenado son usar un programa C# con la biblioteca de evaluación de modelos de CNTK, o usar un script de Python personalizado que emplee las ponderaciones de modelos y los valores de sesgo directamente.

Resumen

A mi entender, CNTK es el sistema de red neuronal más eficaz para Windows, que se encuentra disponible de manera general para los desarrolladores. En este artículo se trata solo una pequeña parte de lo que CNTK puede hacer, pero debería ser suficiente para que pueda empezar a usar redes neuronales simples y comprender la documentación. La eficacia real de CNTK procede del trabajo con redes neuronales complejas (redes que tienen dos o más capas ocultas y quizás conexiones difíciles entre los nodos).

La herramienta CNTK se encuentra en proceso de desarrollo activo, por lo que algunos detalles podrían haber cambiado cuando lea este artículo. No obstante, el equipo de CNTK me indica que, seguramente, los cambios serán poco importantes y que debería poder modificar la demo que se presenta en este artículo sin demasiadas dificultades.


El Dr. James McCaffrey trabaja para Microsoft Research en Redmond, Washington. Ha colaborado en el desarrollo de varios productos de Microsoft como, por ejemplo, Internet Explorer y Bing. Puede ponerse en contacto con James en jammc@microsoft.com.

Gracias a los siguientes expertos técnicos de Microsoft por revisar este artículo: Adam Eversole, John Krumm, Frank Seide y Adam Shirey


Discuta sobre este artículo en el foro de MSDN Magazine