Partilhar via


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. NDLNetworkBuilderA 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.