Delen via


BrainScript-functies

Functies zijn de manier om herbruikbare modules te maken. Functies zijn geparameteriseerde expressies en kunnen op een rechte manier worden gedefinieerd, bijvoorbeeld:

Sqr (x) = x * x

Functies zijn eersteklas gegevenstypen in de zin dat functies andere functies kunnen accepteren en retourneren.

Functies definiëren

Er zijn twee manieren om functies, standaardsyntaxis en anonieme lambda-syntaxis te definiëren.

Standaardsyntaxis

Benoemde functies zijn recordleden die zijn gedefinieerd als recordlidtoewijzingen van deze twee formulieren:

f (arg1, arg2, ..., optionalArg1=optionalArg1Default, ...) = expression of args
f {arg1, arg2, ..., optionalArg1=optionalArg1Default, ...} = expression of args

De twee formulieren zijn syntactisch identiek, maar volgens conventie wordt het tweede formulier (met accolades{ }) gebruikt voor functies die functieobjecten retourneren met ingebouwde leerbare parameters, zoals de vooraf gedefinieerde lagen van CNTK.

De functieargumenten maken een naambereik zoals een record; Functieargumenten zijn bijvoorbeeld alsof ze zijn gedefinieerd als leden in een tussenliggende record.

Optionele parameters

Functies kunnen optionele parameters hebben. Deze worden doorgegeven door de waarde vooraf te gaan door name=. Als er geen optionele parameter wordt doorgegeven, wordt de standaardwaarde gebruikt. In de functiedefinitie worden optionele parameters gedeclareerd met behulp van het formulier name=defaultValue.

Hier volgt een voorbeeld voor een Softplus-functie die een optionele steilheidsparameter gebruikt:

Softplus (x, steepness=1) =
    if steepness == 1                # use the simpler formula if no steepness specified
    then Log (Constant(1) + Exp (x)) # softplus(x)
    else {                           # version with steepness: 1/s softplus (s x)
        steepnessAsConstant = Constant (steepness)
        y = Reciprocal (steepnessAsConstant) .* Softplus (steepnessAsConstant .* x)
        # Note: .* denotes an elementwise product
    }.y

Opmerking: De standaardwaarden van optionele parameters hebben geen toegang tot andere parameters. Als u voor de bovenstaande Softplus-functie bijvoorbeeld een variabele hebt die steilheid definieert die u aan de functie wilt doorgeven, zegt u:

steepness = 4
z = Softplus (x, steepness=steepness)

waarbij de eerste steepness de naam van de parameter is en de tweede de expressie is voor de waarde die ook moet worden doorgegeven. De namen conflicteert niet.

Lambda-syntaxis

Naast de syntaxis van de functiedefinitie staat BrainScript anonieme lambdas toe met behulp van de syntaxis (x => f(x)), die is bedoeld om C# na te bootsen. De volgende twee definities zijn gelijkwaardig:

sqr(x) = x * x
sqr = (x => x * x) 

De BrainScript-parser transformeert de laatste eigenlijk intern in het voormalige formulier.

De lambda-syntaxis is momenteel beperkt tot één parameter en biedt ook geen ondersteuning voor optionele parameters. Lambdas met meer dan één parameter of met optionele parameters moeten worden gedefinieerd als benoemde functies.

Functies aanroepen

Functies worden aangeroepen zoals u verwacht: door de argumenten tussen haakjes door te geven; en optionele argumenten doorgeven als benoemde parameters. Een zelfstabilisator (een schaaltechniek die enigszins vergelijkbaar is met batchnormalisatie) met Behulp van Softplus met steilheid 4 zou bijvoorbeeld worden geschreven met de volgende twee functie-aanroepen:

beta = ParameterTensor {1, initValue=1}
xStabilized = x .* Softplus (beta, steepness=4)

Functies worden lazily uitgevoerd bij aanroepen. Als een functie modelparameters binnen definieert (dat wil gezegd, de varianten van ParameterTensor{} en elke functie met aanroepen binnen), levert elke aanroep van de functie een onafhankelijk exemplaar van deze modelparameters op. Vaak wordt het resultaat van een functie-aanroep toegewezen aan een variabele of doorgegeven als functieargument. Meerdere toepassingen van deze waarde leiden niet tot herhaalde uitvoering.

Puurheid/referentiële transparantie

