Кубическая регрессия (линия наилучшего соответствия) в JavaScript

У меня самые тяжелые времена, когда я пытаюсь найти код JavaScript, который позволил бы мне выполнять кубическую регрессию. Я бы написал это сам, но мое понимание полиномиальной математики, ну, неоптимально.

Итак, вот что я ищу. При вводе массива массивов, где внутренний массив будет [x,y], функция выдаст мне вывод в виде массива с четырьмя параметрами — [a,b,c,d], где a , b, c и d — параметры уравнения y = ax^3 + bx^2 + cx + d.

Пример: ввод представляет собой такой массив [[2,5],[5,10],[07,15],[12,20],[20,25],[32,30],[50,35] ].

Что по существу является представлением таблицы:

|    x   |   y    |
|-----------------|
|   02   |   05   |
|   05   |   10   |
|   07   |   15   |
|   12   |   20   |
|   20   |   25   |
|   32   |   30   |
|   50   |   35   |

Теперь вывод будет [0,000575085,-0,058861065,2,183957502,1,127605507]. Это параметры a, b, c и d кубической функции.

(К вашему сведению, результат, который я получил, используя функцию ЛИНЕЙН в Excel и запустив ее для приведенного выше набора чисел с помощью функции массива {1,2,3}).

Как это можно сделать? Заранее огромное спасибо за любое руководство.

Лучший, Том


person teeZee    schedule 11.03.2014    source источник


Ответы (2)


Вот настоящий рабочий код для решения этой кубической задачи с использованием uncmin неограниченного минимизатора библиотеки numeric.js в качестве задача наименьших квадратов (jsbin здесь):

var data_x = [2,5,7,12,20,32,50];
var data_y = [5,10,15,20,25,30,35];

var cubic = function(params,x) {
  return params[0] * x*x*x +
    params[1] * x*x +
    params[2] * x +
    params[3];
};

var objective = function(params) {
  var total = 0.0;
  for(var i=0; i < data_x.length; ++i) {
    var resultThisDatum = cubic(params, data_x[i]);
    var delta = resultThisDatum - data_y[i];
    total += (delta*delta);
  }
  return total;
};

var initial = [1,1,1,1];
var minimiser = numeric.uncmin(objective,initial);

console.log("initial:");
for(var j=0; j<initial.length; ++j) {
  console.log(initial[j]);  
}

console.log("minimiser:");
for(var j=0; j<minimiser.solution.length; ++j) {
  console.log(minimiser.solution[j]);
}

Я получаю результаты:

 0.0005750849851827991
-0.05886106462847641
 2.1839575020602164
 1.1276055079334206

Поясню: у нас есть функция 'cubic', которая оценивает общую кубическую функцию для набора параметров params и значения x. Эта функция обернута для создания целевой функции, которая принимает набор параметров и пропускает каждое значение x из нашего набора данных через целевую функцию и вычисляет сумму квадратов. Эта функция передается в uncmin из numeric.js с набором начальных значений; uncmin выполняет тяжелую работу и возвращает объект, свойство solution которого содержит оптимизированный набор параметров.

Чтобы сделать это без глобальных переменных (капризно!), вы можете иметь фабрику целевых функций следующим образом:

var makeObjective = function(targetFunc,xlist,ylist) {
  var objective = function(params) {
    var total = 0.0;
    for(var i=0; i < xlist.length; ++i) {
      var resultThisDatum = targetFunc(params, xlist[i]);
      var delta = resultThisDatum - ylist[i];
      total += (delta*delta);
    }
    return total;
  };
  return objective;
};

Которые вы можете использовать для создания целевых функций:

var objective = makeObjective(cubic, data_x, data_y); // then carry on as before

Знание того, как это сделать на практике, очень помогло бы многим людям, поэтому я рад, что это всплыло.

Изменить: разъяснение по cubic

var cubic = function(params,x) {
  return params[0] * x*x*x +
    params[1] * x*x +
    params[2] * x +
    params[3];
};

Cubic определяется как функция, которая принимает массив параметров params и значение x. Учитывая params, мы можем определить функцию f(x). Для куба это f(x) = a x^3 + b x^2 + c x + d, поэтому есть 4 параметра (от [0] до [3]), и с учетом этих 4 значений параметров у нас есть одна функция f(x) с 1 входом x.

Код структурирован так, чтобы вы могли заменить cubic другой функцией той же структуры; это может быть linear с двумя параметрами:

var linear = function(params, x) {
    return params[0]*x + params[1];
};

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

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

