.. _sec_softmax_concise: Implementação Concisa da Regressão *Softmax* ============================================ APIs de alto nível tal como os *frameworks* de *deep learning* tornaram muito mais fácil de implementar a regressão linear em :numref:`sec_linear_concise`, 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 :numref:`sec_softmax_scratch`. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python 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) .. raw:: html
.. raw:: html
.. code:: python 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) .. raw:: html
.. raw:: html
.. code:: python import tensorflow as tf from d2l import tensorflow as d2l batch_size = 256 train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size) .. raw:: html
.. raw:: html
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. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python net = nn.Sequential() net.add(nn.Dense(10)) net.initialize(init.Normal(sigma=0.01)) .. raw:: html
.. raw:: html
.. code:: python # 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); .. raw:: html
.. raw:: html
.. code:: python 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)) .. raw:: html
.. raw:: html
.. _subsec_softmax-implementation-revisited: Implementação do *Softmax* Revisitada ------------------------------------- No exemplo anterior de :numref:`sec_softmax_scratch`, 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 :math:`\hat y_j = \frac{\exp(o_j)}{\sum_k \exp(o_k)}`, onde :math:`\hat y_j` é o elemento :math:`j^\mathrm{th}` da distribuição de probabilidade prevista :math:`\hat{\mathbf{y}}` e :math:`o_j` é o elemento :math:`j^\mathrm{th}` dos *logits* :math:`\mathbf{o}`. Se alguns dos :math:`o_k` forem muito grandes (ou seja, muito positivos), então :math:`\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`` ou\ ``nan`` (não um número) para :math:`\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 :math:`\max(o_k)` de todos :math:`o_k` antes de prosseguir com o cálculo do *softmax*. Você pode ver que este deslocamento de cada :math:`o_k` por um fator constante não altera o valor de retorno de *softmax*: .. math:: \begin{aligned} \hat y_j & = \frac{\exp(o_j - \max(o_k))\exp(\max(o_k))}{\sum_k \exp(o_k - \max(o_k))\exp(\max(o_k))} \\ & = \frac{\exp(o_j - \max(o_k))}{\sum_k \exp(o_k - \max(o_k))}. \end{aligned} Após a etapa de subtração e normalização, pode ser possível que alguns :math:`o_j - \max(o_k)` tenham grandes valores negativos e assim que o :math:`\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 :math:`\hat y_j` zero e dando-nos ``-inf`` para :math:`\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 :math:`\exp(o_j - \max(o_k))` e podemos usar :math:`o_j - \max(o_k)` diretamente devido ao cancelamento em :math:`\log(\exp(\cdot))`: .. math:: \begin{aligned} \log{(\hat y_j)} & = \log\left( \frac{\exp(o_j - \max(o_k))}{\sum_k \exp(o_k - \max(o_k))}\right) \\ & = \log{(\exp(o_j - \max(o_k)))}-\log{\left( \sum_k \exp(o_k - \max(o_k)) \right)} \\ & = o_j - \max(o_k) -\log{\left( \sum_k \exp(o_k - \max(o_k)) \right)}. \end{aligned} 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” `__. .. raw:: html
.. raw:: html
.. code:: python loss = gluon.loss.SoftmaxCrossEntropyLoss() .. raw:: html
.. raw:: html
.. code:: python loss = nn.CrossEntropyLoss() .. raw:: html
.. raw:: html
.. code:: python loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) .. raw:: html
.. raw:: html
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. .. raw:: html
.. raw:: html
.. code:: python trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.1}) .. raw:: html
.. raw:: html
.. code:: python trainer = torch.optim.SGD(net.parameters(), lr=0.1) .. raw:: html
.. raw:: html
.. code:: python trainer = tf.keras.optimizers.SGD(learning_rate=.1) .. raw:: html
.. raw:: html
Trainamento ----------- Em seguida, chamamos a função de treinamento definida em :numref:`sec_softmax_scratch` para treinar o modelo. .. raw:: html
.. raw:: html
.. code:: python num_epochs = 10 d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer) .. figure:: output_softmax-regression-concise_75d138_51_0.svg .. raw:: html
.. raw:: html
.. code:: python num_epochs = 10 d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer) .. figure:: output_softmax-regression-concise_75d138_54_0.svg .. raw:: html
.. raw:: html
.. code:: python num_epochs = 10 d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer) .. figure:: output_softmax-regression-concise_75d138_57_0.svg .. raw:: html
.. raw:: html
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. 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. Exercícios ---------- 1. Tente ajustar os hiperparâmetros, como *batch size*, número de épocas e taxa de aprendizado, para ver quais são os resultados. 2. 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? 3. .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
.. raw:: html