在Octave中,我编写了一个Sigmoid函数的代码,该函数返回0到1之间的值;在一个理想的世界中,它只会为-Inf返回0而对于+ Inf只返回1,但由于浮点不精确,非常接近这些值的值都是四舍五入的。
我的问题是为什么会发生以下情况:对于0和1,舍入的边界明显不同:
>> sigmoid(-709)
ans = 1.2168e-308
>> sigmoid(-710)
ans = 0
>> sigmoid(36)
ans = 1.00000
>> sigmoid(37)
ans = 1
>> (sigmoid(37)-1)==0
ans = 1
>> (sigmoid(36)-1)==0
ans = 0
>> sigmoid(-710)==0
ans = 1
>> sigmoid(-709)==0
ans = 0
在这个例子中,我们可以看到将输出舍入为1所需的值在数量上要小于舍入到0所需的值.37与-710相比是一个非常大的差异,因为它们的幅度应该相同但是相反的迹象......
也许这是我的功能问题:
function [z] = sigmoid(x)
z = 1.0 ./(1.0+exp(-x));
endfunction
另一点是,我改变了函数,将结果加1(基本上将图形翻译为1),边界分别变为+/- 37,分别为2和1 - 这让我觉得它真的与0有关特别是,而不仅仅是功能及其下限。
如果它与我的电脑有关,那会是什么导致这样的事情?
首先,回顾this brilliant answer by gnovice on floating-point representation。
有了这个,让我们来看看你在这里看到的东西:你可以计算一个非常接近于零的值:sigmoid(-709)
大约等于1.2e-308
,但是你不能计算一个类似接近于1的值:sigmoid(709)
完全等于1,而不是1 - 1.2e-308
,甚至sigmoid(36) == 1
,而不是略小于1的值。
但是当我们知道浮点数如何存储在内存中时,我们会发现1 - 1.2e-308
无法准确表示。我们需要308个十进制数字来准确表示这个数字。双精度浮点数(Octave中的默认值)大约有15个十进制数字。也就是说,1 - 1e-16
可以代表,但1 - 1e-17
不能。
eps(1)
的值是2.2204e-16
,这是我们可以在双精度浮点数中编码的最小差值。
但接近0的值可以更精确地表示:eps(0)
是4.9407e-324
。这是因为像1.2e-308
这样的值不需要表示308个十进制数字,而只需要表示2,指数中的值为-308。
在任何情况下,如果您依赖于远离过渡位置的sigmoid函数的精确值,那么您的代码逻辑就会出现问题。
如果你想使这个函数对称,你所能做的就是降低低端的精度。有两种方法可以做到这一点:
z==0
相同的点达到z==1
:
function z = sigmoid(x)
z = 1.0 ./ (1.0+exp(-x));
z(z < eps(1)) = 0;
end
x=0
两侧的计算误差对称:
function z = sigmoid(x)
z = 1.0 ./ (1.0+exp(-abs(x)));
I = x < 0;
z(I) = 0.5 - z(I);
end