Riconoscimento delle immagini di Lab pratico

Si noti che questa esercitazione richiede la versione master più recente o il prossimo CNTK 1.7 che verrà rilasciato presto. Un download binario intermedio è disponibile nelle istruzioni per l'esercitazione CNTK Hands-On KDD che questa esercitazione è stata originariamente progettata per.

lab Hands-On: riconoscimento delle immagini con reti convoluzionali, normalizzazione batch e reti residui

Questo lab pratico illustra come implementare il riconoscimento delle immagini basate sulla convoluzione con CNTK. Si inizierà con un'architettura comune di riconoscimento delle immagini convoluzionali, si aggiunge la normalizzazione batch e si estenderà in una rete residuale (ResNet-20).

Le tecniche che si praticano includono:

  • modifica di una definizione di rete CNTK per aggiungere un'operazione predefinita (elenco a discesa)
  • creazione di funzioni definite dall'utente per estrarre parti ripetitive in una rete in un modulo riutilizzabile
  • implementazione di strutture di rete personalizzate (connessione ignora resNet)
  • creazione di molti livelli contemporaneamente tramite cicli ricorsivi
  • training parallelo
  • reti convolutzionali
  • normalizzazione batch

Prerequisiti

Si supponga di aver già installato CNTK e di poter eseguire il comando CNTK. Questa esercitazione è stata tenuta a KDD 2016 e richiede una compilazione recente, vedere qui per istruzioni sulla configurazione. È sufficiente seguire le istruzioni per scaricare un pacchetto di installazione binaria da tale pagina. Per le attività correlate all'immagine, è consigliabile eseguire questa operazione in un computer con una GPU compatibile con CUDA.

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 ImageHandsOn. Verranno usati i file seguenti:

Infine, è necessario scaricare e convertire il set di dati CIFAR-10. Il passaggio di conversione richiede circa 10 minuti. Eseguire i due script Python seguenti che saranno disponibili anche nella directory di lavoro:

wget -rc http://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
tar xvf www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
python CifarConverter.py cifar-10-batches-py

In questo modo le immagini vengono convertite in file PNG, 50000 per il training e 10000 per il test, che verranno inserite rispettivamente nelle due directory seguenti: cifar-10-batches-py/data/train e cifar-10-batches-py/data/test.

Struttura del modello

Verrà avviata questa esercitazione con un semplice modello convoluzionale. È costituito da 3 livelli di 5x5 convoluzioni + non linearità + riduzione della dimensione 2x per 3x3 max-pooling, che vengono quindi seguiti da un livello nascosto denso e una trasformazione densa per formare l'input a un classificatore softmax a 10 vie.

In alternativa, come descrizione di rete CNTK. Si prega di avere un'occhiata rapida e di corrispondervi con la descrizione precedente:

featNorm = features - Constant (128)
l1 = ConvolutionalLayer {32, (5:5), pad = true, activation = ReLU,
                         init = "gaussian", initValueScale = 0.0043} (featNorm)
p1 = MaxPoolingLayer {(3:3), stride = (2:2)} (l1)
l2 = ConvolutionalLayer {32, (5:5), pad = true, activation = ReLU,
                         init = "gaussian", initValueScale = 1.414} (p1)
p2 = MaxPoolingLayer {(3:3), stride = (2:2)} (l2)
l3 = ConvolutionalLayer {64, (5:5), pad = true, activation = ReLU,
                         init = "gaussian", initValueScale = 1.414} (p2)
p3 = MaxPoolingLayer {(3:3), stride = (2:2)} (l3)
d1 = DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} (p3)
z  = LinearLayer {10, init = "gaussian", initValueScale = 1.5} (d1)

È possibile trovare altre informazioni su questi operatori qui: ConvolutionalLayer{}, , MaxPoolingLayer{}, LinearLayer{}DenseLayer{}.

configurazione CNTK

File di configurazione

Per eseguire il training e testare un modello in CNTK, è necessario fornire un file di configurazione che indica CNTK le operazioni da eseguire (command variabile) e una sezione dei parametri per ogni comando.

