Градиентный спуск, возвращающий nan

#c #machine-learning #gradient-descent

#c #машинное обучение #градиентный спуск

Вопрос:

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

 //solves for y using the form y = a   bx   cx^2 ...
double calc_polynomial(int degree, double x, double* coeffs) {
    double y = 0;

    for (int i = 0; i <= degree; i  )
        y  = coeffs[i] * pow(x, i);

    return y;
}

//find polynomial fit
//returns an array of coefficients degree   1 long
double* poly_fit(double* x, double* y, int count, int degree, double learningRate, int iterations) {
    double* coeffs = malloc(sizeof(double) * (degree   1));
    double* sums = malloc(sizeof(double) * (degree   1));

    for (int i = 0; i <= degree; i  )
        coeffs[i] = 0;

    for (int i = 0; i < iterations; i  ) {
        //reset sums each iteration
        for (int j = 0; j <= degree; j  )
            sums[j] = 0;

        //update weights
        for (int j = 0; j < count; j  ) {
            double error = calc_polynomial(degree, x[j], coeffs) - y[j];

            //update sums
            for (int k = 0; k <= degree; k  )
                sums[k]  = error * pow(x[j], k);
        }

        //subtract sums
        for (int j = 0; j <= degree; j  )
            coeffs[j] -= sums[j] * learningRate;
    }

    free(sums);

    return coeffs;
}
 

И мой тестовый код:

 double x[] = { 0, 1, 2, 3, 4 };
double y[] = { 5, 3, 2, 3, 5 };
int size = sizeof(x) / sizeof(*x);

int degree = 1;
double* coeffs = poly_fit(x, y, size, degree, 0.01, 1000);

for (int i = 0; i <= degree; i  )
    printf("%lfn", coeffs[i]);
 

Приведенный выше код работает, когда степень = 1, но все, что выше, приводит к тому, что коэффициенты возвращаются как nan.

Я также пытался заменить

 coeffs[j] -= sums[j] * learningRate;
 

с

 coeffs[j] -= (1/count) * sums[j] * learningRate;
 

но тогда я получаю обратно 0s вместо nan.

Кто-нибудь знает, что я делаю не так?

Комментарии:

1. 1/count является целочисленным делением , результат которого усекается.

2. Ах, это была проблема… Теперь я чувствую себя глупо

3. Я попробовал degree = 2, iteration = 10 и получил результаты, отличные от nan (значения около нескольких тысяч) Добавление единицы к iteration , кажется, увеличивает величину результатов примерно в 3 раза после этого.

4. Подумайте о том, чтобы избежать повторных вызовов для pow реализации метода Хорнера для вычисления многочлена и чего-то подобного для суммы ошибок.

5. Примечание: использование printf("%len", coeffs[i]); более информативно, чем printf("%lfn", coeffs[i]);

Ответ №1:

Я попробовал degree = 2, iteration = 10 и получил результаты, отличные от nan (значения около нескольких тысяч) Добавление единицы к iteration , кажется, увеличивает величину результатов примерно в 3 раза после этого.

Из этого наблюдения я предположил, что результаты умножаются на count .

В выражении

 coeffs[j] -= (1/count) * sums[j] * learningRate;
 

Оба из 1 и count являются целыми числами, поэтому выполняется целочисленное деление, 1/count и оно станет равным нулю, если count больше 1.

Вместо этого вы можете разделить результат умножения на count .

 coeffs[j] -= sums[j] * learningRate / count;
 

Другой способ — использовать 1.0 ( double значение) вместо 1 .

 coeffs[j] -= (1.0/count) * sums[j] * learningRate;
 

Ответ №2:

В сторону:

NAN Источник-кандидат добавляет противоположные значения со знаком, где единица равна бесконечности. Данный OP использует pow(x, k) , который быстро растет, используя другие методы помощи.

Рассмотрим цепное умножение, а не pow() . Результат обычно более численно стабилен. calc_polynomial() например:

 double calc_polynomial(int degree, double x, double* coeffs) {
  double y = 0;
  // for (int i = 0; i <= degree; i  )
  for (int i = degree; i >= 0; i--)
    //y  = coeffs[i] * pow(x, i);
    y = y*x   coeffs[i];
  }
  return y;
}
 

Аналогичный код можно было бы использовать для main() тела.