person Phil H    schedule 14.03.2014
comment
@PhilH Эй, но после того, как я получу эти числа, что мне делать, чтобы рассчитать прогнозируемое значение? - person BlackMamba; 23.10.2017
comment
Это создает кубическую функцию f(x), поэтому вы вызываете ее со значением x: cubic(minimiser.solution, xvalue). - person Phil H; 24.10.2017
comment
@PhilH Эй, извините, но просто быстрая проверка с вами, скажем, если мои данные находятся в шаблоне, который безумно увеличивается и уменьшается, кубическая регрессия в этом случае не подходит, я прав? Потому что, насколько я понимаю, кубическая регрессия растет по схеме «половина дуги», верно? - person BlackMamba; 25.10.2017
comment
@hyperfkcb: любая полиномиальная подгонка, начиная с квадратичной и далее, может давать дикие кривые. Похоже, у вас есть дополнительное ограничение ограничения перерегулирования? В этом случае вам нужно будет решить, что это за ограничение, прежде чем продолжить. - person Phil H; 30.10.2017
comment
@PhilH Эй, извините, но применимо ли приведенное выше решение для прогнозирования стоимости, которая растет на диких кривых? - person BlackMamba; 03.11.2017
comment
@hyperfkcb: Зависит от того, насколько диким. Это нормально для любой приблизительно кубической кривой, даже если коэффициенты велики. Но вы можете адаптировать этот код для другой функции, заменив там cubic. Если вы хотите разместить некоторые образцы данных и опубликовать вопрос, дайте ссылку здесь. - person Phil H; 10.11.2017
comment
@PhilH Эй, не могли бы вы взглянуть на это: stackoverflow.com/questions/46881282/ - person BlackMamba; 10.11.2017
comment
@PhilH Эй, не могли бы вы объяснить немного больше о кубическом ()? Как я могу соответственно изменить набор данных [347,3, 77, 549,7, 200, 273, 367,7, 382,2, 231,7, 320,6, 209,8, 388,3, 653,7]. - person BlackMamba; 13.12.2017
comment
@hyperfkcb: вам нужно поместить данные в data_x и data_y. Я добавлю немного объяснения cubic к ответу. - person Phil H; 14.12.2017
comment
@PhilH Большое спасибо за объяснение! Однако я понял проблему с приведенным выше кодом, я попробовал с другим набором данных и понял, что график либо всегда будет опускаться до нуля, затем взлетать до определенного значения, затем снова опускаться или наоборот. около. Другими словами, это либо вниз, вверх, вниз или вверх, вниз, вверх, поскольку кривая ограничена только тремя поворотными точками. Это должно так себя вести? Если да, то есть ли какая-нибудь формула, которая не ограничивала бы поворотные точки графа предсказания только тремя? - person BlackMamba; 15.12.2017
comment
@hyperfkcb: Да и да. Кубический означает полиномиальный порядок 3, который может иметь только до 2 точек поворота. Это не общая система для угадывания порядка полинома - вы всегда можете создать точный порядок полинома от n-1 до n точек, поэтому вы никогда не получите «наилучшее соответствие». Чтобы подогнать кривую к точкам данных, вам нужно будет выбрать некоторое предположение о форме кривой, которая, по сути, является моделью, которую вы используете. - person Phil H; 15.12.2017

Я бы сформулировал это как задачу наименьших квадратов. Пусть M будет матрицей n×4, сформированной следующим образом:

x_1^3  x_1^2  x_1  1
x_2^3  x_2^2  x_2  1
  ⋮       ⋮      ⋮
x_n^3  x_n^2  x_n  1

Затем вычислите матрицу 4 × 4 A=MTM и вектор-столбец 4 × 1 b=MTy и решить линейную систему уравнений =б. Результирующий вектор ξ будет содержать ваши коэффициенты от a до d.

Приведенное выше описание позволяет легко понять, что происходит, математически. Однако для реализации, особенно для очень больших n, описанный выше подход может оказаться неприемлемым. В таких случаях вы можете построить A и b напрямую, без явного построения M. Например, A1,2=sum(x_i^3 * x_i^2 for all i). Таким образом, вы можете перебрать все i и добавить соответствующие значения в соответствующие элементы матрицы и вектора.

person MvG    schedule 11.03.2014
comment
Или, более систематически, A_(i,j)=A_(j,i)=sum(x_k^(8-ij) по всем k) и b_i=sum(y_k*x_k^(4-i) по всем k) . Используйте Cholesky или LDLT, чтобы факторизовать матрицу системы A и найти решение, оно должно работать без поворота, поскольку A будет положительно полуопределенным и даже почти наверняка положительно определенным. - person Lutz Lehmann; 11.03.2014