Dela via


Praktisk labbavbildningsigenkänning

Observera att den här självstudien kräver den senaste huvudversionen eller den kommande CNTK 1.7 som snart släpps. En mellanliggande binär nedladdning finns i anvisningarna för KDD-CNTK Hands-On Självstudie som den här självstudien ursprungligen utformades för.

Hands-On Lab: Bildigenkänning med Convolutional Networks, Batch Normalization och Residual Nets

Det här praktiska labbet visar hur du implementerar convolution-baserad bildigenkänning med CNTK. Vi börjar med en gemensam konvolutional bildigenkänningsarkitektur, lägger till Batch Normalization och utökar den sedan till ett residualnätverk (ResNet-20).

De tekniker som du kommer att öva på är:

  • ändra en CNTK nätverksdefinition för att lägga till en fördefinierad åtgärd (listruta)
  • skapa användardefinierade funktioner för att extrahera repetitiva delar i ett nätverk till en återanvändbar modul
  • implementera en anpassad nätverksstruktur (Hoppa över anslutningen till ResNet)
  • skapa många lager samtidigt med rekursiva loopar
  • parallell träning
  • convolutional nätverk
  • batchnormalisering

Förutsättningar

Vi antar att du redan har installerat CNTK och kan köra kommandot CNTK. Den här självstudien hölls på KDD 2016 och kräver en ny version, se här för installationsinstruktioner. Du kan bara följa anvisningarna för att ladda ned ett binärt installationspaket från den sidan. För avbildningsrelaterade uppgifter bör du göra detta på en dator med en kompatibel CUDA-kompatibel GPU.

Ladda sedan ned ett ZIP-arkiv (cirka 12 MB): Klicka på den här länken och sedan på knappen Ladda ned. Arkivet innehåller filerna för den här självstudien. Arkivera och ställ in arbetskatalogen på ImageHandsOn. Du kommer att arbeta med följande filer:

Slutligen måste vi ladda ned och konvertera CIFAR-10-datauppsättningen. Konverteringssteget tar cirka 10 minuter. Kör följande två Python skript som du även hittar i arbetskatalogen:

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

Detta konverterar bilderna till PNG-filer, 50000 för träning och 10000 för testning, som placeras i följande två kataloger, respektive: cifar-10-batches-py/data/train och cifar-10-batches-py/data/test.

Modellstruktur

Vi startar den här självstudien med en enkel convolutional-modell. Den består av 3 lager med 5x5-faltningar + icke-linjäritet + 2x dimensionsreduktion med 3x3 maxpooler, som sedan följs av ett tätt dolt lager och en tät transformering för att bilda indata till en 10-vägs softmax-klassificerare.

Eller som en CNTK nätverksbeskrivning. Ta en snabb titt och matcha den med beskrivningen ovan:

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)

Mer information om dessa operatorer finns här: ConvolutionalLayer{}, MaxPoolingLayer{}, DenseLayer{}, LinearLayer{}.

CNTK konfiguration

Konfigurationsfil

För att träna och testa en modell i CNTK måste vi tillhandahålla en konfigurationsfil som talar om för CNTK vilka åtgärder du vill köra (commandvariabel) och ett parameteravsnitt för varje kommando.

För träningskommandot måste CNTK få veta följande:

  • läsa data (reader avsnitt)
  • modellfunktionen och dess indata och utdata i beräkningsdiagrammet (BrainScriptNetworkBuilder avsnittet)
  • hyperparametrar för learner (SGD avsnitt)

För utvärderingskommandot behöver CNTK veta:

  • läsa testdata (reader avsnitt)
  • vilka mått som ska utvärderas (evalNodeNames parameter)

Följande är konfigurationsfilen som vi börjar med. Som du ser är en CNTK konfigurationsfil en textfil som består av definitioner av parametrar, som är ordnade i en hierarki med poster. Du kan också se hur CNTK stöder grundläggande parameterersättning med hjälp av syntaxen$parameterName$. Den faktiska filen innehåller bara några fler parametrar än vad som nämns ovan, men genomsök den och leta reda på de konfigurationsobjekt som just nämnts:

# 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 }
            }
        })
    }
}

Data- och dataläsning

När du har laddat ned CIFAR-10-data och kört skriptet CifarConverter.py som begärdes i början av den här självstudien hittar du en katalog med namnet cifar-10-batches-py/data, som innehåller två underkataloger train och test, full av PNG-filer. CNTK ImageDeserializer använder standardbildformat.

Du hittar också två filer train_map.txt och test_map.txt. Om man tittar på det senare,

% 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
...

Båda filerna består av två kolumner, där den första innehåller sökvägen till bildfilen och den andra klassetiketten som ett numeriskt index. Dessa kolumner motsvarar läsarens indata features och labels som har definierats som:

 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 }

I det ytterligare transforms avsnittet uppmanas ImageDeserializer att tillämpa en sekvens med (vanliga) transformeringar på bilderna när de läss. Mer information finns här.

Köra den

Du hittar konfigurationsfilen ovan under namnet ImageHandsOn.cntk i arbetsmappen. Kör den genom att köra konfigurationen ovan med det här kommandot:

