Delen via


Basisconcepten van BrainScript

BrainScript- Een Walk-Through

In deze sectie worden de basisconcepten van de BrainScript-taal geïntroduceerd. Een nieuwe taal? Maak je geen zorgen & lees verder, het is heel eenvoudig.

In CNTK worden aangepaste netwerken gedefinieerd met behulp van en BrainScriptNetworkBuilder beschreven in de CNTK-netwerkbeschrijvingstaal 'BrainScript'. Op dezelfde manier worden beschrijvingen van netwerken hersenscripts genoemd.

BrainScript biedt een eenvoudige manier om een netwerk op een codeachtige manier te definiëren met behulp van expressies, variabelen, primitieve en zelf gedefinieerde functies, geneste blokken en andere goed begrepen concepten. Het lijkt op een scripttaal in de syntaxis.

Oké, laten we de voeten nat maken met een compleet BrainScript-voorbeeld!

Een volledige BrainScript-voorbeeldnetwerkdefinitie

In het volgende voorbeeld ziet u de netwerkbeschrijving van een eenvoudig neuraal netwerk met één verborgen laag en één classificatielaag. In dit voorbeeld leggen we concepten uit. Voordat u verdergaat, kunt u misschien een paar minuten besteden aan het voorbeeld en proberen te raden wat dit betekent. Misschien merkt u, zoals u verder leest, dat u het meeste ervan correct hebt geraden.

BrainScriptNetworkBuilder = {   # (we are inside the train section of the CNTK config file)

    SDim = 28*28 # feature dimension
    HDim = 256   # hidden dimension
    LDim = 10    # number of classes

    # define the model function. We choose to name it 'model()'.
    model (features) = {
        # model parameters
        W0 = ParameterTensor {(HDim:SDim)} ; b0 = ParameterTensor {HDim}
        W1 = ParameterTensor {(LDim:HDim)} ; b1 = ParameterTensor {LDim}

        # model formula
        r = RectifiedLinear (W0 * features + b0) # hidden layer
        z = W1 * r + b1                          # unnormalized softmax
    }.z

    # define inputs
    features = Input {SDim}
    labels   = Input {LDim} 

    # apply model to features
    z = model (features)

    # define criteria and output(s)
    ce   = CrossEntropyWithSoftmax (labels, z)  # criterion (loss)
    errs = ErrorPrediction         (labels, z)  # additional metric
    P    = Softmax (z)     # actual model usage uses this

    # connect to the system. These five variables must be named exactly like this.
    featureNodes    = (features)
    inputNodes      = (labels)
    criterionNodes  = (ce)
    evaluationNodes = (errs)
    outputNodes     = (P)
}

Basisprincipes van BrainScript-syntaxis

Voordat we erin graven, een paar algemene opmerkingen over de syntaxis van BrainScript.

BrainScript maakt gebruik van een eenvoudige syntaxis die is gericht op het uitdrukken van neurale netwerken op een manier die lijkt op wiskundige formules. Daarom is de fundamentele syntactische eenheid de toewijzing, die wordt gebruikt in zowel variabeletoewijzingen als functiedefinities. Bijvoorbeeld:

Softplus (x) = Log (1 + Exp (x))
h = Softplus (W * v + b)

Regels, opmerkingen, opnemen

Hoewel een toewijzing doorgaans op één regel wordt geschreven, kunnen expressies meerdere regels omvatten. Als u meerdere opdrachten op één regel wilt plaatsen, moet u ze echter scheiden door een puntkomma. Bijvoorbeeld:

SDim = 28*28 ; HDim = 256 ; LDim = 10    # feature, hidden, and label dimension

Afgezien van het vereisen van een puntkomma tussen toewijzingen in afwezigheid van een regeleinde, is BrainScript niet gevoelig voor witruimte.

BrainScript begrijpt opmerkingen aan de lijn met behulp van zowel de Python-stijl # als de C++-stijl //. Inline-opmerkingen gebruiken C-syntaxis (/* this is a comment*/), maar in tegenstelling tot C kunnen deze niet meerdere regels omvatten.

