【问题标题】:Implementation of a softmax activation function for neural networks神经网络的softmax激活函数的实现
【发布时间】:2012-04-11 23:08:21
【问题描述】:

我在神经网络的最后一层使用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 定义为

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++ 库或技巧可用于计算此 ANN 的准确输出?

编辑: Itamar Katz 提供的解决方案是:

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。我想知道为什么没有人在教科书中写下这些实现细节。

【问题讨论】:

  • “我想知道为什么没有人在教科书中写下这些实现细节。”我一直想知道同样的事情!
  • “它在数学上确实是一样的”——进一步阅读,有人说由于数值稳定性,您的方法更受欢迎。:stackoverflow.com/questions/34968722/softmax-function-python

标签: c++ math neural-network softmax


【解决方案1】:

我知道它已经回答了,但无论如何我都会在这里一步一步地发布。

写日志:

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)}}

如果 m 远大于其他 z_i,则 exp(zi-m) 项可能会出现下溢,但这没关系,因为这意味着 z_i 在归一化后与 softmax 输出无关。最终结果是:

oj = exp (zj - m - log{sum_i{exp(zi-m)}})

【讨论】:

  • 谢谢!你的回答有帮助!您提到“但这没关系,因为这意味着 z_i 与标准化后的 softmax 输出无关”,您的意思是如果发生exp(zi-m) 的下溢。它不会在结果中增加太多错误?
  • 抱歉回复晚了。是的,如果 m >> zi 则 exp(zi-m) 将接近 0,下溢只会将其更改为 0,这不会对最终结果产生太大影响。
【解决方案2】:

首先转到对数刻度,即计算 log(y) 而不是 y。分子的对数是微不足道的。为了计算分母的对数,您可以使用以下“技巧”:http://lingpipe-blog.com/2009/06/25/log-sum-of-exponentials/

【讨论】:

  • 完美的解决方案。我将在一分钟内添加代码。你能确认一下吗?非常感谢。
  • 好像不太对;遵循 log(y(f)) 的代数:log(y(f))=log(exp(y(f))) - log(sum(exp(y(f))) 并插入在总和日志的上述“技巧”结果中。
  • ln(y_f) = ln(exp(a_f)) - ln(f 的总和 exp(a_f')) = af - ln[f 的总和 exp(m)/exp(m ) * exp(a_f')] = a_f - m - ln(f' 的总和 exp(-m) * exp(a_f)) = a_f - m - ln[f' 的总和 exp(a_f'-m)] y_f exp(a_f-m)/(f' exp(a_f' - m) 的总和)。 a_f 在上面列出的代码中是 exp() 之前的 y_f。错误在哪里? :D
  • 我用 a_1 = 1, a_2 = 2, a_3 = 3 做了一个测试。向量 y 在这两种情况下都是 y = (0.090031,0.24473,0.66524)^T。至少在这种情况下它似乎是正确的。
猜你喜欢
  • 1970-01-01
  • 2013-08-20
  • 2018-03-24
  • 2014-01-18
  • 2017-12-08
  • 2013-12-20
  • 2019-07-03
  • 2014-03-15
相关资源
最近更新 更多