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.