c++ - neural - relu softmax sigmoid
Implementación de una función de activación de softmax para redes neuronales. (2)
Estoy usando una función de activación de Softmax en la última capa de una red neuronal. Pero tengo problemas con una implementación segura de esta función.
Una implementación ingenua sería esta:
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();
Esto no funciona muy bien para> 100 nodos ocultos porque la y será NaN
en muchos casos (si y (f)> 709, exp (y (f)) devolverá inf). Se me ocurrió esta versión:
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();
donde se define safeExp
como
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);
}
Esta función limita la entrada de exp. En la mayoría de los casos esto funciona, pero no en todos los casos, y realmente no pude averiguar en qué casos no funciona. Cuando tengo 800 neuronas ocultas en la capa anterior, no funciona en absoluto.
Sin embargo, incluso si esto funcionó, de alguna manera "distorsiono" el resultado de la ANN. ¿Se te ocurre alguna otra forma de calcular la solución correcta? ¿Hay alguna biblioteca o truco en C ++ que pueda usar para calcular el resultado exacto de esta ANN?
Edición: La solución provista por Itamar Katz es:
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();
Y realmente es matemáticamente lo mismo. Sin embargo, en la práctica, algunos valores pequeños se convierten en 0 debido a la precisión del punto flotante. Me pregunto por qué nadie escribe estos detalles de implementación en los libros de texto.
Primero vaya a la escala de registro, es decir, calcule log(y)
lugar de y
. El log del numerador es trivial. Para calcular el registro del denominador, puede utilizar el siguiente ''truco'': http://lingpipe-blog.com/2009/06/25/log-sum-of-exponentials/
Sé que ya está respondida, pero de todos modos publicaré aquí un paso a paso.
poner en el registro:
zj = wj . x + bj
oj = exp(zj)/sum_i{ exp(zi) }
log oj = zj - log sum_i{ exp(zi) }
Sea m el max_i {zi} usando el truco 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)}}
el término exp (zi-m) puede sufrir un desbordamiento si m es mucho mayor que otros z_i, pero está bien ya que esto significa que z_i es irrelevante en la salida de softmax después de la normalización. Los resultados finales son:
oj = exp (zj - m - log{sum_i{exp(zi-m)}})