Per il comando di training, CNTK deve essere detto:

  • come leggere i dati (reader sezione)
  • la funzione del modello e i relativi input e output nel grafico di calcolo (BrainScriptNetworkBuilder sezione)
  • parametri hyper-parameters per il learner (SGD sezione)

Per il comando di valutazione, CNTK deve conoscere:

  • come leggere i dati di test (reader sezione)
  • metriche da valutare (evalNodeNames parametro)

Di seguito è riportato il file di configurazione che verrà avviato. Come si vede, un file di configurazione CNTK è un file di testo costituito da definizioni di parametri, organizzati in una gerarchia di record. È anche possibile vedere come CNTK supporta la sostituzione dei parametri di base usando la $parameterName$ sintassi. Il file effettivo contiene solo alcuni parametri più indicati in precedenza, ma analizzarlo e individuare gli elementi di configurazione appena menzionati:

# CNTK Configuration File for training a simple CIFAR-10 convnet.
# During the hands-on tutorial, this will be fleshed out into a ResNet-20 model.

command = TrainConvNet:Eval

makeMode = false ; traceLevel = 0 ; deviceId = "auto"

rootDir = "." ; dataDir  = "$rootDir$" ; modelDir = "$rootDir$/Models"

modelPath = "$modelDir$/cifar10.cmf"

