Modifica del modello con BrainScript
(Nota: le versioni precedenti di CNTK usano "MEL" (Model Editing Language) a questo scopo. Siamo ancora in fase di conversione degli esempi. Per la documentazione su MEL, vedere il qui.)
CNTK consente di modificare i modelli dopo il fatto. Questa operazione viene eseguita creando un nuovo modello durante la clonazione (parti di) di un modello esistente con le modifiche applicate. Per questo motivo, CNTK fornisce tre funzioni di base:
-
BS.Network.Load()
per caricare un modello esistente -
BS.Network.CloneFunction()
estrarre una sezione di un modello esistente per il riutilizzo -
BS.Network.Edit()
clonare un modello con modifiche node-by-node applicate
L'operazione di modifica non è un passaggio separato. Invece, un comando che dovrebbe funzionare da un modello modificato non specifica un modelPath
da cui caricare il modello, ma piuttosto una sezione BrainScriptNetworkBuilder
che carica il modello all'interno e costruisce un nuovo modello al di fuori di quello caricato in tempo reale.
Esempio: pre-training discriminante
Il pre-training discriminante è una tecnica in cui viene creata una rete profonda eseguendo il training di una sequenza di reti più superficiali. Iniziare con una rete a 1 livello nascosto, eseguire il training alla convergenza parziale, quindi rimuovere il livello di output, aggiungere un nuovo livello nascosto e aggiungere un nuovo livello di output. Ripetere finché non viene raggiunto il numero desiderato di livelli nascosti.
Si supponga che con un modello di partenza molto semplice
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’)
]
Eseguire il training di questo modello e salvare in "model.1.dnn". Successivamente si vuole eseguire il training di un modello con due livelli nascosti, in cui il primo livello nascosto viene inizializzato dai valori sottoposti a training sopra. A tale scopo, viene creata un'azione di training separata che crea un nuovo modello, ma riutilizzando parti del precedente, come indicato di seguito:
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’)
}
Prima di tutto, IL PASSAGGIO 1 usa Load()
per caricare la rete in una variabile BrainScript. La rete si comporta come un record BrainScript, in cui tutti i nodi di primo livello (tutti i nodi che non contengono un .
o [
nei relativi nomi di nodo) sono accessibili tramite la sintassi dei record. Una nuova rete può fare riferimento a qualsiasi nodo in una rete caricata. In questo esempio la rete caricata contiene un nodo h1
che è l'output del primo livello nascosto e un nodo z
che è la probabilità posterior del log nonmalizzata delle classi di output (input per la funzione Softmax). È possibile accedere a entrambi i nodi da BrainScript tramite la sintassi del punto, ad esempio inModel.h1
e inModel.z
.
Si noti che le costanti non vengono archiviate nei modelli, pertanto né N
né M
sono disponibili dal modello. Tuttavia, è possibile ricostruirle dal modello caricato. A tale scopo, i nodi di calcolo si comportano anche come record BrainScript ed espongono una proprietà dim
.
Successivamente, IL PASSAGGIO 2 costruisce il resto della nuova rete usando brainScript normale. Si noti che questa nuova sezione usa semplicemente il nodo h1
dal modello di input come input, come se si usasse qualsiasi altro nodo. Facendo riferimento a un nodo dalla rete di input, tutti i nodi che questo nodo dipenderà anche da parte della rete appena creata. Ad esempio, il nodo di input x
diventerà automaticamente parte della nuova rete.
Si noti anche che il livello di output viene costruito di nuovo. In questo modo, i parametri del modello verranno creati di nuovo. Per non farlo e riutilizzare invece i parametri esistenti, è possibile usare inModel.Wout
, ma si noti che non ha senso dal punto di progettazione di rete in questo particolare esempio.
Esempio: uso di un modello con training preliminare
Di seguito è riportato un esempio di utilizzo di un modello con training preliminare (dal file "./featext.dnn"
) come estrattore di funzionalità:
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)
}
IL PASSAGGIO 1 usa Load()
per caricare la rete in una variabile BrainScript.
IL PASSAGGIO 2 usa CloneFunction()
per clonare la sezione correlata all'estrazione delle funzionalità dalla rete caricata, ovvero il sottografo che connette featExtNetwork.input
a featExtNetwork.feat
. Poiché è stato specificato parameters="constant"
, anche tutti i parametri da cui featExtNetwork.feat
dipendono vengono clonati e resi di sola lettura.
Nel PASSAGGIO 3 e 4 viene definita la nuova rete. Questa operazione viene eseguita come qualsiasi altro modello BrainScript, solo che ora è possibile usare la funzione featExt()
in questo modo.
Problemi con i nomi dei nodi con .
[
e ]
Per fare riferimento a un nodo in una rete contenente .
o [
o ]
, sostituire tali caratteri _
.
Ad esempio, se network
contiene un nodo denominato result.z
, network.result.z
avrà esito negativo; pronunciare invece network.result_z
.
Esempio: Modifica di nodi di una rete esistente
Per modificare le parti interne di una rete esistente, si clonerebbe effettivamente la rete, mentre le modifiche vengono applicate come parte del processo di clonazione. Questa operazione viene eseguita da BS.Network.Edit()
.
Edit()
eseguirà l'iterazione su tutti i nodi di una rete e offrirà a ogni nodo, uno alla sola, alle funzioni lambda passate dal chiamante. Tali funzioni lambda possono quindi esaminare il nodo e restituire il nodo non modificato oppure restituire un nuovo nodo al suo posto.
Edit()
eseguirà l'iterazione dei nodi in ordine non specificato. Se un nodo sostitutivo fa riferimento a un nodo di rete che a sua volta è stato sostituito, Edit()
, come passaggio finale, aggiornare tutti questi riferimenti alle rispettive sostituzioni (noto anche come "fare la cosa giusta").
TODO: esempio.