3.7. Implementação Concisa da Regressão Softmax¶ Open the notebook in SageMaker Studio Lab
APIs de alto nível tal como os frameworks de deep learning tornaram muito mais fácil de implementar a regressão linear em Section 3.3, encontraremos de forma semelhante (ou possivelmente mais) conveniente, implementar modelos de classificação. Vamos ficar com o conjunto de dados Fashion-MNIST e manter o tamanho do lote em 256 como em Section 3.6.
from mxnet import gluon, init, npx
from mxnet.gluon import nn
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)
3.7.1. Inicializando os Parâmetros do Modelo¶
Conforme mencionado em: numref: sec_softmax
, a camada de saída da
regressão softmax é uma camada totalmente conectada. Portanto, para
implementar nosso modelo, só precisamos adicionar uma camada totalmente
conectada com 10 saídas para nosso Sequential
. Novamente, aqui, o
Sequential
não é realmente necessário, mas podemos também criar o
hábito, pois será onipresente ao implementar modelos profundos.
Novamente, inicializamos os pesos aleatoriamente com média zero e desvio
padrão 0,01.
net = nn.Sequential()
net.add(nn.Dense(10))
net.initialize(init.Normal(sigma=0.01))
# PyTorch does not implicitly reshape the inputs. Thus we define the flatten
# layer to reshape the inputs before the linear layer in our network
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights);
net = tf.keras.models.Sequential()
net.add(tf.keras.layers.Flatten(input_shape=(28, 28)))
weight_initializer = tf.keras.initializers.RandomNormal(mean=0.0, stddev=0.01)
net.add(tf.keras.layers.Dense(10, kernel_initializer=weight_initializer))
3.7.2. Implementação do Softmax Revisitada¶
No exemplo anterior de Section 3.6, calculamos a saída do nosso modelo e então executamos esta saída através da perda de entropia cruzada. Matematicamente, isso é uma coisa perfeitamente razoável de se fazer. No entanto, de uma perspectiva computacional, a exponenciação pode ser uma fonte de problemas de estabilidade numérica.
Lembre-se de que a função softmax calcula
\(\hat y_j = \frac{\exp(o_j)}{\sum_k \exp(o_k)}\), onde
\(\hat y_j\) é o elemento \(j^\mathrm{th}\) da distribuição de
probabilidade prevista \(\hat{\mathbf{y}}\) e \(o_j\) é o
elemento \(j^\mathrm{th}\) dos logits \(\mathbf{o}\). Se
alguns dos \(o_k\) forem muito grandes (ou seja, muito positivos),
então \(\exp(o_k)\) pode ser maior que o maior número, podemos ter
para certos tipos de dados (ou seja, estouro). Isso tornaria o
denominador (e/ou numerador) inf
(infinito) e acabamos encontrando
0, inf
ounan
(não um número) para \(\hat y_j\). Nessas
situações, não obtemos uma definição bem definida valor de retorno para
entropia cruzada.
Um truque para contornar isso é primeiro subtrair \(\max(o_k)\) de todos \(o_k\) antes de prosseguir com o cálculo do softmax. Você pode ver que este deslocamento de cada \(o_k\) por um fator constante não altera o valor de retorno de softmax:
Após a etapa de subtração e normalização, pode ser possível que alguns
\(o_j - \max(o_k)\) tenham grandes valores negativos e assim que o
\(\exp(o_j - \max(o_k))\) correspondente assumirá valores próximos a
zero. Eles podem ser arredondados para zero devido à precisão finita (ou
seja, underflow), tornando \(\hat y_j\) zero e dando-nos -inf
para \(\log(\hat y_j)\). Alguns passos abaixo na backpropagation,
podemos nos encontrar diante de uma tela cheia dos temidos resultados
nan
.
Felizmente, somos salvos pelo fato de que embora estejamos computando funções exponenciais, em última análise, pretendemos levar seu log (ao calcular a perda de entropia cruzada). Combinando esses dois operadores softmax e entropia cruzada juntos, podemos escapar dos problemas de estabilidade numérica que poderia nos atormentar durante a backpropagation. Conforme mostrado na equação abaixo, evitamos calcular \(\exp(o_j - \max(o_k))\) e podemos usar \(o_j - \max(o_k)\) diretamente devido ao cancelamento em \(\log(\exp(\cdot))\):
Queremos manter a função softmax convencional acessível no caso de querermos avaliar as probabilidades de saída por nosso modelo. Mas em vez de passar probabilidades de softmax para nossa nova função de perda, nós vamos apenas passar os logits e calcular o softmax e seu log tudo de uma vez dentro da função de perda de entropia cruzada, que faz coisas inteligentes como o “Truque LogSumExp”.
loss = gluon.loss.SoftmaxCrossEntropyLoss()
loss = nn.CrossEntropyLoss()
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
3.7.3. Otimização do Algoritmo¶
Aqui, nós usamos gradiente descendente estocástico de minibatch com uma taxa de aprendizado de 0,1 como o algoritmo de otimização. Observe que este é o mesmo que aplicamos no exemplo de regressão linear e ilustra a aplicabilidade geral dos otimizadores.
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.1})
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
trainer = tf.keras.optimizers.SGD(learning_rate=.1)
3.7.4. Trainamento¶
Em seguida, chamamos a função de treinamento definida em Section 3.6 para treinar o modelo.
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
Como antes, este algoritmo converge para uma solução que atinge uma precisão decente, embora desta vez com menos linhas de código do que antes.
3.7.5. Resumo¶
Usando APIs de alto nível, podemos implementar a regressão softmax de forma muito mais concisa.
De uma perspectiva computacional, a implementação da regressão softmax tem complexidades. Observe que, em muitos casos, uma estrutura de deep learning toma precauções adicionais além desses truques mais conhecidos para garantir a estabilidade numérica, salvando-nos de ainda mais armadilhas que encontraríamos se tentássemos codificar todos os nossos modelos do zero na prática.
3.7.6. Exercícios¶
Tente ajustar os hiperparâmetros, como batch size, número de épocas e taxa de aprendizado, para ver quais são os resultados.
Aumente o número de épocas de treinamento. Por que a precisão do teste pode diminuir depois de um tempo? Como poderíamos consertar isso?