exponenta event banner

Нелинейная подгонка данных с использованием нескольких проблемных подходов

Общий совет для настройки задачи наименьших квадратов состоит в том, чтобы сформулировать проблему таким образом, чтобы это позволяло solve признать, что проблема имеет форму наименьших квадратов. Когда вы это делаете, solve внутренние вызовы lsqnonlin, что эффективно при решении задач наименьших квадратов. См. раздел Целевая функция записи для наименьших квадратов, основанных на проблемах.

Этот пример показывает эффективность решателя наименьших квадратов, сравнивая производительность lsqnonlin с тем, что fminunc по той же проблеме. Кроме того, в примере показаны дополнительные преимущества, которые можно получить, явно распознав и обрабатывая отдельно линейные части задачи.

Настройка проблемы

Рассмотрим следующие данные:

Data = ...
  [0.0000    5.8955
   0.1000    3.5639
   0.2000    2.5173
   0.3000    1.9790
   0.4000    1.8990
   0.5000    1.3938
   0.6000    1.1359
   0.7000    1.0096
   0.8000    1.0343
   0.9000    0.8435
   1.0000    0.6856
   1.1000    0.6100
   1.2000    0.5392
   1.3000    0.3946
   1.4000    0.3903
   1.5000    0.5474
   1.6000    0.3459
   1.7000    0.1370
   1.8000    0.2211
   1.9000    0.1704
   2.0000    0.2636];

Постройте график точек данных.

t = Data(:,1);
y = Data(:,2);
plot(t,y,'ro')
title('Data points')

Figure contains an axes. The axes with title Data points contains an object of type line.

Проблема заключается в подгонке функции

y = c(1)*exp(-lam(1)*t) + c(2)*exp(-lam(2)*t)

к данным.

Подход к решению с использованием решателя по умолчанию

Для начала определите переменные оптимизации, соответствующие уравнению.

c = optimvar('c',2);
lam = optimvar('lam',2);

Произвольно установить начальную точку x0 следующим образом: c(1) = 1, c(2) = 1, lam(1) = 1, и lam(2) = 0:

x0.c = [1,1];
x0.lam = [1,0];

Создание функции, которая вычисляет значение ответа время от времени t когда параметры c и lam.

diffun = c(1)*exp(-lam(1)*t) + c(2)*exp(-lam(2)*t);

Новообращенный diffun в выражение оптимизации, которое суммирует квадраты разностей между функцией и данными y.

diffexpr = sum((diffun - y).^2);

Создать проблему оптимизации, имеющую diffexpr в качестве целевой функции.

ssqprob = optimproblem('Objective',diffexpr);

Решите проблему с помощью решателя по умолчанию.

[sol,fval,exitflag,output] = solve(ssqprob,x0)
Solving problem using lsqnonlin.

Local minimum possible.

lsqnonlin stopped because the final change in the sum of squares relative to 
its initial value is less than the value of the function tolerance.
sol = struct with fields:
      c: [2x1 double]
    lam: [2x1 double]

fval = 0.1477
exitflag = 
    FunctionChangeBelowTolerance

output = struct with fields:
          firstorderopt: 7.8870e-06
             iterations: 6
              funcCount: 7
           cgiterations: 0
              algorithm: 'trust-region-reflective'
               stepsize: 0.0096
                message: '...'
    objectivederivative: "forward-AD"
                 solver: 'lsqnonlin'

Постройте график результирующей кривой на основе возвращенных значений решения sol.c и sol.lam.

resp = evaluate(diffun,sol);
hold on
plot(t,resp)
hold off

Figure contains an axes. The axes with title Data points contains 2 objects of type line.

Посадка выглядит как можно лучше.

Подход к решению с использованием fminunc

Чтобы решить проблему с помощью fminunc решатель, установите 'Solver' опция для 'fminunc' при вызове solve.

[xunc,fvalunc,exitflagunc,outputunc] = solve(ssqprob,x0,'Solver',"fminunc")
Solving problem using fminunc.

Local minimum found.

Optimization completed because the size of the gradient is less than
the value of the optimality tolerance.
xunc = struct with fields:
      c: [2x1 double]
    lam: [2x1 double]

fvalunc = 0.1477
exitflagunc = 
    OptimalSolution

outputunc = struct with fields:
             iterations: 30
              funcCount: 37
               stepsize: 0.0017
           lssteplength: 1
          firstorderopt: 2.9454e-05
              algorithm: 'quasi-newton'
                message: '...'
    objectivederivative: "forward-AD"
                 solver: 'fminunc'

Обратите внимание, что fminunc нашел то же решение, что и lsqcurvefit, но для этого потребовалось гораздо больше оценок функций. Параметры для fminunc находятся в противоположном порядке, чем для lsqcurvefit; большее lam является lam(2), не lam(1). Это неудивительно, порядок переменных произвольный.

fprintf(['There were %d iterations using fminunc,' ...
    ' and %d using lsqcurvefit.\n'], ...
    outputunc.iterations,output.iterations)
There were 30 iterations using fminunc, and 6 using lsqcurvefit.
fprintf(['There were %d function evaluations using fminunc,' ...
    ' and %d using lsqcurvefit.'], ...
    outputunc.funcCount,output.funcCount)
There were 37 function evaluations using fminunc, and 7 using lsqcurvefit.

Разделение линейных и нелинейных задач

