4.2. Implementação de Perceptrons Multicamadas do Zero¶ Open the notebook in SageMaker Studio Lab
Agora que caracterizamos perceptrons multicamadas (MLPs) matematicamente, vamos tentar implementar um nós mesmos. Para comparar com nossos resultados anteriores alcançado com regressão softmax (Section 3.6), vamos continuar a trabalhar com o conjunto de dados de classificação de imagens Fashion-MNIST (Section 3.5).
from mxnet import gluon, np, npx
from d2l import mxnet as d2l
npx.set_np()
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
import torch
from torch import nn
from d2l import torch as d2l
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
import tensorflow as tf
from d2l import tensorflow as d2l
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
4.2.1. Inicializando os Parâmetros do Modelo¶
Lembre-se de que o Fashion-MNIST contém 10 classes, e que cada imagem consiste em uma graded \(28 \times 28 = 784\) de valores de pixel em tons de cinza. Novamente, vamos desconsiderar a estrutura espacial entre os pixels por enquanto, então podemos pensar nisso simplesmente como um conjunto de dados de classificação com 784 características de entrada e 10 classes. Para começar, iremos implementar um MLP com uma camada oculta e 256 unidades ocultas. Observe que podemos considerar essas duas quantidades como hiperparâmetros. Normalmente, escolhemos larguras de camada em potências de 2, que tendem a ser computacionalmente eficientes porque de como a memória é alocada e endereçada no hardware.
Novamente, iremos representar nossos parâmetros com vários tensores. Observe que para cada camada, devemos acompanhar uma matriz de ponderação e um vetor de polarização. Como sempre, alocamos memória para os gradientes da perda com relação a esses parâmetros.
num_inputs, num_outputs, num_hiddens = 784, 10, 256
W1 = np.random.normal(scale=0.01, size=(num_inputs, num_hiddens))
b1 = np.zeros(num_hiddens)
W2 = np.random.normal(scale=0.01, size=(num_hiddens, num_outputs))
b2 = np.zeros(num_outputs)
params = [W1, b1, W2, b2]
for param in params:
param.attach_grad()
num_inputs, num_outputs, num_hiddens = 784, 10, 256
W1 = nn.Parameter(torch.randn(
num_inputs, num_hiddens, requires_grad=True) * 0.01)
b1 = nn.Parameter(torch.zeros(num_hiddens, requires_grad=True))
W2 = nn.Parameter(torch.randn(
num_hiddens, num_outputs, requires_grad=True) * 0.01)
b2 = nn.Parameter(torch.zeros(num_outputs, requires_grad=True))
params = [W1, b1, W2, b2]
num_inputs, num_outputs, num_hiddens = 784, 10, 256
W1 = tf.Variable(tf.random.normal(
shape=(num_inputs, num_hiddens), mean=0, stddev=0.01))
b1 = tf.Variable(tf.zeros(num_hiddens))
W2 = tf.Variable(tf.random.normal(
shape=(num_hiddens, num_outputs), mean=0, stddev=0.01))
b2 = tf.Variable(tf.random.normal([num_outputs], stddev=.01))
params = [W1, b1, W2, b2]
4.2.2. Função de Ativação¶
Para ter certeza de que sabemos como tudo funciona, iremos implementar a
ativação ReLU nós mesmos usar a função máxima em vez de invocar a função
embutida relu
diretamente.
def relu(X):
return np.maximum(X, 0)
def relu(X):
a = torch.zeros_like(X)
return torch.max(X, a)
def relu(X):
return tf.math.maximum(X, 0)
4.2.3. Modelo¶
Porque estamos desconsiderando a estrutura espacial, nós remodelamos
cada imagem bidimensional em um vetor plano de comprimento
num_inputs
. Finalmente, nós implementamos nosso modelo com apenas
algumas linhas de código.
def net(X):
X = X.reshape((-1, num_inputs))
H = relu(np.dot(X, W1) + b1)
return np.dot(H, W2) + b2
def net(X):
X = X.reshape((-1, num_inputs))
H = relu(X@W1 + b1) # Here '@' stands for matrix multiplication
return (H@W2 + b2)
def net(X):
X = tf.reshape(X, (-1, num_inputs))
H = relu(tf.matmul(X, W1) + b1)
return tf.matmul(H, W2) + b2
4.2.4. Função de Perda¶
Para garantir estabilidade numérica, e porque já implementamos a função* softmax* do zero (Section 3.6), alavancamos a função integrada de APIs de alto nível para calcular o softmax e a perda de entropia cruzada. Lembre-se de nossa discussão anterior sobre essas complexidades em Section 3.7.2. Nós encorajamos o leitor interessado a examinar o código-fonte para a função de perda para aprofundar seu conhecimento dos detalhes de implementação.
loss = gluon.loss.SoftmaxCrossEntropyLoss()
loss = nn.CrossEntropyLoss()
def loss(y_hat, y):
return tf.losses.sparse_categorical_crossentropy(
y, y_hat, from_logits=True)
4.2.5. Trainamento¶
Felizmente, o loop de treinamento para MLPs é exatamente igual à
regressão softmax. Aproveitando o pacote d2l
novamente, chamamos a
função train_ch3
(ver Section 3.6), definindo o
número de épocas para 10 e a taxa de aprendizagem para 0,1.
num_epochs, lr = 10, 0.1
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs,
lambda batch_size: d2l.sgd(params, lr, batch_size))
num_epochs, lr = 10, 0.1
updater = torch.optim.SGD(params, lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)
num_epochs, lr = 10, 0.1
updater = d2l.Updater([W1, W2, b1, b2], lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)
Para avaliar o modelo aprendido, nós aplicamos em alguns dados de teste.
d2l.predict_ch3(net, test_iter)
d2l.predict_ch3(net, test_iter)
d2l.predict_ch3(net, test_iter)
4.2.6. Resumo¶
Vimos que implementar um MLP simples é fácil, mesmo quando feito manualmente.
No entanto, com um grande número de camadas, implementar MLPs do zero ainda pode ser complicado (por exemplo, nomear e controlar os parâmetros do nosso modelo).
4.2.7. Exercícios¶
Altere o valor do hiperparâmetro
num_hiddens
e veja como esse hiperparâmetro influencia seus resultados. Determine o melhor valor deste hiperparâmetro, mantendo todos os outros constantes.Experimente adicionar uma camada oculta adicional para ver como isso afeta os resultados.
Como mudar a taxa de aprendizado altera seus resultados? Corrigindo a arquitetura do modelo e outros hiperparâmetros (incluindo o número de épocas), qual taxa de aprendizado oferece os melhores resultados?
Qual é o melhor resultado que você pode obter otimizando todos os hiperparâmetros (taxa de aprendizagem, número de épocas, número de camadas ocultas, número de unidades ocultas por camada) em conjunto?
Descreva por que é muito mais difícil lidar com vários hiperparâmetros.
Qual é a estratégia mais inteligente que você pode imaginar para estruturar uma pesquisa em vários hiperparâmetros?