Udostępnij za pośrednictwem


Podstawowe pojęcia dotyczące języka BrainScript

BrainScript — Walk-Through

W tej sekcji przedstawiono podstawowe pojęcia dotyczące języka "BrainScript". Nowy język? Nie martw się, & przeczytać dalej, jest to bardzo proste.

W pliku CNTK sieci niestandardowe są definiowane przy użyciu języka BrainScriptNetworkBuilder i opisanego w języku opisu sieci CNTK "BrainScript". Podobnie opisy sieci są nazywane skryptami mózgu.

Język BrainScript to prosty sposób definiowania sieci w sposób podobny do kodu, przy użyciu wyrażeń, zmiennych, funkcji pierwotnych i samozdefiniowanych, zagnieżdżonych bloków i innych dobrze zrozumianych pojęć. Wygląda podobnie do języka skryptowego w składni.

OK, możemy zmoczyć nasze stopy z kompletnym przykładem BrainScript!

Kompletna definicja sieci w języku BrainScript

Poniższy przykład przedstawia opis sieci prostej sieci neuronowej z jedną ukrytą warstwą i jedną warstwą klasyfikacji. W tym przykładzie wyjaśnimy pojęcia. Zanim przejdziesz dalej, może poświęć kilka minut z przykładem i spróbuj odgadnąć, co to znaczy. Możesz znaleźć, jak czytasz dalej, że większość z tych zgadnięć poprawnie.

BrainScriptNetworkBuilder = {   # (we are inside the train section of the CNTK config file)

    SDim = 28*28 # feature dimension
    HDim = 256   # hidden dimension
    LDim = 10    # number of classes

    # define the model function. We choose to name it 'model()'.
    model (features) = {
        # model parameters
        W0 = ParameterTensor {(HDim:SDim)} ; b0 = ParameterTensor {HDim}
        W1 = ParameterTensor {(LDim:HDim)} ; b1 = ParameterTensor {LDim}

        # model formula
        r = RectifiedLinear (W0 * features + b0) # hidden layer
        z = W1 * r + b1                          # unnormalized softmax
    }.z

    # define inputs
    features = Input {SDim}
    labels   = Input {LDim} 

    # apply model to features
    z = model (features)

    # define criteria and output(s)
    ce   = CrossEntropyWithSoftmax (labels, z)  # criterion (loss)
    errs = ErrorPrediction         (labels, z)  # additional metric
    P    = Softmax (z)     # actual model usage uses this

    # connect to the system. These five variables must be named exactly like this.
    featureNodes    = (features)
    inputNodes      = (labels)
    criterionNodes  = (ce)
    evaluationNodes = (errs)
    outputNodes     = (P)
}

Podstawy składni języka BrainScript

Zanim przejdziemy od razu, kilka ogólnych uwag dotyczących składni języka BrainScript.

Język BrainScript używa prostej składni, która ma na celu umożliwienie wyrażania sieci neuronowych w sposób, który wygląda jak formuły matematyczne. W związku z tym podstawową jednostką składniową jest przypisanie, które jest używane zarówno w przypisaniach zmiennych, jak i definicjach funkcji. Na przykład:

Softplus (x) = Log (1 + Exp (x))
h = Softplus (W * v + b)

Wiersze, komentarze, dołączanie

Chociaż przypisanie jest zwykle zapisywane w jednym wierszu, wyrażenia mogą obejmować wiele wierszy. Aby umieścić wiele przypisań w jednym wierszu, należy je jednak oddzielić średnikami. Na przykład:

SDim = 28*28 ; HDim = 256 ; LDim = 10    # feature, hidden, and label dimension

Poza wymaganiem średnika między przypisaniami w przypadku braku podziału wiersza język BrainScript nie uwzględnia odstępu.

Język BrainScript rozumie komentarze na końcu wiersza przy użyciu stylu # języka Python i języka //C++. Komentarze wbudowane używają składni języka C (/* this is a comment*/), ale w przeciwieństwie do języka C, mogą nie obejmować wielu wierszy.

