Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Houd er rekening mee dat voor deze zelfstudie de nieuwste hoofdversie of de aanstaande CNTK 1.7.1 is vereist. Deze wordt binnenkort uitgebracht.
In dit praktijklab ziet u hoe u een terugkerend netwerk implementeert om tekst te verwerken, voor de ATIS-taken (Air Travel Information Services) van sitetags en intentieclassificatie. We beginnen met een rechte insluiting, gevolgd door een terugkerende LSTM. We breiden het vervolgens uit om aangrenzende woorden op te nemen en bidirectioneel uit te voeren. Ten slotte wordt dit systeem omgezet in een intentieclassificatie.
De technieken die u gaat oefenen zijn:
- modelbeschrijving door laagblokken op te stellen in plaats van formules te schrijven
- uw eigen laagblok maken
- variabelen met verschillende reekslengten in hetzelfde netwerk
- parallelle training
We gaan ervan uit dat u bekend bent met de basisprincipes van Deep Learning en deze specifieke concepten:
- terugkerende netwerken (Wikipedia-pagina)
- tekst insluiten (Wikipedia-pagina)
Vereisten
We gaan ervan uit dat u CNTK al hebt geïnstalleerd en dat u de CNTK-opdracht kunt uitvoeren. Deze zelfstudie is gehouden op KDD 2016 en vereist een recente build. Zie hier voor installatie-instructies. U kunt gewoon de instructies volgen voor het downloaden van een binair installatiepakket vanaf die pagina.
Download vervolgens een ZIP-archief (ongeveer 12 MB): Klik op deze koppeling en klik vervolgens op de knop Downloaden.
Het archief bevat de bestanden voor deze zelfstudie. Archief en stel uw werkmap in op SLUHandsOn.
De bestanden waarmee u werkt, zijn:
SLUHandsOn.cntk: Het CNTK-configuratiebestand dat we hieronder introduceren en ermee werken.slu.forward.nobn.cmf,slu.forward.cmf,slu.forward.lookahead.cmfenslu.forward.backward.cmf: Vooraf getrainde modellen die het resultaat zijn van de respectieve configuraties die we in deze zelfstudie ontwikkelen.atis.train.ctfenatis.test.ctf: Het trainings- en test-corpus, dat al is geconverteerd naar CNTK Text Format (CTF).
Ten slotte raden we u ten zeerste aan dit uit te voeren op een computer met een compatibele CUDA-compatibele GPU. Deep learning zonder GPU's is niet leuk.
Taak- en modelstructuur
De taak die we in deze zelfstudie willen benaderen, is sitetags. We gebruiken het ATIS-corpus. ATIS bevat query's van mensencomputers uit het domein van Air Travel Information Services en onze taak is om elk woord van een query te voorzien van aantekeningen (taggen) of deze deel uitmaakt van een specifiek informatieitem (slot) en welke.
De gegevens in uw werkmap zijn al geconverteerd naar de 'CNTK Text Format'. Laten we eens kijken naar een voorbeeld uit het testsetbestand atis.test.ctf:
19 |S0 178:1 |# BOS |S1 14:1 |# flight |S2 128:1 |# O
19 |S0 770:1 |# show |S2 128:1 |# O
19 |S0 429:1 |# flights |S2 128:1 |# O
19 |S0 444:1 |# from |S2 128:1 |# O
19 |S0 272:1 |# burbank |S2 48:1 |# B-fromloc.city_name
19 |S0 851:1 |# to |S2 128:1 |# O
19 |S0 789:1 |# st. |S2 78:1 |# B-toloc.city_name
19 |S0 564:1 |# louis |S2 125:1 |# I-toloc.city_name
19 |S0 654:1 |# on |S2 128:1 |# O
19 |S0 601:1 |# monday |S2 26:1 |# B-depart_date.day_name
19 |S0 179:1 |# EOS |S2 128:1 |# O
Dit bestand heeft 7 kolommen:
- een reeks-id (19). Er zijn 11 vermeldingen met deze reeks-id. Dit betekent dat sequentie 19 bestaat uit 11 tokens;
- kolom
S0, die numerieke woordindexen bevat; - een opmerkingkolom aangeduid door
#, zodat een menselijke lezer weet waar de numerieke woordindex voor staat; Opmerkingkolommen worden genegeerd door het systeem.BOSenEOSspeciale woorden zijn om respectievelijk het begin en einde van de zin aan te geven; - kolom
S1is een intentielabel, dat we alleen gebruiken in het laatste deel van de zelfstudie; - een andere kolom met opmerkingen waarin het leesbare label van de numerieke intentieindex wordt weergegeven;
- kolom
S2is het sitelabel, weergegeven als een numerieke index; en - een andere kolom met opmerkingen waarin het leesbare label van de numerieke labelindex wordt weergegeven.
De taak van het neurale netwerk is om de query (kolom S0) te bekijken en het slotlabel (kolom S2) te voorspellen.
Zoals u kunt zien, krijgt elk woord in de invoer een leeg label O of een slotlabel dat begint met B- voor het eerste woord, en met I- elk extra opeenvolgend woord dat deel uitmaakt van dezelfde site.
Het model dat we gaan gebruiken, is een terugkerend model dat bestaat uit een insluitlaag, een terugkerende LSTM-cel en een dichte laag om de posterior waarschijnlijkheden te berekenen:
slot label "O" "O" "O" "O" "B-fromloc.city_name"
^ ^ ^ ^ ^
| | | | |
+-------+ +-------+ +-------+ +-------+ +-------+
| Dense | | Dense | | Dense | | Dense | | Dense | ...
+-------+ +-------+ +-------+ +-------+ +-------+
^ ^ ^ ^ ^
| | | | |
+------+ +------+ +------+ +------+ +------+
0 -->| LSTM |-->| LSTM |-->| LSTM |-->| LSTM |-->| LSTM |-->...
+------+ +------+ +------+ +------+ +------+
^ ^ ^ ^ ^
| | | | |
+-------+ +-------+ +-------+ +-------+ +-------+
| Embed | | Embed | | Embed | | Embed | | Embed | ...
+-------+ +-------+ +-------+ +-------+ +-------+
^ ^ ^ ^ ^
| | | | |
w ------>+--------->+--------->+--------->+--------->+------...
BOS "show" "flights" "from" "burbank"
Of als cntk-netwerkbeschrijving. Zorg dat u een kort overzicht hebt en deze kunt vergelijken met de bovenstaande beschrijving:
model = Sequential (
EmbeddingLayer {150} :
RecurrentLSTMLayer {300} :
DenseLayer {labelDim}
)
Beschrijvingen van deze functies vindt u op: Sequential(), EmbeddingLayer{}, RecurrentLSTMLayer{}, en DenseLayer{}
CNTK-configuratie
Configuratiebestand
Als u een model wilt trainen en testen in CNTK, moet u een configuratiebestand opgeven waarmee CNTK wordt aangegeven welke bewerkingen u wilt uitvoeren (command variabele) en een parametersectie voor elke opdracht.
Voor de trainingsopdracht moet CNTK worden verteld:
- de gegevens lezen (
readersectie) - de modelfunctie en de bijbehorende invoer en uitvoer in de rekengrafiek (
BrainScriptNetworkBuildersectie) - hyperparameters voor de cursist (
SGDsectie)
Voor de evaluatieopdracht moet CNTK weten hoe de testgegevens (reader sectie) moeten worden gelezen.
Hier volgt het configuratiebestand waarmee we beginnen. Zoals u ziet, is een CNTK-configuratiebestand een tekstbestand dat bestaat uit definities van parameters, die zijn ingedeeld in een hiërarchie van records. U kunt ook zien hoe CNTK basisparametervervanging ondersteunt met behulp van de $parameterName$ syntaxis. Het daadwerkelijke bestand bevat slechts enkele parameters die hierboven zijn vermeld, maar scan het en zoek de configuratie-items die zojuist zijn vermeld:
# CNTK Configuration File for creating a slot tagger and an intent tagger.
command = TrainTagger:TestTagger
makeMode = false ; traceLevel = 0 ; deviceId = "auto"
rootDir = "." ; dataDir = "$rootDir$" ; modelDir = "$rootDir$/Models"
modelPath = "$modelDir$/slu.cmf"
vocabSize = 943 ; numLabels = 129 ; numIntents = 26 # number of words in vocab, slot labels, and intent labels
# The command to train the LSTM model
TrainTagger = {
action = "train"
BrainScriptNetworkBuilder = {
inputDim = $vocabSize$
labelDim = $numLabels$
embDim = 150
hiddenDim = 300
model = Sequential (
EmbeddingLayer {embDim} : # embedding
RecurrentLSTMLayer {hiddenDim, goBackwards=false} : # LSTM
DenseLayer {labelDim} # output layer
)
# features
query = Input {inputDim}
slotLabels = Input {labelDim}
# model application
z = model (query)
# loss and metric
ce = CrossEntropyWithSoftmax (slotLabels, z)
errs = ClassificationError (slotLabels, z)
featureNodes = (query)
labelNodes = (slotLabels)
criterionNodes = (ce)
evaluationNodes = (errs)
outputNodes = (z)
}
SGD = {
maxEpochs = 8 ; epochSize = 36000
minibatchSize = 70
learningRatesPerSample = 0.003*2:0.0015*12:0.0003
gradUpdateType = "fsAdaGrad"
gradientClippingWithTruncation = true ; clippingThresholdPerSample = 15.0
firstMBsToShowResult = 10 ; numMBsToShowResult = 100
}
reader = {
readerType = "CNTKTextFormatReader"
file = "$DataDir$/atis.train.ctf"
randomize = true
input = {
query = { alias = "S0" ; dim = $vocabSize$ ; format = "sparse" }
intentLabels = { alias = "S1" ; dim = $numIntents$ ; format = "sparse" }
slotLabels = { alias = "S2" ; dim = $numLabels$ ; format = "sparse" }
}
}
}
# Test the model's accuracy (as an error count)
TestTagger = {
action = "eval"
modelPath = $modelPath$
reader = {
readerType = "CNTKTextFormatReader"
file = "$DataDir$/atis.test.ctf"
randomize = false
input = {
query = { alias = "S0" ; dim = $vocabSize$ ; format = "sparse" }
intentLabels = { alias = "S1" ; dim = $numIntents$ ; format = "sparse" }
slotLabels = { alias = "S2" ; dim = $numLabels$ ; format = "sparse" }
}
}
}
Een kort overzicht van gegevens en gegevens lezen
We hebben de gegevens al bekeken.
Maar hoe genereert u deze indeling?
Voor het lezen van tekst gebruikt deze zelfstudie de CNTKTextFormatReader. Er wordt verwacht dat de invoergegevens een specifieke indeling hebben, die hier wordt beschreven.
Voor deze zelfstudie hebben we de corpora met twee stappen gemaakt:
converteer de onbewerkte gegevens naar een bestand met tekst zonder opmaak dat door tabs gescheiden kolommen met door spaties gescheiden tekst bevat. Bijvoorbeeld:
BOS show flights from burbank to st. louis on monday EOS (TAB) flight (TAB) O O O O B-fromloc.city_name O B-toloc.city_name I-toloc.city_name O B-depart_date.day_name ODit is bedoeld om compatibel te zijn met de uitvoer van de
pasteopdracht.converteer deze naar CNTK Text Format (CTF) met de volgende opdracht:
python Scripts/txt2ctf.py --map query.wl intent.wl slots.wl --annotated True --input atis.test.txt --output atis.test.ctfwaarbij de drie
.wlbestanden het vocabulaire als tekstbestanden zonder opmaak geven, één regel per woord.
In deze CTFG-bestanden worden onze kolommen gelabeld S0, S1en S2.
Deze zijn verbonden met de werkelijke netwerkinvoer door de bijbehorende regels in de definitie van de lezer:
input = {
query = { alias = "S0" ; dim = $vocabSize$ ; format = "sparse" }
intentLabels = { alias = "S1" ; dim = $numIntents$ ; format = "sparse" }
slotLabels = { alias = "S2" ; dim = $numLabels$ ; format = "sparse" }
}
Uitvoeren
U vindt het bovenstaande configuratiebestand onder de naam SLUHandsOn.cntk in de werkmap.
Als u deze wilt uitvoeren, voert u de bovenstaande configuratie uit met deze opdracht:
cntk configFile=SLUHandsOn.cntk
Hiermee wordt onze configuratie uitgevoerd, te beginnen met modeltraining zoals gedefinieerd in de sectie met de naam TrainTagger.
Na een enigszins chattende initiële logboekuitvoer ziet u dit binnenkort:
Training 721479 parameters in 6 parameter tensors.
gevolgd door uitvoer als volgt:
Finished Epoch[ 1 of 8]: [Training] ce = 0.77274927 * 36007; errs = 15.344% * 36007
Finished Epoch[ 2 of 8]: [Training] ce = 0.27009664 * 36001; errs = 5.883% * 36001
Finished Epoch[ 3 of 8]: [Training] ce = 0.16390425 * 36005; errs = 3.688% * 36005
Finished Epoch[ 4 of 8]: [Training] ce = 0.13121604 * 35997; errs = 2.761% * 35997
Finished Epoch[ 5 of 8]: [Training] ce = 0.09308497 * 36000; errs = 2.028% * 36000
Finished Epoch[ 6 of 8]: [Training] ce = 0.08537533 * 35999; errs = 1.917% * 35999
Finished Epoch[ 7 of 8]: [Training] ce = 0.07477648 * 35997; errs = 1.686% * 35997
Finished Epoch[ 8 of 8]: [Training] ce = 0.06114417 * 36018; errs = 1.380% * 36018
Dit laat zien hoe het leren verloopt over epochs (doorloopt de gegevens).
Na twee perioden is het criterium voor kruis-entropie, die we in het configuratiebestand hadden genoemd ce , bijvoorbeeld 0,27 bereikt, zoals gemeten op de 36001-steekproeven van dit tijdvak, en dat de foutpercentage 5,883% is op dezelfde 36016-trainingsvoorbeelden.
De 36001 komt van het feit dat onze configuratie de epoch-grootte definieerde als 36000. De tijdsduur is het aantal steekproeven, geteld als woordtokens, niet zinnen- om te verwerken tussen modelcontrolepunten. Omdat de zinnen verschillende lengte hebben en niet noodzakelijkerwijs tot veelvouden van precies 36000 woorden samentellen, ziet u een kleine variatie.
Zodra de training is voltooid (iets minder dan 2 minuten op een Titan-X of een Surface Book), gaat CNTK verder met de EvalTagger actie
Final Results: Minibatch[1-1]: errs = 2.922% * 10984; ce = 0.14306181 * 10984; perplexity = 1.15380111
In onze testset zijn slotlabels voorspeld met een foutpercentage van 2,9%. Helemaal niet slecht, voor zo'n eenvoudig systeem!
Op een computer met alleen CPU's kan het 4 of meer keer langzamer zijn. Om ervoor te zorgen dat het systeem vroeg vordert, kunt u tracering inschakelen om gedeeltelijke resultaten te zien, die redelijk snel moeten worden weergegeven:
cntk configFile=SLUHandsOn.cntk traceLevel=1
Epoch[ 1 of 8]-Minibatch[ 1- 1, 0.19%]: ce = 4.86535690 * 67; errs = 100.000% * 67
Epoch[ 1 of 8]-Minibatch[ 2- 2, 0.39%]: ce = 4.83886670 * 63; errs = 57.143% * 63
Epoch[ 1 of 8]-Minibatch[ 3- 3, 0.58%]: ce = 4.78657442 * 68; errs = 36.765% * 68
...
Als u niet wilt wachten totdat dit is voltooid, kunt u bijvoorbeeld een tussenliggend model uitvoeren, bijvoorbeeld
cntk configFile=SLUHandsOn.cntk command=TestTagger modelPath=Models/slu.cmf.4
Final Results: Minibatch[1-1]: errs = 3.851% * 10984; ce = 0.18932937 * 10984; perplexity = 1.20843890
of test ook ons vooraf getrainde model, dat u kunt vinden in de werkmap:
cntk configFile=SLUHandsOn.cntk command=TestTagger modelPath=slu.forward.nobn.cmf
Final Results: Minibatch[1-1]: errs = 2.922% * 10984; ce = 0.14306181 * 10984; perplexity = 1.15380111
Het model wijzigen
In het volgende krijgt u taken om te oefenen met het wijzigen van CNTK-configuraties. De oplossingen worden aan het einde van dit document gegeven... maar probeer het alsjeblieft zonder!
Een woord over Sequential()
Laten we eens kijken naar het model dat we zojuist hebben uitgevoerd voordat we naar de taken gaan. Het model wordt beschreven in wat we functie-samenstellingsstijl noemen.
model = Sequential (
EmbeddingLayer {embDim} : # embedding
RecurrentLSTMLayer {hiddenDim, goBackwards=false} : # LSTM
DenseLayer {labelDim, initValueScale=7} # output layer
)
waarbij de dubbele punt (:) de syntaxis van BrainScript is van het uitdrukken van matrices. Is bijvoorbeeld (F:G:H) een matrix met drie elementen, F, Gen H.
Mogelijk bent u bekend met de 'sequentiële' notatie van andere neurale netwerk-toolkits.
Als dat niet het geval is, Sequential() is dit een krachtige bewerking waarmee in een notenshell een zeer algemene situatie in neurale netwerken wordt weergegeven waarin een invoer wordt verwerkt door deze door een voortgang van lagen door te geven.
Sequential() gebruikt een matrix van functies als argument en retourneert een nieuwe functie die deze functie in volgorde aanroept, telkens wanneer de uitvoer van een aan de volgende wordt doorgegeven.
Bijvoorbeeld:
FGH = Sequential (F:G:H)
y = FGH (x)
betekent hetzelfde als
y = H(G(F(x)))
Dit staat bekend als 'functiesamenstelling', en is vooral handig voor het uitdrukken van neurale netwerken, die vaak deze vorm hebben:
+-------+ +-------+ +-------+
x -->| F |-->| G |-->| H |--> y
+-------+ +-------+ +-------+
Als u terugkomt op ons model, zegt de Sequential expressie gewoon dat ons model deze vorm heeft:
+-----------+ +----------------+ +------------+
x -->| Embedding |-->| Recurrent LSTM |-->| DenseLayer |--> y
+-----------+ +----------------+ +------------+
Taak 1: Batchnormalisatie toevoegen
We willen nu nieuwe lagen toevoegen aan het model, met name batchnormalisatie.
Batchnormalisatie is een populaire techniek voor het versnellen van convergentie. Het wordt vaak gebruikt voor installatiekopieën voor het verwerken van afbeeldingen, bijvoorbeeld ons andere praktijklab over beeldherkenning. Maar kan het ook werken voor terugkerende modellen?
Uw taak bestaat dus uit het invoegen van batchnormalisatielagen voor en na de terugkerende LSTM-laag. Als u de praktijklabs hebt voltooid voor het verwerken van afbeeldingen, weet u misschien dat de batchnormalisatielaag deze vorm heeft:
BatchNormalizationLayer{}
Dus ga verder en wijzig de configuratie en kijk wat er gebeurt.
Als alles goed ging, zult u niet alleen merken dat de convergentiesnelheid (ce en errs) is verbeterd ten opzichte van de vorige configuratie, maar ook een beter foutpercentage van 2,0% (vergeleken met 2,9%):
Training 722379 parameters in 10 parameter tensors.
Finished Epoch[ 1 of 8]: [Training] ce = 0.29396894 * 36007; errs = 5.621% * 36007
Finished Epoch[ 2 of 8]: [Training] ce = 0.10104186 * 36001; errs = 2.280% * 36001
Finished Epoch[ 3 of 8]: [Training] ce = 0.05012737 * 36005; errs = 1.258% * 36005
Finished Epoch[ 4 of 8]: [Training] ce = 0.04116407 * 35997; errs = 1.108% * 35997
Finished Epoch[ 5 of 8]: [Training] ce = 0.02602344 * 36000; errs = 0.756% * 36000
Finished Epoch[ 6 of 8]: [Training] ce = 0.02234042 * 35999; errs = 0.622% * 35999
Finished Epoch[ 7 of 8]: [Training] ce = 0.01931362 * 35997; errs = 0.667% * 35997
Finished Epoch[ 8 of 8]: [Training] ce = 0.01714253 * 36018; errs = 0.522% * 36018
Final Results: Minibatch[1-1]: errs = 2.039% * 10984; ce = 0.12888706 * 10984; perplexity = 1.13756164
(Als u niet wilt wachten totdat de training is voltooid, kunt u het resulterende model vinden onder de naam slu.forward.cmf.)
Raadpleeg de oplossing hier.
Taak 2: Een lookahead toevoegen
Ons terugkerende model lijdt aan een structureel tekort: Aangezien het terugkeerpatroon van links naar rechts wordt uitgevoerd, heeft de beslissing voor een slotlabel geen informatie over aanstaande woorden. Het model is een beetje aan de kant. Uw taak is om het model zodanig te wijzigen dat de invoer voor het terugkeerpatroon niet alleen uit het huidige woord bestaat, maar ook uit de volgende (lookahead).
Uw oplossing moet in functiesamenstellingsstijl staan. Daarom moet u een BrainScript-functie schrijven die het volgende doet:
- één invoerargument accepteren;
- de onmiddellijke "toekomstige waarde" van deze invoer berekenen met behulp van de
FutureValue()functie (gebruik deze specifieke vorm:FutureValue (0, input, defaultHiddenActivation=0)); en - voeg de twee samen in een vector van twee keer de insluitdimensie met behulp
Splice()van (gebruik dit formulier: Splice(x:y))
en voeg deze functie vervolgens in tussen de Sequence() insluiting en de terugkerende laag.
Als alles goed gaat, ziet u de volgende uitvoer:
Training 902679 parameters in 10 parameter tensors.
Finished Epoch[ 1 of 8]: [Training] ce = 0.30500536 * 36007; errs = 5.904% * 36007
Finished Epoch[ 2 of 8]: [Training] ce = 0.09723847 * 36001; errs = 2.167% * 36001
Finished Epoch[ 3 of 8]: [Training] ce = 0.04082365 * 36005; errs = 1.047% * 36005
Finished Epoch[ 4 of 8]: [Training] ce = 0.03219930 * 35997; errs = 0.867% * 35997
Finished Epoch[ 5 of 8]: [Training] ce = 0.01524993 * 36000; errs = 0.414% * 36000
Finished Epoch[ 6 of 8]: [Training] ce = 0.01367533 * 35999; errs = 0.383% * 35999
Finished Epoch[ 7 of 8]: [Training] ce = 0.00937027 * 35997; errs = 0.278% * 35997
Finished Epoch[ 8 of 8]: [Training] ce = 0.00584430 * 36018; errs = 0.147% * 36018
Final Results: Minibatch[1-1]: errs = 1.839% * 10984; ce = 0.12023170 * 10984; perplexity = 1.12775812
Dit werkte! Als u weet wat het volgende woord is, kan het tagprogramma de foutsnelheid van 2,0% tot 1,84% verminderen.
(Als u niet wilt wachten totdat de training is voltooid, kunt u het resulterende model vinden onder de naam slu.forward.lookahead.cmf.)
Raadpleeg de oplossing hier.
Taak 3: Bidirectioneel terugkerend model
Aha, kennis van toekomstige woorden helpen. Dus in plaats van één woord lookahead, waarom kijk dan niet vooruit tot het einde van de zin, door middel van een terugkeerpatroon achterwaarts? Laten we een bidirectioneel model maken!
Uw taak is het implementeren van een nieuwe laag die zowel een voorwaartse als een achterwaartse recursie uitvoert over de gegevens en de uitvoervectoren samenvoegt.
Houd er echter rekening mee dat dit verschilt van de vorige taak in dat de bidirectionele laag leerbare modelparameters bevat. In de functie-samenstellingsstijl is het patroon voor het implementeren van een laag met modelparameters het schrijven van een factory-functie waarmee een functieobject wordt gemaakt.
Een functieobject, ook wel functor genoemd, is een object dat zowel een functie als een object is. Dit betekent dat er niets anders is dat deze gegevens bevat, maar nog steeds kan worden aangeroepen alsof het een functie was.
Is bijvoorbeeld LinearLayer{outDim} een fabrieksfunctie die een functieobject retourneert dat een gewichtsmatrix W, een vooroordelen ben een andere functie bevat om te berekenen W * input + b.
Als u bijvoorbeeld zegt LinearLayer{1024} , wordt dit functieobject gemaakt, dat vervolgens kan worden gebruikt als elke andere functie, ook onmiddellijk: LinearLayer{1024}(x).
Verward? Laten we een voorbeeld nemen: We gaan een nieuwe laag implementeren die een lineaire laag combineert met een volgende batchnormalisatie. Om functiesamenstelling mogelijk te maken, moet de laag worden gerealiseerd als een fabrieksfunctie, die er als volgt uit kan zien:
LinearLayerWithBN {outDim} = {
F = LinearLayer {outDim}
G = BatchNormalization {normalizationTimeConstant=2048}
apply (x) = G(F(x))
}.apply
Als u deze fabrieksfunctie aanroept, maakt u eerst een record (aangegeven door {...}) met drie leden: F, Gen apply. In dit voorbeeld FG zijn functieobjecten zelf en apply moet de functie worden toegepast op de gegevens.
.apply Toevoegen aan deze expressie betekent wat .x altijd betekent in BrainScript om toegang te krijgen tot een recordlid. Als u bijvoorbeeld aanroept LinearLayerWithBN{1024} , wordt er een object gemaakt dat een functieobject met een lineaire laag bevat met de naam F, een batchnormalisatiefunctieobject Gen apply dat de functie is die de werkelijke werking van deze laag implementeert met behulp F van en G. Vervolgens wordt de waarde geretourneerd apply. Aan de buitenkant apply() ziet en gedraagt zich als een functie. Onder de motorkap houdt het apply() echter vast aan de record waartoe het behoort, en behoudt dus de toegang tot de specifieke exemplaren van F en G.
Nu terug naar onze taak bij de hand. U moet nu een fabrieksfunctie maken, net zoals in het bovenstaande voorbeeld.
U maakt een factory-functie waarmee twee terugkerende laagexemplaren (één naar voren, één achterwaarts) worden gemaakt en vervolgens een apply (x) functie wordt gedefinieerd die beide laagexemplaren op hetzelfde x toepast en de twee resultaten samenvoegt.
Oké, probeer het eens! Als u wilt weten hoe u een achterwaartse recursie in CNTK kunt realiseren, volgt u een hint van hoe de forward recursie wordt uitgevoerd. Ga ook als volgt te werk:
- verwijder het opzoekhoofd van één woord dat u in de vorige taak hebt toegevoegd, die we willen vervangen; En
- wijzig de
hiddenDimparameter van 300 in 150 om het totale aantal modelparameters beperkt te houden.
Als u dit model uitvoert, wordt de volgende uitvoer gegenereerd:
Training 542379 parameters in 13 parameter tensors.
Finished Epoch[ 1 of 8]: [Training] ce = 0.27651655 * 36007; errs = 5.288% * 36007
Finished Epoch[ 2 of 8]: [Training] ce = 0.08179804 * 36001; errs = 1.869% * 36001
Finished Epoch[ 3 of 8]: [Training] ce = 0.03528780 * 36005; errs = 0.828% * 36005
Finished Epoch[ 4 of 8]: [Training] ce = 0.02602517 * 35997; errs = 0.675% * 35997
Finished Epoch[ 5 of 8]: [Training] ce = 0.01310307 * 36000; errs = 0.386% * 36000
Finished Epoch[ 6 of 8]: [Training] ce = 0.01310714 * 35999; errs = 0.358% * 35999
Finished Epoch[ 7 of 8]: [Training] ce = 0.00900459 * 35997; errs = 0.300% * 35997
Finished Epoch[ 8 of 8]: [Training] ce = 0.00589050 * 36018; errs = 0.161% * 36018
Final Results: Minibatch[1-1]: errs = 1.830% * 10984; ce = 0.11924878 * 10984; perplexity = 1.12665017
Werkt als een charme! Dit model bereikt 1,83%, een klein beetje beter dan het bovenstaande lookaheadmodel. Het bidirectionele model heeft 40% minder parameters dan de lookahead één. Als u echter teruggaat en goed kijkt naar de volledige logboekuitvoer (niet weergegeven op deze webpagina), kan het zijn dat de opzoekkop ongeveer 30% sneller is getraind. Dit komt doordat het lookaheadmodel beide minder horizontale afhankelijkheden (één in plaats van twee terugkeerpatronen) en grotere matrixproducten heeft en dus hogere parallellisme kan bereiken.
Raadpleeg de oplossing hier.
Taak 4: Intentieclassificatie
Het blijkt dat het model dat we tot nu toe hebben gemaakt, gemakkelijk kan worden omgezet in een intentieclassificatie.
Houd er rekening mee dat ons gegevensbestand deze extra kolom bevat met de naam S1.
Deze kolom bevat één label per zin, waarmee de intentie van de query wordt aangegeven om informatie over onderwerpen zoals airport of airfarete vinden.
De taak voor het classificeren van een hele reeks in één label wordt volgordeclassificatie genoemd. Onze reeksclassificatie wordt geïmplementeerd als een terugkerende LSTM (we hebben dat al) waarvan we de verborgen status van de laatste stap nemen. Dit geeft ons één vector voor elke reeks. Deze vector wordt vervolgens ingevoerd in een dichte laag voor softmax-classificatie.
CNTK heeft een bewerking om de laatste status uit een reeks te extraheren, genaamd BS.Sequences.Last().
Deze bewerking eert het feit dat dezelfde minibatch reeksen van zeer verschillende lengten kan bevatten en dat ze in het geheugen zijn gerangschikt in een verpakte indeling.
Ook voor achterwaartse recursie kunnen we gebruiken BS.Sequences.First().
Uw taak bestaat uit het wijzigen van het bidirectionele netwerk van taak 3, zodat het laatste frame wordt geëxtraheerd uit de voorwaartse recursie en het eerste frame wordt geëxtraheerd uit de achterwaartse recursie en de twee vectoren worden samengevoegd. De samengevoegde vector (ook wel een gedachtevector genoemd) moet dan de invoer zijn van de dichte laag.
U moet ook het label wijzigen van de site in het intentielabel: Wijzig in plaats daarvan de naam van de invoervariabele (slotLabels) zodat deze overeenkomt met de naam die wordt gebruikt in de sectie Lezer voor de intentielabels en ook overeenkomen met de dimensie.
Probeer de wijziging. Als u het goed doet, wordt u echter geconfronteerd met een vexing foutbericht en een lange op dat:
EXCEPTION occurred: Dynamic axis layout '*' is shared between inputs 'intentLabels'
and 'query', but layouts generated from the input data are incompatible on this axis.
Are you using different sequence lengths? Did you consider adding a DynamicAxis()
to the Input nodes?
"Gebruikt u verschillende reekslengten?" O ja! De query en het intentielabel:het intentielabel is slechts één token per query. Het is een reeks van 1 element! Hoe kunt u dit oplossen?
MET CNTK kunnen verschillende variabelen in het netwerk verschillende reekslengten hebben. U kunt de reekslengte beschouwen als een extra symbolische tensordimensie. Variabelen van dezelfde lengte hebben dezelfde symbolische lengtedimensie. Als twee variabelen verschillende lengten hebben, moet dit expliciet worden gedeclareerd, anders gaat CNTK ervan uit dat alle variabelen dezelfde symbolische lengte hebben.
Dit wordt gedaan door een nieuw dynamisch asobject te maken en dit als volgt te koppelen aan een van de invoergegevens:
n = DynamicAxis()
query = Input {inputDim, dynamicAxis=n}
CNTK heeft een standaardas. Zoals u kunt raden uit de bovenstaande uitzondering, is de naam *.
Daarom hoeft u slechts één nieuwe as te declareren; de andere invoer (intentLabels) blijft de standaardas gebruiken.
Nu moeten we goed werken en de volgende uitvoer bekijken:
Training 511376 parameters in 13 parameter tensors.
Finished Epoch[ 1 of 8]: [Training] ce = 1.17365003 * 2702; errs = 21.318% * 2702
Finished Epoch[ 2 of 8]: [Training] ce = 0.40112341 * 2677; errs = 9.189% * 2677
Finished Epoch[ 3 of 8]: [Training] ce = 0.17041608 * 2688; errs = 4.167% * 2688
Finished Epoch[ 4 of 8]: [Training] ce = 0.09521124 * 2702; errs = 2.739% * 2702
Finished Epoch[ 5 of 8]: [Training] ce = 0.08287138 * 2697; errs = 2.262% * 2697
Finished Epoch[ 6 of 8]: [Training] ce = 0.07138554 * 2707; errs = 2.032% * 2707
Finished Epoch[ 7 of 8]: [Training] ce = 0.06220047 * 2677; errs = 1.419% * 2677
Finished Epoch[ 8 of 8]: [Training] ce = 0.05072431 * 2686; errs = 1.340% * 2686
Final Results: Minibatch[1-1]: errs = 4.143% * 893; ce = 0.27832144 * 893; perplexity = 1.32091072
Zonder veel moeite hebben we een foutpercentage van 4,1% bereikt. Erg leuk voor een eerste shot (hoewel niet helemaal staat van de kunst op deze taak, dat is op 3%).
Misschien ziet u echter één ding: het aantal steekproeven per epoch is nu ongeveer 2700. Dit komt doordat dit het aantal etiketvoorbeelden is, waarvan we nu slechts één per zin hebben. We hebben een sterk verminderd aantal toezichtsignalen in deze taak. Dit zou ons moeten aanmoedigen om de minibatch groter te maken. Laten we 256 proberen in plaats van 70:
Finished Epoch[ 1 of 8]: [Training] ce = 1.11500325 * 2702; errs = 19.282% * 2702
Finished Epoch[ 2 of 8]: [Training] ce = 0.29961089 * 2677; errs = 6.052% * 2677
Finished Epoch[ 3 of 8]: [Training] ce = 0.09018802 * 2688; errs = 2.418% * 2688
Finished Epoch[ 4 of 8]: [Training] ce = 0.04838102 * 2702; errs = 1.258% * 2702
Finished Epoch[ 5 of 8]: [Training] ce = 0.02996789 * 2697; errs = 0.704% * 2697
Finished Epoch[ 6 of 8]: [Training] ce = 0.02142932 * 2707; errs = 0.517% * 2707
Finished Epoch[ 7 of 8]: [Training] ce = 0.01220149 * 2677; errs = 0.299% * 2677
Finished Epoch[ 8 of 8]: [Training] ce = 0.01312233 * 2686; errs = 0.186% * 2686
Dit systeem leert veel beter! (Houd er echter rekening mee dat dit verschil waarschijnlijk een artefact is dat wordt veroorzaakt door ons fsAdagrad schema voor het normaliseren van kleurovergangen, en meestal binnenkort verdwijnt wanneer u grotere gegevenssets gebruikt.)
De resulterende foutsnelheid is echter hoger:
Final Results: Minibatch[1-1]: errs = 4.479% * 893; ce = 0.31638223 * 893; perplexity = 1.37215463
maar dit verschil komt eigenlijk overeen met 3 fouten, wat niet significant is.
Zie de oplossing hier.
Taak 5: Parallelle training
Als u ten slotte meerdere GPU's hebt, kunt u met CNTK de training parallelliseren met MPI (Message-Passing Interface). Dit model is te klein om een versnelling te verwachten; Het parallelliseren van een dergelijk klein model zal de beschikbare GPU's ernstig onderbenuten. Laten we echter de moties doorlopen, zodat u weet hoe u dit moet doen zodra u verdergaat met de echte werkbelastingen.
Voeg de volgende regels toe aan het SGD blok:
SGD = {
...
parallelTrain = {
parallelizationMethod = "DataParallelSGD"
parallelizationStartEpoch = 1
distributedMBReading = true
dataParallelSGD = { gradientBits = 2 }
}
}
en voer vervolgens deze opdracht uit:
mpiexec -np 4 cntk configFile=SLUHandsOn_Solution4.cntk stderr=Models/log parallelTrain=true command=TrainTagger
Hiermee wordt de training uitgevoerd op 4 GPU's met behulp van het 1-bits SGD-algoritme (in dit geval 2-bits SGD).
De benadering heeft de nauwkeurigheid niet gekwetst: de foutsnelheid is 4,367%, twee fouten meer (voer de TestTagger actie afzonderlijk uit op één GPU).
Conclusie
In deze zelfstudie is een functie-samenstellingsstijl geïntroduceerd als compacte methode voor het vertegenwoordigen van netwerken. Veel neurale-netwerktypen zijn geschikt om ze op deze manier weer te geven. Dit is een directere en minder foutgevoelige vertaling van een grafiek in een netwerkbeschrijving.
In deze zelfstudie is geoefend om een bestaande configuratie in functiesamenstellingsstijl te gebruiken en deze op specifieke manieren te wijzigen:
- een laag toevoegen (uit onze galerie met vooraf gedefinieerde lagen)
- een functie definiëren en gebruiken
- een laagfactoryfunctie definiëren en gebruiken
De zelfstudie heeft ook de verwerking van meerdere tijdsdimensies besproken en we hebben gezien hoe we de training parallelliseren.
Oplossingen
Hieronder ziet u de oplossingen voor de bovenstaande taken. Hé, geen vals spelen!
Oplossing 1: Batchnormalisatie toevoegen
De gewijzigde modelfunctie heeft deze vorm:
model = Sequential (
EmbeddingLayer {embDim} : # embedding
BatchNormalizationLayer {} : ##### added
RecurrentLSTMLayer {hiddenDim, goBackwards=false} : # LSTM
BatchNormalizationLayer {} : ##### added
DenseLayer {labelDim} # output layer
)
Oplossing 2: Een lookahead toevoegen
Uw lookahead-functie kan als volgt worden gedefinieerd:
OneWordLookahead (x) = Splice (x : DelayLayer {T=-1} (x))
en het wordt als volgt ingevoegd in het model:
model = Sequential (
EmbeddingLayer {embDim} :
OneWordLookahead : ##### added
BatchNormalizationLayer {} :
RecurrentLSTMLayer {hiddenDim, goBackwards=false} :
BatchNormalizationLayer {} :
DenseLayer {labelDim}
)
Oplossing 3: Bidirectioneel terugkerend model
De bidirectionele terugkerende laag kan als volgt worden geschreven:
BiRecurrentLSTMLayer {outDim} = {
F = RecurrentLSTMLayer {outDim, goBackwards=false}
G = RecurrentLSTMLayer {outDim, goBackwards=true}
apply (x) = Splice (F(x):G(x))
}.apply
en vervolgens als volgt gebruikt:
hiddenDim = 150 ##### changed from 300 to 150
model = Sequential (
EmbeddingLayer {embDim} :
###OneWordLookahead : ##### removed
BatchNormalizationLayer {} :
BiRecurrentLSTMLayer {hiddenDim} :
BatchNormalizationLayer {} :
DenseLayer {labelDim}
)
Oplossing 4: Intentieclassificatie
Verminder de reeksen tot de laatste/eerste verborgen van de terugkerende laag:
apply (x) = Splice (BS.Sequences.Last(F(x)):BS.Sequences.First(G(x)))
##### added Last() and First() calls ^^^
Wijzig de labelinvoer van site in intentie:
intentDim = $numIntents$ ###### name change
...
DenseLayer {intentDim} ##### different dimension
...
intentLabels = Input {intentDim}
...
ce = CrossEntropyWithSoftmax (intentLabels, z)
errs = ErrorPrediction (intentLabels, z)
...
labelNodes = (intentLabels)
Gebruik een nieuwe dynamische as:
n = DynamicAxis() ##### added
query = Input {inputDim, dynamicAxis=n} ##### use dynamic axis
Erkenning
We willen Derek Liu bedanken voor het voorbereiden van de basis van deze zelfstudie.