Partilhar via


BrainScript Network Builder

As redes personalizadas são descritas na linguagem de descrição de rede personalizada "BrainScript" da CNTK. Para definir uma rede personalizada, inclua uma secção com o nome BrainScriptNetworkBuilder na configuração de preparação. Pode encontrar uma descrição detalhada no idioma de descrição da rede na página Conceitos Básicos e nas subpáginas correspondentes.

Existem duas formas de utilizar o construtor de rede BrainScript, uma com parênteses (...)e uma forma de mão curta com chavetas {...}. Para descrever a sua rede num ficheiro externo, especifique um bloco semelhante ao seguinte:

BrainScriptNetworkBuilder = (new ComputationNetwork {
    include "yourNetwork.bs"
})

onde yourNetwork.bs contém a rede descrita com o BrainScript. O ficheiro yourNetwork.bs é apresentado primeiro no mesmo diretório que o ficheiro de configuração e, se não for encontrado, no diretório do executável CNTK. Os nomes de caminho absolutos e relativos são aceites aqui. Por exemplo, significa um ficheiro localizado num diretório bs junto ao ficheiro de configuração (ou, em alternativa, bs/yourNetwork.bs um diretório bs dentro do diretório executável CNTK).

Nota: até ao CNTK 1.6, o BrainScript utilizou parênteses [...] em vez de chavetas {...}. Os parênteses ainda são aceites, mas preteridos.

Em alternativa, pode definir a sua rede inline, diretamente dentro do ficheiro de configuração. Isto pode simplificar a configuração se não planear partilhar o mesmo script cerebral em várias configurações. Utilize este formulário:

BrainScriptNetworkBuilder = {
    # insert network description here
}

Então, qual é o aspeto do código BrainScript que vai para os parênteses? Para descobrir, avance para os Conceitos Básicos do BrainScript.

Em alternativa, mantenha-se nesta página e leia sobre alguns detalhes menos frequentemente necessários para si.

O {...} formulário acima é, na verdade, apenas uma mão curta para isto:

BrainScriptNetworkBuilder = (new ComputationNetwork {
    # insert network description here
})

Por fim, como uma utilização avançada, o (...) formulário não se limita a utilizar new. Em vez disso, qualquer expressão BrainScript que seja avaliada como um objeto de ComputationNetwork é permitida dentro dos parênteses. Por exemplo:

BrainScriptNetworkBuilder = ({
    include "myNetworks.bs"
    network = CreateMyNetworkOfType42()
}.network)

Esta é uma utilização avançada que, por vezes, também ocorre no contexto da edição de modelos.

Seguinte: Conceitos Básicos do BrainScript.

Legado NDLNetworkBuilder

Nas versões mais antigas do CNTK, o construtor de rede chamava-se NDLNetworkBuilder. A linguagem de definição é um subconjunto do BrainScript. O velho analisador era menos capaz, mas também mais clemente. Há também outras pequenas diferenças.

NDLNetworkBuilder foi preterido, mas devido à semelhança, não é difícil atualizar para BrainScriptNetworkBuilder. Segue-se um guia sobre como converter NDLNetworkBuilder descrições de rede em BrainScriptNetworkBuilder"s".

Atualizar de NDLNetworkBuilder para BrainScriptNetworkBuilder

A conversão de uma definição de rede existente para o NDLNetworkBuilder para BrainScriptNetworkBuilder é simples na maioria dos casos. As principais alterações são a sintaxe circundante. A descrição da rede principal em si é compatível em grande parte para cima e provavelmente idêntica ou quase idêntica se não tirar partido das novas funcionalidades de idioma.

Para converter as suas descrições, tem de mudar o construtor de rede, adaptar a sintaxe externa w.r.t. e, possivelmente, fazer pequenas adaptações ao próprio código de rede.

Passo 1. Mudar o construtor de rede. Substitua o NDLNetworkBuilder pelo bloco correspondente BrainScriptNetworkBuilder no ficheiro de configuração CNTK. Se a descrição da rede estiver num ficheiro separado:

# change from:
NDLNetworkBuilder = [
    ndlMacros = "shared.ndl"   # (if any)
    networkDescription = "yourNetwork.ndl"
]
# ...to:
BrainScriptNetworkBuilder = (new ComputationNetwork {
    include "shared.bs"        # (if any)
    include "yourNetwork.bs"
})

(A alteração da extensão de nome de ficheiro não é estritamente necessária, mas recomendada.)

Se a descrição da rede estiver no .cntk próprio ficheiro de configuração:

# change from:
NDLNetworkBuilder = [
    # macros
    load = [
        SigmoidNetwork (x, W, b) = Sigmoid (Plus (Times (W, x), b))
    ]
    # network description
    run = [
        feat = Input (13)
        ...
        ce = CrossEntropyWithSoftmax (labels, z, tag="criterion")
    ]
]
# ...to:
BrainScriptNetworkBuilder = {
    # macros are just defined inline
    SigmoidNetwork (x, W, b) = Sigmoid (Plus (Times (W, x), b))  # or: Sigmoid (W * x + b)
    # network description
    feat = Input {13}
    ...
    ce = CrossEntropyWithSoftmax (labels, z, tag="criterion")
}

