Fonctions BrainScript
Les fonctions sont le moyen de créer des modules réutilisables par BrainScript. Les fonctions sont des expressions paramétrables et peuvent être définies de manière directe, par exemple :
Sqr (x) = x * x
Les fonctions sont des types de données de première classe dans le sens où les fonctions peuvent accepter et retourner d’autres fonctions.
Définition de fonctions
Il existe deux façons de définir des fonctions, une syntaxe standard et une syntaxe lambda anonyme.
Syntaxe standard
Les fonctions nommées sont des membres d’enregistrement définis en tant qu’affectations de membres d’enregistrement de ces deux formulaires :
f (arg1, arg2, ..., optionalArg1=optionalArg1Default, ...) = expression of args
f {arg1, arg2, ..., optionalArg1=optionalArg1Default, ...} = expression of args
Les deux formes sont syntactiques identiques, mais par convention, le deuxième formulaire (à l’aide de accolades{ }
) est utilisé pour les fonctions qui retournent des objets de fonction avec des paramètres appris intégrés, tels que les couches prédéfinies de CNTK.
Les arguments de fonction créent une étendue de nom comme un enregistrement ; C’est-à-dire que les arguments de fonction sont comme s’ils étaient définis comme membres dans un enregistrement englobant.
Paramètres facultatifs
Les fonctions peuvent avoir des paramètres facultatifs. Celles-ci sont passées par leur valeur par name=
. Si un paramètre facultatif n’est pas passé, sa valeur par défaut est utilisée. Dans la définition de fonction, les paramètres facultatifs sont déclarés à l’aide du formulaire name=defaultValue
.
Voici un exemple pour une fonction Softplus qui prend un paramètre de raideur facultatif :
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
Remarque : Les valeurs par défaut des paramètres facultatifs ne peuvent pas accéder à d’autres paramètres. Par exemple, si pour la fonction Softplus ci-dessus, vous avez une variable qui définit la raideur que vous souhaitez passer à la fonction, dites simplement :
steepness = 4
z = Softplus (x, steepness=steepness)
où le premier steepness
est le nom du paramètre, et la seconde est l’expression pour que la valeur passe également.
Les noms ne sont pas en conflit.
Syntaxe lambda
En plus de la syntaxe de définition de fonction, BrainScript autorise les lambda anonymes à l’aide de la syntaxe (x => f(x))
, qui est destinée à imiter C#. Les deux définitions suivantes sont équivalentes :
sqr(x) = x * x
sqr = (x => x * x)
En fait, l’analyseur BrainScript transforme en interne ce dernier sous la forme antérieure.
La syntaxe lambda est actuellement limitée à un paramètre et ne prend pas en charge les paramètres facultatifs. Les lambda avec plusieurs paramètres ou avec des paramètres facultatifs doivent être définis en tant que fonctions nommées.
Invocation des fonctions
Les fonctions sont appelées comme prévu : en passant les arguments entre parenthèses ; et passage d’arguments facultatifs en tant que paramètres nommés. Par exemple, un auto-stabilisateur (une technique de mise à l’échelle similaire à la normalisation par lots) à l’aide de Softplus avec raideur 4 serait écrit avec les deux appels de fonction suivants :
beta = ParameterTensor {1, initValue=1}
xStabilized = x .* Softplus (beta, steepness=4)
Les fonctions sont exécutées de manière différée lors de l’appel. Plus précisément, si une fonction définit des paramètres de modèle à l’intérieur (autrement dit, les variantes et ParameterTensor{}
toute fonction qui a des appels à l’intérieur), chaque appel de la fonction génère une instance indépendante de ces paramètres de modèle. Souvent, le résultat d’un appel de fonction est ensuite attribué à une variable ou passé en tant qu’argument de fonction. Plusieurs utilisations de cette valeur ne entraînent pas l’exécution répétée.
Transparence référentielle/pureté
Bien que BrainScript ait des caractéristiques communes avec les langages fonctionnels, sachez que les fonctions BrainScript ne sont pas entièrement pures, car elles peuvent avoir un effet secondaire très spécifique : celle de l’instanciation de nouveaux paramètres de modèle. Par exemple, alors que l’expression 2 * sqr(x)
équivaut à sqr(x) + sqr(x)
(avec sqr(x) = x*x
), ce n’est pas le cas pour 2 * ParameterTensor{N}
vs. ParameterTensor{N} + ParameterTensor{N}
Par convention, utilisez des parenthèses rondes ( )
où la transparence référentielle est donnée, mais accolades { }
pour indiquer où elle n’est pas.
Fonctions en tant que valeurs
Dans BrainScript, les fonctions sont des valeurs. Une fonction nommée peut être affectée à une variable et transmise en tant qu’argument. En fait, les fonctions nommées sont simplement des membres d’enregistrement qui ont le type « function ». La recherche de nom de fonction est identique à celle de la recherche de membre d’enregistrement. Cela permet, par exemple, de regrouper des fonctions dans des « espaces de noms » en les définissant à l’intérieur d’une expression d’enregistrement.
Fonctions nommées en tant que valeurs
Si un nom de fonction est utilisé sans parenthèses suivantes, il fait référence à la fonction elle-même en tant qu’objet.
Application partielle/Currying
BrainScript n’a actuellement pas de prise en charge syntactique d’application partielle ou de currying. Pour obtenir un effet similaire, on peut définir une nouvelle fonction qui appelle une autre fonction avec des paramètres liés/capturés :
Layer (x, m, n, f) = f (ParameterTensor {(m:n)} * x + ParameterTensor {n})
Sigmoid512Layer (x) = Layer (x, 512, 512, Sigmoid)
Exemples
L’exemple suivant définit une hiérarchie simple de types de couche réseau courants :
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)
}
qui peut être utilisé comme suit :
# 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)
C’est le même que l’exemple initial dans Concepts de base, mais à l’aide de fonctions.
Suivant : Découvrez la modification du modèle ou accédez directement à la référence de fonction complète.
NDLNetworkBuilder (déconseillé)
Les versions antérieures de CNTK ont utilisé le maintenant déconseillé NDLNetworkBuilder
au lieu de BrainScriptNetworkBuilder
. NDLNetworkBuilder
La définition de fonction diffère de quelques façons. La définition suivante est valide dans 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
En particulier, la valeur de retour est retournée via la variable locale qui a le même nom que la fonction; ou si aucune variable de ce type n’est trouvée, la variable définie en dernier est retournée. Dans BrainScript, il doit maintenant être écrit comme suit :
FF (X1, W1, B1) =
{
T = Times (W1, X1)
Y = Plus (T, B1)
}.Y
En particulier, la valeur Y
de retour doit être explicitement sélectionnée à la fin à l’aide .
de la syntaxe, sinon la valeur de la fonction serait l’enregistrement entier.