Reconhecimento de imagem prático dos laboratórios
Note que este tutorial requer a versão master mais recente, ou o próximo CNTK 1.7 que será lançado em breve. Um download binário intermédio pode ser encontrado nas instruções para o KDD CNTK Hands-On Tutorial para o quais este tutorial foi originalmente concebido para.
Hands-On Lab: Reconhecimento de imagem com redes convolucionais, normalização de lotes e redes residuais
Este laboratório prático mostra como implementar o reconhecimento de imagem baseado em convolução com CNTK. Começaremos com uma arquitetura comum de reconhecimento de imagem convolucional, adicionamos a Normalização do Lote e, em seguida, alargá-la-emos numa Rede Residual (ResNet-20).
As técnicas que vai praticar incluem:
- modificando uma definição de rede CNTK para adicionar uma operação predefinida (abandono)
- criando funções definidas pelo utilizador para extrair peças repetitivas numa rede num módulo reutilizável
- implementação de estruturas de rede personalizadas (ligação de salto ResNet)
- criando muitas camadas ao mesmo tempo usando laços recursivos
- formação paralela
- redes convolucional
- normalização do lote
Pré-requisitos
Assumimos que já instalou CNTK e pode executar o comando CNTK. Este tutorial foi realizado na KDD 2016 e requer uma construção recente, por favor consulte aqui para instruções de configuração. Basta seguir as instruções para descarregar um pacote de instalação binário a partir dessa página. Para tarefas relacionadas com a imagem deve fazê-lo numa máquina com uma GPU compatível com CUDA capaz.
Em seguida, descarregue um arquivo ZIP (cerca de 12 MB): Clique neste link e, em seguida, no botão Descarregar.
O arquivo contém os ficheiros deste tutorial. Por favor, o arquivo e desemote o seu diretório de trabalho para ImageHandsOn
.
Estará a trabalhar com os seguintes ficheiros:
ImageHandsOn.cntk
: O ficheiro de configuração CNTK que iremos introduzir abaixo e trabalharemos com.cifar10.pretrained.cmf
: Resultante modelo da configuração que iremos começar.cifar10.ResNet.cmf
: Resultante modelo da versão ResNet que iremos criar abaixo.
Por último, temos de descarregar e converter o conjunto de dados CIFAR-10. O passo de conversão levará cerca de 10 minutos. Execute os seguintes dois scripts Python que também encontrará no diretório de trabalho:
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
Isto converte as imagens em ficheiros PNG, 50000 para treino e 10000 para testes, que serão colocados nos dois diretórios seguintes, respectivamente: cifar-10-batches-py/data/train
e cifar-10-batches-py/data/test
.
Estrutura modelo
Vamos começar este tutorial com uma simples modelo convolucional. Consiste em 3 camadas de convoluções 5x5 + não linearidade + redução da dimensão 2x por 3x3 max-pooling, que são seguidas por uma camada escondida densa e uma transformação densa para formar a entrada para um classificador softmax de 10 vias.
Ou, como uma CNTK descrição da rede. Por favor, dê uma olhada rápida e combine com a descrição acima:
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)
Pode encontrar mais informações sobre estes operadores aqui: ConvolutionalLayer{}
, MaxPoolingLayer{}
, . DenseLayer{}
. . LinearLayer{}
Configuração CNTK
Arquivo Config
Para treinar e testar uma modelo em CNTK, precisamos de fornecer um ficheiro de configuração que diga CNTK que operações pretende executar (command
variável) e uma secção de parâmetros para cada comando.
Para o comando de treino, CNTK precisa de ser informado:
- como ler os dados (
reader
secção) - a função modelo e as suas entradas e saídas no gráfico de cálculo (
BrainScriptNetworkBuilder
secção) - hiper-parâmetros para o aluno (
SGD
secção)
Para o comando de avaliação, CNTK precisa de saber:
- como ler os dados do teste (
reader
secção) - que métricas para avaliar (
evalNodeNames
parâmetro)
Segue-se o ficheiro de configuração com que começaremos. Como vê, um ficheiro de configuração CNTK é um ficheiro de texto constituído por definições de parâmetros, que são organizados numa hierarquia de registos. Também pode ver como CNTK suporta a substituição básica dos parâmetros utilizando a $parameterName$
sintaxe. O ficheiro real contém apenas mais alguns parâmetros do que mencionados acima, mas por favor digitalize-o e localize os itens de configuração mencionados:
# 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 }
}
})
}
}
Leitura de Dados e Dados
Depois de ter descarregado os dados do CIFAR-10 e executado o CifarConverter.py
script como solicitado no início deste tutorial, encontrará um diretório chamado cifar-10-batches-py/data
, que contém duas subdiretorias, train
e test
cheio de ficheiros PNG.
O CNTK ImageDeserializer
consome formatos de imagem padrão.
Também encontrará dois ficheiros train_map.txt
e test_map.txt
. Olhando para este último,
% 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
...
Ambos os ficheiros consistem em duas colunas, onde a primeira contém o caminho para o ficheiro de imagem, e a segunda a etiqueta de classe, como um índice numérico. Estas colunas correspondem às entradas features
do leitor e labels
que foram definidas como:
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 }
A secção adicional transforms
diz para ImageDeserializer
aplicar uma sequência de (comum) transforma-se nas imagens à medida que estão a ser lidas.
Para mais informações, consulte aqui.
Executando-o
Pode encontrar o ficheiro de configuração acima no nome ImageHandsOn.cntk
da pasta de trabalho.
Para executá-lo, execute a configuração acima por este comando:
cntk configFile=ImageHandsOn.cntk
O seu ecrã ganhará vida com uma onda de mensagens de registo (CNTK pode ser falador às vezes), mas se tudo correr bem, logo verá isto:
Training 116906 parameters in 10 out of 10 parameter tensors and 28 nodes with gradient
seguido por uma produção como esta:
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
Isto diz-te que está a aprender. Cada época representa uma passagem através das 50000 imagens de treino.
Diz-lhe também que após a segunda época, o critério de formação, a que a configuração nomeou ce
, atingiu 1,33 como medido nas 50000 amostras desta época, e que a taxa de erro é de 47% nas mesmas 50000 amostras de treino.
Note que as máquinas só com CPU são cerca de 20 vezes mais lentas. Levará muitos minutos até ver a primeira saída de log. Para garantir que o sistema está a progredir, pode permitir rastrear para ver resultados parciais, que devem aparecer razoavelmente 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
Quando o treino estiver concluído (que leva cerca de 3 minutos numa Surface Book e numa máquina de desktop com uma GPU Titan-X), a mensagem final será semelhante a esta:
Finished Epoch[10 of 10]: [Training] ce = 0.74679766 * 50000; errs = 25.486% * 50000
o que mostra que a rede reduziu com sucesso a ce
perda, e atingiu um erro de classificação de 25,5% no conjunto de treino. Como a nossa command
variável especifica um segundo comandoEval
, CNTK seguirão com essa ação. Mede a taxa de erro de classificação nas imagens 10000 do conjunto de testes.
Final Results: Minibatch[1-625]: errs = 24.180% * 10000
A taxa de erro de teste está próxima do treino. Uma vez que o CIFAR-10 é um conjunto de dados bastante pequeno, este é um indicador de que o nosso modelo ainda não convergiu totalmente (e, na verdade, executá-lo por 30 épocas vai levá-lo a cerca de 20%).
Se não quiser esperar até que isto termine, pode executar uma modelo intermédia, por exemplo.
cntk configFile=ImageHandsOn.cntk command=Eval modelPath=Models/cifar10.cmf.5
Final Results: Minibatch[1-625]: errs = 31.710% * 10000
ou executar a nossa modelo pré-treinada também:
cntk configFile=ImageHandsOn.cntk command=Eval modelPath=cifar10.pretrained.cmf
Final Results: Minibatch[1-625]: errs = 24.180% * 10000
Modificar o Modelo
No seguinte, serão-lhe dadas tarefas para praticar a modificação das configurações CNTK. As soluções são dadas no final deste documento... mas, por favor, tente sem!
Tarefa 1: Adicionar Abandono
Uma técnica comum para melhorar a generalização dos modelos é o abandono. Para adicionar abandono a um CNTK modelo, você precisa
- adicionar uma chamada à função
Dropout()
CNTK onde pretende inserir a operação de abandono - adicionar um parâmetro
dropoutRate
àSGD
secção chamada para definir a probabilidade de abandono
Nesta tarefa específica, por favor especifique nenhum abandono para a primeira época de 1, seguido de uma taxa de abandono de 50%.
Por favor, dê uma olhada na Dropout()
documentação para ver como fazê-lo.
Se tudo correr bem, não se notará qualquer alteração para a primeira época de 1, mas uma melhoria muito menor de ce
uma vez que o abandono começa com a segunda época. Isto era esperado. (Para esta configuração específica, a precisão do reconhecimento não melhora, na verdade.) O resultado final quando se treina apenas 10 épocas é de cerca de 32%.
10 épocas não são suficientes para desistir.
Por favor, veja a solução aqui.
Tarefa 2: Simplificar a definição do modelo extraindo peças repetitivas numa função
No exemplo, a sequência (Convolution >> ReLU >> Pooling) é repetida três vezes. A sua tarefa é escrever uma função BrainScript que agruta estas três operações num módulo reutilizável. Note que as três utilizações desta sequência utilizam parâmetros diferentes (dimensões de saída, peso de inicialização). Assim, a função que escreve deve tomar estes dois como parâmetros, além dos dados de entrada. Por exemplo, poderia ser definido como
MyLayer (x, depth, initValueScale)
Quando se faz isto, espera-se que os valores resultantes ce
e errs
os valores sejam os mesmos.
No entanto, se correr na GPU, o não determinismo na implementação de propagação de back-propagação da CUDNN causará pequenas variações.
Por favor, veja a solução aqui.
Tarefa 3: Adicionar LotNormalização
(Esta tarefa requer uma GPU, uma vez que a implementação da normalização do lote por CNTK se baseia em cuDNN.)
A normalização do lote é uma técnica popular para acelerar e melhorar a convergência.
Em CNTK, a normalização do lote é implementada como BatchNormalizationLayer{}
.
A forma espacial (onde todas as posições dos pixels são normalizadas com parâmetros partilhados) é invocada por um parâmetro opcional: BatchNormalizationLayer{spatialRank=2}
.
Por favor, adicione a normalização do lote às três camadas de convolução e entre as duas camadas densas.
Note que a normalização do lote deve ser inserida imediatamente antes da não linearidade.
Por isso, deve remover o activation
parâmetro e, em vez disso, inserir chamadas explícitas na função ReLU()
CNTK .
Além disso, a normalização do lote altera a velocidade de convergência. Por isso, aumentemos as taxas de aprendizagem das primeiras 7 épocas 3 vezes, e desativemos o ímpeto e a regularização L2, fixando os seus parâmetros para 0.
Ao correr, verá parâmetros aprecíveis adicionais listados no registo de treino. O resultado final será de cerca de 28%, o que é 4 pontos melhor do que sem normalização do lote após o mesmo número de iterações. A convergência acelera, de facto.
Por favor, veja a solução aqui.
Tarefa 4: Converter para Rede Residual
A configuração acima é um exemplo de "brinquedo" para sujar as mãos com as configurações de funcionamento e modificação CNTK, e intencionalmente não corremos para a convergência total para manter os tempos de reviravolta baixos. Então vamos agora avançar para uma configuração mais real- uma Rede Residual. A Rede Residual (https://arxiv.org/pdf/1512.03385v1.pdf) é uma estrutura de rede profunda modificada onde camadas, em vez de aprender um mapeamento da entrada para a saída, aprendem um termo de correção.
(Esta Tarefa também requer uma GPU para a operação de normalização do lote, embora se tiver muito tempo, pode tentar executá-la num CPU editando as chamadas normalização do lote, com alguma perda de precisão.)
Para começar, por favor modifique a configuração anterior. Primeiro, substitua a função model(features)
do modelo por esta:
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 alterar a configuração SGD para:
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
}
A sua tarefa é modificar ResNetNode()
e ResNetNodeInc()
implementar a estrutura estabelecida na seguinte arte ASCII premiada:
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
Por favor, confirme com a saída de validação no registo que o fez corretamente.
Isto vai demorar muito até ao fim. A produção esperada será semelhante a esta:
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
que o modelo incorreto sem as ligações de salto é mais parecido com este:
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
Por favor, veja a solução aqui.
Tarefa 5: Gerar automaticamente muitas camadas
Por último, o ResNet com melhor desempenho tem 152 camadas. Uma vez que seria muito entediante e com tendência a escrever 152 expressões individuais, vamos agora modificar a definição para gerar automaticamente uma pilha de ResNetNode()
.
A sua tarefa é escrever uma função com esta assinatura:
ResNetNodeStack (x, depth, L)
onde L
denota quantos devem ser empilhados ResNetNodes
, de modo a que possamos substituir a expressão por cima por rn1
uma chamada parametrizada:
rn1 = ResNetNodeStack (conv1, 16, 3) # 3 means 3 such nodes
e da mesma forma para rn2
e rn3
.
As ferramentas que vai precisar são a expressão condicional :
z = if cond then x else y
e recursão.
Este treino vai funcionar por cerca de metade do nosso num Titan-X. Se o fez bem, no início do registo conterá esta mensagem:
Training 200410 parameters in 51 out of 51 parameter tensors and 112 nodes with gradient:
Para referência, incluímos uma versão pré-treinada deste modelo. Pode medir a taxa de erro com este comando:
cntk configFile=ImageHandsOn.ResNet.cntk command=Eval
e deve ver um resultado como este:
Final Results: Minibatch[1-625]: errs = 8.400% * 10000; top5Errs = 0.290% * 10000
Esta taxa de erro é muito próxima da reportada no papel original da ResNet (https://arxiv.org/pdf/1512.03385v1.pdf, Quadro 6).
Por favor, veja a solução aqui.
Tarefa 6: Formação Paralela
Por último, se tiver várias GPUs, CNTK permite-lhe paralelizar o treino utilizando MPI (Interface de passagem de mensagens). Este modelo é demasiado pequeno para se esperar de muita velocidade sem afinar mais, por exemplo, de tamanhos de minibatch (a configuração atual do tamanho de minibatch é demasiado pequena para obter uma utilização completa dos núcleos de GPU disponíveis). No entanto, passemos pelas moções, para que saiba como fazê-lo quando passar para a carga de trabalho no mundo real.
Por favor, adicione a seguinte linha ao SGD
bloco:
SGD = {
...
parallelTrain = {
parallelizationMethod = "DataParallelSGD"
parallelizationStartEpoch = 2
distributedMBReading = true
dataParallelSGD = { gradientBits = 1 }
}
}
e, em seguida, executar este comando:
mpiexec -np 4 cntk configFile=ImageHandsOn.cntk stderr=Models/log parallelTrain=true
O que se segue?
Este tutorial tem praticado para tomar uma configuração existente, e modificá-lo de formas específicas:
- adicionando uma operação predefinida (abandono)
- extrair peças repetitivas em módulos reutilizáveis (funções)
- refactorização (para inserir a normalização do lote)
- estruturas de rede personalizadas (ligação de salto ResNet)
- parametrizar estruturas repetitivas usando a recursão
e vimos como acelerar o treino por paralelização.
Então, para onde vamos a partir daqui? Já deve ter descoberto que o padrão usado nestes exemplos, a que chamamos estilo de construção de gráficos , pode ser bastante propenso a erros. Detetou o erro?
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
Uma forma de evitar este erro é utilizar a composição da função. Segue-se uma forma alternativa, mais concisa de escrever o mesmo:
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}
)
Este estilo será introduzido e utilizado no próximo tutorial prático, Compreensão de Texto com Redes Recorrentes.
Soluções
Solução 1: Adicionar Abandono
Modificar a definição do modelo da seguinte forma:
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 a secção SGD:
SGD = {
...
dropoutRate = 0*5:0.5 ##### added
...
}
Solução 2: Simplificar a definição do modelo extraindo peças repetitivas numa função
Adicione uma definição de função da seguinte forma:
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 atualizar a definição de modelo para usá-lo
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)
Solução 3: Adicionar LotNormalização
Modificar 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 usá-lo. Insira também a normalização do lote antes z
de:
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)
E atualizar estes parâmetros na secção SGD:
SGD = {
....
learningRatesPerSample = 0.00046875*7:0.00015625*10:0.000046875*10:0.000015625
momentumAsTimeConstant = 0
L2RegWeight = 0
...
}
Solução 4: Converter em Rede Residual
As implementações corretas para ResNetNode()
e ResNetNodeInc()
são:
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
Solução 5: Gerar automaticamente muitas camadas
Esta é a implementação:
ResNetNodeStack (x, depth, L) =
{
r = if L == 0
then x
else ResNetNode (ResNetNodeStack (x, depth, L-1), depth)
}.r
ou, mais curto:
ResNetNodeStack (x, depth, L) =
if L == 0
then x
else ResNetNode (ResNetNodeStack (x, depth, L-1), depth)
Também precisa modificar a função do seu modelo:
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