# Training action for a convolutional network
TrainConvNet = {
    action = "train"

    BrainScriptNetworkBuilder = {
        imageShape = 32:32:3
        labelDim = 10

        model (features) = {
            featNorm = features - Constant (128)
            l1 = ConvolutionalLayer {32, (5:5), pad=true, activation=ReLU,
                                     init="gaussian", initValueScale=0.0043} (featNorm)
            p1 = MaxPoolingLayer {(3:3), stride=(2:2)} (l1)
            l2 = ConvolutionalLayer {32, (5:5), pad=true, activation=ReLU,
                                     init="gaussian", initValueScale=1.414} (p1)
            p2 = MaxPoolingLayer {(3:3), stride=(2:2)} (l2)
            l3 = ConvolutionalLayer {64, (5:5), pad=true, activation=ReLU,
                                     init="gaussian", initValueScale=1.414} (p2)
            p3 = MaxPoolingLayer {(3:3), stride=(2:2)} (l3)
            d1 = DenseLayer {64, activation=ReLU, init="gaussian", initValueScale=12} (p3)
            z  = LinearLayer {10, init="gaussian", initValueScale=1.5} (d1)
        }.z

        # inputs
        features = Input {imageShape}
        labels   = Input {labelDim}

        # apply model to features
        z = model (features)

        # connect to system
        ce       = CrossEntropyWithSoftmax (labels, z)
        errs     = ErrorPrediction         (labels, z)

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

    SGD = {
        epochSize = 50000

        maxEpochs = 30 ; minibatchSize = 64
        learningRatesPerSample = 0.00015625*10:0.000046875*10:0.000015625
        momentumAsTimeConstant = 600*20:6400
        L2RegWeight = 0.03

        firstMBsToShowResult = 10 ; numMBsToShowResult = 100
    }

    reader = {
        verbosity = 0 ; randomize = true
        deserializers = ({
            type = "ImageDeserializer" ; module = "ImageReader"
            file = "$dataDir$/cifar-10-batches-py/train_map.txt"
            input = {
                features = { transforms = (
                    { type = "Crop" ; cropType = "RandomSide" ; sideRatio = 0.8 ; jitterType = "UniRatio" } :
                    { type = "Scale" ; width = 32 ; height = 32 ; channels = 3 ; interpolations = "linear" } :
                    { type = "Transpose" }
                )}
                labels = { labelDim = 10 }
            }
        })
    }
}

# Eval action
Eval = {
    action = "eval"
    minibatchSize = 16
    evalNodeNames = errs
    reader = {
        verbosity = 0 ; randomize = true
        deserializers = ({
            type = "ImageDeserializer" ; module = "ImageReader"
            file = "$dataDir$/cifar-10-batches-py/test_map.txt"
            input = {
                features = { transforms = (
                   { type = "Scale" ; width = 32 ; height = 32 ; channels = 3 ; interpolations = "linear" } :
                   { type = "Transpose" }
                )}
                labels = { labelDim = 10 }
            }
        })
    }
}

Lettura dati e dati

Dopo aver scaricato i dati CIFAR-10 ed eseguire lo CifarConverter.py script come richiesto all'inizio di questa esercitazione, è disponibile una directory denominata cifar-10-batches-py/data, che contiene due sottodirectory train e test, pieno di file PNG. L'CNTK ImageDeserializer usa i formati di immagine standard.

Sono disponibili anche due file train_map.txt e test_map.txt. Guardando a quest'ultimo,

% more cifar-10-batches-py/test_map.txt
cifar-10-batches-py/data/test/00000.png 3
cifar-10-batches-py/data/test/00001.png 8
cifar-10-batches-py/data/test/00002.png 8
...

Entrambi i file sono costituiti da due colonne, in cui il primo contiene il percorso del file di immagine e la seconda etichetta di classe, come indice numerico. Queste colonne corrispondono agli input features del lettore e labels che sono stati definiti come:

 features = { transforms = (
     { type = "Crop" ; cropType = "RandomSide" ; sideRatio = 0.8 ; jitterType = "UniRatio" } :
     { type = "Scale" ; width = 32 ; height = 32 ; channels = 3 ; interpolations = "linear" } :
     { type = "Transpose" }
 )}
 labels = { labelDim = 10 }

La sezione aggiuntiva transforms indica all'utente di ImageDeserializer applicare una sequenza di trasformazioni (comuni) alle immagini durante la lettura. Per altre informazioni, vedere qui.

Esecuzione

È possibile trovare il file di configurazione precedente sotto il nome ImageHandsOn.cntk nella cartella di lavoro. Per eseguirlo, eseguire la configurazione precedente tramite questo comando:

cntk  configFile=ImageHandsOn.cntk

Lo schermo verrà vivo con un flurry di messaggi di log (CNTK può essere parlativo a volte), ma se tutto è andato bene, verrà presto visualizzato questo:

Training 116906 parameters in 10 out of 10 parameter tensors and 28 nodes with gradient

seguito dall'output simile al seguente:

Finished Epoch[ 1 of 10]: [Training] ce = 1.66950797 * 50000; errs = 61.228% * 50000
Finished Epoch[ 2 of 10]: [Training] ce = 1.32699016 * 50000; errs = 47.394% * 50000
Finished Epoch[ 3 of 10]: [Training] ce = 1.17140398 * 50000; errs = 41.168% * 50000

Questo indica che sta imparando. Ogni epoca rappresenta un passaggio attraverso le immagini di training 50000. Indica inoltre che dopo la seconda epoca, il criterio di training, che la configurazione denominata ce, ha raggiunto 1,33 come misurato sui 50000 campioni di questo periodo, e che la frequenza di errore è del 47% su quelli stessi 50000 campioni di training.

Si noti che i computer solo CPU sono circa 20 volte più lente. Sarà necessario molti minuti finché non viene visualizzato anche il primo output del log. Per assicurarsi che il sistema sia in corso, è possibile abilitare la traccia per visualizzare i risultati parziali, che dovrebbero essere visualizzati ragionevolmente rapidamente:

cntk  configFile=ImageHandsOn.cntk  traceLevel=1

Epoch[ 1 of 10]-Minibatch[-498-   1, 0.13%]: ce = 2.30260658 * 64; errs = 90.625% * 64
...
Epoch[ 1 of 10]-Minibatch[   1- 100, 12.80%]: ce = 2.10434176 * 5760; errs = 78.472% * 5760
Epoch[ 1 of 10]-Minibatch[ 101- 200, 25.60%]: ce = 1.82372971 * 6400; errs = 68.172% * 6400
Epoch[ 1 of 10]-Minibatch[ 201- 300, 38.40%]: ce = 1.69708496 * 6400; errs = 62.469% * 6400

Al termine del training (che richiede circa 3 minuti su un Surface Book e in un computer desktop con una GPU Titan-X), il messaggio finale sarà simile al seguente:

Finished Epoch[10 of 10]: [Training] ce = 0.74679766 * 50000; errs = 25.486% * 50000

che mostra che la rete ha ridotto correttamente la ce perdita e ha raggiunto un errore di classificazione del 25,5% nel set di training. Poiché la command variabile specifica un secondo comando Eval, CNTK procederà con tale azione. Misura la frequenza di errore di classificazione sulle immagini 10000 del set di test.

Final Results: Minibatch[1-625]: errs = 24.180% * 10000

La frequenza di errore di test è vicina al training. Poiché CIFAR-10 è un set di dati piuttosto piccolo, questo è un indicatore che il modello non ha ancora convergente completamente (e in effetti, l'esecuzione per 30 epoche otterrà circa il 20%).

Se non si vuole attendere il completamento, è possibile eseguire un modello intermedio, ad esempio.

cntk  configFile=ImageHandsOn.cntk  command=Eval  modelPath=Models/cifar10.cmf.5
Final Results: Minibatch[1-625]: errs = 31.710% * 10000

o eseguire anche il modello pre-sottoposto a training:

cntk  configFile=ImageHandsOn.cntk  command=Eval  modelPath=cifar10.pretrained.cmf
Final Results: Minibatch[1-625]: errs = 24.180% * 10000

Modifica del modello

Di seguito verranno fornite attività per praticare la modifica delle configurazioni di CNTK. Le soluzioni vengono fornite alla fine di questo documento... ma provare senza!

Attività 1: Aggiunta dell'eliminazione

Una tecnica comune per migliorare la generalizzabilità dei modelli è a discesa. Per aggiungere l'eliminazione a un modello di CNTK, è necessario

  • aggiungere una chiamata alla funzione Dropout() CNTK in cui si vuole inserire l'operazione di rilascio
  • aggiungere un parametro dropoutRate alla SGD sezione chiamata per definire la probabilità di eliminazione

In questa attività specifica specifica specificare nessun dropout per il primo periodo 1, seguito da un tasso di rilascio del 50%. Per informazioni su come eseguire questa operazione, vedere la Dropout() documentazione.

Se tutto è andato bene, si osserverà nessun cambiamento per la prima 1 epoca, ma un miglioramento molto minore di ce una volta a dropout calci in con la seconda epoca. Si tratta di un comportamento previsto. Per questa configurazione specifica, l'accuratezza del riconoscimento non migliora, in realtà. Il risultato finale quando l'allenamento ha circa 10 epoche è circa il 32%. 10 epoche non sono sufficienti per l'eliminazione.

Vedere la soluzione qui.

Attività 2: Semplificare la definizione del modello estraendo parti ripetitive in una funzione

Nell'esempio la sequenza (Convolution >> ReLU >> Pooling) viene ripetuta tre volte. L'attività consiste nel scrivere una funzione BrainScript che raggruppa queste tre operazioni in un modulo riutilizzabile. Si noti che tutti e tre gli usi di questa sequenza usano parametri diversi (dimensioni di output, peso di inizializzazione). Pertanto, la funzione scritta deve accettare questi due parametri, oltre ai dati di input. Ad esempio, potrebbe essere definito come

MyLayer (x, depth, initValueScale)

Quando si esegue questa operazione, si prevede che i valori e errs risultanti ce siano uguali. Tuttavia, se si esegue sulla GPU, non determinism nell'implementazione back-propagazione di cuDNN causerà variazioni minori.

Vedere la soluzione qui.

Attività 3: Aggiunta di BatchNormalization

Questa attività richiede una GPU, poiché l'implementazione della normalizzazione batch di CNTK è basata su cuDNN.

La normalizzazione batch è una tecnica popolare per velocizzare e migliorare la convergenza. In CNTK la normalizzazione batch viene implementata come BatchNormalizationLayer{}.

Il modulo spaziale (in cui tutte le posizioni pixel vengono normalizzate con parametri condivisi) viene richiamato da un parametro facoltativo: BatchNormalizationLayer{spatialRank=2}.

Aggiungere la normalizzazione batch a tutti e tre i livelli di convoluzione e tra i due livelli densi. Si noti che la normalizzazione batch deve essere inserita direttamente prima della non linearità. Di conseguenza, è necessario rimuovere il activation parametro e invece inserire chiamate esplicite alla funzione ReLU()CNTK .

Inoltre, la normalizzazione batch cambia la velocità di convergenza. Quindi, aumentare i tassi di apprendimento per i primi 7 periodi 3 volte e disabilitare la regolarizzazione e L2 impostando i parametri su 0.

Quando si esegue, verranno visualizzati altri parametri appresi elencati nel log di training. Il risultato finale sarà intorno al 28%, ovvero 4 punti migliori rispetto a senza normalizzazione batch dopo lo stesso numero di iterazioni. La convergenza accelera effettivamente.

Vedere la soluzione qui.

Attività 4: Convertire in rete rimanente

La configurazione precedente è un esempio "toy" per ottenere le mani sporche con l'esecuzione e la modifica di configurazioni CNTK e non è stato intenzionalmente eseguito a una convergenza completa per mantenere bassi i tempi di turnaround. Quindi ora passiamo avanti a una configurazione più reale--a Residual Net. Residual Net (https://arxiv.org/pdf/1512.03385v1.pdf) è una struttura di rete profonda modificata in cui i livelli, anziché imparare un mapping dall'input all'output, apprendere un termine di correzione.

Questa attività richiede anche una GPU per l'operazione di normalizzazione batch, anche se si dispone di un sacco di tempo, è possibile provare a eseguirla in una CPU modificando le chiamate di normalizzazione batch, in caso di perdita di accuratezza.

Per iniziare, modificare la configurazione precedente. Sostituire prima di tutto la funzione model(features) del modello con questa:

        MySubSampleBN (x, depth, stride) =
        {
            s = Splice ((MaxPoolingLayer {(1:1), stride = (stride:stride)} (x) : ConstantTensor (0, (1:1:depth/stride))), axis = 3)  # sub-sample and pad: [W x H x depth/2] --> [W/2 x H/2 x depth]
            b = BatchNormalizationLayer {spatialRank = 2, normalizationTimeConstant = 4096} (s)
        }.b
        MyConvBN (x, depth, initValueScale, stride) =
        {
            c = ConvolutionalLayer {depth, (3:3), pad = true, stride = (stride:stride), bias = false,
                                    init = "gaussian", initValueScale = initValueScale} (x)
            b = BatchNormalizationLayer {spatialRank = 2, normalizationTimeConstant = 4096} (c)
        }.b
        ResNetNode (x, depth) =
        {
            c1 = MyConvBN (x,  depth, 7.07, 1)
            r1 = ReLU (c1)
            c2 = MyConvBN (r1, depth, 7.07, 1)
            r  = ReLU (c2)
        }.r
        ResNetIncNode (x, depth) =
        {
            c1 = MyConvBN (x,  depth, 7.07, 2)  # note the 2
            r1 = ReLU (c1)
            c2 = MyConvBN (r1, depth, 7.07, 1)
            r  = ReLU (c2)
        }.r
        model (features) =
        {
            conv1 = ReLU (MyConvBN (features, 16, 0.26, 1))
            rn1   = ResNetNode (ResNetNode (ResNetNode (conv1, 16), 16), 16)

            rn2_1 = ResNetIncNode (rn1, 32)
            rn2   = ResNetNode (ResNetNode (rn2_1, 32), 32)

            rn3_1 = ResNetIncNode (rn2, 64)
            rn3   = ResNetNode (ResNetNode (rn3_1, 64), 64)

            pool = AveragePoolingLayer {(8:8)} (rn3)

            z = LinearLayer {labelDim, init = "gaussian", initValueScale = 0.4} (pool)
        }.z

e modificare la configurazione SGD in:

SGD = {
    epochSize = 50000

    maxEpochs = 160 ; minibatchSize = 128
    learningRatesPerSample = 0.0078125*80:0.00078125*40:0.000078125
    momentumAsTimeConstant = 1200
    L2RegWeight = 0.0001

    firstMBsToShowResult = 10 ; numMBsToShowResult = 500
}

L'attività consiste nel modificare ResNetNode() e ResNetNodeInc() in modo da implementare la struttura disposta nell'arte ASCII seguente:

            ResNetNode                   ResNetNodeInc

                |                              |
         +------+------+             +---------+----------+
         |             |             |                    |
         V             |             V                    V
    +----------+       |      +--------------+   +----------------+
    | Conv, BN |       |      | Conv x 2, BN |   | SubSample, BN  |
    +----------+       |      +--------------+   +----------------+
         |             |             |                    |
         V             |             V                    |
     +-------+         |         +-------+                |
     | ReLU  |         |         | ReLU  |                |
     +-------+         |         +-------+                |
         |             |             |                    |
         V             |             V                    |
    +----------+       |        +----------+              |
    | Conv, BN |       |        | Conv, BN |              |
    +----------+       |        +----------+              |
         |             |             |                    |
         |    +---+    |             |       +---+        |
         +--->| + |<---+             +------>+ + +<-------+
              +---+                          +---+
                |                              |
                V                              V
            +-------+                      +-------+
            | ReLU  |                      | ReLU  |
            +-------+                      +-------+
                |                              |
                V                              V

Confermare con l'output di convalida nel log a cui è stato eseguito correttamente.

Questa operazione richiederà molto tempo per il completamento. L'output previsto sarà simile al seguente:

Finished Epoch[ 1 of 160]: [Training] ce = 1.57037109 * 50000; errs = 58.940% * 50000
Finished Epoch[ 2 of 160]: [Training] ce = 1.06968234 * 50000; errs = 38.166% * 50000
Finished Epoch[ 3 of 160]: [Training] ce = 0.85858969 * 50000; errs = 30.316% * 50000

mentre il modello non corretto senza le connessioni ignora è più simile al seguente:

Finished Epoch[ 1 of 160]: [Training] ce = 1.72901219 * 50000; errs = 66.232% * 50000
Finished Epoch[ 2 of 160]: [Training] ce = 1.30180430 * 50000; errs = 47.424% * 50000
Finished Epoch[ 3 of 160]: [Training] ce = 1.04641961 * 50000; errs = 37.568% * 50000

Vedere la soluzione qui.

Attività 5: Generazione automatica di molti livelli

Infine, resnet con prestazioni ottimali ha 152 livelli. Poiché sarebbe molto noioso ed errore soggetto a scrittura di 152 singole espressioni, verrà ora modificata la definizione per generare automaticamente uno stack di ResNetNode().

L'attività consiste nel scrivere una funzione con questa firma:

ResNetNodeStack (x, depth, L)

dove L indica quanti ResNetNodes devono essere in pila, in modo che sia possibile sostituire l'espressione per rn1 sopra con una chiamata con parametri:

rn1   = ResNetNodeStack (conv1, 16, 3)  # 3 means 3 such nodes

e analogamente per rn2 e rn3. Gli strumenti necessari sono l'espressione condizionale :

z = if cond then x else y

e ricorsione.

Questo training verrà eseguito per circa mezzo nostro su un Titan-X. Se l'hai fatto correttamente, all'inizio del log conterrà questo messaggio:

Training 200410 parameters in 51 out of 51 parameter tensors and 112 nodes with gradient:

Per informazioni di riferimento, è inclusa una versione pre-training di questo modello. È possibile misurare la frequenza di errore con questo comando:

cntk  configFile=ImageHandsOn.ResNet.cntk  command=Eval

e dovrebbe visualizzare un risultato simile al seguente:

Final Results: Minibatch[1-625]: errs = 8.400% * 10000; top5Errs = 0.290% * 10000

Questa frequenza di errore è molto vicina a quella segnalata nel documento ResNet originale (https://arxiv.org/pdf/1512.03385v1.pdftabella 6).

Vedere la soluzione qui.

Attività 6: Training parallelo

Infine, se sono presenti più GPU, CNTK consente di parallelizzare il training usando MPI (Interfaccia di passaggio messaggi). Questo modello è troppo piccolo da aspettare molto velocità senza ulteriore ottimizzazione, ad esempio di dimensioni minibatch (l'impostazione corrente minibatch-size è troppo piccola per ottenere l'utilizzo completo dei core GPU disponibili). Tuttavia, passiamo attraverso i movimenti, in modo da sapere come farlo una volta che si passa ai carichi di lavoro reali.

Aggiungere la riga seguente al SGD blocco:

SGD = {
    ...
    parallelTrain = {
        parallelizationMethod = "DataParallelSGD"
        parallelizationStartEpoch = 2
        distributedMBReading = true
        dataParallelSGD = { gradientBits = 1 }
    }
}

e quindi eseguire questo comando:

mpiexec -np 4 cntk  configFile=ImageHandsOn.cntk  stderr=Models/log  parallelTrain=true

in ordine di priorità

Questa esercitazione ha praticato per eseguire una configurazione esistente e modificarla in modi specifici:

  • aggiunta di un'operazione predefinita (dropout)
  • estrazione di parti ripetitive in moduli riutilizzabili (funzioni)
  • refactoring (per inserire la normalizzazione batch)
  • strutture di rete personalizzate (ignora connessione ResNet)
  • parametrizzazione di strutture ripetitive tramite ricorsione

e abbiamo visto come velocizzare il training in parallelo.

Dove andiamo da qui? Potrebbe essere già stato scoperto che il modello usato in questi esempi, che chiamiamo stile di compilazione grafico , può essere abbastanza soggetto a errori. Individuare l'errore?

model (features) =
{
    l1 = ConvolutionalLayer {32, (5:5), pad = true, activation = ReLU,
                             init = "gaussian", initValueScale = 0.0043} (featNorm)
    p1 = MaxPoolingLayer {(3:3), stride = (2:2)} (l1)
    l2 = ConvolutionalLayer {64, (5:5), pad = true, activation = ReLU,
                             init = "gaussian", initValueScale = 1.414} (p1)
    p2 = MaxPoolingLayer {(3:3), stride = (2:2)} (l1)
    d1 = DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} (p2)
    z  = LinearLayer {10, init = "gaussian", initValueScale = 1.5} (d1)
}.z

Un modo per evitare questo errore consiste nell'usare la composizione delle funzioni. Di seguito è riportato un modo più conciso di scrivere lo stesso:

model = Sequential (
    ConvolutionalLayer {32, (5:5), pad = true, activation = ReLU,
                        init = "gaussian", initValueScale = 0.0043} :
    MaxPoolingLayer {(3:3), stride = (2:2)} :
    ConvolutionalLayer {64, (5:5), pad = true, activation = ReLU,
                        init = "gaussian", initValueScale = 1.414} :
    MaxPoolingLayer {(3:3), stride = (2:2)} :
    DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} :
    LinearLayer {10, init = "gaussian", initValueScale = 1.5}
)

Questo stile verrà introdotto e usato nell'esercitazione pratica successiva, Analisi del testo con reti ricorrenti.

Soluzioni

Soluzione 1: Aggiunta dell'elenco a discesa

Modificare la definizione del modello come indicato di seguito:

p3 = MaxPoolingLayer {(3:3), stride = (2:2)} (l3)
d1 = DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} (p3)
d1_d = Dropout (d1)    ##### added
z  = LinearLayer {10, init = "gaussian", initValueScale = 1.5} (d1_d)  ##### d1 -> d1_d

