Compartilhar via


Edição de modelo com BrainScript

(Observação: versões mais antigas do CNTK usaram "MEL" (Linguagem de Edição de Modelo) para estas finalidades. Ainda estamos no processo de converter os exemplos. Para obter a documentação sobre MEL, consulte o aqui.)

O CNTK permite que os modelos sejam editados após o fato. Isso é feito criando um novo modelo durante a clonagem (partes de) um modelo existente com modificações aplicadas. Para isso, o CNTK fornece três funções básicas:

  • BS.Network.Load() carregar um modelo existente
  • BS.Network.CloneFunction() extrair uma seção de um modelo existente para reutilização
  • BS.Network.Edit() clonar um modelo com modificações de nó por nó aplicadas

A operação de edição não é uma etapa separada. Em vez disso, um comando que deve funcionar fora de um modelo modificado não especificaria um modelPath para carregar o modelo, mas sim uma seção BrainScriptNetworkBuilder que carrega o modelo dentro e constrói um novo modelo do carregado em tempo real.

Exemplo: pré-treinamento discriminativo

O pré-treinamento discriminativo é uma técnica em que uma rede profunda é criada treinando uma sequência de redes mais rasas. Comece com uma rede de camada oculta 1, treine para convergência parcial, remova a camada de saída, adicione uma nova camada oculta e adicione uma nova camada de saída. Repita até que o número desejado de camadas ocultas seja atingido.

Vamos supor com um modelo inicial muito simples

BrainScriptNetworkBuilder = [
    N = 40; M = 9000; H = 512
    W1   = Parameter (H, N); b1   = Parameter (H)
    Wout = Parameter (M, H); bout = Parameter (M)
    x = Input (N, tag=‘feature’) ; labels = Input (M, tag=‘labels’)
    h1 = Sigmoid (W1 * x  + b1)
    z  = Wout * h1 + bout
    ce = CrossEntropyWithSoftmax (labels, z, tag=‘criterion’)
]

Vamos treinar esse modelo e salvar em "model.1.dnn". Em seguida, queremos treinar um modelo com duas camadas ocultas, em que a primeira camada oculta é inicializada dos valores treinados acima. Para fazer isso, criamos uma ação de treinamento separada que cria um novo modelo, mas reutilizando partes da anterior, da seguinte maneira:

BrainScriptNetworkBuilder = {
    # STEP 1: load 1-hidden-layer model
    inModel = BS.Network.Load ("model.1.dnn")
    # get its h1 variable --and also recover its dimension
    h1 = inModel.h1
    H = h1.dim
    # also recover the number of output classes
    M = inModel.z.dim

    # STEP 2: create the rest of the extended network as usual
    W2   = Parameter (H, H); b2   = Parameter (H)
    Wout = Parameter (M, H); bout = Parameter (M)
    h2 = Sigmoid (W2 * h1  + b2)
    z  = Wout * h2 + bout
    ce = CrossEntropyWithSoftmax (labels, z, tag=‘criterion’)
}

Primeiro, a ETAPA 1 usa Load() para carregar a rede em uma variável BrainScript. A rede se comporta como um registro BrainScript, em que todos os nós de nível superior (todos os nós que não contêm um . ou [ em seus nomes de nó) são acessíveis por meio da sintaxe do registro. Uma nova rede pode referenciar qualquer nó em uma rede carregada. Neste exemplo, a rede carregada contém um nó h1 que é a saída da primeira camada oculta e um nó z que é a probabilidade posterior de log nãomalizada das classes de saída (entrada para a função Softmax). Ambos os nós podem ser acessados do BrainScript por meio da sintaxe de ponto, por exemplo, inModel.h1 e inModel.z.

Observe que as constantes não são armazenadas em modelos, portanto, nem N nem M estão disponíveis no modelo. No entanto, é possível reconstruí-los do modelo carregado. Para essa finalidade, os nós de computação também se comportam como registros BrainScript e expõem uma propriedade dim.

Em seguida, a ETAPA 2 constrói o restante da nova rede usando o BrainScript regular. Observe que esta nova seção simplesmente usa o nó h1 do modelo de entrada como uma entrada, como se ele usaria qualquer outro nó. Fazer referência a um nó da rede de entrada fará automaticamente todos os nós dos quais esse nó depende também de parte da rede recém-criada. Por exemplo, o nó de entrada x se tornará automaticamente parte da nova rede.

Observe também que a camada de saída é construída novamente. Dessa forma, seus parâmetros de modelo serão criados recentemente. (Para não fazer isso e reutilizar os parâmetros existentes, é possível usar inModel.Wout, mas observe que isso não faz sentido do ponto de vista de design de rede neste exemplo específico.)

Exemplo: usando um modelo pré-treinado

Veja a seguir um exemplo de como usar um modelo pré-treinado (de "./featext.dnn"de arquivo) como extrator de recursos:

BrainScriptNetworkBuilder = {
    # STEP 1: load existing model
    featExtNetwork = BS.Network.Load ("./featext.dnn")

    # STEP 2: extract a read-only section that is the feature extractor function
    featExt = BS.Network.CloneFunction (
                  featExtNetwork.input,    # input node that AE model read data from
                  featExtNetwork.feat,     # output node in AE model that holds the desired features
                  parameters="constant")   # says to freeze that part of the network

    # STEP 3: define the part of your network that uses the feature extractor
    # from the loaded model, which above we isolated into featExt().
    # featExt() can be used like any old BrainScript function.
    input = Input (...)
    features = featExt (input)  # this will instantiate a clone of the above network

    # STEP 4: and add the remaining bits of the network in BrainScript, e.g.
    h = Sigmoid (W_hid * features + b_hid) # whatever your hidden layer looks like
    z = W_out * h + b_out
    ce = CrossEntropyWithSoftmax (labels, z)
    criterionNodes = (ce)
}

A ETAPA 1 usa Load() para carregar a rede em uma variável BrainScript.

A ETAPA 2 usa CloneFunction() para clonar a seção relacionada à extração de recursos da rede carregada, que é o sub-grafo que conecta featExtNetwork.input a featExtNetwork.feat. Como especificamos parameters="constant", todos os parâmetros dos quais featExtNetwork.feat dependem também são clonados e feitos somente leitura.

Na ETAPA 3 e 4, a nova rede é definida. Isso é feito como qualquer outro modelo do BrainScript, apenas que agora podemos usar a função featExt() ao fazer isso.

Problemas com nomes de nó com .[ e ]

Para fazer referência a um nó em uma rede que contém . ou [ ou ], substitua esses caracteres por _. Por exemplo, se network contiver um nó chamado result.z, network.result.z falhará; em vez disso, diga network.result_z.

Exemplo: modificando nós de uma rede existente

Para modificar partes internas de uma rede existente, seria realmente clonar a rede, enquanto as modificações são aplicadas como parte do processo de clonagem. Isso é feito por BS.Network.Edit(). Edit() iterará em todos os nós de uma rede e oferecerá cada nó, um por um, para funções lambda passadas pelo chamador. Essas funções lambda podem inspecionar o nó e retornar o nó sem modificação ou retornar um novo nó em seu lugar. Edit() iterará em nós em ordem não especificada. Se um nó de substituição fizer referência a um nó de rede que, por sua vez, foi substituído, Edit() atualizará todas essas referências para as respectivas substituições (também conhecidas como "fazer a coisa certa").

TODO: Exemplo.

Próximo: de referência de função completa