3.7 Softmax-регрессия: краткая реализация
В разделе 3.3 мы убедились, что высокоуровневый API фреймворков упрощает реализацию линейной регрессии. Точно так же с помощью высокоуровневого API удобно реализовать и softmax-регрессию. В этом разделе, как и в разделе 3.6, мы продолжаем работать с набором Fashion-MNIST и сохраняем размер батча равным 256.
MXNet:
from d2l import mxnet as d2l
from mxnet import gluon, init, npx
from mxnet.gluon import nn
npx.set_np()
PyTorch:
from d2l import torch as d2l
import torch
from torch import nn
TensorFlow:
from d2l import tensorflow as d2l
import tensorflow as tf
PaddlePaddle:
from d2l import paddle as d2l
import warnings
warnings.filterwarnings("ignore")
import paddle
from paddle import nn
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
3.7.1 Инициализация параметров модели
Как описано в разделе 3.4, выходной слой softmax-регрессии — полносвязный слой.
Поэтому для реализации модели достаточно добавить в Sequential один полносвязный слой с 10 выходами.
Здесь Sequential формально тоже не нужен, но он служит основой для построения глубоких моделей.
Веса по-прежнему инициализируем случайными значениями из нормального распределения со средним 0 и стандартным отклонением 0.01.
MXNet:
net = nn.Sequential()
net.add(nn.Dense(10))
net.initialize(init.Normal(sigma=0.01))
PyTorch:
# PyTorch не меняет форму входных данных неявно. Поэтому перед линейным слоем
# мы определяем слой Flatten, который приводит входную форму сети к нужному виду
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);
TensorFlow:
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))
PaddlePaddle:
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))
def init_weights(m):
if type(m) == nn.Linear:
nn.initializer.Normal(m.weight, std=0.01)
net.apply(init_weights);
3.7.2 Переосмысление реализации softmax
В примере из раздела 3.6 мы сначала вычисляли выход модели, а затем передавали его в кросс-энтропийную потерю. С математической точки зрения это вполне корректно. Однако с вычислительной точки зрения экспоненты могут создавать проблемы с численной стабильностью.
Напомним, что softmax задаётся формулой , где — предсказанное распределение вероятностей, а — -й элемент ненормированного предсказания .
Если некоторые очень велики, то может превысить максимальное число, которое допускает тип данных, — возникает переполнение (overflow).
Тогда числитель или знаменатель становится равным inf (бесконечности), и в итоге получаем , равные 0, inf или nan (не число).
В таких случаях корректное значение кросс-энтропии вычислить невозможно.
Один из приёмов решения этой проблемы: перед вычислением softmax вычесть из всех величину . Видно, что сдвиг всех на одну и ту же константу не меняет результат softmax:
После вычитания и нормировки некоторые величины могут иметь большие по модулю отрицательные значения.
Из-за ограниченной точности окажется очень близким к нулю, то есть возникнет антипереполнение (underflow).
Эти значения могут округлиться до нуля, так что станет равным нулю, а — -inf.
После нескольких шагов обратного распространения мы можем получить целый экран неприятных nan.
Хотя мы вычисляем экспоненты, в итоге в кросс-энтропийной потере берётся их логарифм. Поэтому, объединив softmax и кросс-энтропию, можно избежать проблем численной стабильности при обратном распространении. Как видно из следующих равенств, можно не вычислять , а сразу использовать , поскольку сокращается:
Традиционная функция softmax по-прежнему нужна, если мы хотим оценивать вероятности, которые выдаёт модель. Однако в функцию потерь мы передаём не вероятности softmax, а ненормированные предсказания: softmax и его логарифм вычисляются внутри кросс-энтропийной потери одновременно. Это разумный приём, родственный «трюку LogSumExp».
MXNet:
loss = gluon.loss.SoftmaxCrossEntropyLoss()
PyTorch, PaddlePaddle:
loss = nn.CrossEntropyLoss(reduction='none')
TensorFlow:
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
3.7.3 Алгоритм оптимизации
Здесь используем минибатчевый стохастический градиентный спуск со скоростью обучения 0.1. Это тот же алгоритм, что и в примере линейной регрессии, что показывает универсальность оптимизаторов.
MXNet:
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.1})
PyTorch:
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
TensorFlow:
trainer = tf.keras.optimizers.SGD(learning_rate=.1)
PaddlePaddle:
trainer = paddle.optimizer.SGD(learning_rate=0.1, parameters=net.parameters())
3.7.4 Обучение
Далее вызываем функцию обучения, определённую в разделе 3.6, и обучаем модель.
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
Как и раньше, алгоритм сходится к довольно высокой точности, а код теперь заметно короче.
Резюме
- С помощью высокоуровневого API фреймворков softmax-регрессию можно реализовать значительно короче.
- С вычислительной точки зрения реализация softmax-регрессии довольно сложна. Во многих случаях фреймворки дополнительно принимают меры предосторожности сверх известных приёмов, чтобы обеспечить численную стабильность. Это избавляет нас от ловушек, которые возникают на практике, когда модели пишутся с нуля.
Упражнения
- Попробуйте подобрать гиперпараметры, например размер батча, число эпох и скорость обучения, и посмотрите, как меняются результаты.
- Увеличьте число эпох. Почему через некоторое время точность на тестовых данных начинает снижаться? Как можно решить эту проблему?
MXNet: Обсуждение
PyTorch: Обсуждение
TensorFlow: Обсуждение
PaddlePaddle: Обсуждение
Литература
- LeCun, Y., Bottou, L., Bengio, Y., & Haffner, P. (1998). Gradient-based learning applied to document recognition. Proceedings of the IEEE, 86(11), 2278–2324.
- Russell, S. J., & Norvig, P. (2016). Artificial intelligence: A modern approach. Pearson Education.
- Xiao, H., Rasul, K., & Vollgraf, R. (2017). Fashion-MNIST: A novel image dataset for benchmarking machine learning algorithms. arXiv preprint arXiv:1708.07747.
- Zhang, A., Tay, Y., Zhang, S., Chan, A., Luu, A. T., Hui, S. C., & Fu, J. (2021). Beyond fully-connected layers with quaternions: Parameterization of hypercomplex multiplications with 1/n parameters. In International Conference on Learning Representations.