Lab pratici Language Understanding con reti ricorrenti
Si noti che questa esercitazione richiede la versione master più recente o la prossima versione CNTK 1.7.1 che verrà rilasciata a breve.
Questo lab pratico illustra come implementare una rete ricorrente per elaborare il testo, per le attività di Air Travel Information Services (ATIS) di assegnazione di tag di slot e classificazione delle finalità. Inizieremo con un incorporamento semplice seguito da un LSTM ricorrente. Verrà quindi esteso in modo da includere le parole adiacenti ed eseguire in modo bidirezionale. Infine, questo sistema verrà trasformato in un classificatore finalità.
Le tecniche da praticare includono:
- descrizione del modello componendo blocchi di livello invece di scrivere formule
- creazione di un blocco di livello personalizzato
- variabili con lunghezze di sequenza diverse nella stessa rete
- training parallelo
Si presuppone che l'utente abbia familiarità con le nozioni di base di Deep Learning e questi concetti specifici:
- reti ricorrenti (pagina Wikipedia)
- incorporamento di testo (pagina Wikipedia)
Prerequisiti
Si supponga di aver già installato CNTK ed eseguire il comando CNTK. Questa esercitazione è stata tenuta a KDD 2016 e richiede una build recente. Per istruzioni sulla configurazione, vedere qui . È sufficiente seguire le istruzioni per scaricare un pacchetto di installazione binaria da tale pagina.
Scaricare quindi un archivio ZIP (circa 12 MB): fare clic su questo collegamento e quindi sul pulsante Scarica.
L'archivio contiene i file per questa esercitazione. Archiviare e impostare la directory di lavoro su SLUHandsOn
.
I file con cui si sta lavorando sono:
SLUHandsOn.cntk
: il file di configurazione CNTK verrà presentato di seguito e verrà introdotto con .slu.forward.nobn.cmf
,slu.forward.cmf
,slu.forward.lookahead.cmf
eslu.forward.backward.cmf
: modelli con training preliminare risultanti dalle rispettive configurazioni sviluppate in questa esercitazione.atis.train.ctf
eatis.test.ctf
: il corpus di training e test, già convertito in CNTK Text Format (CTF).
Infine, è consigliabile eseguire questa operazione in un computer con una GPU compatibile con CUDA. L'apprendimento avanzato senza GPU non è divertente.
Attività e struttura del modello
L'attività che si vuole affrontare in questa esercitazione è l'assegnazione di tag agli slot. Usiamo il corpus ATIS. ATIS contiene query sul computer umano dal dominio di Air Travel Information Services e l'attività sarà annotare (tag) ogni parola di una query se appartiene a un elemento specifico di informazioni (slot) e a quale parola.
I dati nella cartella di lavoro sono già stati convertiti in "Formato testo CNTK". Di seguito è riportato un esempio del file atis.test.ctf
di set di test:
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
Questo file ha 7 colonne:
- id sequenza (19). Sono presenti 11 voci con questo ID sequenza. Ciò significa che la sequenza 19 è costituita da 11 token;
- colonna
S0
, che contiene indici di parole numeriche; - una colonna di commento indicata da
#
, per consentire a un lettore umano di conoscere qual è l'indice numerico delle parole; Le colonne di commento vengono ignorate dal sistema.BOS
eEOS
sono parole speciali per indicare rispettivamente l'inizio e la fine della frase; - column
S1
è un'etichetta di finalità, che verrà usata solo nell'ultima parte dell'esercitazione; - un'altra colonna di commento che mostra l'etichetta leggibile dell'indice finalità numerico;
- colonna
S2
è l'etichetta dello slot, rappresentata come indice numerico; e - un'altra colonna di commento che mostra l'etichetta leggibile dell'indice di etichetta numerica.
L'attività della rete neurale consiste nell'esaminare la query (colonna S0
) e prevedere l'etichetta dello slot (colonna S2
).
Come si può notare, a ogni parola nell'input viene assegnata un'etichetta vuota o un'etichetta O
slot che inizia con B-
per la prima parola e con I-
per qualsiasi parola consecutiva aggiuntiva appartenente allo stesso slot.
Il modello che verrà usato è un modello ricorrente costituito da un livello di incorporamento, una cella LSTM ricorrente e un livello denso per calcolare le probabilità posterior:
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"
In alternativa, come descrizione di rete CNTK. Dare un'occhiata rapida e associarla alla descrizione precedente:
model = Sequential (
EmbeddingLayer {150} :
RecurrentLSTMLayer {300} :
DenseLayer {labelDim}
)
Le descrizioni di queste funzioni sono disponibili in: Sequential()
, EmbeddingLayer{}
, RecurrentLSTMLayer{}
e DenseLayer{}
Configurazione di CNTK
File di configurazione
Per eseguire il training e il test di un modello in CNTK, è necessario fornire un file di configurazione che indica a CNTK quali operazioni eseguire (command
variabile) e una sezione di parametro per ogni comando.
Per il comando di training, CNTK deve essere indicato:
- come leggere i dati (
reader
sezione) - la funzione del modello e i relativi input e output nel grafico di calcolo (
BrainScriptNetworkBuilder
sezione) - iper-parametri per lo learner (
SGD
sezione)
Per il comando di valutazione, CNTK deve sapere come leggere i dati di test (reader
sezione).
Di seguito è riportato il file di configurazione con cui si inizierà. Come si può notare, un file di configurazione CNTK è un file di testo costituito da definizioni di parametri, organizzati in una gerarchia di record. È anche possibile vedere in che modo CNTK supporta la sostituzione dei parametri di base usando la $parameterName$
sintassi . Il file effettivo contiene solo alcuni altri parametri di quanto indicato in precedenza, ma analizzarlo e individuare gli elementi di configurazione appena menzionati:
# 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" }
}
}
}
Breve analisi dei dati e della lettura dei dati
I dati sono già stati esaminati.
Ma come si genera questo formato?
Per la lettura del testo, questa esercitazione usa .CNTKTextFormatReader
Si prevede che i dati di input siano di un formato specifico, descritto qui.
Per questa esercitazione è stato creato il corpora in due passaggi:
convertire i dati non elaborati in un file di testo normale che contiene colonne separate da TAB di testo delimitato da spazi. Ad esempio:
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 O
Questo è progettato per essere compatibile con l'output del
paste
comando.convertirlo in CNTK Text Format (CTF) con il comando seguente:
python Scripts/txt2ctf.py --map query.wl intent.wl slots.wl --annotated True --input atis.test.txt --output atis.test.ctf
dove i tre
.wl
file danno il vocabolario come file di testo normale, una riga per parola.
In questi file CTFG le colonne sono etichettate S0
, S1
e S2
.
Questi elementi sono connessi agli input di rete effettivi dalle righe corrispondenti nella definizione del lettore:
input = {
query = { alias = "S0" ; dim = $vocabSize$ ; format = "sparse" }
intentLabels = { alias = "S1" ; dim = $numIntents$ ; format = "sparse" }
slotLabels = { alias = "S2" ; dim = $numLabels$ ; format = "sparse" }
}
Esecuzione
È possibile trovare il file di configurazione precedente sotto il nome SLUHandsOn.cntk
nella cartella di lavoro.
Per eseguirlo, eseguire la configurazione precedente con questo comando:
cntk configFile=SLUHandsOn.cntk
Verrà eseguita la configurazione, a partire dal training del modello, come definito nella sezione denominata TrainTagger
.
Dopo un output di log iniziale piuttosto chiacchiere, si noterà presto quanto segue:
Training 721479 parameters in 6 parameter tensors.
seguito dall'output simile al seguente:
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
Questo illustra come l'apprendimento procede attraverso i periodi (passa attraverso i dati).
Ad esempio, dopo due periodi, il criterio di entropia incrociata, denominato ce
nel file di configurazione, ha raggiunto 0,27 come misurato sui campioni 36001 di questo periodo e che la percentuale di errore è 5,883% sugli stessi campioni di training 36016.
Il 36001 deriva dal fatto che la configurazione definiva le dimensioni dell'epoca come 36000. La dimensione del periodo è il numero di campioni conteggiati come token di parola, non frasi da elaborare tra checkpoint del modello. Poiché le frasi hanno una lunghezza variabile e non necessariamente sommano a multipli di esattamente 36000 parole, si noterà una piccola variazione.
Una volta completato il training (poco meno di 2 minuti su Titan-X o un Surface Book), CNTK procederà con l'azione EvalTagger
Final Results: Minibatch[1-1]: errs = 2.922% * 10984; ce = 0.14306181 * 10984; perplexity = 1.15380111
Ad esempio, nel set di test, le etichette slot sono state stimate con una percentuale di errore del 2,9%. Non affatto male, per un sistema così semplice!
In un computer solo CPU può essere 4 o più volte più lento. Per assicurarsi che il sistema sia in corso, è possibile abilitare la traccia per visualizzare i risultati parziali, che dovrebbero apparire ragionevolmente rapidamente:
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
...
Se non si vuole attendere il completamento di questa operazione, è possibile eseguire un modello intermedio, ad esempio
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
o testare anche il modello con training preliminare, che è possibile trovare nella cartella di lavoro:
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
Modifica del modello
Di seguito verranno fornite attività per modificare le configurazioni CNTK. Le soluzioni vengono fornite alla fine di questo documento... ma per favore prova senza!
Informazioni su word Sequential()
Prima di passare alle attività, si esaminerà di nuovo il modello appena eseguito. Il modello è descritto in quello che chiamiamo stile di composizione della funzione.
model = Sequential (
EmbeddingLayer {embDim} : # embedding
RecurrentLSTMLayer {hiddenDim, goBackwards=false} : # LSTM
DenseLayer {labelDim, initValueScale=7} # output layer
)
dove i due punti (:
) è la sintassi di BrainScript di esprimere matrici. Ad esempio, (F:G:H)
è una matrice con tre elementi, F
, G
e H
.
È possibile avere familiarità con la notazione "sequenziale" di altri toolkit di rete neurale.
In caso contrario, Sequential()
è un'operazione potente che, in breve, consente di esprimere in modo compatto una situazione molto comune nelle reti neurali in cui un input viene elaborato propagandolo attraverso una progressione dei livelli.
Sequential()
accetta una matrice di funzioni come argomento e restituisce una nuova funzione che richiama questa funzione in ordine, ogni volta che passa l'output di uno al successivo.
Ad esempio,
FGH = Sequential (F:G:H)
y = FGH (x)
significa uguale a
y = H(G(F(x)))
Questo è noto come "composizione di funzioni" ed è particolarmente utile per esprimere le reti neurali, che spesso hanno questo formato:
+-------+ +-------+ +-------+
x -->| F |-->| G |-->| H |--> y
+-------+ +-------+ +-------+
Tornare al modello a portata di mano, l'espressione indica semplicemente che il Sequential
modello ha questo formato:
+-----------+ +----------------+ +------------+
x -->| Embedding |-->| Recurrent LSTM |-->| DenseLayer |--> y
+-----------+ +----------------+ +------------+
Attività 1: Aggiungere la normalizzazione batch
Si vogliono ora aggiungere nuovi livelli al modello, in particolare la normalizzazione batch.
La normalizzazione batch è una tecnica comune per velocizzare la convergenza. Viene spesso usato per le configurazioni di elaborazione delle immagini, ad esempio l'altro lab pratico sul riconoscimento delle immagini. Ma potrebbe funzionare anche per i modelli ricorrenti?
L'attività sarà quindi quella di inserire livelli di normalizzazione batch prima e dopo il livello LSTM ricorrente. Se sono stati completati i lab pratici per l'elaborazione delle immagini, è possibile ricordare che il livello di normalizzazione batch ha questo formato:
BatchNormalizationLayer{}
Quindi, procedere e modificare la configurazione e vedere cosa accade.
Se tutto è andato bene, si noterà non solo una maggiore velocità di convergenza (ce
e errs
) rispetto alla configurazione precedente, ma anche una migliore percentuale di errore del 2,0% (rispetto al 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
Se non si vuole attendere il completamento del training, è possibile trovare il modello risultante sotto il nome slu.forward.cmf
.
Vedere la soluzione qui.
Attività 2: Aggiungere un lookahead
Il nostro modello ricorrente soffre di un deficit strutturale: poiché la ricorrenza viene eseguita da sinistra a destra, la decisione per un'etichetta slot non ha informazioni sulle parole future. Il modello è leggermente inclinato. L'attività consisterà nel modificare il modello in modo che l'input alla ricorrenza sia costituito non solo dalla parola corrente, ma anche da quello successivo (lookahead).
La soluzione deve essere in stile di composizione della funzione. Di conseguenza, è necessario scrivere una funzione BrainScript che esegue le operazioni seguenti:
- accettare un argomento di input;
- calcolare il "valore futuro" immediato di questo input usando la
FutureValue()
funzione (usare questo formato specifico:FutureValue (0, input, defaultHiddenActivation=0)
); e - concatenare i due in un vettore di due volte la dimensione di incorporamento usando
Splice()
(usare questo formato: Splice(x:y)
)
e quindi inserire questa funzione nell'oggetto tra l'incorporamento Sequence()
e il livello ricorrente.
Se tutto va bene, verrà visualizzato l'output seguente:
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
Questo ha funzionato! Sapere qual è la parola successiva consente al tagger slot di ridurre il tasso di errore dal 2,0% al 1,84%.
Se non si vuole attendere il completamento del training, è possibile trovare il modello risultante sotto il nome slu.forward.lookahead.cmf
.
Vedere la soluzione qui.
Attività 3: Modello ricorrente bidirezionale
Aha, conoscenza delle parole future aiutano. Quindi, invece di una sola parola lookahead, perché non guardare avanti fino alla fine della frase, attraverso una ricorrenza indietro? Creare un modello bidirezionale.
L'attività consiste nell'implementare un nuovo livello che esegue una ricorsione avanti e indietro sui dati e concatena i vettori di output.
Si noti, tuttavia, che questo comportamento differisce dall'attività precedente in quanto il livello bidirezionale contiene parametri del modello imparabili. Nello stile di composizione della funzione, il modello per implementare un livello con i parametri del modello consiste nel scrivere una funzione factory che crea un oggetto funzione.
Un oggetto funzione, noto anche come functor, è un oggetto che è sia una funzione che un oggetto . Ciò significa che non sono ancora presenti dati che possono essere richiamati come se fosse una funzione.
Ad esempio, LinearLayer{outDim}
è una funzione factory che restituisce un oggetto funzione che contiene una matrice W
di peso, una distorsione b
e un'altra funzione per calcolare W * input + b
.
Ad esempio, verrà LinearLayer{1024}
creato questo oggetto funzione, che può quindi essere usato come qualsiasi altra funzione, anche immediatamente: LinearLayer{1024}(x)
.
Confuso? Di seguito viene illustrato come implementare un nuovo livello che combina un livello lineare con una normalizzazione batch successiva. Per consentire la composizione della funzione, il livello deve essere realizzato come funzione factory, che potrebbe essere simile al seguente:
LinearLayerWithBN {outDim} = {
F = LinearLayer {outDim}
G = BatchNormalization {normalizationTimeConstant=2048}
apply (x) = G(F(x))
}.apply
Richiamare questa funzione factory creerà prima di tutto un record (indicato da {...}
) con tre membri: F
, G
e apply
. In questo esempio F
e G
sono oggetti funzione stessi ed apply
è la funzione da applicare ai dati.
L'aggiunta .apply
a questa espressione significa cosa .x
significa sempre in BrainScript, per accedere a un membro del record. Ad esempio, la chiamata LinearLayerWithBN{1024}
creerà un oggetto contenente un oggetto funzione a livello lineare denominato F
, un oggetto G
funzione di normalizzazione batch e apply
che è la funzione che implementa l'operazione effettiva di questo livello usando F
e G
. Verrà quindi restituito apply
. All'esterno, apply()
sembra e si comporta come una funzione. Sotto le quinte, tuttavia, apply()
manterrà il record a cui appartiene e quindi manterrà l'accesso alle istanze specifiche di F
e G
.
Ora torniamo al nostro compito a portata di mano. Sarà ora necessario creare una funzione factory, molto simile all'esempio precedente.
Si creerà una funzione factory che crea due istanze ricorrenti del livello (una avanti, una all'indietro) e quindi definisce una apply (x)
funzione che applica entrambe le istanze di livello alla stessa x
e concatena i due risultati.
Va bene, prova! Per sapere come realizzare una ricorsione indietro in CNTK, prendere un suggerimento da come viene eseguita la ricorsione in avanti. Eseguire anche le operazioni seguenti:
- rimuovere il lookahead di una parola aggiunto nell'attività precedente, che si mira a sostituire; E
- modificare il
hiddenDim
parametro da 300 a 150 per mantenere limitato il numero totale di parametri del modello.
L'esecuzione di questo modello produrrà correttamente l'output seguente:
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
Funziona come un fascino! Questo modello raggiunge il 1,83%, un bit leggermente migliore rispetto al modello lookahead precedente. Il modello bidirezionale ha meno parametri del 40% rispetto a quello lookahead. Tuttavia, se si torna indietro e si esamina attentamente l'output completo del log (non visualizzato in questa pagina Web), è possibile che lookahead uno sottoposto a training circa il 30% più velocemente. Ciò è dovuto al fatto che il modello lookahead ha sia dipendenze orizzontali meno orizzontali (una anziché due ricorrenze) che prodotti matrice più grandi e può quindi ottenere un parallelismo più elevato.
Vedere la soluzione qui.
Attività 4: Classificazione finalità
Si scopre che il modello costruito finora può essere facilmente trasformato in un classificatore finalità.
Tenere presente che il file di dati contiene questa colonna aggiuntiva denominata S1
.
Questa colonna contiene una singola etichetta per frase, che indica la finalità della query di trovare informazioni su argomenti come airport
o airfare
.
L'attività di classificazione di un'intera sequenza in una singola etichetta è denominata classificazione sequenza. Il classificatore di sequenza verrà implementato come LSTM ricorrente (già presente) di cui prendiamo lo stato nascosto del passaggio finale. In questo modo viene specificato un singolo vettore per ogni sequenza. Questo vettore viene quindi inserito in un livello denso per la classificazione softmax.
CNTK ha un'operazione per estrarre l'ultimo stato da una sequenza, denominata BS.Sequences.Last()
.
Questa operazione rispetta il fatto che lo stesso minibatch può contenere sequenze di lunghezze molto diverse e che sono disposte in memoria in un formato compresso.
Analogamente, per la ricorsione all'indietro, è possibile usare BS.Sequences.First()
.
L'attività consiste nel modificare la rete bidirezionale dall'attività 3 in modo che l'ultimo fotogramma venga estratto dalla ricorsione in avanti e il primo fotogramma venga estratto dalla ricorsione indietro e i due vettori siano concatenati. Il vettore concatenato (talvolta chiamato vettore di pensiero) sarà quindi l'input dello strato denso.
Inoltre, è necessario modificare l'etichetta dallo slot all'etichetta della finalità: rinominare semplicemente la variabile di input (slotLabels
) in modo che corrisponda al nome usato nella sezione reader per le etichette delle finalità e che corrisponda anche alla dimensione.
Provare la modifica. Se lo fai bene, tuttavia, sarai confrontato con un messaggio di errore vexing, e un lungo a quel punto:
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?
"Si usano lunghezze di sequenza diverse?" Oh sì! La query e l'etichetta della finalità do-the intent label è un solo token per ogni query. È una sequenza di 1 elemento! Quindi, come risolvere questo problema?
CNTK consente a variabili diverse nella rete di avere lunghezze di sequenza diverse. È possibile considerare la lunghezza della sequenza come una dimensione di tensore simbolica aggiuntiva. Le variabili della stessa lunghezza condividono la stessa dimensione di lunghezza simbolica. Se due variabili hanno lunghezze diverse, questa deve essere dichiarata in modo esplicito. In caso contrario, CNTK presuppone che tutte le variabili condividono la stessa lunghezza simbolica.
Questa operazione viene eseguita creando un nuovo oggetto asse dinamico e associandolo a uno degli input, come indicato di seguito:
n = DynamicAxis()
query = Input {inputDim, dynamicAxis=n}
CNTK ha un asse predefinito. Come si può immaginare dall'eccezione precedente, il nome è '*'.
Pertanto, è sufficiente dichiarare un nuovo asse; l'altro input (intentLabels
) continuerà a usare l'asse predefinito.
A questo momento è consigliabile eseguire e vedere l'output seguente:
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
Senza molta fatica, abbiamo raggiunto un tasso di errore del 4,1%. Molto bello per un primo colpo (anche se non abbastanza stato dell'arte su questo compito, che è al 3%).
Si noterà però che il numero di campioni per periodo è ora di circa 2700. Questo perché questo è il numero di campioni di etichetta, di cui ora abbiamo solo una per frase. Abbiamo un numero notevolmente ridotto di segnali di supervisione in questa attività. Questo dovrebbe incoraggiarci a cercare di aumentare le dimensioni del minibatch. Provare 256 invece di 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
Questo sistema impara molto meglio! Si noti, tuttavia, che questa differenza è probabilmente un artefatto causato dal nostro fsAdagrad
schema di normalizzazione del gradiente e in genere scomparirà presto quando si usano set di dati di dimensioni maggiori.
La percentuale di errori risultante è superiore, tuttavia:
Final Results: Minibatch[1-1]: errs = 4.479% * 893; ce = 0.31638223 * 893; perplexity = 1.37215463
ma questa differenza corrisponde effettivamente a 3 errori, che non è significativo.
Vedere la soluzione qui.
Attività 5: Training parallelo
Infine, se sono presenti più GPU, CNTK consente di parallelizzare il training usando MPI (Message-Passing Interface). Questo modello è troppo piccolo per aspettarsi qualsiasi accelerazione; la parallelizzazione di un modello di piccole dimensioni sottoutilizzerà gravemente le GPU disponibili. Tuttavia, facciamoci passare attraverso i movimenti, in modo che tu sappia come farlo una volta che passi ai carichi di lavoro reali.
Aggiungere le righe seguenti al SGD
blocco:
SGD = {
...
parallelTrain = {
parallelizationMethod = "DataParallelSGD"
parallelizationStartEpoch = 1
distributedMBReading = true
dataParallelSGD = { gradientBits = 2 }
}
}
e quindi eseguire questo comando:
mpiexec -np 4 cntk configFile=SLUHandsOn_Solution4.cntk stderr=Models/log parallelTrain=true command=TrainTagger
Verrà eseguito il training su 4 GPU usando l'algoritmo SGD a 1 bit (in questo caso SGD a 2 bit).
L'approssimazione non ha danneggiato l'accuratezza: la frequenza di errore è 4,367%, due errori in più (eseguire l'azione TestTagger
separatamente in una singola GPU).
Conclusioni
Questa esercitazione ha introdotto lo stile di composizione delle funzioni come mezzo compatto per rappresentare le reti. Molti tipi di rete neurale sono adatti per rappresentarli in questo modo, ovvero una traduzione più diretta e meno soggetta a errori di un grafico in una descrizione di rete.
Questa esercitazione ha fatto pratica per accettare una configurazione esistente nello stile di composizione delle funzioni e modificarla in modi specifici:
- aggiunta di un livello (dalla raccolta di livelli predefiniti)
- definizione e uso di una funzione
- definizione e uso di una funzione factory di livello
L'esercitazione ha anche illustrato la gestione di più dimensioni temporali ed è stato illustrato come parallelizzare il training.
Soluzioni
Di seguito sono riportate le soluzioni alle attività precedenti. Ehi, no barare!
Soluzione 1: Aggiunta della normalizzazione batch
La funzione del modello modificata ha questo formato:
model = Sequential (
EmbeddingLayer {embDim} : # embedding
BatchNormalizationLayer {} : ##### added
RecurrentLSTMLayer {hiddenDim, goBackwards=false} : # LSTM
BatchNormalizationLayer {} : ##### added
DenseLayer {labelDim} # output layer
)
Soluzione 2: Aggiungere un lookahead
La funzione lookahead può essere definita come segue:
OneWordLookahead (x) = Splice (x : DelayLayer {T=-1} (x))
e verrà inserito nel modello come segue:
model = Sequential (
EmbeddingLayer {embDim} :
OneWordLookahead : ##### added
BatchNormalizationLayer {} :
RecurrentLSTMLayer {hiddenDim, goBackwards=false} :
BatchNormalizationLayer {} :
DenseLayer {labelDim}
)
Soluzione 3: Modello ricorrente bidirezionale
Il livello ricorrente bidirezionale può essere scritto come segue:
BiRecurrentLSTMLayer {outDim} = {
F = RecurrentLSTMLayer {outDim, goBackwards=false}
G = RecurrentLSTMLayer {outDim, goBackwards=true}
apply (x) = Splice (F(x):G(x))
}.apply
e quindi usato come segue:
hiddenDim = 150 ##### changed from 300 to 150
model = Sequential (
EmbeddingLayer {embDim} :
###OneWordLookahead : ##### removed
BatchNormalizationLayer {} :
BiRecurrentLSTMLayer {hiddenDim} :
BatchNormalizationLayer {} :
DenseLayer {labelDim}
)
Soluzione 4: Classificazione finalità
Ridurre le sequenze all'ultimo/primo nascosto del livello ricorrente:
apply (x) = Splice (BS.Sequences.Last(F(x)):BS.Sequences.First(G(x)))
##### added Last() and First() calls ^^^
Modificare l'input dell'etichetta dallo slot alla finalità:
intentDim = $numIntents$ ###### name change
...
DenseLayer {intentDim} ##### different dimension
...
intentLabels = Input {intentDim}
...
ce = CrossEntropyWithSoftmax (intentLabels, z)
errs = ErrorPrediction (intentLabels, z)
...
labelNodes = (intentLabels)
Usare un nuovo asse dinamico:
n = DynamicAxis() ##### added
query = Input {inputDim, dynamicAxis=n} ##### use dynamic axis
Acknowledgment (Riconoscimento)
Vorremmo ringraziare Derek Liu per aver preparato la base di questa esercitazione.