Dela via


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 modell
  • BS.Network.CloneFunction() för att extrahera ett avsnitt i en befintlig modell för återanvändning
  • BS.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_zi 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.

Nästa: Fullständig funktionsreferens