Функция стоимости в логистической регрессии дает NaN в результате

Я реализую логистическую регрессию с использованием группового градиентного спуска. Существует два класса, в которые должны быть классифицированы входные выборки. Классы 1 и 0. При подготовке данных я использую следующую сигмоидную функцию:

t = 1 ./ (1 + exp(-z));

где

z = x*theta

И я использую следующую функцию затрат для расчета стоимости, чтобы определить, когда прекратить обучение.

htheta = sigmoid(x*theta);
cost = sum(-y .* log(htheta) - (1-y) .* log(1-htheta));

Я получаю на каждом этапе стоимость NaN, так как значения htheta в большинстве случаев равны 1 или нулю. Что мне делать, чтобы определить стоимость на каждой итерации?

Это код спуска градиента для логистической регрессии:

function [theta,cost_history] = batchGD(x,y,theta,alpha)

cost_history = zeros(1000,1);

for iter=1:1000
  htheta = sigmoid(x*theta);
  new_theta = zeros(size(theta,1),1);
  for feature=1:size(theta,1)
    new_theta(feature) = theta(feature) - alpha * sum((htheta - y) .*x(:,feature))                         
  end
  theta = new_theta;
  cost_history(iter) = computeCost(x,y,theta);
end
end

Ответ 1

Есть две возможные причины, почему это может происходить с вами.

Данные не нормализованы

Это связано с тем, что когда вы применяете сигмоидную/логитную функцию к своей гипотезе, вероятность выхода почти равна приблизительно 0 или 1, а с вашей функцией затрат log(1 - 1) или log(0) будет выдавать -Inf. Накопление всех этих индивидуальных условий в вашей функции стоимости в конечном итоге приведет к NaN.

В частности, если y = 0 для примера обучения и если вывод вашей гипотезы log(x), где x - очень небольшое число, близкое к 0, рассмотрение первой части функции стоимости даст нам 0*log(x) и на самом деле произведет NaN. Аналогично, если y = 1 для примера обучения, и если результат вашей гипотезы также log(x), где x - очень маленькое число, это снова даст нам 0*log(x) и произведет NaN. Проще говоря, вывод вашей гипотезы либо очень близок к 0, либо очень близок к 1.

Это, скорее всего, связано с тем, что динамический диапазон каждой функции сильно отличается, и поэтому часть вашей гипотезы, в частности, взвешенная сумма x*theta для каждого учебного примера, который у вас есть, даст вам либо очень большой отрицательный результат или положительные значения, и если вы примените сигмоидную функцию к этим значениям, вы окажетесь очень близко к 0 или 1.

Один из способов борьбы с этим - нормализовать данные в вашей матрице перед началом обучения с использованием градиентного спуска. Типичным подходом является нормализация с нулевым средним и единичным изменением. Учитывая функцию ввода x_k где k = 1, 2, ... n, где у вас есть функции n, новая нормализованная функция x_k^{new} может быть найдена:

m_k является средним значением функции k и s_k является стандартным отклонением функции k. Это также известно как стандартизация данных. Вы можете прочитать более подробную информацию об этом в другом ответе, который я привел здесь: Как работает этот код для стандартизации данных?

Поскольку вы используете подход линейной алгебры к градиентному спуску, я предполагаю, что вы добавили свою матрицу данных со столбцом всех. Зная это, мы можем нормализовать ваши данные следующим образом:

mX = mean(x,1); 
mX(1) = 0; 
sX = std(x,[],1); 
sX(1) = 1; 
xnew = bsxfun(@rdivide, bsxfun(@minus, x, mX), sX);

Среднее и стандартное отклонения каждой функции сохраняются в mX и sX соответственно. Вы можете узнать, как работает этот код, прочитав сообщение, которое я связал с вами выше. Я не буду повторять этот материал здесь, потому что это не объем этой публикации. Чтобы обеспечить нормальную нормализацию, я сделал среднее и стандартное отклонение первого столбца равным 0 и 1 соответственно. xnew содержит новую нормированную матрицу данных. Используйте xnew с вашим алгоритмом спуска градиента. Теперь, когда вы найдете параметры, для выполнения любых прогнозов вы должны нормализовать любые новые тестовые экземпляры со средним и стандартным отклонением от набора обучения. Поскольку полученные параметры являются статистическими данными набора тренировок, вы также должны применять те же преобразования к любым тестовым данным, которые вы хотите отправить в модель прогнозирования.

Предполагая, что у вас есть новые точки данных, хранящиеся в матрице с именем xx, вы должны выполнить нормализацию, а затем выполнить предсказания:

xxnew = bsxfun(@rdivide, bsxfun(@minus, xx, mX), sX);

Теперь, когда у вас есть это, вы можете выполнить свои прогнозы:

pred = sigmoid(xxnew*theta) >= 0.5;

Вы можете изменить порог 0,5, чтобы быть тем, что, по вашему мнению, лучше всего определяет, относятся ли примеры к положительному или отрицательному классу.

Слишком большая скорость обучения

Как вы упомянули в комментариях, как только вы нормализуете данные, затраты кажутся конечными, но затем внезапно перейдите к NaN после нескольких итераций. Нормализация может довести вас до сих пор. Если ваша скорость обучения или alpha слишком велика, каждая итерация будет превышать по направлению к минимуму, и, таким образом, стоимость каждой итерации будет колебаться или даже расходиться, что и происходит. В вашем случае стоимость расходится или увеличивается на каждой итерации до такой степени, что она не может быть представлена ​​с использованием точности с плавающей запятой.

Таким образом, еще один вариант - уменьшить скорость обучения alpha, пока вы не увидите, что функция затрат уменьшается на каждой итерации. Популярным методом определения наилучшей скорости обучения является выполнение градиентного спуска в диапазоне значений с логарифмически разнесенными значениями alpha и просмотр значения конечной стоимости функции и выбор скорости обучения, которая привела к наименьшей стоимости.


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

Ответ 2

Предположим, у вас есть наблюдение, где:

  • истинное значение: y_i = 1
  • ваша модель весьма экстремальна и говорит, что P (y_i = 1) = 1

Тогда ваша функция стоимости получит значение NaN, потому что вы добавляете 0 * log(0), который равен undefined. Следовательно:

Ваша формула для функции стоимости имеет проблему (есть проблема с тонкостью 0, бесконечность)!

Как отметил @rayryeng, 0 * log(0) создает NaN, потому что 0 * Inf не кошерный. На самом деле это огромная проблема: если ваш алгоритм считает, что он может точно предсказать значение, он неправильно присваивает стоимость NaN.

Вместо:

cost = sum(-y .* log(htheta) - (1-y) .* log(1-htheta));

Вы можете избежать умножения 0 на бесконечность, вместо написания своей функции затрат в Matlab следующим образом:

y_logical = y == 1;
cost = sum(-log(htheta(y_logical))) + sum( - log(1 - htheta(~y_logical)));

Идея состоит в том, что если y_i равно 1, мы добавляем -log(htheta_i) к стоимости, но если y_i равно 0, добавим -log(1 - htheta_i) к стоимости. Это математически эквивалентно -y_i * log(htheta_i) - (1 - y_i) * log(1- htheta_i), но не работает в числовые задачи, которые по существу связаны с htheta_i, равными 0 или 1 в пределах плавающей запятой двойной точности.