W przypadku języka BrainScript osadzonego w plikach konfiguracji CNTK (w przeciwieństwie do języka BrainScript odczytanego z oddzielnego pliku za pomocą include dyrektywy), ze względu na interakcję z analizatorem konfiguracji istnieje (nieco dziwne) dodatkowe ograniczenie, że wszelkie nawiasy, nawiasy klamrowe lub nawiasy muszą być zrównoważone wewnątrz komentarzy stylu języka C/C++ i literałów ciągu. W związku z tym nie ma uśmiechnięć w komentarzach w stylu C/C++!

include "PATH" Dyrektywa może być używana w dowolnym miejscu do wstawiania zawartości pliku w punkcie instrukcji . PATH W tym miejscu może być ścieżką bezwzględną lub względną względną (z podkatalogami lub bez). Jeśli jest to ścieżka względna, następujące lokalizacje są wyszukiwane w następującej kolejności: bieżący katalog roboczy; katalogi zawierające zewnętrzne, w tym pliki, jeśli istnieją; katalogi zawierające pliki konfiguracji; i na koniec katalog zawierający plik wykonywalny CNTK. Wszystkie wbudowane funkcje BrainScript są dołączane w ten sposób z pliku o nazwie CNTK.core.bs , który znajduje się obok pliku wykonywalnego CNTK.

Wyrażenia

Następnie musisz znać wyrażenia BrainScript — formuły opisujące sieć. Wyrażenia BrainScript są pisane w sposób matematyczny w składni podobnej do popularnych języków programowania. Najprostsze wyrażenia to literały, np. liczby i ciągi. Przykład matematyczny to W1 * r + b, gdzie * odwołuje się do produktu skalarnego, macierzowego lub tensorowego w zależności od typu zmiennych. Innym typowym rodzajem wyrażenia jest wywołanie funkcji, np. RectifiedLinear (.).

BrainScript to język typizowane dynamicznie. Ważnym typem wyrażenia jest rekord zdefiniowany przy użyciu {...} składni i uzyskiwany dostęp za pośrednictwem składni kropki. Na przykład r = { x = 13 ; y = 42 } przypisuje rekord z dwoma członkami do relementu , do którego można uzyskać dostęp do pierwszego elementu członkowskiego jako r.x.

Oprócz zwykłych operatorów matematycznych język BrainScript ma wyrażenie warunkowe (if c then t else f), wyrażenie tablicy i proste wyrażenia lambda. Na koniec, aby połączyć się z kodem C++ CNTK, BrainScript może bezpośrednio utworzyć wystąpienie ograniczonego zestawu wstępnie zdefiniowanych obiektów języka C++, głównie ComputationNode tych, z których składają się sieci obliczeniowe. Aby uzyskać więcej informacji, zobacz Wyrażenia.

Wyrażenia BrainScript są oceniane przy pierwszym użyciu. Głównym celem języka BrainScript jest opisanie sieci, więc wartość wyrażenia często nie jest wartością końcową, ale raczej węzłem na grafie obliczeniowym na potrzeby odroczonych obliczeń (jak w pliku W1 * r + b). Tylko wyrażenia brainscript skalarnych (np. 28*28) są "obliczane" w czasie analizowania języka BrainScript. Wyrażenia, które nigdy nie są używane (np. ze względu na warunek), nigdy nie są obliczane.

Uwaga: teraz przestarzała NDLNetworkBuilder wersja obsługiwała tylko składnię wywołania funkcji, np. trzeba by napisać polecenie Plus (Times (W1, r), b1).

Zmienne

Zmienne mogą przechowywać wartość dowolnego wyrażenia BrainScript (liczba, ciąg, rekord, tablica, lambda, obiekt C++ CNTK) i są zastępowane w wyrażeniu. Zmienne są niezmienne, tj. przypisane tylko raz. Na przykład powyższa definicja sieci zaczyna się od:

SDim = 28*28  
HDim = 256
LDim = 10

