CNTK - Rede Neural Convolucional
Neste capítulo, vamos estudar como construir uma Rede Neural Convolucional (CNN) no CNTK.
Introdução
Redes neurais convolucionais (CNNs) também são compostas por neurônios, que têm pesos e vieses aprendíveis. É por isso que, dessa maneira, eles são como redes neurais comuns (NNs).
Se nos lembrarmos do funcionamento de NNs comuns, cada neurônio recebe uma ou mais entradas, faz uma soma ponderada e passou por uma função de ativação para produzir a saída final. Aqui, surge a questão de que, se CNNs e NNs comuns têm tantas semelhanças, o que torna essas duas redes diferentes uma da outra?
O que os torna diferentes é o tratamento de dados de entrada e tipos de camadas? A estrutura dos dados de entrada é ignorada no NN comum e todos os dados são convertidos em array 1-D antes de alimentá-los na rede.
Porém, a arquitetura da Rede Neural Convolucional pode considerar a estrutura 2D das imagens, processá-las e permitir que extraia as propriedades específicas das imagens. Além disso, as CNNs têm a vantagem de ter uma ou mais camadas convolucionais e camadas de pool, que são os principais blocos de construção das CNNs.
Essas camadas são seguidas por uma ou mais camadas totalmente conectadas como em NNs multicamadas padrão. Então, podemos pensar na CNN, como um caso especial de redes totalmente conectadas.
Arquitetura de Rede Neural Convolucional (CNN)
A arquitetura da CNN é basicamente uma lista de camadas que transforma o volume tridimensional, ou seja, largura, altura e profundidade do volume da imagem em um volume de saída tridimensional. Um ponto importante a ser observado aqui é que cada neurônio na camada atual está conectado a um pequeno patch da saída da camada anterior, que é como sobrepor um filtro N * N na imagem de entrada.
Ele usa filtros M, que são basicamente extratores de recursos que extraem recursos como bordas, cantos e assim por diante. A seguir estão as camadas [INPUT-CONV-RELU-POOL-FC] que são usados para construir redes neurais convolucionais (CNNs) -
INPUT- Como o nome indica, esta camada contém os valores de pixel brutos. Os valores de pixel brutos significam os dados da imagem como ela é. Exemplo, INPUT [64 × 64 × 3] é uma imagem RGB de 3 canais de largura-64, altura-64 e profundidade-3.
CONV- Esta camada é um dos blocos de construção das CNNs, pois a maior parte da computação é feita nesta camada. Exemplo - se usarmos 6 filtros na ENTRADA mencionada acima [64 × 64 × 3], isso pode resultar no volume [64 × 64 × 6].
RELU−Também chamado de camada de unidade linear retificada, que aplica uma função de ativação à saída da camada anterior. De outra maneira, uma não linearidade seria adicionada à rede por RELU.
POOL- Esta camada, ou seja, a camada Pooling, é outro bloco de construção das CNNs. A principal tarefa dessa camada é a redução da amostra, o que significa que ela opera independentemente em cada fatia da entrada e a redimensiona espacialmente.
FC- É chamada de camada Fully Connected ou, mais especificamente, camada de saída. É usado para calcular a pontuação da classe de saída e a saída resultante é um volume de tamanho 1 * 1 * L, onde L é o número correspondente à pontuação da classe.
O diagrama abaixo representa a arquitetura típica das CNNs-
Criando estrutura CNN
Vimos a arquitetura e os fundamentos da CNN, agora vamos construir uma rede convolucional usando CNTK. Aqui, primeiro veremos como montar a estrutura da CNN e depois veremos como treinar seus parâmetros.
Por fim, veremos como podemos melhorar a rede neural alterando sua estrutura com várias configurações de camadas diferentes. Vamos usar o conjunto de dados de imagens MNIST.
Então, primeiro vamos criar uma estrutura CNN. Geralmente, quando construímos uma CNN para reconhecer padrões em imagens, fazemos o seguinte−
Usamos uma combinação de camadas de convolução e pooling.
Uma ou mais camadas ocultas no final da rede.
Por fim, finalizamos a rede com uma camada softmax para fins de classificação.
Com a ajuda das etapas a seguir, podemos construir a estrutura da rede
Step 1- Primeiro, precisamos importar as camadas necessárias para a CNN.
from cntk.layers import Convolution2D, Sequential, Dense, MaxPooling
Step 2- Em seguida, precisamos importar as funções de ativação para CNN.
from cntk.ops import log_softmax, relu
Step 3- Depois disso, para inicializar as camadas convolucionais mais tarde, precisamos importar o glorot_uniform_initializer como segue -
from cntk.initializer import glorot_uniform
Step 4- Em seguida, para criar variáveis de entrada, importe o input_variablefunção. E importardefault_option função, para tornar a configuração do NN um pouco mais fácil.
from cntk import input_variable, default_options
Step 5- Agora, para armazenar as imagens de entrada, crie um novo input_variable. Ele conterá três canais: vermelho, verde e azul. Ele teria o tamanho de 28 por 28 pixels.
features = input_variable((3,28,28))
Step 6−Em seguida, precisamos criar outro input_variable para armazenar os rótulos para prever.
labels = input_variable(10)
Step 7- Agora, precisamos criar o default_optionpara o NN. E, precisamos usar oglorot_uniform como a função de inicialização.
with default_options(initialization=glorot_uniform, activation=relu):
Step 8- A seguir, para definir a estrutura do NN, precisamos criar um novo Sequential conjunto de camadas.
Step 9- Agora precisamos adicionar um Convolutional2D camada com um filter_shape de 5 e um strides definição de 1, dentro do Sequentialconjunto de camadas. Além disso, ative o preenchimento, para que a imagem seja preenchida para manter as dimensões originais.
model = Sequential([
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=8, pad=True),
Step 10- Agora é hora de adicionar um MaxPooling camada com filter_shape de 2 e um strides configuração de 2 para comprimir a imagem pela metade.
MaxPooling(filter_shape=(2,2), strides=(2,2)),
Step 11- Agora, como fizemos na etapa 9, precisamos adicionar outro Convolutional2D camada com um filter_shape de 5 e um stridesconfiguração de 1, use 16 filtros. Além disso, ative o preenchimento, para que o tamanho da imagem produzida pela camada de pool anterior seja mantido.
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=16, pad=True),
Step 12- Agora, como fizemos na etapa 10, adicione outro MaxPooling camada com um filter_shape de 3 e um strides configuração de 3 para reduzir a imagem a um terço.
MaxPooling(filter_shape=(3,3), strides=(3,3)),
Step 13- Por fim, adicione uma camada Densa com dez neurônios para as 10 classes possíveis, a rede pode prever. Para transformar a rede em um modelo de classificação, use umlog_siftmax função de ativação.
Dense(10, activation=log_softmax)
])
Exemplo completo para a criação de estrutura CNN
from cntk.layers import Convolution2D, Sequential, Dense, MaxPooling
from cntk.ops import log_softmax, relu
from cntk.initializer import glorot_uniform
from cntk import input_variable, default_options
features = input_variable((3,28,28))
labels = input_variable(10)
with default_options(initialization=glorot_uniform, activation=relu):
model = Sequential([
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=8, pad=True),
MaxPooling(filter_shape=(2,2), strides=(2,2)),
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=16, pad=True),
MaxPooling(filter_shape=(3,3), strides=(3,3)),
Dense(10, activation=log_softmax)
])
z = model(features)
Treinamento CNN com imagens
Como criamos a estrutura da rede, é hora de treinar a rede. Mas antes de iniciar o treinamento de nossa rede, precisamos configurar fontes de minibatch, pois treinar um NN que trabalha com imagens requer mais memória do que a maioria dos computadores.
Já criamos fontes de minibatch nas seções anteriores. A seguir está o código Python para configurar duas fontes de minibatch -
Como temos o create_datasource função, agora podemos criar duas fontes de dados separadas (treinamento e teste) para treinar o modelo.
train_datasource = create_datasource('mnist_train')
test_datasource = create_datasource('mnist_test', max_sweeps=1, train=False)
Agora, como preparamos as imagens, podemos começar a treinar nosso NN. Como fizemos nas seções anteriores, podemos usar o método de trem na função de perda para iniciar o treinamento. A seguir está o código para isso -
from cntk import Function
from cntk.losses import cross_entropy_with_softmax
from cntk.metrics import classification_error
from cntk.learners import sgd
@Function
def criterion_factory(output, targets):
loss = cross_entropy_with_softmax(output, targets)
metric = classification_error(output, targets)
return loss, metric
loss = criterion_factory(z, labels)
learner = sgd(z.parameters, lr=0.2)
Com a ajuda do código anterior, configuramos a perda e o aluno para o NN. O código a seguir irá treinar e validar o NN−
from cntk.logging import ProgressPrinter
from cntk.train import TestConfig
progress_writer = ProgressPrinter(0)
test_config = TestConfig(test_datasource)
input_map = {
features: train_datasource.streams.features,
labels: train_datasource.streams.labels
}
loss.train(train_datasource,
max_epochs=10,
minibatch_size=64,
epoch_size=60000,
parameter_learners=[learner],
model_inputs_to_streams=input_map,
callbacks=[progress_writer, test_config])
Exemplo de implementação completo
from cntk.layers import Convolution2D, Sequential, Dense, MaxPooling
from cntk.ops import log_softmax, relu
from cntk.initializer import glorot_uniform
from cntk import input_variable, default_options
features = input_variable((3,28,28))
labels = input_variable(10)
with default_options(initialization=glorot_uniform, activation=relu):
model = Sequential([
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=8, pad=True),
MaxPooling(filter_shape=(2,2), strides=(2,2)),
Convolution2D(filter_shape=(5,5), strides=(1,1), num_filters=16, pad=True),
MaxPooling(filter_shape=(3,3), strides=(3,3)),
Dense(10, activation=log_softmax)
])
z = model(features)
import os
from cntk.io import MinibatchSource, StreamDef, StreamDefs, ImageDeserializer, INFINITELY_REPEAT
import cntk.io.transforms as xforms
def create_datasource(folder, train=True, max_sweeps=INFINITELY_REPEAT):
mapping_file = os.path.join(folder, 'mapping.bin')
image_transforms = []
if train:
image_transforms += [
xforms.crop(crop_type='randomside', side_ratio=0.8),
xforms.scale(width=28, height=28, channels=3, interpolations='linear')
]
stream_definitions = StreamDefs(
features=StreamDef(field='image', transforms=image_transforms),
labels=StreamDef(field='label', shape=10)
)
deserializer = ImageDeserializer(mapping_file, stream_definitions)
return MinibatchSource(deserializer, max_sweeps=max_sweeps)
train_datasource = create_datasource('mnist_train')
test_datasource = create_datasource('mnist_test', max_sweeps=1, train=False)
from cntk import Function
from cntk.losses import cross_entropy_with_softmax
from cntk.metrics import classification_error
from cntk.learners import sgd
@Function
def criterion_factory(output, targets):
loss = cross_entropy_with_softmax(output, targets)
metric = classification_error(output, targets)
return loss, metric
loss = criterion_factory(z, labels)
learner = sgd(z.parameters, lr=0.2)
from cntk.logging import ProgressPrinter
from cntk.train import TestConfig
progress_writer = ProgressPrinter(0)
test_config = TestConfig(test_datasource)
input_map = {
features: train_datasource.streams.features,
labels: train_datasource.streams.labels
}
loss.train(train_datasource,
max_epochs=10,
minibatch_size=64,
epoch_size=60000,
parameter_learners=[learner],
model_inputs_to_streams=input_map,
callbacks=[progress_writer, test_config])
Resultado
-------------------------------------------------------------------
average since average since examples
loss last metric last
------------------------------------------------------
Learning rate per minibatch: 0.2
142 142 0.922 0.922 64
1.35e+06 1.51e+07 0.896 0.883 192
[………]
Transformações de imagem
Como vimos, é difícil treinar NN usados para reconhecimento de imagem e, para treinar, eles também exigem muitos dados. Mais um problema é que eles tendem a se ajustar demais às imagens usadas durante o treinamento. Vejamos com um exemplo, quando temos fotos de rostos na posição vertical, nosso modelo terá dificuldade em reconhecer rostos que são girados em outra direção.
Para contornar esse problema, podemos usar o aumento de imagem e o CNTK suporta transformações específicas, ao criar fontes de minibatch para imagens. Podemos usar várias transformações da seguinte maneira−
Podemos cortar aleatoriamente imagens usadas para treinamento com apenas algumas linhas de código.
Podemos usar uma escala e uma cor também.
Vamos ver, com a ajuda do seguinte código Python, como podemos alterar a lista de transformações incluindo uma transformação de recorte na função usada para criar a origem do minibatch anteriormente.
import os
from cntk.io import MinibatchSource, StreamDef, StreamDefs, ImageDeserializer, INFINITELY_REPEAT
import cntk.io.transforms as xforms
def create_datasource(folder, train=True, max_sweeps=INFINITELY_REPEAT):
mapping_file = os.path.join(folder, 'mapping.bin')
image_transforms = []
if train:
image_transforms += [
xforms.crop(crop_type='randomside', side_ratio=0.8),
xforms.scale(width=28, height=28, channels=3, interpolations='linear')
]
stream_definitions = StreamDefs(
features=StreamDef(field='image', transforms=image_transforms),
labels=StreamDef(field='label', shape=10)
)
deserializer = ImageDeserializer(mapping_file, stream_definitions)
return MinibatchSource(deserializer, max_sweeps=max_sweeps)
Com a ajuda do código acima, podemos aprimorar a função para incluir um conjunto de transformações de imagem, para que, quando estivermos em treinamento, possamos recortar a imagem aleatoriamente, para obtermos mais variações da imagem.