Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Tato část je specifikace výrazů BrainScriptu, i když záměrně používáme neformální jazyk k tomu, aby byl čitelný a přístupný. Jeho protějškem je specifikace syntaxe definice funkce BrainScriptu, kterou najdete zde.
Každý mozek skript je výraz, který se zase skládá z výrazů přiřazených k proměnným člena záznamu. Vnější úroveň popisu sítě je implicitní výraz záznamu. BrainScript má následující druhy výrazů:
- literály, jako jsou čísla a řetězce
- matematické operace jako infix a unární operace, jako je
a + b - ternární podmíněný výraz
- vyvolání funkce
- záznamy, přístupy členů záznamů
- pole, přístup k prvkům pole
- výrazy funkcí (lambdas)
- integrovaná konstrukce objektů jazyka C++
Záměrně jsme zachovali syntaxi pro každou z těchto jazyků co nejblíže k oblíbeným jazykům, takže mnoho toho, co najdete níže, bude vypadat velmi dobře.
Koncepty
Než popíšete jednotlivé druhy výrazů, nejprve některé základní koncepty.
Okamžité vs. Odložené výpočty
BrainScript zná dva druhy hodnot: okamžité a odložené. Okamžité hodnoty se vypočítají během zpracování BrainScriptu, zatímco odložené hodnoty jsou objekty, které představují uzly ve výpočetní síti. Výpočetní síť popisuje skutečné výpočty prováděné CNTK spouštěcím strojem během trénování a používání modelu.
Okamžité hodnoty v BrainScriptu jsou určeny k parametrizaci výpočtů. Označují dimenze tensoru, počet vrstev sítě, název cesty pro načtení modelu z atd. Vzhledem k tomu, že proměnné BrainScriptu jsou neměnné, okamžité hodnoty jsou vždy konstanty.
Odložené hodnoty vznikají z primárního účelu mozek skriptů: popsat výpočetní síť. Výpočetní síť lze považovat za funkci, která se předává rutině trénování nebo odvozování, která pak provede síťovou funkci přes CNTK spouštěcí modul. Výsledkem mnoha výrazů BrainScriptu je tedy výpočetní uzel ve výpočetní síti, nikoli skutečná hodnota. Z pohledu BrainScriptu je odložená hodnota objektem ComputationNode typu C++, který představuje síťový uzel. Když například vezmete součet dvou síťových uzlů, vytvoří se nový síťový uzel, který představuje operaci součtu, která přebírá dva uzly jako vstupy.
Skalární vs. matice vs. Tensors
Všechny hodnoty v výpočetní síti jsou číselné ndimenzionální pole, která voláme tensory, a n označuje pořadí tensoru. Dimenze tensoru jsou explicitně určené pro vstupy a parametry modelu; a automaticky odvozuje operátory.
Nejběžnějším datovým typem pro výpočty, matice jsou jen tensory pořadí 2. Vektory sloupců jsou tensory pořadí 1, zatímco vektory řádků jsou pořadí 2. Maticový produkt je běžná operace v neurálních sítích.
Tensory jsou vždy odložené hodnoty, tj. objekty v odloženého výpočetním grafu. Každá operace, která zahrnuje matici nebo tensor, se stane součástí grafu výpočtů a vyhodnotí se během trénování a odvozování. Rozměry tensoru se však odvozují/kontrolují předem v době zpracování BS.
Skalární můžou být buď okamžité, nebo odložené hodnoty. Skaláry, které parametrizují samotnou výpočetní síť, jako jsou rozměry tensoru, musí být okamžitě, tj. v době zpracování BrainScriptu. Odložené skaláry jsou pořadí-1 tensory dimenze [1]. Jsou součástí samotné sítě, včetně naučitelných skalárních parametrů, jako jsou samoobslužné stabilizátory, a konstanty jako v Log (Constant (1) + Exp(x)).
Dynamické psaní
BrainScript je dynamicky napsaný jazyk s extrémně jednoduchým systémem typů. Typy se kontrolují při zpracování BrainScriptu při použití hodnoty.
Okamžité hodnoty jsou typu číslo, logická hodnota, řetězec, záznam, pole, funkce/lambda nebo některé z předdefinovaných tříd jazyka C++ CNTK. Jejich typy se kontrolují v době použití (například COND argument if příkazu je ověřen jako argument a Booleanpřístup k prvku pole vyžaduje, aby objekt byl polem).
Všechny odložené hodnoty jsou tensory. Rozměry tensoru jsou součástí jejich typu, které se kontrolují nebo odvozují během zpracování BrainScriptu.
Výrazy mezi okamžitým skalárním a odloženým tensorem musí explicitně převést skalár na odložené Constant(). Například nelineární hodnota Softplus musí být napsána jako Log (Constant(1) + Exp (x)). (Plánuje se odebrat tento požadavek v nadcházející aktualizaci.)
Typy výrazů
Literály
Literály jsou číselné, logické nebo řetězcové konstanty, jak byste očekávali. Příklady:
13,42,3.1415926538,1e30true,false"my_model.dnn",'single quotes'
Číselné literály jsou vždy s dvojitou přesností s plovoucí desetinou čárkou. V BrainScriptu neexistuje explicitní celočíselné typy, i když některé výrazy, jako jsou indexy matice, selžou s chybou, pokud se zobrazí hodnoty, které nejsou celá čísla.
Řetězcové literály mohou používat jednoduché nebo dvojité uvozovky, ale nemají žádný způsob, jak uvozovky nebo jiné znaky uvnitř (řetězec obsahující jednoduché i dvojité uvozovky musí být vypočítán, např. "He'd say " + '"Yes!" in a jiffy.'). Řetězcové literály mohou zahrnovat více řádků; Příklad:
I3 = Parameter (3, 3, init='fromLiteral', initFromLiteral = '1 0 0
0 1 0
0 0 1')
Oprava a unární operace
BrainScript podporuje operátory uvedené níže. Operátory BrainScriptu jsou zvoleny tak, aby znamenaly, co by očekávalo z oblíbených .* jazyků, s výjimkou (produktu moudrého prvku), * (maticového produktu) a speciálního vysílání sémantiky operací moudrých prvků.
Operátory +číselných oprav, -, , , */.*
+,-a*platí pro skaláry, matice a tensory..*označuje produkt, který je moudrý. Poznámka pro uživatele Pythonu: Toto je ekvivalent numpy's*./je podporován pouze pro skaláry. Dělení podle elementů lze psát pomocí předdefinovanýchReciprocal(x)výpočtů element-moudrý1/x.
Logické operátory &&infixu , ||
Označují se v nich logické hodnoty AND a OR.
Zřetězení řetězců (+)
Řetězce jsou zřetězeny s +. Příklad: BS.Networks.Load (dir + "/model.dnn").
Operátory porovnání
Šest relačních operátorů jsou <, , >==a jejich negace >=, !=, <=. Lze je použít u všech okamžitých hodnot podle očekávání; výsledkem je logická hodnota.
Aby bylo možné použít relační operátory na tensory, musí místo toho používat předdefinované funkce, například Greater().
Unární-, !
Tyto hodnoty označují negaci a logickou negaci. ! lze v současné době používat pouze pro skaláry.
Elementwise Operations and Broadcasting Sémantics
Při použití na matice/tensory, +, -a .* jsou použity element-moudrý.
Všechny operace moudré prvky podporují sémantiku vysílání. Vysílání znamená, že všechny dimenze zadané jako 1 se automaticky opakují tak, aby odpovídaly libovolné dimenzi.
Například vektor řádku dimenze [1 x N] lze přímo přidat do matice dimenze [M x N]. Dimenze 1 se automaticky opakuje M . Rozměry tensoru jsou dále automaticky vycpané s 1 rozměry. Například je možné přidat vektor sloupce dimenze [M][M x N] matice. V tomto případě se rozměry vektoru sloupce automaticky vyřadí tak, aby [M x 1] odpovídaly pořadí matice.
Poznámka pro uživatele Pythonu: Na rozdíl od numpy jsou dimenze vysílání zarovnané doleva.
Operátor maticového produktu*
Operace A * B označuje maticový produkt. Lze ji také použít u řídkých matic, což zlepšuje efektivitu zpracování textových vstupů nebo popisků, které jsou reprezentovány jako 1-horké vektory. V CNTK má maticový produkt rozšířenou interpretaci, která umožňuje použití s tensory pořadí > 2. Je například možné násobit každý sloupec v matici 3 tensoru pořadí-3 jednotlivě.
Maticový produkt a jeho rozšíření tensoru jsou podrobně popsány zde.
Poznámka: Pokud chcete násobit skalárem, použijte produkt, který je moudrý..*
Uživatelům Pythonu doporučujeme, aby numpy používali * operátor pro produkt , který je moudrý , nikoli maticový produkt. * operátor CNTK odpovídá numpyoperátoru "sdot()", zatímco CNTK ekvivalentem operátoru * Pythonu pro numpy pole je .*.
Podmíněný operátor if
Podmíněné výrazy v BrainScriptu jsou výrazy, jako je operátor C++ ? . Syntaxe BrainScriptu je if COND then TVAL else EVAL, kde COND musí být okamžitý logický výraz a výsledek výrazu je, pokud COND je TVAL pravdivý, a EVAL jinak. Výraz if je užitečný pro implementaci více podobných konfigurací parametrizovaných příznakem ve stejném BrainScriptu a také pro rekurzi.
(Operátor if funguje jenom pro okamžité skalární hodnoty. K implementaci podmíněných pro odložené objekty použijte předdefinovanou funkci BS.Boolean.If(), která umožňuje vybrat hodnotu z jednoho ze dvou tensorů na základě tensoru příznaku. Má formulář If (cond, tval, eval).)
Vyvolání funkcí
BrainScript má tři druhy funkcí: integrované primitivy (s implementacemi jazyka C++), funkce knihovny (napsané v BrainScriptu) a uživatelem definované (BrainScript). Příklady předdefinovaných funkcí jsou Sigmoid() a MaxPooling(). Knihovny a uživatelem definované funkce jsou mechanicky stejné, právě uložené v různých zdrojových souborech. Všechny druhy jsou vyvolány podobně jako matematické a běžné jazyky pomocí formuláře f (arg1, arg2, ...).
Některé funkce přijímají volitelné parametry. Volitelné parametry se předávají jako pojmenované parametry, například f (arg1, arg2, option1=..., option2=...).
Funkce se dají vyvolat rekurzivně, například:
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)
Všimněte si, jak if se operátor používá k ukončení rekurze.
Vytvoření vrstvy
Funkce můžou vytvářet celé vrstvy nebo modely, které jsou objekty funkcí, které se chovají stejně jako funkce.
Podle konvence funkce, která vytvoří vrstvu s naučitelnými parametry, používá místo závorek složené závorky { }( ).
Narazíte na výrazy, jako je tato:
h = DenseLayer {1024} (v)
Tady jsou ve hře dvě vyvolání. První je DenseLayer{1024}volání funkce, které vytvoří objekt funkce, který se pak použije na data (v).
Vzhledem k tomu, že DenseLayer{} vrátí objekt funkce s naučitelnými parametry, používá { } k označení.
Záznamy a Record-Member Access
Výrazy záznamů jsou přiřazení obklopená složenými závorkami. Příklad:
{
x = 13
y = x * factorParameter
f (z) = y + z
}
Tento výraz definuje záznam se třemi členy , x, ya f, kde f je funkce.
V záznamu můžou výrazy odkazovat na jiné členy záznamů pouze podle jejich názvu, podobně jako x k výše uvedenému v přiřazení y.
Na rozdíl od mnoha jazyků lze položky záznamů deklarovat v libovolném pořadí. Např. x lze deklarovat po y. To usnadňuje definici opakujících se sítí. Každý člen záznamu je přístupný z výrazu jiného člena záznamu. To se liší od Pythonu. a podobá se F# let rec. Cyklické odkazy jsou zakázané, s výjimkou PastValue() operací a FutureValue() operací.
Když jsou záznamy vnořené (výrazy záznamů používané uvnitř jiných záznamů), členové záznamů se vyhledávají v celé hierarchii uzavřených oborů. Ve skutečnosti je každé přiřazení proměnné součástí záznamu: Vnější úroveň BrainScriptu je také implicitní záznam. V předchozím příkladu factorParameter by se muselo přiřadit jako člen záznamu ohraničujícího oboru.
Funkce přiřazené uvnitř záznamu budou zaznamenávat členy záznamů, na které odkazují. f() Například se zachytí y, který zase závisí na x a externě definovaném factorParameter. Zachytávání těchto prostředků znamená, že f() je možné předat jako lambda do externích oborů, které neobsahují factorParameter nebo k nim nemají přístup.
Mimo provoz se k členům záznamů přistupuje pomocí operátoru . . Například pokud jsme k proměnné rpřiřadili výše uvedený výraz záznamu , r.x pak by se hodnota poté zobrazila 13. Operátor . neprojde uzavřením oborů: r.factorParameter selže s chybou.
(Všimněte si, že do CNTK 1,6 místo složených závorek { ... }, záznamů použitých závorek [ ... ]. To je stále povolené, ale zastaralé.)
Pole a přístup k polím
BrainScript má jednorozměrný typ pole pro okamžité hodnoty (nesmí být zaměňován s tensory). Pole jsou indexována pomocí [index]. Multidimenzionální pole je možné emulovat jako pole polí.
Pole s alespoň 2 prvky lze deklarovat pomocí operátoru : . Například následující deklaruje 3rozměrné pole s názvem imageDims , které se pak předá ParameterTensor{} deklarování tensoru parametrů rank-3:
imageDims = (256 : 256 : 3)
inputFilter = ParameterTensor {imageDims}
Je také možné deklarovat pole, jejichž hodnoty vzájemně odkazují. V tomto případě je nutné použít poněkud více zapojenou syntaxi přiřazení pole:
arr[i:i0..i1] = f(i)
který vytváří pole s arr dolní vazbou i0 indexu a horní vazbou i1indexu , i označující proměnnou, která označuje proměnnou indexu ve výrazuf(i) inicializátoru, který zase označuje hodnotu arr[i]. Hodnoty pole se vyhodnocují líně. To umožňuje inicializačnímu výrazu pro určitý index i přístup k jiným prvkům arr[j] stejného pole, pokud neexistuje žádná cyklická závislost. Dá se například použít k deklaraci zásobníku síťových vrstev:
layers[l:1..L] =
if l == 1
then DNNLayer (x, hiddenDim, featDim)
else DNNLayer (layers[l-1], hiddenDim, hiddenDim)
Na rozdíl od rekurzivní verze, kterou jsme zavedli dříve, tato verze zachovává přístup ke každé jednotlivé vrstvě tím, že říká layers[i].
Alternativně existuje také syntaxe výrazu array[i0..i1] (i => f(i)), která je méně pohodlná, ale někdy užitečná. Výše uvedený postup by vypadal takto:
layers = array[1..L] (l =>
if l == 1
then DNNLayer (x, hiddenDim, featDim)
else DNNLayer (layers[l-1], hiddenDim, hiddenDim)
)
Poznámka: V současné době neexistuje způsob, jak deklarovat pole 0 prvků. Tento problém bude vyřešen v budoucí verzi CNTK.
Výrazy funkcí a lambda
V BrainScriptu jsou funkce hodnotami. Pojmenovanou funkci lze přiřadit k proměnné a předat ji jako argument, například:
Layer (x, m, n, f) = f (ParameterTensor {(m:n)} * x + ParameterTensor {n})
h = Layer (x, 512, 40, Sigmoid)
kde Sigmoid se předává jako funkce, která se používá uvnitř Layer(). Případně syntaxe (x => f(x)) lambda podobná C#umožňuje vytvářet anonymní funkce vložené. To například definuje síťovou vrstvu s aktivací Softplus:
h = Layer (x, 512, 40, (x => Log (Constant(1) + Exp (x)))
Syntaxe lambda je v současné době omezená na funkce s jedním parametrem.
Vzor vrstvy
Výše uvedený Layer() příklad kombinuje vytváření parametrů a aplikaci funkcí.
Upřednostňovaným vzorem je rozdělit tyto kroky do dvou kroků:
- vytvoření parametrů a vrácení objektu funkce, který obsahuje tyto parametry
- vytvoření funkce, která použije parametry na vstup
Konkrétně je druhým členem objektu funkce. Výše uvedený příklad lze přepsat takto:
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
a vyvolá se takto:
h = Layer {512, 40, Sigmoid} (x)
Důvodem tohoto vzoru je, že typické síťové typy se skládají z použití jedné funkce za jinou na vstup, který lze snadněji zapsat pomocí Sequential() funkce.
CNTK obsahuje bohatou sadu předdefinovaných vrstev, které jsou zde popsané.
Vytváření předdefinovaných objektů CNTK C++
Všechny hodnoty BrainScriptu jsou nakonec objekty C++. Speciální operátor new BrainScriptu se používá pro propojení s podkladovými objekty CNTK C++. Má formulář new TYPE ARGRECORD , kde TYPE je jednou z pevně zakódované sady předdefinovaných objektů C++ vystavených v BrainScriptu a ARGRECORD je výraz záznamu, který se předá konstruktoru C++.
Pravděpodobně se k tomuto formuláři dostanete pouze v případě, že používáte závorku , BrainScriptNetworkBuildertj. BrainScriptNetworkBuilder = (new ComputationNetwork { ... }), jak je popsáno zde.
Teď ale víte, co to znamená: new ComputationNetwork vytvoří nový objekt C++ typu ComputationNetwork, kde { ... } jednoduše definuje záznam, který je předán do konstruktoru C++ interního ComputationNetwork objektu C++, který pak bude hledat 5 konkrétních členů featureNodes, , labelNodes, criterionNodesevaluationNodesa outputNodes, jak je vysvětleno zde.
Všechny předdefinované funkce jsou ve skutečnosti new výrazy, které vytvářejí objekty CNTK třídy ComputationNodeC++ . Obrázek najdete v tom, jak Tanh() je integrovaný objekt ve skutečnosti definován jako vytvoření objektu C++:
Tanh (z, tag=') = new ComputationNode { operation = 'Tanh' ; vstupy = z /plus funkce args/ }
Sémantika vyhodnocení výrazu
Při prvním použití se vyhodnocují výrazy BrainScriptu. Vzhledem k tomu, že primárním účelem BrainScriptu je popsat síť, je hodnota výrazu často uzlem v grafu výpočtů pro odložené výpočty. Například z úhlu BrainScriptu se W1 * r + b1 v příkladu výše "vyhodnocuje" na ComputationNode objekt místo číselné hodnoty; zatímco skutečné číselné hodnoty, které se týkají, se vypočítá pomocí modulu pro spouštění grafu. V době parsování BrainScriptu jsou "vypočítané" pouze výrazy BrainScriptu skalárních (např 28*28. )). Výrazy, které se nikdy nepoužívají (např. kvůli podmínce), se nikdy nevyhodnocují (ani nezkontrolují chyby typu).
Běžné vzory použití výrazů
Níže jsou uvedeny některé běžné vzory používané s BrainScriptem.
Obory názvů pro funkce
Když seskupíte přiřazení funkcí do záznamů, můžete dosáhnout formy názvových mezer. Příklad:
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))
Místně vymezené proměnné
Někdy je žádoucí mít místně vymezené proměnné a/nebo funkce pro složitější výrazy. Toho lze dosáhnout uzavřením celého výrazu do záznamu a okamžitým přístupem k jeho výsledné hodnotě. Příklad:
{ x = 13 ; y = x * x }.y
vytvoří dočasný záznam s členem y , který se okamžitě přečte. Tento záznam je dočasný, protože není přiřazen k proměnné, a proto její členy nejsou přístupné s výjimkou y.
Tento model se často používá k vytváření vrstev sítě NN s integrovanými parametry, které jsou čitelnější, například:
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 Tady si můžete představit návratové hodnoty této funkce.
Další: Informace o definování funkcí BrainScriptu
NDLNetworkBuilder (zastaralé)
Dřívější verze CNTK používaly NDLNetworkBuilder nyní zastaralé místo BrainScriptNetworkBuilder. NDLNetworkBuilder implementovala mnohem sníženou verzi BrainScriptu. Měla následující omezení:
- Žádná syntaxe infixu. Všechny operátory musí být vyvolány prostřednictvím volání funkce. Např.
Plus (Times (W1, r), b1)místoW1 * r + b1. - Žádné vnořené výrazy záznamů. Existuje pouze jeden implicitní vnější záznam.
- Žádný podmíněný výraz ani rekurzivní vyvolání funkce
- Uživatelem definované funkce musí být deklarovány ve speciálních
loadblocích a nelze vnořit. - Poslední přiřazení záznamu se automaticky použije jako hodnota funkce.
- Jazyková
NDLNetworkBuilderverze není Turing dokončena.
NDLNetworkBuilder už by se nemělo používat.