Обратите внимание, что проблема фитинга является линейной в параметрах c(1) и c(2). Это означает для любых значений lam(1) и lam(2), можно использовать оператор обратной косой черты для поиска значений c(1) и c(2) которые решают проблему наименьших квадратов.

Переработка задачи как двумерной задачи, поиск лучших значений lam(1) и lam(2). Значения c(1) и c(2) вычисляют на каждом этапе с использованием оператора обратной косой черты, как описано выше. Для этого используйте fitvector функция, которая выполняет операцию обратной косой черты для получения c(1) и c(2) при каждой итерации решателя.

type fitvector
function yEst = fitvector(lam,xdata,ydata)
%FITVECTOR  Used by DATDEMO to return value of fitting function.
%   yEst = FITVECTOR(lam,xdata) returns the value of the fitting function, y
%   (defined below), at the data points xdata with parameters set to lam.
%   yEst is returned as a N-by-1 column vector, where N is the number of
%   data points.
%
%   FITVECTOR assumes the fitting function, y, takes the form
%
%     y =  c(1)*exp(-lam(1)*t) + ... + c(n)*exp(-lam(n)*t)
%
%   with n linear parameters c, and n nonlinear parameters lam.
%
%   To solve for the linear parameters c, we build a matrix A
%   where the j-th column of A is exp(-lam(j)*xdata) (xdata is a vector).
%   Then we solve A*c = ydata for the linear least-squares solution c,
%   where ydata is the observed values of y.

A = zeros(length(xdata),length(lam));  % build A matrix
for j = 1:length(lam)
   A(:,j) = exp(-lam(j)*xdata);
end
c = A\ydata; % solve A*c = y for linear parameters c
yEst = A*c; % return the estimated response based on c

Решить проблему с помощью solve начиная с двухмерной начальной точки x02.lam = [1,0]. Для этого сначала преобразуйте fitvector функция в выражение оптимизации с использованием fcn2optimexpr. См. раздел Преобразование нелинейной функции в выражение оптимизации. Чтобы избежать предупреждения, задайте выходной размер результирующего выражения. Создание новой задачи оптимизации с целью получения суммы квадратичных разностей между преобразованными fitvector функция и данные y.

x02.lam = x0.lam;
F2 = fcn2optimexpr(@(x) fitvector(x,t,y),lam,'OutputSize',[length(t),1]);
ssqprob2 = optimproblem('Objective',sum((F2 - y).^2));
[sol2,fval2,exitflag2,output2] = solve(ssqprob2,x02)
Solving problem using lsqnonlin.

Local minimum possible.

lsqnonlin stopped because the final change in the sum of squares relative to 
its initial value is less than the value of the function tolerance.
sol2 = struct with fields:
    lam: [2x1 double]

fval2 = 0.1477
exitflag2 = 
    FunctionChangeBelowTolerance

output2 = struct with fields:
          firstorderopt: 4.4018e-06
             iterations: 10
              funcCount: 33
           cgiterations: 0
              algorithm: 'trust-region-reflective'
               stepsize: 0.0080
                message: '...'
    objectivederivative: "finite-differences"
                 solver: 'lsqnonlin'

Эффективность двумерного решения аналогична эффективности четырехмерного решения:

fprintf(['There were %d function evaluations using the 2-d ' ...
    'formulation, and %d using the 4-d formulation.'], ...
    output2.funcCount,output.funcCount)
There were 33 function evaluations using the 2-d formulation, and 7 using the 4-d formulation.

Проблема разделения более надежна к первоначальному предположению

Выбор плохой начальной точки для исходной задачи с четырьмя параметрами приводит к локальному решению, которое не является глобальным. Выбор начальной точки с таким же плохим lam(1) и lam(2) значения для разделенной двухпараметрической задачи приводят к глобальному решению. Чтобы показать это, повторно запустите исходную проблему с начальной точкой, которая приводит к относительно плохому локальному решению, и сравните результирующее соответствие с глобальным решением.

x0bad.c = [5 1];
x0bad.lam = [1 0];
[solbad,fvalbad,exitflagbad,outputbad] = solve(ssqprob,x0bad)
Solving problem using lsqnonlin.

Local minimum possible.

lsqnonlin stopped because the final change in the sum of squares relative to 
its initial value is less than the value of the function tolerance.
solbad = struct with fields:
      c: [2x1 double]
    lam: [2x1 double]

fvalbad = 2.2173
exitflagbad = 
    FunctionChangeBelowTolerance

outputbad = struct with fields:
          firstorderopt: 0.0036
             iterations: 31
              funcCount: 32
           cgiterations: 0
              algorithm: 'trust-region-reflective'
               stepsize: 0.0012
                message: '...'
    objectivederivative: "forward-AD"
                 solver: 'lsqnonlin'

respbad = evaluate(diffun,solbad);
hold on
plot(t,respbad,'g')
legend('Data','Global fit','Bad local fit','Location','NE')
hold off

Figure contains an axes. The axes with title Data points contains 3 objects of type line. These objects represent Data, Global fit, Bad local fit.

fprintf(['The residual norm at the good ending point is %f,' ...
   ' and the residual norm at the bad ending point is %f.'], ...
   fval,fvalbad)
The residual norm at the good ending point is 0.147723, and the residual norm at the bad ending point is 2.217300.

См. также

|

Связанные темы