e la sezione SGD:

SGD = {
    ...
    dropoutRate = 0*5:0.5   ##### added
    ...
}

Soluzione 2: Semplificare la definizione del modello estraendo parti ripetitive in una funzione

Aggiungere una definizione di funzione come indicato di seguito:

MyLayer (x, depth, initValueScale) =
{
    c = ConvolutionalLayer {depth, (5:5), pad = true, activation = ReLU,
                            init = "gaussian", initValueScale = initValueScale} (x)
    p = MaxPoolingLayer {(3:3), stride = (2:2)} (c)
}.p

e aggiornare la definizione del modello per usarla

featNorm = features - Constant (128)
p1 = MyLayer (featNorm, 32, 0.0043)  ##### replaced
p2 = MyLayer (p1,       32, 1.414)   ##### replaced
p3 = MyLayer (p2,       64, 1.414)   ##### replaced
d1 = DenseLayer {64, activation = ReLU, init = "gaussian", initValueScale = 12} (p3)

Soluzione 3: Aggiunta di BatchNormalization

Modificare MyLayer():

MyLayer (x, depth, initValueScale) =
{
    c = ConvolutionalLayer {depth, (5:5), pad = true,  ##### no activation=ReLU
                            init = "gaussian", initValueScale = initValueScale} (x)
    b = BatchNormalizationLayer {spatialRank = 2} (c)
    r = ReLU (b)   ##### now called explicitly
    p = MaxPoolingLayer {(3:3), stride = (2:2)} (r)
}.p