W tym miejscu zmienne są ustawione na wartości liczbowe skalarne, które są używane jako parametry w kolejnych wyrażeniach. Te wartości to wymiary próbek danych, ukryte warstwy i etykiety używane podczas trenowania. Ten konkretny przykład konfiguracji dotyczy zestawu danych MNIST, który jest kolekcją obrazów [28 x 28]-pixel. Każdy obraz jest cyfrą odręczną (0–9), więc istnieje 10 możliwych etykiet, które można zastosować do każdego obrazu. Ukryty wymiar HDim aktywacji to wybór użytkownika.

Większość zmiennych to elementy członkowskie rekordów (zewnętrzny blok BrainScript jest niejawnie rekordem). Ponadto zmienne mogą być argumentami funkcji lub przechowywane jako elementy tablic.

Ok, możesz przejść przez definicję modelu.

Definiowanie sieci

Sieć jest opisywana głównie przez formuły sposobu obliczania danych wyjściowych sieci z danych wejściowych. Nazywamy to funkcją modelu, która jest często definiowana jako rzeczywista funkcja w języku BrainScript. W ramach funkcji modelu użytkownik musi zadeklarować parametry modelu. Na koniec należy zdefiniować dane wejściowe sieci i kryteria/dane wyjściowe. Wszystkie te wartości są definiowane jako zmienne. Następnie należy przekazać zmienne wejściowe i kryteria/wyjściowe do systemu.

Funkcja modelu i parametry modelu sieci

Funkcja modelu zawiera rzeczywiste formuły sieciowe i odpowiednie parametry modelu. W naszym przykładzie użyto produktu macierzy i dodania oraz funkcji "pierwotnej" (wbudowanej) dla funkcji RectifiedLinear()energetycznej , więc rdzeń funkcji sieciowej składa się z następujących równań:

r = RectifiedLinear (W0 * features + b0)
z = W1 * r + b1 

Parametry modelu to macierze, wektory stronniczości lub inne tensor, które stanowią poznany model po zakończeniu trenowania. Tensory parametrów modelu są używane w przekształcaniu danych przykładowych danych wejściowych w żądane dane wyjściowe i są aktualizowane przez proces uczenia. Przykładowa sieć powyżej zawiera następujące parametry macierzy:

W0 = ParameterTensor {(HDim:SDim)}
b0 = ParameterTensor {(HDim)}

W tym przypadku W0 jest to macierz wagi i b0 jest wektorem stronniczości. ParameterTensor{} określa specjalny element pierwotny CNTK, który tworzy wystąpienie wektora, macierzy lub dowolny tensor rangi i przyjmuje parametry wymiaru jako tablicę BrainScript (liczby łączone dwukropkiem :). Wymiar wektora jest jedną liczbą, a wymiar macierzy powinien być określony jako (numRows:numCols). Domyślnie parametry są inicjowane przy użyciu jednolitych liczb losowych bezpośrednio, a heNormal w przypadku użycia za pośrednictwem warstw, ale istnieją inne opcje (zobacz tutaj), aby uzyskać pełną listę. W przeciwieństwie do zwykłych funkcji, ParameterTensor{} przyjmuje argumenty w nawiasach klamrowych zamiast nawiasów. Nawiasy klamrowe są konwencją języka BrainScript dla funkcji tworzących parametry lub obiekty, w przeciwieństwie do funkcji.

To wszystko jest następnie opakowane w funkcję BrainScript. Funkcje BrainScript są deklarowane w postaci f(x) = an expression of x. Na przykład Sqr (x) = x * x jest prawidłową deklaracją funkcji BrainScript. Nie można być znacznie prostszy i bezpośredni, prawda?

Teraz rzeczywista funkcja modelu w powyższym przykładzie jest nieco bardziej złożona:

model (features) = {
    # model parameters
    W0 = ParameterTensor {(HDim:SDim)} ; b0 = ParameterTensor {HDim}  
    W1 = ParameterTensor {(LDim:HDim)} ; b1 = ParameterTensor {LDim}

    # model formula
    r = RectifiedLinear (W0 * features + b0) # hidden layer
    z = W1 * r + b1                          # unnormalized softmax
}.z