Voor BrainScript dat is ingesloten in CNTK-configuratiebestanden (in tegenstelling tot BrainScript die worden gelezen uit een afzonderlijk bestand via een include instructie), is er vanwege een interactie met de configuratieparser de (enigszins vreemde) extra beperking dat haakjes, accolades of haken moeten worden gebalanceerd binnen opmerkingen in C/C++-stijl en letterlijke tekenreeksen. Daarom geen smileys in C/C++-stijl opmerkingen!

Een include "PATH" instructie kan op elke plaats worden gebruikt om de inhoud van een bestand in te voegen op het punt van de verklaring. PATH Hier kan een absoluut of een relatief relatief pad zijn (met of zonder submappen). Als het een relatief pad is, worden de volgende locaties in volgorde doorzocht: huidige werkmap; map(s) met outer, inclusief bestanden, indien van toepassing; map(s) met de configuratiebestand(en); en ten slotte de map met het uitvoerbare bestand CNTK. Alle ingebouwde BrainScript-functies worden op deze manier opgenomen vanuit een bestand met de naam CNTK.core.bs dat zich naast het cntk-uitvoerbare bestand bevindt.

Expressies

Vervolgens moet u weten wat BrainScript-expressies zijn, de formules die uw netwerk beschrijven. BrainScript-expressies worden als wiskunde geschreven in een syntaxis die vergelijkbaar is met populaire programmeertalen. De eenvoudigste expressies zijn letterlijke waarden, bijvoorbeeld getallen en tekenreeksen. Een rekenkundig voorbeeld is W1 * r + b, waarbij * wordt verwezen naar een scalaire, matrix of tensor-product, afhankelijk van het type van de variabelen. Een ander veelvoorkomend type expressie is de functie-aanroep, bijvoorbeeld RectifiedLinear (.).

BrainScript is een dynamisch getypte taal. Een belangrijk expressietype is de record, gedefinieerd met behulp van de {...} syntaxis en toegankelijk via de puntsyntaxis. Wijst bijvoorbeeld r = { x = 13 ; y = 42 } een record met twee leden toe aan r, waarbij het eerste lid kan worden geopend als r.x.

Naast de gebruikelijke wiskundige operatoren heeft BrainScript een voorwaardelijke expressie (if c then t else f), een matrixexpressie en eenvoudige lambdas. Ten slotte kan BrainScript, om te kunnen werken met de CNTK C++-code, rechtstreeks een beperkte set vooraf gedefinieerde C++-objecten instantiëren, voornamelijk de ComputationNode waaruit rekenkundige netwerken bestaan. Zie Expressies voor meer informatie.

BrainScript-expressies worden geëvalueerd bij het eerste gebruik. Het primaire doel van BrainScript is het beschrijven van het netwerk, zodat de waarde van een expressie vaak geen uiteindelijke waarde is, maar eerder een knooppunt in een rekengrafiek voor uitgestelde berekeningen (zoals in W1 * r + b). Alleen BrainScript-expressies van scalaire waarden (bijvoorbeeld 28*28) worden 'berekend' op het moment dat de BrainScript wordt geparseerd. Expressies die nooit worden gebruikt (bijvoorbeeld vanwege een voorwaarde) worden nooit geëvalueerd.

Opmerking: de nu afgeschafte NDLNetworkBuilder versie ondersteunde alleen functie-aanroepsyntaxis; u moet bijvoorbeeld schrijven Plus (Times (W1, r), b1).

Variabelen

Variabelen kunnen de waarde bevatten van een BrainScript-expressie (getal, tekenreeks, record, matrix, lambda, CNTK C++-object) en worden vervangen wanneer ze in een expressie worden gebruikt. Variabelen zijn onveranderbaar, dat wil zeggen slechts eenmaal toegewezen. De bovenstaande netwerkdefinitie begint bijvoorbeeld met:

