Почему октава округляется до 1 "раньше", чем 0?

Контекст:

В Octave я написал код для функции Sigmoid, которая возвращает значения от 0 до 1; в идеальном мире он будет возвращать только 0 для -Inf и 1 для +Inf, но из-за неточности с плавающей запятой значения, которые очень близки к любому из них, округляются.

Вопрос:

Мой вопрос, почему происходит следующее: Граница для округления явно отличается для 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 в частности, а не только функции и ее нижней границы в частности.

Если это как-то связано с моим компьютером, то что может вызвать такую ​​вещь?

1 ответ

Решение

Прежде всего, просмотрите этот блестящий ответ gnovice о представлении с плавающей точкой.

Теперь давайте посмотрим, что вы видите здесь: вы можете вычислить значение, очень близкое к нулю: sigmoid(-709) примерно равно 1.2e-308, но вы не можете вычислить значение, близкое к единице: sigmoid(709) точно равен 1, а не 1 - 1.2e-308, и даже sigmoid(36) == 1, а не значение немного меньше, чем 1.

Но когда мы узнаем, как числа с плавающей точкой хранятся в памяти, мы поймем, что 1 - 1.2e-308 не может быть представлен точно. Нам нужно 308 десятичных цифр, чтобы точно представить это число. Числа с плавающей запятой двойной точности (по умолчанию в октаве) имеют около 15 десятичных цифр. То есть, 1 - 1e-16 можно представить, но 1 - 1e-17 не могу.

Значение eps(1) является 2.2204e-16это наименьшее отличие от 1, которое мы можем кодировать с плавающей запятой двойной точности.

Но значения, близкие к 0, можно представить гораздо точнее: eps(0) является 4.9407e-324, Это потому, что значение, такое как 1.2e-308 не нужно представлять 308 десятичных цифр, а только 2, со значением -308 в показателе степени.

В любом случае, если вы зависите от точных значений сигмоидальной функции так далеко от места перехода, значит, что-то не так с вашей логикой кода.

Если вы хотите сделать эту функцию симметричной, все, что вы можете сделать, это снизить точность на нижнем уровне. Есть два способа сделать это:

  1. Просто установите на ноль очень маленькие значения, чтобы z==0 достигается в той же точке, что и z==1 на другой стороне:

    function z = sigmoid(x)
      z = 1.0 ./ (1.0+exp(-x));
      z(z < eps(1)) = 0;
    end
    
  2. Всегда вычисляйте правую половину функции, затем уменьшайте ее для получения отрицательных входных данных. Это делает ошибку вычисления на обеих сторонах x=0 симметричен:

    function z = sigmoid(x)
      z = 1.0 ./ (1.0+exp(-abs(x)));
      I = x < 0;
      z(I) = 0.5 - z(I);
    end
    
Другие вопросы по тегам