Hoewel BrainScript veelvoorkomende kenmerken heeft met functionele talen, moet u er rekening mee houden dat BrainScript-functies niet volledig zuiver zijn, waardoor dit een zeer specifiek neveneffect kan hebben: die van het instantiëren van nieuwe modelparameters. Als de expressie 2 * sqr(x) bijvoorbeeld gelijk is aan sqr(x) + sqr(x) (metsqr(x) = x*x), is dat niet het geval voor 2 * ParameterTensor{N} vs. ParameterTensor{N} + ParameterTensor{N}

Gebruik volgens conventie ronde haakjes ( ) waar referentiële transparantie wordt gegeven, maar accolades { } om aan te geven waar dat niet is.

Functies als waarden

In BrainScript zijn functies waarden. Een benoemde functie kan worden toegewezen aan een variabele en worden doorgegeven als argument. Benoemde functies zijn zelfs recordleden met het type 'functie'. Opzoeken van functienamen is hetzelfde als het opzoeken van recordleden. Hierdoor kunnen functies bijvoorbeeld worden gegroepeerd in 'naamruimten' door ze in een recordexpressie te definiëren.

Benoemde functies als waarden

Als een functienaam wordt gebruikt zonder haakjes te volgen, verwijst deze naar de functie zelf als een object.

Gedeeltelijke toepassing/currying

BrainScript heeft momenteel geen syntactische ondersteuning voor gedeeltelijke toepassing of currying. Als u een vergelijkbaar effect wilt bereiken, kunt u een nieuwe functie definiëren die een andere functie aanroept met afhankelijke/vastgelegde parameters:

Layer (x, m, n, f) = f (ParameterTensor {(m:n)} * x + ParameterTensor {n})
Sigmoid512Layer (x) = Layer (x, 512, 512, Sigmoid)

Voorbeelden

In het volgende voorbeeld wordt een eenvoudige hiërarchie van algemene typen netwerklagen gedefinieerd:

Layers = {
    # base feed-forward without and with parameterized energy function
    # AffineLayer() defines model parameters inside.
    AffineLayer (x, m, n) = ParameterTensor {(m:n), init='heNormal'} * x
                            + ParameterTensor {n, initValue=0}
    # NLLayer applies a non-linearity on top of an affine layer
    NLLayer (x, m, n, f) = f (AffineLayer (x, m, n))
    # a few non-linearities
    SigmoidLayer (x, m, n)  = NLLayer (x, m, n, Sigmoid) # pass Sigmoid() function as the non-linaerity
    SoftplusLayer (x, m, n) = NLLayer (x, m, n, (x => Log (Constant(1) + Exp(x)))/*Softplus as lambda*/)
    ReLULayer (x, m, n)     = NLLayer (x, m, n, RectifiedLinear)
    SoftmaxLayer (x, m, n)  = NLLayer (x, m, n, Softmax)
}

die als volgt kan worden gebruikt:

# constants defined
# Sample, Hidden, and Label dimensions
SDim = 28*28 # feature dimension
HDim = 256   # hidden dimension
LDim = 10    # number of classes

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

# layers
h = Layers.ReLULayer (features, HDim, SDim)
z = Layers.AffineLayer (h, LDim, HDim) # input to softmax; same as log softmax without normalization  

# output and criteria
P    = Softmax (z)
ce   = CrossEntropyWithSoftmax (labels, z)
errs = ErrorPrediction         (labels, z)

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

Dit is hetzelfde als het eerste voorbeeld in Basisconcepten, maar het gebruik van functies.

Volgende: Meer informatie over het bewerken van modellen of rechtstreeks naar volledige functiereferentie gaan.

NDLNetworkBuilder (afgeschaft)

Eerdere versies van CNTK de nu afgeschafte NDLNetworkBuilder versie gebruikt in plaats van BrainScriptNetworkBuilder. NDLNetworkBuilderDe functiedefinitie is op een aantal manieren verschillend. De volgende definitie is geldig in NDLNetworkBuilder:

FF (X1, W1, B1) # no equal sign
[ # both curly braces and brackets are allowed
    T = Times (W1, X1)
    FF = Plus (T, B1)
] # return value is FF, and not the entire record

Met name wordt de retourwaarde geretourneerd via de lokale variabele die dezelfde naam heeft als de functie; of als er geen dergelijke variabele wordt gevonden, wordt de variabele die het laatst is gedefinieerd, geretourneerd. In BrainScript moet dit nu worden geschreven als:

FF (X1, W1, B1) =
{
    T = Times (W1, X1)
    Y = Plus (T, B1)
}.Y

Met name moet de retourwaarde Y expliciet aan het einde worden geselecteerd met behulp van . syntaxis, anders is de waarde van de functie de hele record.