Conceptos básicos de BrainScript
BrainScript: Walk-Through
En esta sección se presentan los conceptos básicos del lenguaje "BrainScript". ¿Un nuevo idioma? No te preocupes & leer, es muy sencillo.
En CNTK, las redes personalizadas se definen mediante BrainScriptNetworkBuilder
y se describen en el lenguaje de descripción de red de CNTK "BrainScript". Del mismo modo, las descripciones de redes se denominan scripts cerebrales.
BrainScript proporciona una manera sencilla de definir una red de forma similar a código, mediante expresiones, variables, funciones primitivas y autodefinidas, bloques anidados y otros conceptos bien entendidos. Tiene un aspecto similar a un lenguaje de scripting en la sintaxis.
Bien, vamos a mojar nuestros pies con un ejemplo completo de BrainScript.
Una definición de red de ejemplo completa de BrainScript
En el ejemplo siguiente se muestra la descripción de red de una red neuronal simple con una capa oculta y una capa de clasificación. Explicaremos los conceptos a lo largo de este ejemplo. Antes de continuar, puede pasar unos minutos con el ejemplo e intentar adivinar lo que significa. Es posible que encuentre, como leyó, que adivino la mayor parte de ella correctamente.
BrainScriptNetworkBuilder = { # (we are inside the train section of the CNTK config file)
SDim = 28*28 # feature dimension
HDim = 256 # hidden dimension
LDim = 10 # number of classes
# define the model function. We choose to name it 'model()'.
model (features) = {
# model parameters
W0 = ParameterTensor {(HDim:SDim)} ; b0 = ParameterTensor {HDim}
W1 = ParameterTensor {(LDim:HDim)} ; b1 = ParameterTensor {LDim}
# model formula
r = RectifiedLinear (W0 * features + b0) # hidden layer
z = W1 * r + b1 # unnormalized softmax
}.z
# define inputs
features = Input {SDim}
labels = Input {LDim}
# apply model to features
z = model (features)
# define criteria and output(s)
ce = CrossEntropyWithSoftmax (labels, z) # criterion (loss)
errs = ErrorPrediction (labels, z) # additional metric
P = Softmax (z) # actual model usage uses this
# connect to the system. These five variables must be named exactly like this.
featureNodes = (features)
inputNodes = (labels)
criterionNodes = (ce)
evaluationNodes = (errs)
outputNodes = (P)
}
Conceptos básicos de la sintaxis de BrainScript
Antes de profundizar justo en, algunas notas generales sobre la sintaxis de BrainScript.
BrainScript usa una sintaxis sencilla destinada a permitir expresar redes neuronales de una manera similar a las fórmulas matemáticas. Por lo tanto, la unidad sintáctica fundamental es la asignación, que se usa tanto en las asignaciones de variables como en las definiciones de función. Por ejemplo:
Softplus (x) = Log (1 + Exp (x))
h = Softplus (W * v + b)
Líneas, comentarios, incluir
Aunque una asignación se escribe normalmente en una sola línea, las expresiones pueden abarcar varias líneas. Sin embargo, para colocar varias asignaciones en una sola línea, debe separarlas por punto y coma. Por ejemplo:
SDim = 28*28 ; HDim = 256 ; LDim = 10 # feature, hidden, and label dimension
Aparte de requerir un punto y coma entre las asignaciones en ausencia de un salto de línea, BrainScript no distingue el espacio en blanco.
BrainScript comprende los comentarios de línea mediante el estilo #
de Python y el estilo //
de C++. Los comentarios insertados usan la sintaxis de C (/* this is a comment*/
), pero a diferencia de C, pueden no abarcar varias líneas.
Para BrainScript incrustado en archivos de configuración de CNTK (en lugar de brainScript leídos de un archivo independiente a través de una include
directiva), debido a una interacción con el analizador de configuración, existe la restricción adicional (algo extraña) de que los paréntesis, llaves o corchetes deben equilibrarse dentro de los comentarios de estilo de C/C++ y los literales de cadena. Por lo tanto, no sonríe en comentarios de estilo C/C++!
Se puede usar una include "PATH"
directiva en cualquier lugar para insertar el contenido de un archivo en el punto de la instrucción . Aquí, PATH
puede ser una ruta de acceso absoluta o relativa relativa (con o sin subdirectorios). Si es una ruta de acceso relativa, se buscan las siguientes ubicaciones en orden: directorio de trabajo actual; directorios que contienen archivos externos incluidos, si los hubiera; directorios que contienen los archivos de configuración; y, por último, el directorio que contiene el ejecutable de CNTK. Todas las funciones de BrainScript integradas se incluyen de esta manera desde un archivo denominado CNTK.core.bs
que se encuentra junto al ejecutable de CNTK.
Expresiones
A continuación, debe conocer las expresiones de BrainScript: las fórmulas que describen la red.
Las expresiones BrainScript se escriben como matemáticas en una sintaxis similar a los lenguajes de programación populares. Las expresiones más sencillas son literales, por ejemplo, números y cadenas. Un ejemplo de tipo matemático es W1 * r + b
, donde *
hace referencia a un producto escalar, matriz o tensor según el tipo de las variables. Otro tipo común de expresión es la invocación de función, por ejemplo, RectifiedLinear (.)
.
BrainScript es un lenguaje con tipo dinámico. Un tipo de expresión importante es el registro, definido mediante la {...}
sintaxis y al que se accede a través de la sintaxis de puntos. Por ejemplo, r = { x = 13 ; y = 42 }
asigna un registro con dos miembros a r
, donde se puede tener acceso al primer miembro como r.x
.
Además de los operadores matemáticos habituales, BrainScript tiene una expresión condicional (if c then t else f
), una expresión de matriz y expresiones lambda simples. Por último, para interactuar con el código CNTK de C++, BrainScript puede crear directamente una instancia de un conjunto limitado de objetos C++ predefinidos, principalmente los ComputationNode
que componen las redes computacionales. Para más información, consulte Expresiones.
Las expresiones brainScript se evalúan al usarse por primera vez. El propósito principal de BrainScript es describir la red, por lo que el valor de una expresión no suele ser un valor final, sino un nodo en un gráfico de cálculo para el cálculo diferido (como en W1 * r + b
). Solo las expresiones BrainScript de escalares (por ejemplo, 28*28
) se "calculan" en el momento en que se analiza BrainScript. Las expresiones que nunca se usan (por ejemplo, debido a una condición) nunca se evalúan.
Nota: La versión ahora en desuso NDLNetworkBuilder
solo admitía la sintaxis de invocación de función; por ejemplo, tendría que escribir Plus (Times (W1, r), b1)
.
Variables
Las variables pueden contener el valor de cualquier expresión BrainScript (número, cadena, registro, matriz, lambda, objeto CNTK C++) y se sustituyen cuando se usan en una expresión. Las variables son inmutables, es decir, se asignan una sola vez. Por ejemplo, la definición de red anterior comienza por:
SDim = 28*28
HDim = 256
LDim = 10
Aquí las variables se establecen en valores numéricos escalares que se usan como parámetros en expresiones posteriores. Estos valores son las dimensiones de los ejemplos de datos, las capas ocultas y las etiquetas usadas en el entrenamiento. Esta configuración de ejemplo concreta es para el conjunto de datos MNIST, que es una colección de [28 x 28]
imágenes de píxeles. Cada imagen es un dígito manuscrito (0-9), por lo que hay 10 etiquetas posibles que se pueden aplicar a cada imagen. La dimensión HDim
de activación oculta es una opción del usuario.
La mayoría de las variables son miembros de registro (el bloque BrainScript externo es implícitamente un registro). Además, las variables pueden ser argumentos de función o almacenarse como elementos de matrices.
Ok, ready to go through the model definition.
Definición de la red
Una red se describe principalmente mediante fórmulas de cómo se calculan las salidas de la red a partir de las entradas. Llamamos a esta función del modelo, que a menudo se define como una función real en BrainScript. Como parte de la función de modelo, el usuario debe declarar los parámetros del modelo. Por último, uno debe definir las entradas de la red y los criterios o salidas. Todos se definen como variables. Las variables input y criteria/output deben comunicarse al sistema.
Parámetros de modelo y función de modelo de la red
La función de modelo contiene las fórmulas de red reales y los parámetros del modelo respectivos. En nuestro ejemplo se usa el producto de matriz y la adición, y la función "primitiva" (integrada) para la función RectifiedLinear()
de energía , por lo que el núcleo de la función de red consta de estas ecuaciones:
r = RectifiedLinear (W0 * features + b0)
z = W1 * r + b1
Los parámetros del modelo son matrices , vectores de sesgo o cualquier otro tensor que constituye el modelo aprendido al finalizar el entrenamiento. Los tensores de parámetro de modelo se usan para transformar los datos de ejemplo de entrada en la salida deseada y se actualizan mediante el proceso de aprendizaje. La red de ejemplo anterior contiene los siguientes parámetros de matriz:
W0 = ParameterTensor {(HDim:SDim)}
b0 = ParameterTensor {(HDim)}
En este caso, W0
es la matriz de peso y b0
es el vector de sesgo. ParameterTensor{}
denota un primitivo CNTK especial, que crea una instancia de un tensor de vector, matriz o rango arbitrario, y toma los parámetros de dimensión como una matriz BrainScript (números concatenados por dos puntos :
). La dimensión de un vector es un único número, mientras que una dimensión de matriz debe especificarse como (numRows:numCols)
. De forma predeterminada, los parámetros se inicializan con números aleatorios uniformes cuando se crean instancias directamente y heNormal
cuando se usan a través de capas, pero existen otras opciones (consulte aquí) para la lista completa.
A diferencia de las funciones normales, ParameterTensor{}
toma sus argumentos entre llaves en lugar de paréntesis. Las llaves son la convención brainScript para funciones que crean parámetros u objetos, en lugar de funciones.
A continuación, todo se ajusta a una función BrainScript. Las funciones brainScript se declaran con el formato f(x) = an expression of x
. Por ejemplo, Sqr (x) = x * x
es una declaración de función BrainScript válida. No podría ser mucho más sencillo y directo, ¿verdad?
Ahora, la función de modelo real de nuestro ejemplo anterior es un poco más compleja:
model (features) = {
# model parameters
W0 = ParameterTensor {(HDim:SDim)} ; b0 = ParameterTensor {HDim}
W1 = ParameterTensor {(LDim:HDim)} ; b1 = ParameterTensor {LDim}
# model formula
r = RectifiedLinear (W0 * features + b0) # hidden layer
z = W1 * r + b1 # unnormalized softmax
}.z
El exterior { ... }
y ese final .z
merece una explicación. Los curlies { ... }
externos y su contenido definen realmente un registro con 6 miembros de registro (W0
, b0
, W1
b1
, r
, y z
). Sin embargo, el valor de la función del modelo es simplemente z
; todos los demás son internos de la función. Por lo tanto, usamos .z
para seleccionar el miembro de registro que nos interesa devolver. Esta es solo la sintaxis de punto para acceder a los miembros del registro. De este modo, los demás miembros del registro no son accesibles desde fuera. Pero siguen existiendo como parte de la expresión para calcular z
.
El { ... ; x = ... }.x
patrón es una manera de usar variables locales.
Tenga en cuenta que la sintaxis del registro no es necesaria. Como alternativa, model(features)
también podría haberse declarado sin el desvío a través del registro, como una expresión única:
model (features) = ParameterTensor {(LDim:HDim)} * (RectifiedLinear (ParameterTensor {(HDim:SDim)}
* features + ParameterTensor {HDim})) + ParameterTensor {LDim}
Esto es mucho más difícil de leer y, lo que es más importante, no permitirá usar el mismo parámetro en varios lugares de la fórmula.
Entradas
Las entradas en la red se definen mediante los datos de ejemplo y las etiquetas asociadas a los ejemplos:
features = Input {SDim}
labels = Input {LDim}
Input{}
es la segunda primitiva CNTK especial necesaria para la definición del modelo (la primera es Parameter{}
). Crea una variable que recibe la entrada desde fuera de la red: desde el lector. El argumento de Input{}
es la dimensión de datos. En este ejemplo, la features
entrada tendrá las dimensiones de los datos de ejemplo (que definimos en la variable SDim
) y la labels
entrada tendrá las dimensiones de las etiquetas. Se espera que los nombres de variable de las entradas coincidan con las entradas correspondientes en la definición del lector.
Criterios de entrenamiento y salidas de red
Todavía es necesario declarar cómo interactúa la salida de la red con el mundo. Nuestra función de modelo calcula valores logit (probabilidades de registro no normalizadas). Estos valores logit se pueden usar para
- definir el criterio de entrenamiento,
- medir la precisión y
- calcular la probabilidad sobre las clases de salida dadas una entrada, para basar una decisión de clasificación en (tenga en cuenta que el registro no normalizado posterior
z
a menudo se puede usar para la clasificación directamente).
En la red de ejemplo se usan etiquetas de categoría, que se representan como vectores de un solo uso. Para el ejemplo de MNIST, estos aparecerán como una matriz de 10 valores de punto flotante, todos los cuales son cero excepto para la categoría de etiqueta adecuada que es 1.0.
Las tareas de clasificación como la nuestra suelen usar la SoftMax()
función para obtener las probabilidades de cada etiqueta. A continuación, la red se optimiza para maximizar la probabilidad de registro de la clase correcta (entropía cruzada) y minimizar la de todas las demás clases. Este es nuestro criterio de entrenamiento o función de pérdida. En CNTK, estas dos acciones se combinan normalmente en una función para mejorar la eficacia:
ce = CrossEntropyWithSoftmax (labels, z)
CrossEntropyWithSoftmax()
function toma la entrada, calcula la SoftMax()
función, calcula el error del valor real mediante la entropía cruzada y esa señal de error se usa para actualizar los parámetros de la red a través de la propagación inversa. Por lo tanto, en el ejemplo anterior, el valor normalizado Softmax()
, que calculamos como P
, no se usa durante el entrenamiento. Sin embargo, será necesario para usar la red (tenga en cuenta de nuevo que, en muchos casos, z
suele ser suficiente para la clasificación; en ese caso, z
sí mismo sería la salida).
CNTK usa el descenso de degradado estocástico (SGD) como algoritmo de aprendizaje. SGD debe calcular el degradado de la función objetivo con respecto a todos los parámetros del modelo. Importantemente, CNTK no requiere que los usuarios especifiquen esos degradados. En su lugar, cada función integrada en CNTK también tiene una función equivalente derivada y el sistema realiza automáticamente la actualización de propagación inversa de los parámetros de red. Esto no es visible para el usuario. Los usuarios nunca tienen que preocuparse por los degradados. Nunca.
Además del criterio de entrenamiento, las tasas de error previstas a menudo se calculan durante la fase de entrenamiento para validar la mejora del sistema a medida que el entrenamiento va más allá. Esto se controla en CNTK mediante la siguiente función:
errs = ClassificationError (labels, z)
Las probabilidades producidas por la red se comparan con la etiqueta real y se calcula la tasa de errores. Por lo general, el sistema muestra esto. Aunque esto es útil, no es obligatorio usar ClassificationError()
.
Comunicación de entradas, salidas y criterios al sistema
Ahora que se definen todas las variables, debemos indicar al sistema cuál de las variables debe tratar como entradas, salidas y criterios. Para ello, se definen 5 variables especiales que deben tener exactamente estos nombres:
featureNodes = (features)
labelNodes = (labels)
criterionNodes = (ce)
evaluationNodes = (errs)
outputNodes = (z:P)
Los valores son matrices, donde los valores deben estar separados por dos puntos (los dos :
puntos son un operador BrainScript que forma una matriz mediante la concatenación de dos valores o matrices). Esto se muestra anteriormente para outputNodes
, que declara y z
P
como salidas.
(Nota: En su lugar, el elemento en desuso NDLNetworkBuilder
requería que los elementos de matriz se separaran por comas).
Resumen de nombres especiales
Como vimos anteriormente, hay 7 nombres especiales que debemos tener en cuenta, que llevan propiedades "mágicas":
ParameterTensor{}
: declara e inicializa un parámetro aprendiz.Input{}
: declara una variable que se conecta y se alimenta desde un lector de datos.featureNodes
,labelNodes
,criterionNodes
,evaluationNodes
youtputNodes
: declara al sistema cuál de nuestras variables se va a usar como entradas, salidas y criterios.
Además, hay 3 funciones especiales más con "magia" integrada en CNTK, que se tratan en otro lugar:
Constant()
: declara una constante.PastValue()
yFutureValue()
: acceda a una variable de una función de red en un paso de tiempo diferente para los bucles recurrentes de formulario.
Cualquier otro nombre predefinido es una función primitiva integrada, como Sigmoid()
o Convolution()
con una implementación de C++, una función de biblioteca predefinida realizada en BrainScript, como BS.RNNs.LSTMP()
, o un registro que actúa como espacio de nombres para las funciones de biblioteca (por ejemplo BS.RNNs
, ). Consulte BrainScript-Full-Function-Reference para obtener una lista completa.
Siguiente: Expresiones brainScript