Passo 2. Remover load e run bloquear. Com BrainScriptNetworkBuilder, as definições de macro/função e o código principal são combinados. Os load blocos e run têm de ser simplesmente removidos. Por exemplo, isto:

load = ndlMnistMacros
run = DNN
ndlMnistMacros = [
    featDim = 784
    ...
    labels = InputValue(labelDim)
]
DNN = [
    hiddenDim = 200
    ...
    outputNodes = (ol)
]

torna-se simplesmente:

featDim = 784
...
labels = InputValue(labelDim)
hiddenDim = 200
...
outputNodes = (ol)

Pode ter utilizado a run variável para selecionar uma de múltiplas configurações com uma variável externa, por exemplo:

NDLNetworkBuilder = [
    run = $whichModel$   # outside parameter selects model, must be either "model1" or "model2"
    model1 = [ ... (MODEL 1 DEFINITION) ]
    model2 = [ ... (MODEL 1 DEFINITION) ]
]

Este padrão era maioritariamente necessário porque o NDL não tinha expressões condicionais. No BrainScript, isto seria agora escrito com uma expressão if :

BrainScriptNetworkBuilder = (new ComputationNetwork
    if      "$whichModel$" == "model1" then { ... (MODEL 1 DEFINITION) }
    else if "$whichModel$" == "model2" then { ... (MODEL 2 DEFINITION) }
    else Fail ("Invalid model selector value '$whichModel$'")
)

No entanto, muitas vezes, os modelos selecionados são muito semelhantes, pelo que uma forma melhor seria intercalar as suas descrições e, em vez disso, utilizar condicionais no interior apenas para onde diferem. Eis um exemplo em que um parâmetro é utilizado para escolher entre um LSTM unidirecional e bidirecional:

encoderFunction =
    if useBidirectionalEncoder
    then BS.RNNs.RecurrentBirectionalLSTMPStack
    else BS.RNNs.RecurrentLSTMPStack
encoder = encoderFunction (encoderDims, inputEmbedded, inputDim=inputEmbeddingDim)

Passo 3. Ajustar a descrição da rede. Relativamente à descrição da rede (fórmulas), o BrainScript é em grande parte compatível com o NDL. Estas são as principais diferenças:

  • O valor devolvido das macros (funções) já não é a última variável definida nas mesmas, mas todo o conjunto de variáveis. Tem de selecionar explicitamente o valor de saída no final. Por exemplo:

      # NDL:  
      f(x) = [  
          x2 = Times (x, x)  
          y = Plus (x2, Constant (1))  
      ]  # <-- return value defaults to last entry, i.e. y  
      # BrainScript:
      f(x) = {
          x2 = x*x  
          y = x2 + 1  
      }.y  # <-- return value y must be explicitly dereferenced
    

    Sem esta alteração, o valor de retorno da função seria o registo completo e o erro típico que obterá é que era esperado um ComputationNode onde foi encontrado um ComputationNetwork .

  • O BrainScript não permite funções com números variáveis de parâmetros. Isto é importante principalmente para a Parameter() função: um parâmetro de vetor já não pode ser escrito como Parameter(N), agora tem de ser explicitamente escrito como um tensor ParameterTensor{N} ou uma matriz Parameter(N, 1)de 1 coluna . Sem esta alteração, obterá um erro sobre o número de parâmetros posicionais que não correspondem. Esta notação também funciona com o NDL, para que possa efetuar esta alteração primeiro e testá-la com o NDL antes de a converter. Esta é também uma boa oportunidade para mudar o nome de qualquer utilização do nome LearnableParameter() legado para ParameterTensor{}.

    Também é importante para a RowStack() função, que no BrainScript utiliza um único parâmetro que é uma matriz de entradas. As entradas têm de ser separadas por dois pontos (:) em vez de uma vírgula, por exemplo RowStack (a:b:c) , em vez de RowStack (a, b, c).

  • Algumas predefinições foram atualizadas, principalmente o parâmetro opcional imageLayout de Convolution(), as operações de agrupamento e ImageInput(). Para NDL, estes predefiniram para legacy, enquanto agora a predefinição é cudnn a que é necessária para ser compatível com os primitivos de convolução cuDNN. (Todos os exemplos de código NDL especificam explicitamente este parâmetro como cudnn já.)

  • O analisador do BrainScript é mais restritivo:

    • Os identificadores são agora sensíveis às maiúsculas e minúsculas. As funções incorporadas utilizam PascalCase (por exemplo, RectifiedLinear()), e as variáveis incorporadas e os nomes de parâmetros utilizam camelCase (por exemplo modelPath, , criterionNodes), tal como as cadeias de opção (init="fixedValue", tag="criterion"). Tenha em atenção que, para nomes de parâmetros opcionais, as ortografias incorretas nem sempre são apanhadas como um erro. Em vez disso, alguns parâmetros opcionais escritos incorretamente são simplesmente ignorados. Um exemplo são as definições de "nós especiais". A ortografia correta para estes é agora:

        featureNodes    = ...
        labelNodes      = ...
        criterionNodes  = ...
        evaluationNodes = ...
        outputNodes     = ...
      
    • Os nomes alternativos abreviados já não são permitidos, como Const() deve ser Constant(), tag="eval" deve ser tag="evaluation", e evalNodes é agora evaluationNodes.

    • Alguns nomes mal escritos foram corrigidos: é agora criterion (da mesma formacriterionNodes), defaultHiddenActivity é agora defaultHiddenActivation. criteria

    • O = sinal já não é opcional para definições de funções.

    • Embora seja permitido utilizar parênteses para blocos ([ ... ]), é preterido. Utilize chavetas ({ ... }).

    • As etiquetas de opção têm de ser citadas como cadeias, por exemplo init="uniform" , em vez de init=uniform. Sem as aspas, o BrainScript falhará com uma mensagem de erro a indicar que o símbolo uniform é desconhecido.

    • Os primitivos do BrainScript que criam parâmetros (Input{} e ParameterTensor{}) devem utilizar chavetas para os respetivos argumentos (por exemplo, f = Input{42}). Esta é uma convenção que não é imposta, mas recomendada para avançar.

    Esta sintaxe mais restrita ainda é aceite pelo , pelo NDLNetworkBuilderque recomendamos que faça primeiro estas alterações sintacticas e as teste com NDL, antes de mudar para o BrainScript.