Zewnętrzny { ... } i ten finał .z zasługuje na pewne wyjaśnienie. Zewnętrzne curlies { ... } i ich zawartość faktycznie definiują rekord z 6 elementami członkowskimi rekordu (W0, b0, W1, b1, ri z). Jednak wartość funkcji modelu jest tylko zwartością ; wszystkie inne są wewnętrzne dla funkcji. W związku z tym używamy .z polecenia , aby wybrać członka rekordu, który chcemy wrócić. Jest to tylko składnia kropki na potrzeby uzyskiwania dostępu do elementów członkowskich rekordu. Dzięki temu inni członkowie rekordu nie są dostępni spoza niego. Jednak nadal istnieją w ramach wyrażenia, aby obliczyć zwartość . Wzorzec { ... ; x = ... }.x jest sposobem używania zmiennych lokalnych.

Należy pamiętać, że składnia rekordów nie jest konieczna. Alternatywnie model(features) można było również zadeklarować bez objazdu za pośrednictwem rekordu, jako pojedyncze wyrażenie:

model (features) = ParameterTensor {(LDim:HDim)} * (RectifiedLinear (ParameterTensor {(HDim:SDim)}
                   * features + ParameterTensor {HDim})) + ParameterTensor {LDim}

Jest to znacznie trudniejsze do odczytania i, co ważniejsze, nie pozwoli używać tego samego parametru w wielu miejscach w formule.

Dane wejściowe

Dane wejściowe do sieci są definiowane przez przykładowe dane i etykiety skojarzone z przykładami:

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

Input{} jest drugim specjalnym elementem pierwotnym CNTK wymaganym do definicji modelu (pierwsza jest Parameter{}). Tworzy zmienną, która odbiera dane wejściowe spoza sieci: od czytnika. Argumentem Input{} jest wymiar danych. W tym przykładzie features dane wejściowe będą miały wymiary przykładowych danych (które zdefiniowaliśmy w zmiennej SDim), a labels dane wejściowe będą miały wymiary etykiet. Nazwy zmiennych danych wejściowych powinny być zgodne z odpowiednimi wpisami w definicji czytnika.

Kryteria trenowania i dane wyjściowe sieci

Nadal musimy zadeklarować, jak dane wyjściowe sieci współdziałają ze światem. Nasza funkcja modelu oblicza wartości logit (prawdopodobieństwa dziennika nienormalizowanego). Te wartości logit mogą służyć do

  • definiowanie kryterium trenowania,
  • dokładność pomiarów i
  • oblicz prawdopodobieństwo dla klas wyjściowych przy użyciu danych wejściowych, aby oprzeć decyzję o klasyfikacji (należy pamiętać, że nieznormalizowany posterior z dziennika może być często używany do klasyfikacji bezpośrednio).

Przykładowa sieć używa etykiet kategorii, które są reprezentowane jako wektory z gorącą gorącą wartością. W przykładzie MNIST będą one wyświetlane jako tablica 10 wartości zmiennoprzecinkowych, z których wszystkie są zerowe, z wyjątkiem odpowiedniej kategorii etykiety, która wynosi 1,0. Zadania klasyfikacji, takie jak nasza, często używają SoftMax() funkcji do uzyskiwania prawdopodobieństwa dla każdej etykiety. Sieć jest następnie zoptymalizowana w celu zmaksymalizowania prawdopodobieństwa dziennika poprawnej klasy (cross-entropy) i zminimalizowania tego, że wszystkie inne klasy. Jest to nasze kryterium trenowania lub funkcja utraty. W CNTK te dwie akcje są zwykle łączone w jednej funkcji w celu zwiększenia wydajności:

ce = CrossEntropyWithSoftmax (labels, z)