cntk  configFile=ImageHandsOn.cntk

Skärmen kommer att bli levande med en mängd loggmeddelanden (CNTK kan vara pratsamma ibland), men om allt gick rätt kommer du snart att se detta:

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

följt av utdata så här:

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

Detta talar om för dig att det är lärande. Varje epok representerar en genomströmning av träningsbilderna på 50 000. Den visar också att efter den andra epoken har träningskriteriet, som konfigurationen med namnet ce, nått 1,33 mätt på de 5 0000 exemplen på denna epok, och att felfrekvensen är 47 % på samma träningsexempel på 5 0000.

Observera att datorer som endast är processorer är ungefär 20 gånger långsammare. Det tar många minuter tills du ser de första loggutdata. För att se till att systemet fortskrider kan du aktivera spårning för att se partiella resultat, vilket bör visas relativt snabbt:

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

När träningen har slutförts (vilket tar cirka 3 minuter på en Surface Book och på en stationär dator med en Titan-X GPU), kommer det slutliga meddelandet att likna detta:

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

som visar att nätverket har minskat ce förlusten och nått ett klassificeringsfel på 25,5 % på träningsuppsättningen. Eftersom vår command variabel anger ett andra kommando Evalfortsätter CNTK sedan med den åtgärden. Den mäter klassificeringsfelfrekvensen på 10 000 bilder av testuppsättningen.

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

Felfrekvensen för testning ligger nära träningen. Eftersom CIFAR-10 är en ganska liten datamängd är detta en indikator på att vår modell ännu inte har konvergerat helt (och faktum är att om du kör den i 30 epoker kommer du till cirka 20 %).

Om du inte vill vänta tills detta har slutförts kan du köra en mellanliggande modell, t.ex.

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

eller kör vår förtränade modell också:

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

Ändra modellen

I följande avsnitt får du uppgifter för att öva på att ändra CNTK konfigurationer. Lösningarna ges i slutet av det här dokumentet... men försök utan!

Uppgift 1: Lägga till listruta

En vanlig metod för att förbättra generaliserbarheten för modeller är dropout. Om du vill lägga till en listruta i en CNTK modell behöver du

  • lägg till ett anrop till funktionen Dropout() CNTK där du vill infoga listrutan
  • lägg till en parameter dropoutRate i avsnittet SGD som anropas för att definiera sannolikheten för avhopp

I den här specifika aktiviteten anger du ingen avhopp för den första 1 epoken, följt av en avhoppsfrekvens på 50 %. Ta en titt på dokumentationen Dropout() för att se hur du gör det.

Om allt gick bra kommer du inte att observera någon förändring för de första 1 epoken, men en mycket mindre förbättring av ce när dropout sparkar in med den andra epoken. Detta är förväntat. (För den här specifika konfigurationen förbättras faktiskt inte igenkänningsprecisionen.) Slutresultatet när endast 10 epoker tränas är cirka 32 %. 10 epoker räcker inte för dropout.

Se lösningen här.

Uppgift 2: Förenkla modelldefinitionen genom att extrahera repetitiva delar till en funktion

I exemplet upprepas sekvensen (Convolution >> ReLU >> Pooling) tre gånger. Din uppgift är att skriva en BrainScript-funktion som grupperar dessa tre åtgärder i en återanvändbar modul. Observera att alla tre användningarna av den här sekvensen använder olika parametrar (utdatadimensioner, initieringsvikt). Den funktion som du skriver bör därför ta dessa två som parametrar, utöver indata. Det kan till exempel definieras som

MyLayer (x, depth, initValueScale)

När du kör detta förväntar du dig att de resulterande ce värdena och errs är desamma. Men om du kör på GPU:n orsakar icke-determinism i cuDNN:s implementering av bakåtspridning mindre variationer.

Se lösningen här.

Uppgift 3: Lägga till BatchNormalization

(Den här uppgiften kräver en GPU, eftersom CNTK implementering av batchnormalisering baseras på cuDNN.)

Batchnormalisering är en populär teknik för att påskynda och förbättra konvergensen. I CNTK implementeras batchnormalisering som BatchNormalizationLayer{}.

Den rumsliga formen (där alla pixelpositioner normaliseras med delade parametrar) anropas av en valfri parameter: BatchNormalizationLayer{spatialRank=2}.

Lägg till batchnormalisering i alla tre faltningsskikten och mellan de två kompakta lagren. Observera att batchnormalisering ska infogas precis före icke-linjäriteten. Därför måste du ta bort parametern activation och i stället infoga explicita anrop till funktionen CNTK ReLU().

Dessutom ändrar batchnormalisering konvergenshastigheten. Så låt oss öka inlärningstakten för de första 7 epokerna 3-faldig och inaktivera momentum och L2-regularisering genom att ange deras parametrar till 0.

När du kör visas ytterligare inlärningsparametrar i träningsloggen. Slutresultatet blir cirka 28 %, vilket är 4 punkter bättre än utan batchnormalisering efter samma antal iterationer. Konvergensen ökar verkligen.

Se lösningen här.

Uppgift 4: Konvertera till residualnät