SDim = 28*28  
HDim = 256
LDim = 10

Hier worden de variabelen ingesteld op scalaire numerieke waarden die worden gebruikt als parameters in volgende expressies. Deze waarden zijn de dimensies van de gegevensvoorbeelden, verborgen lagen en labels die in de training worden gebruikt. Deze specifieke voorbeeldinstallatie is voor de MNIST-gegevensset, een verzameling [28 x 28]pixelafbeeldingen. Elke afbeelding is een handgeschreven cijfer (0-9), dus er zijn 10 mogelijke labels die op elke afbeelding kunnen worden toegepast. De verborgen activeringsdimensie HDim is een keuze van de gebruiker.

De meeste variabelen zijn recordleden (het buitenste BrainScript-blok is impliciet een record). Daarnaast kunnen variabelen functieargumenten zijn of worden opgeslagen als matrixelementen.

Ok, klaar om de modeldefinitie te doorlopen.

Het netwerk definiëren

Een netwerk wordt voornamelijk beschreven door formules van de wijze waarop de uitvoer van het netwerk wordt berekend op basis van de invoer. We noemen dit de modelfunctie, die vaak wordt gedefinieerd als een werkelijke functie in BrainScript. Als onderdeel van de modelfunctie moet de gebruiker de modelparameters declareren. Ten slotte moet u de invoer en criteria/uitvoer van het netwerk definiëren. Deze worden allemaal gedefinieerd als variabelen. De invoer- en criteria/uitvoervariabelen moeten vervolgens worden doorgegeven aan het systeem.

De modelfunctie en modelparameters van het netwerk

De modelfunctie bevat de werkelijke netwerkformules en de respectieve modelparameters. In ons voorbeeld worden matrixproduct en optellen gebruikt, en de 'primitieve' (ingebouwde) functie voor de energiefunctie RectifiedLinear(), dus de kern van de netwerkfunctie bestaat uit deze vergelijkingen:

r = RectifiedLinear (W0 * features + b0)
z = W1 * r + b1 

Modelparameters zijn matrices, bias vectoren of een andere tensor die het geleerde model vormen na voltooiing van de training. De modelparametertensors worden gebruikt bij het transformeren van de invoervoorbeeldgegevens naar de gewenste uitvoer en worden bijgewerkt door het leerproces. Het bovenstaande voorbeeldnetwerk bevat de volgende matrixparameters:

W0 = ParameterTensor {(HDim:SDim)}
b0 = ParameterTensor {(HDim)}

In dit geval W0 is de gewichtsmatrix en b0 de biasvector. ParameterTensor{} geeft een speciale CNTK-primitieve aan, die een vector, matrix of willekeurige tensor start en de dimensieparameters als een BrainScript-matrix (getallen samengevoegd door een dubbele punt :). De dimensie van een vector is één getal, terwijl een matrixdimensie moet worden opgegeven als (numRows:numCols). Parameters worden standaard geïnitialiseerd met uniforme willekeurige getallen wanneer ze rechtstreeks worden geïnstantieerd en heNormal wanneer ze worden gebruikt via lagen, maar er zijn andere opties (zie hier) voor de volledige lijst. In tegenstelling tot normale functies, ParameterTensor{} worden de argumenten tussen accolades in plaats van haakjes gebruikt. Accolades zijn de BrainScript-conventie voor functies die parameters of objecten maken, in tegenstelling tot functies.

Dit wordt vervolgens allemaal verpakt in een BrainScript-functie. BrainScript-functies worden gedeclareerd in de vorm f(x) = an expression of x. Is bijvoorbeeld Sqr (x) = x * x een geldige BrainScript-functiedeclaratie. Veel eenvoudiger en direct kan niet, toch?

Nu is de werkelijke modelfunctie van het bovenstaande voorbeeld iets complexer:

