Funções BrainScript
As funções são a forma do BrainScript criar módulos reutilizáveis. As funções são expressões parametrizadas e podem ser definidas de forma 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 devolver outras funções.
Definição de Funções
Existem duas formas de definir funções, sintaxe padrão e sintaxe de lambda anónima.
Sintaxe padrão
As funções nomeadas são membros de registo que são definidas como atribuições de membros de registo destes 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, a segunda (usando chaves encaracoladas) é usada para funções que devolvem objetos de função com parâmetros aprendizagens incorporados, como as camadas predefinidas de CNTK.
Os argumentos de função criam um âmbito de nome como um registo; ou seja, os argumentos de função são como se fossem definidos como membros num registo envolvente.
Parâmetros opcionais
As funções podem ter parâmetros opcionais. Estes são passados precedendo o seu valor por name=
. Se um parâmetro opcional não for aprovado, o seu valor predefinido é utilizado. Na definição de função, os parâmetros opcionais são declarados utilizando o formulário name=defaultValue
.
Aqui está um exemplo para uma função Softplus que leva 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
Nota: Os valores predefinidos dos parâmetros opcionais não podem aceder a outros parâmetros. Por exemplo, se para a função Softplus acima tem uma variável que define a inclinação que pretende 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 vão entrar em conflito.
Sintaxe Lambda
Além da sintaxe de definição de função, o BrainScript permite que lambdas anónimas utilizem a sintaxe (x => f(x))
, que se destina a imitar C#. As duas definições seguintes são equivalentes:
sqr(x) = x * x
sqr = (x => x * x)
Na verdade, o analisador BrainScript transforma-o internamente na forma anterior.
A sintaxe lambda está atualmente limitada a um parâmetro, e também não suporta parâmetros opcionais. Lambdas com mais de um parâmetro ou com parâmetros opcionais devem ser definidos como funções nomeadas.
Invocar Funções
As funções são invocadas como se espera: Ao passar os argumentos em parênteses; e passando argumentos opcionais como parâmetros nomeados. Por exemplo, um auto-estabilizador (uma técnica de escala um pouco semelhante à normalização do lote) utilizando Softplus com inclinação 4 seria escrito com as seguintes duas invocações de função:
beta = ParameterTensor {1, initValue=1}
xStabilized = x .* Softplus (beta, steepness=4)
As funções são preguiçosamente executadas após a invocação. Especificamente, se uma função define modelo parâmetros no interior (isto é, as variantes e ParameterTensor{}
qualquer função que tenha chamadas para o interior), cada invocação da função produz uma instância independente destes parâmetros modelo. Muitas vezes, o resultado de uma invocação de função é então atribuído a uma variável ou passado como um argumento de função. Utilizações múltiplas de tal valor não resultam em execução repetida.
Transparência de Pureza/Referencial
Embora o BrainScript tenha comunhões com linguagens funcionais, esteja ciente de que as funções BrainScript não são inteiramente puras, na medida em que podem ter um efeito colateral muito específico: o de instantanear novos parâmetros de modelo. Por exemplo, enquanto a expressão 2 * sqr(x)
é equivalente a sqr(x) + sqr(x)
(com sqr(x) = x*x
), isso não é o caso de 2 * ParameterTensor{N}
vs. ParameterTensor{N} + ParameterTensor{N}
.
Por convenção, utilize parênteses ( )
redondos onde seja dada transparência referencial, mas aparelhos { }
encaracolados para indicar onde 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 argumento. Na verdade, as funções nomeadas são apenas membros de gravação que têm o tipo de "função". A procura de nomes de funções é a mesma que a procura de membros de registo. Isto permite, por exemplo, agrupar funções em "espaços de nome", definindo-as dentro de uma expressão de registo.
Funções nomeadas como Valores
Se um nome de função for usado sem seguir parênteses, refere-se à função em si como um objeto.
Aplicação/Currying parcial
O BrainScript não tem atualmente aplicação parcial de suporte sintático ou currying. Para obter um efeito semelhante, pode-se definir uma nova função que chama outra função com parâmetros ligados/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 camadas 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 assim:
# 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)
Isto é o mesmo que o exemplo inicial em Conceitos Básicos, mas usando funções.
Seguinte: Saiba mais sobre a Edição de Modelos ou vá diretamente para a Referência de Função Completa.
NDLNetworkBuilder (Deprecado)
Versões anteriores de CNTK usaram o agora prevadizado NDLNetworkBuilder
em vez de BrainScriptNetworkBuilder
. NDLNetworkBuilder
A definição de função diferiu de algumas formas. A seguinte definição é 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 de retorno é devolvido através da variável local que tem o mesmo nome que a função; ou se não for encontrada tal variável, a variável definida por último será devolvida. No BrainScript isto deve agora ser escrito como:
FF (X1, W1, B1) =
{
T = Times (W1, X1)
Y = Plus (T, B1)
}.Y
Especialmente, o valor Y
de devolução deve ser explicitamente selecionado no final usando .
sintaxe, caso contrário o valor da função seria todo o registo.