Partilhar via


Edição de modelos com BrainScript

(Nota: As versões mais antigas da CNTK usavam "MEL" (Model Editing Language) para este fim. Ainda estamos no processo de conversão dos exemplos. Para obter documentação sobre o MEL, consulte o aqui.)

A CNTK permite que os modelos sejam editados após o fato. Isso é feito criando um novo modelo enquanto clona (partes de) um modelo existente com modificações aplicadas. Para isso, a CNTK disponibiliza 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 nó a nó aplicadas

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

Exemplo: Pré-formação discriminativa

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

Vamos supor com um modelo de partida 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 este modelo e economizar em "model.1.dnn". Em seguida, queremos treinar um modelo com duas camadas ocultas, onde a primeira camada oculta é inicializada a partir dos valores treinados acima. Para isso, criamos uma ação de treinamento separada que cria um novo modelo, mas reutilizando partes do anterior, da seguinte forma:

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, o PASSO 1 usa Load() para carregar a rede em uma variável BrainScript. A rede se comporta como um registro BrainScript, onde 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) são acessíveis através da sintaxe de registro. Uma nova rede pode fazer referência a 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ão normalizada das classes de saída (entrada para a função Softmax). Ambos os nós podem ser acessados a partir do BrainScript através da sintaxe de pontos, 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 a partir do modelo carregado. Para esse propósito, os nós de computação também se comportam como registros do BrainScript e expõem uma propriedade dim.

Em seguida, o PASSO 2 constrói o resto 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 faria com qualquer outro nó. A referência a um nó da rede de entrada fará com que todos os nós dos quais esse nó depende também façam 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. Desta forma, seus parâmetros de modelo serão criados recentemente. (Para não fazer isso e, em vez disso, reutilizar os parâmetros existentes, pode-se usar inModel.Wout, mas observe que isso não faz sentido do ponto de vista do design de rede neste exemplo específico.)

Exemplo: Usando um modelo pré-treinado

A seguir está um exemplo de uso de um modelo pré-treinado (do arquivo "./featext.dnn") como um 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)
}

O PASSO 1 utiliza Load() para carregar a rede numa variável BrainScript.

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

Nos PASSOS 3 e 4, a nova rede é definida. Isso é feito como qualquer outro modelo BrainScript, só que agora podemos usar a função featExt() ao fazê-lo.

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, pode-se realmente clonar a rede, enquanto as modificações são aplicadas como parte do processo de clonagem. Isto é conseguido por BS.Network.Edit(). Edit() iterará em todos os nós de uma rede e oferecerá cada nó, um a um, para funções lambda passadas pelo chamador. Essas funções lambda podem então inspecionar o nó e retornar o nó sem modificações ou retornar um novo nó em seu lugar. Edit() irá iterar sobre 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() irá, como etapa final, atualizar todas essas referências para as respetivas substituições (também conhecido como "fazer a coisa certa").

TODO: Exemplo.

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