Passo 4: Remover NDLNetworkBuilder das secções "escrever" e "testar". Reveja as secções "escrever" e "testar" para NDLNetworkBuilder as secções e remova-as. Alguns dos nossos exemplos de NDL de ações têm secções estranhas NDLNetworkBuilder . Não são utilizados e não devem estar lá. Se a configuração se basear num destes exemplos, também poderá ter essas secções. Costumavam ser ignorados, mas com a atualização do BrainScript, definir uma nova rede nestas secções tem agora um significado (edição de modelo), pelo que já não são ignorados e, por conseguinte, devem ser removidos.

NDLNetworkBuilder referência (preterida)

A sintaxe dos preteridos NDLNetworkBuilder é:

NDLNetworkBuilder = [
    networkDescription = "yourNetwork.ndl"
]

O NDLNetworkBuilder bloco tem os seguintes parâmetros:

  • networkDescription: o caminho do ficheiro de descrição da rede. Com o preterido NDLNetworkBuilder, era costume utilizar a extensão .ndlde ficheiro . Se não existir nenhum networkDescription parâmetro especificado, assume-se que a descrição da rede está indicada no mesmo NDLNetworkBuilder subbloqueio, especificado com o run parâmetro abaixo. Tenha em atenção que apenas um caminho de ficheiro pode ser especificado através do networkDescription parâmetro . Para carregar vários ficheiros de macros, utilize o ndlMacros parâmetro .

  • run: o bloco do NDL que será executado. Se for especificado um ficheiro NDL externo através do networkDescription parâmetro , o run parâmetro identifica um bloco nesse ficheiro. Este parâmetro substitui quaisquer run parâmetros que possam já existir no ficheiro. Se não for especificado nenhum networkDescription ficheiro, o run parâmetro identifica um bloco no ficheiro de configuração atual.

  • load: os blocos de scripts NDL a carregar. Podem ser especificados vários blocos através de uma lista separada por ":". Normalmente, os load blocos especificados pelo parâmetro contêm macros para utilização pelo run bloco. Semelhante ao run parâmetro , o load parâmetro identifica os blocos num ficheiro NDL externo e substitui quaisquer load parâmetros que possam já existir no ficheiro, se um ficheiro for especificado pelo networkDescription parâmetro . Se não for especificado nenhum networkDescription ficheiro, load identifica um bloco no ficheiro de configuração atual.

  • ndlMacros: o caminho do ficheiro no qual as macros NDL podem ser carregadas. Normalmente, este parâmetro é utilizado para carregar um conjunto predefinido de macros NDL que podem ser utilizadas por todos os scripts NDL. Vários ficheiros NDL, cada um especificando diferentes conjuntos de macros, podem ser carregados ao especificar uma lista separada "+" de caminhos de ficheiro para este ndlMacros parâmetro. Para partilhar macros com outros blocos de comandos, como blocos mel (model-editing language) do NDL, deve defini-lo ao nível da raiz do ficheiro de configuração.

  • randomSeedOffset: um valor de desvio aleatório não negativo ao inicializar os parâmetros de aprendizagem. O valor predefinido é 0. Isto permite que os utilizadores executem experimentações com inicialização aleatória diferente.