model (features) = {
    # model parameters
    W0 = ParameterTensor {(HDim:SDim)} ; b0 = ParameterTensor {HDim}  
    W1 = ParameterTensor {(LDim:HDim)} ; b1 = ParameterTensor {LDim}

    # model formula
    r = RectifiedLinear (W0 * features + b0) # hidden layer
    z = W1 * r + b1                          # unnormalized softmax
}.z

De buitenste { ... } en dat laatste .z verdient enige uitleg. De buitenste curlies { ... } en hun inhoud definiëren in feite een record met 6 recordleden (W0, b0, W1, b1, ren z). De waarde van de modelfunctie is echter slechts; zalle andere zijn intern van de functie. Daarom gebruiken .z we om het recordlid te selecteren dat we willen retourneren. Dit is slechts de puntsyntaxis voor toegang tot recordleden. Op deze manier zijn de andere recordleden niet toegankelijk van buitenaf. Maar ze blijven bestaan als onderdeel van de expressie voor het berekenen zvan . Het { ... ; x = ... }.x patroon is een manier om lokale variabelen te gebruiken.

Houd er rekening mee dat de recordsyntaxis niet nodig is. model(features) Als alternatief kan ook zijn gedeclareerd zonder de omleiding via de record, als één expressie:

model (features) = ParameterTensor {(LDim:HDim)} * (RectifiedLinear (ParameterTensor {(HDim:SDim)}
                   * features + ParameterTensor {HDim})) + ParameterTensor {LDim}

Dit is veel moeilijker te lezen en, belangrijker, staat niet toe dat dezelfde parameter op meerdere plaatsen in de formule wordt gebruikt.

Invoerwaarden

Invoer in het netwerk wordt gedefinieerd door de voorbeeldgegevens en de labels die zijn gekoppeld aan de voorbeelden:

features = Input {SDim}
labels   = Input {LDim}

Input{} is de tweede speciale CNTK-primitieve die nodig is voor modeldefinitie (de eerste is Parameter{}). Er wordt een variabele gemaakt die invoer van buiten het netwerk ontvangt: van de lezer. Het argument van Input{} is de gegevensdimensie. In dit voorbeeld bevat de features invoer de dimensies van de voorbeeldgegevens (die we hebben gedefinieerd in de variabele SDim) en de labels invoer de dimensies van de labels. De variabelenamen van de invoer komen naar verwachting overeen met de overeenkomende vermeldingen in de definitie van de lezer.

Trainingscriteria en netwerkuitvoer

We moeten nog steeds aangeven hoe de uitvoer van het netwerk communiceert met de wereld. Onze modelfunctie berekent logit-waarden (niet-genormaliseerde logboekkansen). Deze logit-waarden kunnen worden gebruikt om

  • het opleidingscriterium definiëren,
  • meetnauwkeurigheid, en
  • de kans op uitvoerklassen berekenen die een invoer hebben gegeven, om een classificatiebeslissing op te baseren (houd er rekening mee dat het niet-genormaliseerde logboekposterieur z vaak rechtstreeks voor classificatie kan worden gebruikt).

Het voorbeeldnetwerk maakt gebruik van categorielabels, die worden weergegeven als one-hot vectoren. Voor het MNIST-voorbeeld worden deze weergegeven als een matrix van 10 drijvendekommawaarden, die allemaal nul zijn, met uitzondering van de juiste labelcategorie die 1,0 is. Classificatietaken zoals de onze gebruiken vaak de SoftMax() functie om de waarschijnlijkheden voor elk label te verkrijgen. Het netwerk wordt vervolgens geoptimaliseerd om de logboekkans van de juiste klasse (cross-entropie) te maximaliseren en die van alle andere klassen te minimaliseren. Dit is ons trainingscriterium of verliesfunctie. In CNTK worden deze twee acties doorgaans gecombineerd in één functie voor efficiëntie:

ce = CrossEntropyWithSoftmax (labels, z)

