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:
ImageHandsOn.cntk
: Den CNTK konfigurationsfil som vi kommer att introducera nedan och arbeta med.cifar10.pretrained.cmf
: Resulterande modell för konfigurationen som vi börjar med.cifar10.ResNet.cmf
: Resulterande modell för den ResNet-version som vi skapar nedan.
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 (command
variabel) 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 Eval
fortsä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 avsnittetSGD
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