e usarlo. Inserire anche la normalizzazione batch prima zdi :

d1 = DenseLayer {64, init = "gaussian", initValueScale = 12} (p3)
d1_bnr = ReLU (BatchNormalizationLayer {} (d1))  ##### added BN and explicit ReLU
d1_d = Dropout (d1_bnr)                          ##### d1 -> d1_bnr
z  = LinearLayer {10, init = "gaussian", initValueScale = 1.5} (d1_d)

Aggiornare questi parametri nella sezione SGD:

SGD = {
    ....
    learningRatesPerSample = 0.00046875*7:0.00015625*10:0.000046875*10:0.000015625
    momentumAsTimeConstant = 0
    L2RegWeight = 0
    ...
}

Soluzione 4: Convertire in rete rimanente

Le implementazioni corrette per ResNetNode() e ResNetNodeInc() sono:

    ResNetNode (x, depth) =
    {
        c1 = MyConvBN (x,  depth, 7.07, 1)
        r1 = ReLU (c1)
        c2 = MyConvBN (r1, depth, 7.07, 1)
        r  = ReLU (x + c2)   ##### added skip connection
    }.r
    ResNetIncNode (x, depth) =
    {
        c1 = MyConvBN (x,  depth, 7.07, 2)  # note the 2
        r1 = ReLU (c1)
        c2 = MyConvBN (r1, depth, 7.07, 1)

        xs = MySubSampleBN (x, depth, 2)

        r  = ReLU (xs + c2)   ##### added skip connection
    }.r

Soluzione 5: generazione automatica di molti livelli

Questa è l'implementazione:

    ResNetNodeStack (x, depth, L) =
    {
        r = if L == 0
            then x
            else ResNetNode (ResNetNodeStack (x, depth, L-1), depth)
    }.r

o, più breve:

    ResNetNodeStack (x, depth, L) =
        if L == 0
        then x
        else ResNetNode (ResNetNodeStack (x, depth, L-1), depth)

È anche necessario modificare la funzione del modello:

        conv1 = ReLU (MyConvBN (features, 16, 0.26, 1))
        rn1   = ResNetNodeStack (conv1, 16, 3)  ##### replaced

        rn2_1 = ResNetIncNode (rn1, 32)
        rn2   = ResNetNodeStack (rn2_1, 32, 2)  ##### replaced

        rn3_1 = ResNetIncNode (rn2, 64)
        rn3   = ResNetNodeStack (rn3_1, 64, 2)  ##### replaced