Modellredigering med BrainScript
(Obs! Äldre versioner av CNTK använde "MEL" (modellredigeringsspråk) för detta ändamål. Vi håller fortfarande på att konvertera exemplen. Dokumentation om MEL finns här.)
CNTK tillåter att modeller redigeras i efterhand. Detta görs genom att skapa en ny modell vid kloning (delar av) en befintlig modell med ändringar som tillämpas. För detta tillhandahåller CNTK tre grundläggande funktioner:
BS.Network.Load()
för att läsa in en befintlig modellBS.Network.CloneFunction()
för att extrahera ett avsnitt i en befintlig modell för återanvändningBS.Network.Edit()
klona en modell med nod-för-nod-ändringar tillämpade
Redigeringsåtgärden är inte ett separat steg. I stället skulle ett kommando som ska fungera av en modifierad modell inte ange en modelPath
att läsa in modellen från, utan snarare ett BrainScriptNetworkBuilder
avsnitt som läser in modellen inuti och konstruerar en ny modell från den inlästa i farten.
Exempel: Discriminative pre-training
Discriminative pre-training är en teknik där ett djupt nätverk skapas genom att träna en sekvens av grundare nätverk. Börja med ett nätverk med ett dolt lager, träna till partiell konvergens, ta sedan bort utdatalagret, lägg till ett nytt dolt lager och lägg till ett nytt utdatalager. Upprepa tills önskat antal dolda lager har nåtts.
Låt oss anta med en mycket enkel startmodell
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’)
]
Låt oss träna den här modellen och spara under "model.1.dnn". Sedan vill vi träna en modell med två dolda lager, där det första dolda lagret initieras från de värden som tränas ovan. För att göra det skapar vi en separat träningsåtgärd som skapar en ny modell, men återanvänder delar av den föregående, enligt följande:
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’)
}
Först använder Load()
STEG 1 för att läsa in nätverket i en BrainScript-variabel. Nätverket fungerar som en BrainScript-post, där alla noder på den översta nivån (alla noder som inte innehåller ett .
eller [
i sina nodnamn) är tillgängliga via postsyntax. Ett nytt nätverk kan referera till valfri nod i ett inläst nätverk. I det här exemplet innehåller det inlästa nätverket en nod h1
som är utdata från det första dolda lagret och en nod z
som är den onormaliserade loggens bakre sannolikhet för utdataklasserna (indata till Funktionen Softmax). Båda noderna kan nås från BrainScript via punktsyntax, t.ex. inModel.h1
och inModel.z
.
Observera att konstanter inte lagras i modeller, så varken N
eller M
är tillgängliga från modellen. Det är dock möjligt att rekonstruera dem från den inlästa modellen. För det ändamålet fungerar beräkningsnoder också som BrainScript-poster och exponerar en dim
egenskap.
Steg 2 skapar sedan resten av det nya nätverket med hjälp av vanlig BrainScript. Observera att det här nya avsnittet helt enkelt använder noden h1
från indatamodellen som indata, precis som med andra noder. Om du refererar till en nod från indatanätverket görs automatiskt alla noder som den här noden är beroende av även en del av det nyligen skapade nätverket. Indatanoden x
blir till exempel automatiskt en del av det nya nätverket.
Observera också att utdatalagret konstrueras på nytt. På så sätt skapas dess modellparametrar på nytt. (Om du inte vill göra det och i stället återanvända de befintliga parametrarna kan man använda inModel.Wout
, men observera att det inte är meningsfullt ur nätverksdesignsynpunkt i det här exemplet.)
Exempel: Använda en förtränad modell
Följande är ett exempel på hur du använder en förtränad modell (från filen "./featext.dnn"
) som funktionsextraktor:
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)
}
STEG 1 använder Load()
för att läsa in nätverket i en BrainScript-variabel.
STEG 2 använder CloneFunction()
för att klona avsnittet om funktionsextrahering från det inlästa nätverket, vilket är underdiagrammet som ansluter featExtNetwork.input
till featExtNetwork.feat
. Eftersom vi angav parameters="constant"
klonas även alla parametrar som featExtNetwork.feat
är beroende av och skrivskyddade.
I STEG 3 och 4 definieras det nya nätverket. Detta görs som vilken annan BrainScript-modell som helst, men nu kan vi använda featExt()
funktionen för att göra det.
Problem med nodnamn med .
[
och ]
Om du vill referera till en nod i ett nätverk som innehåller .
eller [
]
, ersätter du dessa tecken med _
.
T.ex. om network
innehåller en nod med namnet result.z
, network.result.z
kommer att misslyckas. Säg network.result_z
i stället .
Exempel: Ändra noder i ett befintligt nätverk
Om du vill ändra inre delar av ett befintligt nätverk klonar man faktiskt nätverket, medan ändringar tillämpas som en del av kloningsprocessen. Detta åstadkoms av BS.Network.Edit()
. Edit()
itererar över alla noder i ett nätverk och erbjuder varje nod, en i taget, lambda-funktioner som skickas av anroparen. Dessa lambda-funktioner kan sedan inspektera noden och antingen returnera noden oförändrad eller returnera en ny nod i stället. Edit()
itererar över noder i ospecificerad ordning. Om en ersättningsnod refererar till en nätverksnod som i sin tur ersattes Edit()
, kommer som ett sista steg att uppdatera alla sådana referenser till respektive ersättningar (även kallat "gör det rätta").
TODO: Exempel.