Funzioni BrainScript
Le funzioni sono il modo di creare moduli riutilizzabili di BrainScript. Le funzioni sono espressioni con parametri e possono essere definite in modo diretto, ad esempio:
Sqr (x) = x * x
Le funzioni sono tipi di dati di prima classe nel senso che le funzioni possono accettare e restituire altre funzioni.
Definizione di funzioni
Esistono due modi per definire funzioni, sintassi standard e sintassi lambda anonima.
Sintassi standard
Le funzioni denominate sono membri record definiti come assegnazioni di record-member di questi due moduli:
f (arg1, arg2, ..., optionalArg1=optionalArg1Default, ...) = expression of args
f {arg1, arg2, ..., optionalArg1=optionalArg1Default, ...} = expression of args
Le due forme sono sintatticamente identiche, ma per convenzione, la seconda (usando { }
parentesi graffe) viene usata per le funzioni che restituiscono oggetti funzione con parametri appresi predefiniti, ad esempio i livelli predefiniti di CNTK.
Gli argomenti della funzione creano un ambito nome come un record; Ad esempio, gli argomenti delle funzioni sono come se fossero definiti come membri in un record racchiuso.
Parametri facoltativi
Le funzioni possono avere parametri facoltativi. Questi vengono passati prima del relativo valore in name=
base a . Se non viene passato un parametro facoltativo, viene usato il relativo valore predefinito. Nella definizione della funzione i parametri facoltativi vengono dichiarati usando il modulo name=defaultValue
.
Ecco un esempio per una funzione Softplus che accetta un parametro di ripidazione facoltativo:
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
Nota: i valori predefiniti dei parametri facoltativi non possono accedere ad altri parametri. Ad esempio, se per la funzione Softplus precedente si dispone di una variabile che definisce la ripidezza che si vuole passare alla funzione, basta dire:
steepness = 4
z = Softplus (x, steepness=steepness)
dove il primo steepness
è il nome del parametro e il secondo è l'espressione per il valore da passare troppo.
I nomi non verranno in conflitto.
Sintassi lambda
Oltre alla sintassi per la definizione della funzione, BrainScript consente di usare le lambda anonime usando la sintassi (x => f(x))
, che è destinata a simulare C#. Le due definizioni seguenti sono equivalenti:
sqr(x) = x * x
sqr = (x => x * x)
In realtà, il parser BrainScript trasforma internamente quest'ultimo nella forma precedente.
La sintassi lambda è attualmente vincolata a un parametro e non supporta anche parametri facoltativi. Le lambda con più parametri o con parametri facoltativi devono essere definite come funzioni denominate.
Chiamata di funzioni
Le funzioni vengono richiamate come previsto: passando gli argomenti nelle parentesi; e passando argomenti facoltativi come parametri denominati. Ad esempio, un auto-stabilizzatore (una tecnica di ridimensionamento leggermente simile alla normalizzazione batch) usando Softplus con ripidezza 4 verrebbe scritta con le due chiamate di funzione seguenti:
beta = ParameterTensor {1, initValue=1}
xStabilized = x .* Softplus (beta, steepness=4)
Le funzioni vengono eseguite in modo più pigre alla chiamata. In particolare, se una funzione definisce i parametri del modello all'interno, ovvero le varianti di ParameterTensor{}
e qualsiasi funzione che ha chiamate all'interno, ogni chiamata della funzione restituisce un'istanza indipendente di questi parametri del modello. Spesso, il risultato di una chiamata a una funzione viene quindi assegnato a una variabile o passata come argomento di funzione. Più usi di questo valore non comportano l'esecuzione ripetuta.
Purezza/Trasparenza referenziale
Anche se BrainScript ha comunità con i linguaggi funzionali, tenere presente che le funzioni BrainScript non sono completamente pure, in questo può avere un effetto collaterale molto specifico: quello di creare un'istanza di nuovi parametri del modello. Ad esempio, mentre l'espressione 2 * sqr(x)
è equivalente a sqr(x) + sqr(x)
(con sqr(x) = x*x
), che non è il caso per 2 * ParameterTensor{N}
vs. ParameterTensor{N} + ParameterTensor{N}
.
Per convenzione, usare parentesi ( )
graffe in cui viene specificata la trasparenza referenziale, ma parentesi { }
graffe per indicare dove non è.
Funzioni come valori
In BrainScript le funzioni sono valori. Una funzione denominata può essere assegnata a una variabile e passata come argomento. Infatti, le funzioni denominate sono solo membri di record con il tipo "function". La ricerca del nome della funzione è uguale alla ricerca dei membri del record. Ciò consente, ad esempio, di raggruppare le funzioni in "spazi dei nomi" definendoli all'interno di un'espressione di record.
Funzioni denominate come valori
Se viene usato un nome di funzione senza parentesi seguenti, si farà riferimento alla funzione stessa come oggetto.
Applicazione parziale/currying
BrainScript attualmente non ha supporto sintattico parziale applicazione o currying. Per ottenere un effetto simile, è possibile definire una nuova funzione che chiama un'altra funzione con parametri associati/acquisiti:
Layer (x, m, n, f) = f (ParameterTensor {(m:n)} * x + ParameterTensor {n})
Sigmoid512Layer (x) = Layer (x, 512, 512, Sigmoid)
Esempio
Nell'esempio seguente viene definita una semplice gerarchia di tipi di livello di rete comuni:
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)
}
che può essere usato come segue:
# 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)
Questo è lo stesso dell'esempio iniziale in Concetti di base, ma l'uso delle funzioni.
Successivamente: informazioni sulla modifica dei modelli o passare direttamente a Riferimento alla funzione completa.
NDLNetworkBuilder (deprecato)
Versioni precedenti di CNTK usato l'oggetto ora deprecato NDLNetworkBuilder
anziché BrainScriptNetworkBuilder
. NDLNetworkBuilder
La definizione della funzione è diversa in alcuni modi. La definizione seguente è valida 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
In particolare, il valore restituito viene restituito tramite la variabile locale con lo stesso nome della funzione; o se non viene trovata alcuna variabile di questo tipo, verrà restituita l'ultima variabile definita. In BrainScript questo deve ora essere scritto come:
FF (X1, W1, B1) =
{
T = Times (W1, X1)
Y = Plus (T, B1)
}.Y
In particolare, il valore Y
restituito deve essere selezionato in modo esplicito alla fine usando .
la sintassi, altrimenti il valore della funzione sarebbe l'intero record.