Реализация функции активации softmax для нейронных сетей
Я использую Softmax функции активации в последнем слое нейронной сети. Но у меня есть проблемы с безопасной реализацией этой функции.
наивная реализация будет такой:
Vector y = mlp(x); // output of the neural network without softmax activation function
for(int f = 0; f < y.rows(); f++)
y(f) = exp(y(f));
y /= y.sum();
это не очень хорошо работает для > 100 скрытых узлов, потому что y будет NaN
во многих случаях(если y(f) > 709, exp(y (f)) вернет inf). Я придумал такую версию:
Vector y = mlp(x); // output of the neural network without softmax activation function
for(int f = 0; f < y.rows(); f++)
y(f) = safeExp(y(f), y.rows());
y /= y.sum();
здесь safeExp
определяется as
double safeExp(double x, int div)
{
static const double maxX = std::log(std::numeric_limits<double>::max());
const double max = maxX / (double) div;
if(x > max)
x = max;
return std::exp(x);
}
эта функция ограничивает вход exp. В большинстве случаев это работает, но не во всех случаях и не удается выяснить, в каких случаях он не работает. Когда у меня есть 800 скрытых нейронов в предыдущем слое, он вообще не работает.
однако, даже если это сработало, я как-то" искажаю " результат ANN. Можете ли вы придумать какой-либо другой способ вычислить правильное решение? Есть ли какие-либо библиотеки или приемы C++, которые я могу использовать для вычисления точный выход этой Энн?
edit: решение, предоставленное Итамаром Кацем:
Vector y = mlp(x); // output of the neural network without softmax activation function
double ymax = maximal component of y
for(int f = 0; f < y.rows(); f++)
y(f) = exp(y(f) - ymax);
y /= y.sum();
и это действительно математически то же самое. Однако на практике некоторые небольшие значения становятся 0 из-за точности с плавающей запятой. Интересно, почему никто никогда не записывает эти детали реализации в учебники.
2 ответов
сначала перейдите в масштаб журнала, i.e вычислить log(y)
вместо y
. Лог числителя тривиален. Для того, чтобы вычислить лог знаменателя, вы можете использовать следующий "трюк":http://lingpipe-blog.com/2009/06/25/log-sum-of-exponentials/
Я знаю, что на него уже ответили, но я все равно отправлю здесь шаг за шагом.
поставить на входе:
zj = wj . x + bj
oj = exp(zj)/sum_i{ exp(zi) }
log oj = zj - log sum_i{ exp(zi) }
пусть m-max_i { zi } используйте трюк log-sum-exp:
log oj = zj - log {sum_i { exp(zi + m - m)}}
= zj - log {sum_i { exp(m) exp(zi - m) }},
= zj - log {exp(m) sum_i {exp(zi - m)}}
= zj - m - log {sum_i { exp(zi - m)}}
термин exp (zi-m) может страдать от underflow, если m намного больше, чем другие z_i, но это нормально, так как это означает, что z_i не имеет значения на выходе softmax после нормализации. окончательные результаты:
oj = exp (zj - m - log{sum_i{exp(zi-m)}})