CrossEntropyWithSoftmax() funkcja pobiera dane wejściowe, oblicza SoftMax() funkcję, oblicza błąd z rzeczywistej wartości przy użyciu entropii krzyżowej, a sygnał błędu jest używany do aktualizowania parametrów w sieci za pośrednictwem propagacji wstecznej. W związku z tym w powyższym przykładzie znormalizowana Softmax() wartość obliczona jako P, nie jest używana podczas trenowania. Będzie jednak potrzebne do korzystania z sieci (ponownie należy pamiętać, z że w wielu przypadkach jest to często wystarczające do klasyfikacji; w takim przypadku z sama w sobie byłaby to wartość wyjściowa).

CNTK używa stochastycznego spadku gradientu (SGD) jako algorytmu uczenia. SgD musi obliczyć gradient funkcji celu w odniesieniu do wszystkich parametrów modelu. Co ważne, CNTK nie wymaga od użytkowników określenia tych gradientów. Zamiast tego każda wbudowana funkcja w CNTK ma również pochodną funkcję odpowiednika, a system automatycznie wykonuje aktualizację propagacji wstecznej parametrów sieciowych. Nie jest to widoczne dla użytkownika. Użytkownicy nigdy nie muszą zajmować się gradientami. Nigdy.

Oprócz kryterium trenowania przewidywane współczynniki błędów są często obliczane w fazie trenowania w celu zweryfikowania poprawy systemu w miarę dalszego trenowania. Jest to obsługiwane w cnTK przy użyciu następującej funkcji:

errs = ClassificationError (labels, z)

Prawdopodobieństwa generowane przez sieć są porównywane z rzeczywistą etykietą, a współczynnik błędów jest obliczany. Jest to zazwyczaj wyświetlane przez system. Chociaż jest to przydatne, nie jest to obowiązkowe do użycia .ClassificationError()

Komunikacja danych wejściowych, danych wyjściowych i kryteriów z systemem

Teraz, gdy wszystkie zmienne są zdefiniowane, musimy poinformować system, które zmienne powinny traktować jako dane wejściowe, wyjściowe i kryteria. W tym celu należy zdefiniować 5 specjalnych zmiennych, które muszą mieć dokładnie następujące nazwy:

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

Wartości to tablice, w których wartości powinny być rozdzielone dwukropkiem (dwukropek : jest operatorem BrainScript, który tworzy tablicę przez łączenie dwóch wartości lub tablic). Jest to pokazane powyżej dla outputNodeselementu , który deklaruje zarówno z dane wyjściowe, jak i P jako dane wyjściowe.

(Uwaga: przestarzałe NDLNetworkBuilder elementy tablicy muszą być rozdzielone przecinkami.

Podsumowanie nazw specjalnych

Jak widzieliśmy powyżej, istnieje 7 specjalnych nazw, o których musimy wiedzieć, które noszą właściwości "magii":

  • ParameterTensor{}: deklaruje i inicjuje parametr learnable.
  • Input{}: deklaruje zmienną, która jest połączona i pobierana z czytnika danych.
  • featureNodes, , labelNodescriterionNodesevaluationNodesi outputNodes: deklaruje systemowi, które z naszych zmiennych mają być używane jako dane wejściowe, dane wyjściowe i kryteria.

Ponadto istnieje 3 więcej specjalnych funkcji z wbudowanym "magią" w CNTK, które są omawiane gdzie indziej:

  • Constant(): deklaruje stałą.
  • PastValue() i FutureValue(): Uzyskiwanie dostępu do zmiennej w funkcji sieciowej w innym kroku czasu dla cyklicznych pętli formularzy.

Każda inna wstępnie zdefiniowana nazwa jest wbudowaną funkcją pierwotną, taką jak Sigmoid() lub Convolution() z implementacją języka C++, wstępnie zdefiniowaną funkcją biblioteki zrealizowaną w języku BrainScript, taką jak BS.RNNs.LSTMP(), lub rekordem, który działa jako przestrzeń nazw funkcji biblioteki (np. BS.RNNs). Aby uzyskać pełną listę, zobacz BrainScript-Full-Function-Reference (Pełne odwołanie funkcji w języku BrainScript ).

Dalej: Wyrażenia BrainScript