CrossEntropyWithSoftmax() functie neemt de invoer, berekent de SoftMax() functie, berekent de fout van de werkelijke waarde met behulp van cross-entropie en dat foutsignaal wordt gebruikt om de parameters in het netwerk bij te werken via back-doorgifte. Daarom wordt in het bovenstaande voorbeeld de genormaliseerde Softmax() waarde, die we hebben berekend als P, niet gebruikt tijdens de training. Dit is echter wel nodig voor het gebruik van het netwerk (merk nogmaals op dat in veel gevallen z vaak voldoende is voor classificatie; in dat geval z zou zelf de uitvoer zijn).

CNTK gebruikt Stochastic Gradient Descent (SGD) als leeralgoritmen. SGD moet de kleurovergang van de doelfunctie berekenen met betrekking tot alle modelparameters. Belangrijk is dat CNTK niet vereist dat gebruikers deze kleurovergangen opgeven. In plaats daarvan heeft elke ingebouwde functie in CNTK ook een afgeleide tegenhangerfunctie en voert het systeem automatisch de back-doorgifte-update van de netwerkparameters uit. Dit is niet zichtbaar voor de gebruiker. Gebruikers hoeven zich nooit bezig te houden met kleurovergangen. Wanneer dan ook.

Naast het trainingscriterium worden voorspelde foutpercentages vaak berekend tijdens de trainingsfase om de verbetering van het systeem te valideren naarmate de training verder gaat. Dit wordt verwerkt in CNTK met behulp van de volgende functie:

errs = ClassificationError (labels, z)

De waarschijnlijkheden die door het netwerk worden geproduceerd, worden vergeleken met het werkelijke label en het foutpercentage wordt berekend. Dit wordt over het algemeen weergegeven door het systeem. Hoewel dit nuttig is, is het niet verplicht om te gebruiken ClassificationError().

Invoer, uitvoer en criteria communiceren met het systeem

Nu alle variabelen zijn gedefinieerd, moeten we het systeem vertellen welke van de variabelen moeten worden behandeld als invoer, uitvoer en criteria. Dit wordt gedaan door vijf speciale variabelen te definiëren die precies deze namen moeten hebben:

featureNodes    = (features)
labelNodes      = (labels)
criterionNodes  = (ce)
evaluationNodes = (errs)
outputNodes     = (z:P)

De waarden zijn matrices, waarbij de waarden moeten worden gescheiden door een dubbele punt (de dubbele punt : is een BrainScript-operator die een matrix vormt door twee waarden of matrices samen te voegen). Dit wordt hierboven weergegeven voor outputNodes, die zowel als zP als uitvoer declareert.

(Opmerking: de afgeschafte NDLNetworkBuilder vereist dat de matrixelementen worden gescheiden door komma's.)

Samenvatting van speciale namen

Zoals we hierboven hebben gezien, zijn er 7 speciale namen waar we rekening mee moeten houden, die "magische" eigenschappen hebben:

  • ParameterTensor{}: Declareert en initialiseert een leerbare parameter.
  • Input{}: Declareert een variabele die wordt verbonden en ingevoerd vanuit een gegevenslezer.
  • featureNodes, labelNodes, criterionNodes, evaluationNodesen outputNodes: declareert aan het systeem welke van onze variabelen moeten worden gebruikt als invoer, uitvoer en criteria.

Daarnaast zijn er nog 3 speciale functies met ingebouwde "magie" in CNTK, die elders worden besproken:

  • Constant(): Declareert een constante.
  • PastValue() en FutureValue(): Toegang tot een variabele in een netwerkfunctie in een andere tijdstap, voor het vormen van terugkerende lussen.

Een andere vooraf gedefinieerde naam is een ingebouwde primitieve functie, zoals Sigmoid() of Convolution() met een C++-implementatie, een vooraf gedefinieerde bibliotheekfunctie die in BrainScript wordt gerealiseerd, zoals BS.RNNs.LSTMP(), of een record die fungeert als een naamruimte voor bibliotheekfuncties (bijvoorbeeld BS.RNNs). Zie BrainScript-Full-Function-Reference voor een volledige lijst.

Volgende: BrainScript-expressies