在计算Logistic损失函数的值和梯度时避免数值溢出

Ber*_* U. 5 floating-point matlab numerical-methods numerical-stability logistic-regression

我目前正在尝试实现一种涉及MATLAB中逻辑损失函数的机器学习算法.不幸的是,由于数字溢出,我遇到了一些麻烦.

通常,对于给定的输入s,逻辑函数的值为:

 log(1 + exp(s))
Run Code Online (Sandbox Code Playgroud)

并且逻辑损失函数的斜率是:

 exp(s)./(1 + exp(s)) = 1./(1 + exp(-s))
Run Code Online (Sandbox Code Playgroud)

在我的算法中,值为s = X*beta.这X是一个矩阵,每个数据点具有N数据点和P特征(即size(X)=[N,P]),并且betaP每个特征的系数向量size(beta)=[P 1].

我特别感兴趣的是计算给定值的Logistic函数的平均值和梯度beta.

Logistic函数的平均值为beta值为:

 L = 1/N * sum(log(1+exp(X*beta)),1)
Run Code Online (Sandbox Code Playgroud)

Logistic函数斜率的平均值为b值为:

 dL = 1/N * sum((exp(X*beta)./(1+exp(X*beta))' X, 1)'
Run Code Online (Sandbox Code Playgroud)

注意 size(dL) = [P 1].

我的问题是这些表达式不断产生数值溢出.问题有效地来自于一个事实,即exp(s)=Infs>1000exp(s)=0s<-1000.

我正在寻找一种解决方案,s可以采用浮点运算中的任何值.理想情况下,我也非常感谢一种解决方案,它允许我以矢量化/有效的方式评估值和梯度.

A. *_*nda 9

以下近似值如何:

- 对于计算L,如果s很大,那么exp(s)将远大于1:

1 + exp(s) ? exp(s)
Run Code Online (Sandbox Code Playgroud)

因此

log(1 + exp(s)) ? log(exp(s)) = s.
Run Code Online (Sandbox Code Playgroud)

如果s很小,那么使用exp()的泰勒系列

exp(s) ? 1 + s
Run Code Online (Sandbox Code Playgroud)

并使用泰勒系列的log()

log(1 + exp(s)) ? log(2 + s) ? log(2) + s / 2.
Run Code Online (Sandbox Code Playgroud)

- 用于计算dL,用于大型s

exp(s) ./ (1 + exp(s)) ? 1
Run Code Online (Sandbox Code Playgroud)

并为小 s

exp(s) ./ (1 + exp(s)) ? 1/2 + s / 4.
Run Code Online (Sandbox Code Playgroud)

- 计算代码L可以像这样查看:

s = X*beta;
l = log(1+exp(s));
ind = isinf(l);
l(ind) = s(ind);
ind = (l == 0);
l(ind) = log(2) + s(ind) / 2;
L = 1/N * sum(l,1)
Run Code Online (Sandbox Code Playgroud)

  • 答对了.实际上,您可以获得关于第二个等于保持浮点运算的大小需要的严格界限.(当然,这完全是矫枉过正,但它提供了一个非常有力的理由说明为什么这样做是合法的.) (4认同)

Flo*_*ris 5

我发现了一篇关于这个问题的好文章.

通过翻译很多单词,我们可以简化论证,说明原始表达

log(1 + exp(s)) 
Run Code Online (Sandbox Code Playgroud)

可以改写为

log(exp(s)*(exp(-s) + 1))
= log(exp(s)) + log(exp(-s) + 1)
= s + log(exp(-s) + 1)
Run Code Online (Sandbox Code Playgroud)

这会阻止溢出 - 它不会阻止下溢,但是到了发生的时候,你就有了答案(即s).你不能只使用它而不是原版,因为它仍然会给你带来问题.但是,我们现在有了一个可以编写的函数的基础,该函数将是准确的并且不会产生上溢/下溢:

function LL = logistic(s)
if s<0
  LL = log(1 + exp(s));
else
  LL = s + logistic(-s);
Run Code Online (Sandbox Code Playgroud)

我认为这保持了相当好的准确性.

现在编辑您的问题 - 使这个矢量化,并允许计算斜率.我们一次拿这些:

function LL = logisticVec(s)
  LL = zeros(size(s));
  LL(s<0) = log(1 + exp(s(s<0)));
  LL(s>=0) = s(s>=0) + log(1 + exp(-s(s>=0)));
Run Code Online (Sandbox Code Playgroud)

要获得您想要的平均值:

L = logisticVec(X*beta) / N;
Run Code Online (Sandbox Code Playgroud)

坡度有点棘手; 请注意我相信你的表达可能有一个拼写错误(缺少乘法符号).

dL/dbeta = sum(X * exp(X*beta) ./ (1 + exp(X*beta))) / N;
Run Code Online (Sandbox Code Playgroud)

如果exp(X*beta)我们得到顶部和底部

dL = sum(X ./ (exp(-X*beta) + 1)) / N;
Run Code Online (Sandbox Code Playgroud)

再次,溢出已经消失,我们留下了下溢 - 但由于下溢的值已经1添加到它,这产生的错误是微不足道的.