Funções BrainScript
As funções são a maneira do BrainScript criar módulos reutilizáveis. As funções são expressões parametrizadas e podem ser definidas de maneira direta, por exemplo:
Sqr (x) = x * x
As funções são tipos de dados de primeira classe no sentido de que as funções podem aceitar e retornar outras funções.
Definindo funções
Há duas maneiras de definir funções, sintaxe padrão e sintaxe lambda anônima.
Sintaxe Padrão
As funções nomeadas são membros de registro definidos como atribuições de membro de registro desses dois formulários:
f (arg1, arg2, ..., optionalArg1=optionalArg1Default, ...) = expression of args
f {arg1, arg2, ..., optionalArg1=optionalArg1Default, ...} = expression of args
As duas formas são sintaticamente idênticas, mas, por convenção, o segundo formulário (usando chaves{ }
) é usado para funções que retornam objetos de função com parâmetros de aprendizado internos, como as camadas predefinidas do CNTK.
Os argumentos de função criam um escopo de nome como um registro; Ou seja, os argumentos de função são como se fossem definidos como membros em um registro circunscrito.
Parâmetros opcionais
As funções podem ter parâmetros opcionais. Eles são passados antes de seu valor por name=
. Se um parâmetro opcional não for passado, seu valor padrão será usado. Na definição de função, os parâmetros opcionais são declarados usando o formulário name=defaultValue
.
Aqui está um exemplo para uma função Softplus que usa um parâmetro de inclinação opcional:
Softplus (x, steepness=1) =
if steepness == 1 # use the simpler formula if no steepness specified
then Log (Constant(1) + Exp (x)) # softplus(x)
else { # version with steepness: 1/s softplus (s x)
steepnessAsConstant = Constant (steepness)
y = Reciprocal (steepnessAsConstant) .* Softplus (steepnessAsConstant .* x)
# Note: .* denotes an elementwise product
}.y
Observação: os valores padrão dos parâmetros opcionais não podem acessar outros parâmetros. Por exemplo, se para a função softplus acima você tiver uma variável que defina a inclinação que você deseja passar para a função, basta dizer:
steepness = 4
z = Softplus (x, steepness=steepness)
onde o primeiro steepness
é o nome do parâmetro e o segundo é a expressão para o valor passar também.
Os nomes não entrarão em conflito.
Sintaxe Lambda
Além da sintaxe de definição de função, o BrainScript permite lambdas anônimas usando a sintaxe (x => f(x))
, que se destina a imitar C#. As duas definições a seguir são equivalentes:
sqr(x) = x * x
sqr = (x => x * x)
Na verdade, o analisador BrainScript transforma internamente o último no formulário anterior.
Atualmente, a sintaxe lambda é restrita a um parâmetro e também não dá suporte a parâmetros opcionais. Lambdas com mais de um parâmetro ou com parâmetros opcionais devem ser definidos como funções nomeadas.
Invocando funções
As funções são invocadas conforme esperado: passando os argumentos entre parênteses; e passando argumentos opcionais como parâmetros nomeados. Por exemplo, um auto-estabilizador (uma técnica de dimensionamento um pouco semelhante à normalização em lote) usando o Softplus com inclinação 4 seria escrito com as duas invocações de função a seguir:
beta = ParameterTensor {1, initValue=1}
xStabilized = x .* Softplus (beta, steepness=4)
As funções são executadas preguiçosamente após a invocação. Especificamente, se uma função definir parâmetros de modelo dentro (ou seja, as variantes e ParameterTensor{}
qualquer função que tenha chamadas para ele dentro), cada invocação da função produzirá uma instância independente desses parâmetros de modelo. Geralmente, o resultado de uma invocação de função é atribuído a uma variável ou passado como um argumento de função. Vários usos desse valor não resultam em execução repetida.
Pureza/Transparência Referencial
Embora o BrainScript tenha commonalidades com linguagens funcionais, lembre-se de que as funções BrainScript não são totalmente puras, na qual pode ter um efeito colateral muito específico: o de instanciar novos parâmetros de modelo. Por exemplo, enquanto a expressão 2 * sqr(x)
é equivalente a sqr(x) + sqr(x)
(comsqr(x) = x*x
), esse não é o caso de 2 * ParameterTensor{N}
vs. . ParameterTensor{N} + ParameterTensor{N}
Por convenção, use parênteses ( )
redondos em que a transparência referencial é dada, mas chaves { }
para indicar onde ela não está.
Funções como valores
No BrainScript, as funções são valores. Uma função nomeada pode ser atribuída a uma variável e passada como um argumento. Na verdade, as funções nomeadas são apenas membros de registro que têm o tipo "função". A pesquisa de nome de função é igual à pesquisa de membro de registro. Isso permite, por exemplo, agrupar funções em "namespaces" definindo-as dentro de uma expressão de registro.
Funções nomeadas como valores
Se um nome de função for usado sem parênteses a seguir, ele se referirá à própria função como um objeto.
Aplicativo Parcial/Currying
O BrainScript atualmente não tem suporte sintactico para aplicação parcial ou currying. Para obter um efeito semelhante, é possível definir uma nova função que chama outra função com parâmetros associados/capturados:
Layer (x, m, n, f) = f (ParameterTensor {(m:n)} * x + ParameterTensor {n})
Sigmoid512Layer (x) = Layer (x, 512, 512, Sigmoid)
Exemplos
O exemplo a seguir define uma hierarquia simples de tipos comuns de camada de rede:
Layers = {
# base feed-forward without and with parameterized energy function
# AffineLayer() defines model parameters inside.
AffineLayer (x, m, n) = ParameterTensor {(m:n), init='heNormal'} * x
+ ParameterTensor {n, initValue=0}
# NLLayer applies a non-linearity on top of an affine layer
NLLayer (x, m, n, f) = f (AffineLayer (x, m, n))
# a few non-linearities
SigmoidLayer (x, m, n) = NLLayer (x, m, n, Sigmoid) # pass Sigmoid() function as the non-linaerity
SoftplusLayer (x, m, n) = NLLayer (x, m, n, (x => Log (Constant(1) + Exp(x)))/*Softplus as lambda*/)
ReLULayer (x, m, n) = NLLayer (x, m, n, RectifiedLinear)
SoftmaxLayer (x, m, n) = NLLayer (x, m, n, Softmax)
}
que poderia ser usado da seguinte maneira:
# constants defined
# Sample, Hidden, and Label dimensions
SDim = 28*28 # feature dimension
HDim = 256 # hidden dimension
LDim = 10 # number of classes
features = Input {SDim}
labels = Input {LDim}
# layers
h = Layers.ReLULayer (features, HDim, SDim)
z = Layers.AffineLayer (h, LDim, HDim) # input to softmax; same as log softmax without normalization
# output and criteria
P = Softmax (z)
ce = CrossEntropyWithSoftmax (labels, z)
errs = ErrorPrediction (labels, z)
featureNodes = (features)
labelNodes = (labels)
criterionNodes = (ce)
evaluationNodes = (errs)
outputNodes = (P)
Isso é o mesmo que o exemplo inicial em Conceitos Básicos, mas usando funções.
Em seguida: descubra sobre a Edição de Modelo ou vá direto para a Referência de Função Completa.
NDLNetworkBuilder (preterido)
Versões anteriores do CNTK usaram o agora preterido NDLNetworkBuilder
em vez de BrainScriptNetworkBuilder
. NDLNetworkBuilder
A definição de função 's difere em alguns aspectos. A definição a seguir é válida em NDLNetworkBuilder
:
FF (X1, W1, B1) # no equal sign
[ # both curly braces and brackets are allowed
T = Times (W1, X1)
FF = Plus (T, B1)
] # return value is FF, and not the entire record
Especialmente, o valor retornado é retornado por meio da variável local que tem o mesmo nome que a função; ou se nenhuma variável desse tipo for encontrada, a variável definida por último será retornada. No BrainScript, isso agora deve ser escrito como:
FF (X1, W1, B1) =
{
T = Times (W1, X1)
Y = Plus (T, B1)
}.Y
Especialmente, o valor Y
retornado deve ser explicitamente selecionado no final usando .
sintaxe, caso contrário, o valor da função seria todo o registro.