Предотвращение переполнения при вычислении значения И градиента функции логистических потерь

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

В общем, для данного входа s, значение логистической функции:

 log(1 + exp(s))

и наклон функции логистических потерь:

 exp(s)./(1 + exp(s)) = 1./(1 + exp(-s))

В моем алгоритме значение s = X*beta, Вот X это матрица с N точки данных и P особенности на точку данных (т.е. size(X)=[N,P]) а также beta это вектор P коэффициенты для каждой функции, так что size(beta)=[P 1],

Я особенно заинтересован в расчете среднего значения и градиента функции логистики для данного значения beta,

Среднее значение логистической функции по отношению к значению beta является:

 L = 1/N * sum(log(1+exp(X*beta)),1)

Среднее значение наклона логистической функции по отношению к значению b является:

 dL = 1/N * sum((exp(X*beta)./(1+exp(X*beta))' X, 1)'

Обратите внимание, что size(dL) = [P 1].

Моя проблема в том, что эти выражения продолжают вызывать числовые переполнения. Фактически проблема заключается в том, что exp(s)=Inf когда s>1000 а также exp(s)=0 когда s<-1000.

Я ищу решение, которое s может принимать любое значение в арифметике с плавающей запятой. В идеале я также был бы очень признателен за решение, которое позволяет мне оценивать значение и градиент векторизованным / эффективным способом.

2 ответа

Решение

Как насчет следующих приближений:

- для вычислений L, если s большой, то exp(s) будет намного больше чем 1:

1 + exp(s) ≅ exp(s)

и следовательно

log(1 + exp(s)) ≅ log(exp(s)) = s.

Если s мал, то с помощью ряда Тейлора exp()

exp(s) ≅ 1 + s

и используя серию журнала Тейлора ()

log(1 + exp(s)) ≅ log(2 + s) ≅ log(2) + s / 2.

- для вычислений dLдля большого s

exp(s) ./ (1 + exp(s)) ≅ 1

и для маленьких s

exp(s) ./ (1 + exp(s)) ≅ 1/2 + s / 4.

- код для вычисления 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)

Я нашел хорошую статью об этой проблеме.

Обрезая много слов, мы можем упростить аргумент, утверждая, что исходное выражение

log(1 + exp(s)) 

может быть переписан как

log(exp(s)*(exp(-s) + 1))
= log(exp(s)) + log(exp(-s) + 1)
= s + log(exp(-s) + 1)

Это предотвращает переполнение - это не предотвращает переполнение, но к тому времени, когда это происходит, у вас есть ответ (а именно, s). Вы не можете просто использовать это вместо оригинала, так как это все равно создаст вам проблемы. Однако теперь у нас есть основа для функции, которая может быть написана, которая будет точной и не приведет к переполнению:

function LL = logistic(s)
if s<0
  LL = log(1 + exp(s));
else
  LL = s + logistic(-s);

Я думаю, что это поддерживает достаточно хорошую точность.

Отредактируйте теперь суть вашего вопроса - сделать это векторизации, а также позволяет рассчитать наклон. Давайте возьмем эти по одному:

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

Чтобы получить среднее значение, которое вы хотели:

L = logisticVec(X*beta) / N;

Склон немного сложнее; Заметьте, я полагаю, что в вашем выражении может быть опечатка (отсутствует знак умножения).

dL/dbeta = sum(X * exp(X*beta) ./ (1 + exp(X*beta))) / N;

Если мы разделим верх и низ на exp(X*beta) мы получаем

dL = sum(X ./ (exp(-X*beta) + 1)) / N;

Еще раз, переполнение исчезло, и у нас остался недостаток - но так как значение недостаточного 1 добавленное к этому, ошибка, которую это создает, незначительна.

Другие вопросы по тегам