Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Deze sectie is de specificatie van BrainScript-expressies, hoewel we opzettelijk informele taal gebruiken om deze leesbaar en toegankelijk te houden. De tegenhanger is de specificatie van de syntaxis van de brainScript-functiedefinitie, die hier te vinden is.
Elk hersenscript is een expressie, die op zijn beurt bestaat uit expressies die zijn toegewezen aan recordlidvariabelen. Het buitenste niveau van een netwerkbeschrijving is een impliciete recordexpressie. BrainScript heeft de volgende soorten expressies:
- letterlijke waarden, zoals getallen en tekenreeksen
- wiskundig-achtige infix- en unaire bewerkingen zoals
a + b - een ternaire voorwaardelijke expressie
- functie-aanroepen
- records, toegang tot recordleden
- matrices, toegang tot matrixelementen
- functie-expressies (lambdas)
- ingebouwde C++-objectconstructie
We hebben de syntaxis voor elk van deze zo dicht mogelijk bij populaire talen bewaard, zoveel van wat u hieronder vindt, zal er zeer bekend uitzien.
Concepten
Voordat u de afzonderlijke soorten expressies beschrijft, moet u eerst enkele basisconcepten gebruiken.
Directe versus uitgestelde berekening
BrainScript kent twee soorten waarden: onmiddellijk en uitgesteld. Onmiddellijke waarden worden berekend tijdens de verwerking van BrainScript, terwijl uitgestelde waarden objecten zijn die knooppunten in het rekennetwerk vertegenwoordigen. Het rekennetwerk beschrijft de werkelijke berekening die wordt uitgevoerd door de CNTK uitvoeringsengine tijdens de training en het gebruik van het model.
Directe waarden in BrainScript zijn bedoeld om de berekening te parameteriseren. Ze geven tensordimensies aan, het aantal netwerklagen, een padnaam waaruit een model moet worden geladen, enzovoort. Omdat BrainScript-variabelen onveranderbaar zijn, zijn directe waarden altijd constanten.
Uitgestelde waarden komen voort uit het primaire doel van hersenscripts: om het rekennetwerk te beschrijven. Het rekennetwerk kan worden gezien als een functie die wordt doorgegeven aan de trainings- of deductieroutine, die vervolgens de netwerkfunctie uitvoert via de CNTK uitvoeringsengine. Daarom is het resultaat van veel BrainScript-expressies een rekenknooppunt in een rekennetwerk, in plaats van een werkelijke waarde. Vanuit het oogpunt van BrainScript is een uitgestelde waarde een C++-object van het type ComputationNode dat een netwerkknooppunt vertegenwoordigt. Als u bijvoorbeeld de som van twee netwerkknooppunten maakt, wordt een nieuw netwerkknooppunt gemaakt dat de optelbewerking vertegenwoordigt die de twee knooppunten als invoer gebruikt.
Scalars versus Matrices versus Tensors
Alle waarden in het rekennetwerk zijn numerieke n-dimensionale matrices die we tensors aanroepen, en n geeft de positie van de tensor aan. Tensor-dimensies worden expliciet opgegeven voor invoer- en modelparameters; en automatisch afgeleid door operators.
Het meest voorkomende gegevenstype voor berekeningen, matrices, zijn slechts tensors van rang 2. Kolomvectoren zijn tensors van rang 1, terwijl rijvectoren rang 2 zijn. Het matrixproduct is een veelvoorkomende bewerking in neurale netwerken.
Tensors zijn altijd uitgestelde waarden, dat wil gezegd objecten in de uitgestelde rekenkundige grafiek. Elke bewerking die een matrix of tensor omvat, maakt deel uit van de rekengrafiek en wordt geëvalueerd tijdens de training en deductie. Tensor-dimensies worden echter vooraf afgeleid/gecontroleerd op de verwerkingstijd van BS.
Scalaire waarden kunnen onmiddellijke of uitgestelde waarden zijn. Scalaire waarden die het rekennetwerk zelf parameteriseren, zoals tensor-dimensies, moeten onmiddellijk zijn, d.w. computeerbaar op het moment van verwerking van BrainScript. Uitgestelde scalaire waarden zijn rank-1 tensors van dimensie [1]. Ze maken deel uit van het netwerk zelf, waaronder leerbare scalaire parameters zoals zelfstabilisatoren en constanten zoals in Log (Constant (1) + Exp(x)).
Dynamisch typen
BrainScript is een dynamisch getypte taal met een extreem eenvoudig typesysteem. Typen worden gecontroleerd tijdens het verwerken van BrainScript wanneer een waarde wordt gebruikt.
Onmiddellijke waarden zijn van het type getal, Booleaanse waarde, tekenreeks, record, matrix, functie/lambda of een van de vooraf gedefinieerde C++-klassen van CNTK. De typen worden gecontroleerd op het moment van gebruik (het argument voor de if instructie wordt bijvoorbeeld COND geverifieerd als een Boolean, en toegang tot een matrixelement vereist dat het object een matrix is).
Alle uitgestelde waarden zijn tensors. Tensor-dimensies maken deel uit van hun type, die worden gecontroleerd of afgeleid tijdens de verwerking van BrainScript.
Expressies tussen een onmiddellijke scalaire en een uitgestelde tensor moeten de scalaire waarde expliciet converteren naar een uitgestelde Constant()waarde. De niet-lineariteit van Softplus moet bijvoorbeeld worden geschreven als Log (Constant(1) + Exp (x)). (Het is gepland om deze vereiste in een toekomstige update te verwijderen.)
Expressietypen
Literals
Letterlijke waarden zijn numerieke, Booleaanse of tekenreeksconstanten, zoals u zou verwachten. Voorbeelden:
13,42,3.1415926538,1e30true,false"my_model.dnn",'single quotes'
Numerieke letterlijke waarden zijn altijd drijvende komma met dubbele precisie. Er is geen expliciet geheel getaltype in BrainScript, hoewel sommige expressies zoals matrixindexen mislukken met een fout als er waarden worden weergegeven die geen gehele getallen zijn.
Letterlijke tekenreeksen kunnen enkele of dubbele aanhalingstekens gebruiken, maar hebben geen manier om aanhalingstekens of andere tekens binnen te ontsnappen (tekenreeks met zowel enkele als dubbele aanhalingstekens moet worden berekend, bijvoorbeeld "He'd say " + '"Yes!" in a jiffy.'). Letterlijke tekenreeksen kunnen meerdere regels omvatten; bijvoorbeeld:
I3 = Parameter (3, 3, init='fromLiteral', initFromLiteral = '1 0 0
0 1 0
0 0 1')
Infix- en Unary-bewerkingen
BrainScript ondersteunt de onderstaande operators. BrainScript-operators worden gekozen om te betekenen wat men zou verwachten van populaire talen, met uitzondering van .* (element-wise product), * (matrixproduct) en speciale uitzendsemantiek van elementwijze bewerkingen.
Operatoren +voor numerieke invoegsels, -, , /*.*
+,-en*van toepassing op scalaire, matrices en tensors..*geeft een elementgewijze product aan. Opmerking voor Python gebruikers: dit is het equivalent van numpy's*./wordt alleen ondersteund voor scalaire waarden. Een deling van elementen kan worden geschreven met behulp van ingebouwdeReciprocal(x)berekeningen op basis van een element1/x.
Booleaanse operatoren &&voor infixen, ||
Deze geven respectievelijk Booleaanse EN en OR-waarden aan.
Tekenreekssamenvoeging (+)
Tekenreeksen worden samengevoegd met +. Bijvoorbeeld: BS.Networks.Load (dir + "/model.dnn").
Vergelijkingsoperators
De zes vergelijkingsoperatoren zijn <, ==en >hun negaties >=, !=. <= Ze kunnen worden toegepast op alle onmiddellijke waarden zoals verwacht; hun resultaat is een Booleaanse waarde.
Als u vergelijkingsoperatoren wilt toepassen op tensors, moet u in plaats daarvan ingebouwde functies gebruiken, zoals Greater().
Unary-, !
Deze geven respectievelijk negatie en logische negatie aan. ! kan momenteel alleen worden gebruikt voor scalaire waarden.
Elementwise Bewerkingen en Semantiek uitzenden
Wanneer ze worden toegepast op matrices/tensors, +-en .* worden ze toegepast op elementen.
Alle elementgewijze bewerkingen ondersteunen het uitzenden van semantiek. Uitzenden betekent dat elke dimensie die is opgegeven als 1 automatisch wordt herhaald om aan elke dimensie te voldoen.
Een rijvector van dimensie [1 x N] kan bijvoorbeeld rechtstreeks worden toegevoegd aan een matrix met dimensies [M x N]. De 1 dimensie wordt automatisch herhaald M . Verder worden tensor-afmetingen automatisch gevuld met 1 dimensies. Het is bijvoorbeeld toegestaan om een kolomvector van dimensie [M] een [M x N] matrix toe te voegen. In dit geval worden de afmetingen van de kolomvector automatisch opgevuld zodat [M x 1] deze overeenkomt met de rang van de matrix.
Opmerking voor Python gebruikers: in tegenstelling tot numpy worden uitzenddimensies links uitgelijnd.
De operator Matrix-Product*
De A * B bewerking geeft het matrixproduct aan. Het kan ook worden toegepast op parseringsmatrices, waardoor de efficiëntie wordt verbeterd voor het verwerken van tekstinvoer of labels die worden weergegeven als one-hot vectoren. In CNTK heeft het matrixproduct een uitgebreide interpretatie waarmee het kan worden gebruikt met tensors van rang > 2. Het is bijvoorbeeld mogelijk om elke kolom in een rank-3-tensor afzonderlijk te vermenigvuldigen met een matrix.
Het matrixproduct en de bijbehorende tensor-extensie worden hier gedetailleerd beschreven.
Opmerking: Als u wilt vermenigvuldigen met een scalaire waarde, gebruikt u het elementgewijze product .*.
Python gebruikers worden geadviseerd om numpy de * operator te gebruiken voor het elementgewijze product, niet het matrixproduct. * de operator van CNTK komt overeen met numpy'sdot(), terwijl CNTK gelijk is .*aan de operator van Python * voor numpy matrices.
De voorwaardelijke operator if
Conditionals in BrainScript zijn expressies, zoals de C++ ? -operator. De Syntaxis van BrainScript is if COND then TVAL else EVAL, waarbij COND een directe Booleaanse expressie moet zijn en het resultaat van de expressie waar is TVALCOND en EVAL anders. De if expressie is handig voor het implementeren van meerdere vergelijkbare, met vlag geparameteriseerde configuraties in hetzelfde BrainScript en ook voor recursie.
(De if operator werkt alleen voor onmiddellijke scalaire waarden. Als u voorwaardelijke voorwaarden voor uitgestelde objecten wilt implementeren, gebruikt u de ingebouwde functie BS.Boolean.If(), waarmee u een waarde kunt selecteren uit een van twee tensors op basis van een vlagtensor. Het heeft het formulier If (cond, tval, eval).)
Functie-aanroepen
BrainScript heeft drie soorten functies: ingebouwde primitieven (met C++-implementaties), bibliotheekfuncties (geschreven in BrainScript) en door de gebruiker gedefinieerd (BrainScript). Voorbeelden van ingebouwde functies zijn Sigmoid() en MaxPooling(). Bibliotheek- en door de gebruiker gedefinieerde functies zijn mechanisch hetzelfde, net opgeslagen in verschillende bronbestanden. Alle soorten worden aangeroepen, vergelijkbaar met wiskundige en algemene talen, met behulp van het formulier f (arg1, arg2, ...).
Sommige functies accepteren optionele parameters. Optionele parameters worden bijvoorbeeld doorgegeven als benoemde parameters f (arg1, arg2, option1=..., option2=...).
Functies kunnen recursief worden aangeroepen, bijvoorbeeld:
DNNLayerStack (x, numLayers) =
if numLayers == 1
then DNNLayer (x, hiddenDim, featDim)
else DNNLayer (DNNLayerStack (x, numLayers-1), # add a layer to a stack of numLayers-1
hiddenDim, hiddenDim)
Let op hoe de if operator wordt gebruikt om de recursie te beëindigen.
Laag maken
Functies kunnen volledige lagen of modellen maken die functieobjecten zijn die zich ook gedragen als functies.
Een functie die een laag met leerbare parameters maakt, maakt standaard gebruik van accolades { } in plaats van haakjes ( ).
U krijgt expressies als volgt te zien:
h = DenseLayer {1024} (v)
Hier zijn twee aanroepen in het spel. De eerste, DenseLayer{1024}is een functie-aanroep waarmee een functieobject wordt gemaakt, dat vervolgens op zijn beurt wordt toegepast op gegevens (v).
Omdat DenseLayer{} een functieobject met leerbare parameters wordt geretourneerd, wordt dit gebruikt { } om dit aan te geven.
Records en Record-Member Access
Recordexpressies zijn toewijzingen tussen accolades. Bijvoorbeeld:
{
x = 13
y = x * factorParameter
f (z) = y + z
}
Deze expressie definieert een record met drie leden, xyen f, waarbij f een functie is.
In de record kunnen expressies verwijzen naar andere recordleden op basis van hun naam, zoals x hierboven wordt geopend in de toewijzing van y.
In tegenstelling tot veel talen kunnen recordvermeldingen echter in elke volgorde worden gedeclareerd. Kan bijvoorbeeld x worden gedeclareerd na y. Dit is bedoeld om de definitie van terugkerende netwerken te vergemakkelijken. Elk recordlid is toegankelijk vanuit de expressie van een ander recordlid. Dit verschilt van bijvoorbeeld Python; en vergelijkbaar met F#'slet rec. Cyclische verwijzingen zijn verboden, met uitzondering van de PastValue() en FutureValue() bewerkingen.
Wanneer records zijn genest (recordexpressies die in andere records worden gebruikt), worden recordleden opgezoekd via de hele hiërarchie van tussenliggende bereiken. In feite maakt elke variabeletoewijzing deel uit van een record: het buitenste niveau van een BrainScript is ook een impliciete record. In het bovenstaande voorbeeld factorParameter moet deze worden toegewezen als recordlid van een insluitbereik.
Met functies die binnen een record zijn toegewezen, worden recordleden vastgelegd waarnaar ze verwijzen. Hiermee wordt bijvoorbeeld f() vastgelegd y, wat op zijn beurt afhankelijk is x van en de extern gedefinieerde factorParameter. Het vastleggen van deze betekent dat deze f() kunnen worden doorgegeven als lambda in externe bereiken die er geen toegang toe hebben factorParameter of die er geen toegang toe hebben.
Van buiten worden recordleden geopend met behulp van de . operator. Als we bijvoorbeeld de bovenstaande recordexpressie aan een variabele rhadden toegewezen, zou dit r.x de waarde 13opleveren. De . operator doorsluit bereiken niet: r.factorParameter mislukt met een fout.
(Merk op dat tot CNTK 1,6, in plaats van accolades{ ... }[ ... ], records gebruikte haken . Dit is nog steeds toegestaan, maar afgeschaft.)
Matrices en toegang tot matrices
BrainScript heeft een eendimensionaal matrixtype voor directe waarden (niet te verwarren met tensors). Matrices worden geïndexeerd met behulp van [index]. Multidimensionale matrices kunnen worden geëmuleerd als matrices van matrices.
Matrices van ten minste 2 elementen kunnen worden gedeclareerd met behulp van de : operator. Het volgende declareert bijvoorbeeld een driedimensionale matrix met de naam imageDims die vervolgens wordt doorgegeven om een tensor van de parameter rank-3 te ParameterTensor{} declareren:
imageDims = (256 : 256 : 3)
inputFilter = ParameterTensor {imageDims}
Het is ook mogelijk om matrices te declareren waarvan de waarden naar elkaar verwijzen. Hiervoor moet u de iets meer betrokken syntaxis voor matrixtoewijzing gebruiken:
arr[i:i0..i1] = f(i)
hiermee wordt een matrix gemaakt met de naam arr onderindexgrens i0 en bovenindexgrens i1, i die de variabele aangeeft om de indexvariabele in de initialisatie-expressief(i) aan te geven, die op zijn beurt de waarde van arr[i]. De waarden van de matrix worden lui geëvalueerd. Hierdoor kan de initialisatie-expressie voor een specifieke index i toegang krijgen tot andere elementen arr[j] van dezelfde matrix, zolang er geen cyclische afhankelijkheid is. Dit kan bijvoorbeeld worden gebruikt om een stapel netwerklagen te declareren:
layers[l:1..L] =
if l == 1
then DNNLayer (x, hiddenDim, featDim)
else DNNLayer (layers[l-1], hiddenDim, hiddenDim)
In tegenstelling tot de recursieve versie van deze versie die we eerder hebben geïntroduceerd, behoudt deze versie de toegang tot elke afzonderlijke laag door te zeggen layers[i].
U kunt ook een syntaxis voor expressies array[i0..i1] (i => f(i))gebruiken. Dit is minder handig, maar soms handig. Het bovenstaande ziet er als volgt uit:
layers = array[1..L] (l =>
if l == 1
then DNNLayer (x, hiddenDim, featDim)
else DNNLayer (layers[l-1], hiddenDim, hiddenDim)
)
Opmerking: Momenteel is er geen manier om een matrix van 0 elementen te declareren. Dit wordt behandeld in een toekomstige versie van CNTK.
Functies-expressies en Lambdas
In BrainScript zijn functies waarden. Een benoemde functie kan worden toegewezen aan een variabele en als argument worden doorgegeven, bijvoorbeeld:
Layer (x, m, n, f) = f (ParameterTensor {(m:n)} * x + ParameterTensor {n})
h = Layer (x, 512, 40, Sigmoid)
waar Sigmoid wordt doorgegeven als een functie die binnen Layer()wordt gebruikt. U kunt ook een C#-achtige lambda-syntaxis (x => f(x)) gebruiken om anonieme functies inline te maken. Dit definieert bijvoorbeeld een netwerklaag met een Softplus-activering:
h = Layer (x, 512, 40, (x => Log (Constant(1) + Exp (x)))
De lambda-syntaxis is momenteel beperkt tot functies met één parameter.
Laagpatroon
In het bovenstaande Layer() voorbeeld worden het maken van parameters en de functietoepassing gecombineerd.
Een voorkeurspatroon is om deze in twee stappen te scheiden:
- parameters maken en een functieobject retourneren dat deze parameters bevat
- de functie maken die de parameters toepast op een invoer
Deze laatste is ook lid van het functieobject. Het bovenstaande voorbeeld kan als volgt worden herschreven:
Layer {m, n, f} = {
W = ParameterTensor {(m:n)} # parameter creation
b = ParameterTensor {n}
apply (x) = f (W * x + b) # the function to apply to data
}.apply
en wordt aangeroepen als:
h = Layer {512, 40, Sigmoid} (x)
De reden voor dit patroon is dat typische netwerktypen bestaan uit het toepassen van een functie na een andere op een invoer, die gemakkelijker kan worden geschreven met behulp van de Sequential() functie.
CNTK wordt geleverd met een uitgebreide set vooraf gedefinieerde lagen, die hier worden beschreven.
Ingebouwde C++ CNTK-objecten maken
Uiteindelijk zijn alle BrainScript-waarden C++-objecten. De speciale BrainScript-operator new wordt gebruikt voor interfacing met de onderliggende CNTK C++-objecten. Het heeft de vorm new TYPE ARGRECORD waarin TYPE zich een van een in code vastgelegde set van de vooraf gedefinieerde C++-objecten bevindt die beschikbaar zijn voor BrainScript en ARGRECORD een recordexpressie is die wordt doorgegeven aan de C++-constructor.
Waarschijnlijk krijgt u dit formulier alleen te zien als u de haakjesvorm van BrainScriptNetworkBuilder, dat wil bijvoorbeeld BrainScriptNetworkBuilder = (new ComputationNetwork { ... }), zoals hier wordt beschreven.
Maar nu weet u wat dit betekent: new ComputationNetwork hiermee maakt u een nieuw C++-object van het type ComputationNetwork, waarbij { ... } eenvoudig een record wordt gedefinieerd die wordt doorgegeven aan de C++-constructor van het interne ComputationNetwork C++-object, dat vervolgens zoekt naar 5 specifieke leden featureNodes, labelNodes, criterionNodes, evaluationNodesen outputNodes, zoals hier wordt uitgelegd.
Onder de kap zijn alle ingebouwde functies echt new expressies waarmee objecten van de klasse CNTK C++ ComputationNodeworden samengesteld. Zie voor een afbeelding hoe de Tanh() ingebouwde module daadwerkelijk wordt gedefinieerd als het maken van een C++-object:
Tanh (z, tag=')) = new ComputationNode { operation = 'Tanh' ; invoer = z /plus de functie args/ }
Semantiek voor expressieevaluatie
BrainScript-expressies worden geëvalueerd bij het eerste gebruik. Omdat het primaire doel van BrainScript is om het netwerk te beschrijven, is de waarde van een expressie vaak een knooppunt in een rekengrafiek voor uitgestelde berekening. Bijvoorbeeld, vanuit de BrainScript-hoek, W1 * r + b1 in het bovenstaande voorbeeld 'evalueert' naar een ComputationNode object in plaats van een numerieke waarde; terwijl de werkelijke numerieke waarden die betrokken zijn, worden berekend door de grafiekuitvoeringsengine. Alleen BrainScript-expressies van scalaire waarden (bijvoorbeeld 28*28) worden 'berekend' op het moment dat BrainScript wordt geparseerd. Expressies die nooit worden gebruikt (bijvoorbeeld vanwege een voorwaarde) worden nooit geëvalueerd (noch gecontroleerd op typefouten).
Algemene gebruikspatronen van expressies
Hieronder ziet u enkele veelvoorkomende patronen die worden gebruikt met BrainScript.
Naamruimten voor Functies
Door functietoewijzingen in records te groeperen, kan men een vorm van namenpacing bereiken. Bijvoorbeeld:
Layers = {
Affine (x, m, n) = ParameterTensor {(m:n)} * x + ParameterTensor {n}
Sigmoid (x, m, n) = Sigmoid (Affine (x, m, n))
ReLU (x, m, n) = RectifiedLinear (Affine (x, m, n))
}
# 1-hidden layer MLP
ce = CrossEntropyWithSoftmax (Layers.Affine (Layers.Sigmoid (feat, 512, 40), 9000, 512))
Lokaal bereikvariabelen
Soms is het wenselijk om lokaal bereikvariabelen en/of functies te hebben voor complexere expressies. Dit kan worden bereikt door de volledige expressie in een record in te sluiten en onmiddellijk toegang te krijgen tot de resultaatwaarde. Bijvoorbeeld:
{ x = 13 ; y = x * x }.y
maakt een 'tijdelijke' record met een lid y dat onmiddellijk wordt gelezen. Deze record is 'tijdelijk' omdat deze niet is toegewezen aan een variabele en daarom zijn de leden niet toegankelijk, met uitzondering van y.
Dit patroon wordt vaak gebruikt om NN-lagen met ingebouwde parameters beter leesbaar te maken, bijvoorbeeld:
SigmoidLayer (m, n, x) = {
W = Parameter (m, n, init='uniform')
b = Parameter (m, 1, init='value', initValue=0)
h = Sigmoid (W * x + b)
}.h
h Hier kan worden gedacht aan de 'retourwaarde' van deze functie.
Volgende: Meer informatie over het definiëren van BrainScript-functies
NDLNetworkBuilder (afgeschaft)
Eerdere versies van CNTK de nu afgeschafte NDLNetworkBuilder versie gebruikt in plaats van BrainScriptNetworkBuilder. NDLNetworkBuilder implementeerde een veel gereduceerde versie van BrainScript. Het had de volgende beperkingen:
- Geen invoegselsyntaxis. Alle operators moeten worden aangeroepen via functie-aanroepen.
Plus (Times (W1, r), b1)Bijvoorbeeld in plaats vanW1 * r + b1. - Geen geneste recordexpressies. Er is slechts één impliciete outer record.
- Geen voorwaardelijke expressie of recursieve functie-aanroep.
- Door de gebruiker gedefinieerde functies moeten worden gedeclareerd in speciale
loadblokken en kunnen niet worden genest. - De laatste recordtoewijzing wordt automatisch gebruikt als de waarde van een functie.
- De
NDLNetworkBuildertaalversie is niet Turing-complete.
NDLNetworkBuilder mag niet meer worden gebruikt.