Ovanstående konfiguration är ett "leksaksexempel" för att få händerna smutsiga med att köra och ändra CNTK konfigurationer, och vi körde avsiktligt inte till fullständig konvergens för att hålla vändningstiderna för dig låga. Så låt oss nu gå vidare till en mer verklig konfiguration - ett residualnät. Residualnätet (https://arxiv.org/pdf/1512.03385v1.pdf) är en modifierad djup nätverksstruktur där lager, i stället för att lära sig en mappning från indata till utdata, lär sig en korrigeringsterm.

(Den här uppgiften kräver också en GPU för batchnormaliseringsåtgärden, men om du har mycket tid kan du prova att köra den på en CPU genom att redigera anropen batchnormalisering, med viss noggrannhetsförlust.)

Kom igång genom att ändra den tidigare konfigurationen. Ersätt först modellfunktionen model(features) med den här:

        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

och ändra SGD-konfigurationen till:

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
}

Din uppgift är att ändra ResNetNode() och ResNetNodeInc() så att de implementerar den struktur som anges i följande prisbelönta ASCII-konst:

            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

Bekräfta med valideringsutdata i loggen att du har gjort det rätt.

Det tar lång tid att slutföra. Förväntade utdata ser ut ungefär så här:

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

Den felaktiga modellen utan hoppa över anslutningar ser mer ut så här:

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

Se lösningen här.

Uppgift 5: Generera automatiskt många lager

Slutligen har det bäst presterande ResNet 152 lager. Eftersom det skulle vara mycket omständligt och felbenäget att skriva 152 enskilda uttryck, kommer vi nu att ändra definitionen för att automatiskt generera en stack med ResNetNode().

Din uppgift är att skriva en funktion med den här signaturen:

ResNetNodeStack (x, depth, L)

där L anger hur många ResNetNodes som ska staplas, så att vi kan ersätta uttrycket för rn1 ovan med ett parametriserat anrop:

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

och på samma sätt för rn2 och rn3. De verktyg du behöver är villkorsuttrycket :

z = if cond then x else y

och rekursion.

Den här träningen kommer att köras i ungefär hälften av vår på en Titan-X. Om du gjorde det rätt kommer tidigt i loggen att innehålla det här meddelandet:

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

Som referens inkluderar vi en förtränad version av den här modellen. Du kan mäta felfrekvensen med det här kommandot:

cntk  configFile=ImageHandsOn.ResNet.cntk  command=Eval

och bör se ett resultat som det här:

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

Den här felfrekvensen ligger mycket nära den som rapporterades i det ursprungliga ResNet-dokumentet (https://arxiv.org/pdf/1512.03385v1.pdftabell 6).

Se lösningen här.

Uppgift 6: Parallell träning

Om du har flera GPU:er kan du CNTK parallellisera träningen med hjälp av MPI (Message-Passing Interface). Den här modellen är för liten för att förvänta sig mycket snabbare utan ytterligare justering, t.ex. av minibatchstorlekar (den aktuella minibatch-storleksinställningen är för liten för att få full användning av tillgängliga GPU-kärnor). Låt oss ändå gå igenom rörelserna, så att du vet hur du ska göra det när du går vidare till verkliga arbetsbelastningar.

Lägg till följande rad i SGD blocket:

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

och kör sedan det här kommandot:

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

Vad är nästa steg?

Den här självstudien har övat på att ta en befintlig konfiguration och ändra den på specifika sätt:

  • lägga till en fördefinierad åtgärd (listruta)
  • extrahera repetitiva delar till återanvändbara moduler (funktioner)
  • refaktorisering (för att infoga batchnormalisering)
  • anpassade nätverksstrukturer (hoppa över anslutningen till resnet)
  • parametrisera repetitiva strukturer med rekursion

och vi har sett hur man påskyndar träningen genom parallellisering.

Så vart ska vi gå härifrån? Du kanske redan har upptäckt att mönstret som används i de här exemplen, som vi kallar grafskapande stil, kan vara ganska felbenäget. Upptäcker du felet?

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

Ett sätt att undvika det här felet är att använda funktionssammansättning. Följande är ett alternativt, mer koncist sätt att skriva på samma sätt:

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}
)

Det här formatet kommer att introduceras och användas i nästa praktiska självstudie, Text Understanding with Recurrent Networks (Text Understanding with Recurrent Networks).

Lösningar

Lösning 1: Lägga till listruta

Ändra modelldefinitionen på följande sätt:

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

och SGD-avsnittet:

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

Lösning 2: Förenkla modelldefinitionen genom att extrahera repetitiva delar till en funktion

Lägg till en funktionsdefinition på följande sätt:

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

och uppdatera modelldefinitionen så att den används

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)

Lösning 3: Lägga till BatchNormalization

Ändra 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

och använd den. Infoga även batchnormalisering före z:

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)

Och uppdatera dessa parametrar i avsnittet SGD:

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

Lösning 4: Konvertera till residualnät

Rätt implementeringar för ResNetNode() och ResNetNodeInc() är:

    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

Lösning 5: Generera automatiskt många lager

Det här är implementeringen:

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

eller kortare:

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

Du måste också ändra modellfunktionen:

        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