Предотвращение переполнения при вычислении значения И градиента функции логистических потерь
В настоящее время я пытаюсь реализовать алгоритм машинного обучения, который включает в себя функцию логистических потерь в 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
добавленное к этому, ошибка, которую это создает, незначительна.