Подгонка модели к наблюдаемым данным путем оптимизации параметров с использованием optim(), минимизирующей остаточную сумму квадратов

Я пытаюсь сопоставить эту очень простую линейную модель конкуренции Лотки-Вольтерры с четырьмя видами для наблюдаемых данных, но по какой-то причине, когда я пробую функцию optim(), что-то в отношении deSolve кажется неудачным.

# Data
data <- data.frame(Cod = c(0.1966126, 0.1989563, 0.2567677, 0.3158896, 0.4225435, 0.7219856,
                           1.0570824, 0.7266830, 0.6286763, 0.6389475),
                   Herring = c(1.988372, 2.788014, 3.397138, 2.557245, 2.627013, 3.045617, 
                               3.161002, 3.531306, 3.432021, 3.617174),
                   Sprat = c(2.030273, 3.480469, 3.009277, 1.895996, 2.457520, 1.991211, 2.350098,
                             2.118164, 1.693359, 1.869141),
                   Flounder = c(0.4758220, 0.4425532, 0.4185687, 0.4967118, 0.7102515, 0.5733075,
                                0.7404255, 0.5996132, 0.6235977, 0.7187621))
# Model formulation
LLV <- function(time, state, parameters) {
  with(as.list(c(state, parameters)), {
    db1.dt = b1*(r1+a11*b1+a12*b2+a13*b3+a14*b4)
    db2.dt = b2*(r2+a22*b2+a21*b1+a23*b3+a24*b4)
    db3.dt = b3*(r3+a33*b3+a31*b1+a32*b2+a34*b4)
    db4.dt = b4*(r4+a44*b4+a41*b1+a42*b2+a43*b3)
    list(c(db1.dt, db2.dt, db3.dt, db4.dt))
  })
}
# Model input and simulation
# Model input
params <- c(r1 = -0.342085, r2 = 0.6855681, r3 = 2.757769, r4 = 0.9744113,
            a11 = -1.05973762, a12 = 0.09577309, a13 = -0.01915480, a14 = 1.36098939,
            a21 = 0.17533326, a22 = -0.32247342, a23 = 0.03111628, a24 = 0.30212711,
            a31 = 0.5303516, a32 = -0.4869761, a33 = -0.3194882, a34 = -1.5089027,
            a41 = 0.004418133, a42 = 0.163716414, a43 = -0.237873378, a44 = -1.519158802)
ini <- c(b1 = data[1,1], b2 = data[1,2], b3 = data[1,3], b4 = data[1,4])
tmax <- 10
t <- seq(1,tmax,0.1)
# Results and first parameter guess is more or less okay
results <- deSolve::ode(y = ini, times = t, func = LLV, parms = params)
matplot(data, pch = 1)
matplot(x = results[,1], y = results[,-1], type = "l", add = TRUE)

Здесь я продолжаю и пишу функцию, которая минимизирует остаточную сумму квадратов, которая при включении в optim() с указанным выше исходным параметром должна давать то, что я ищу.

min.RSS <- function(data, params) {
  output <- deSolve::ode(y = ini, times = t, func = LLV, parms = params)
  predictions <- exp(output[,-1])
  observations <- data
  return(sum((predictions-observations)^2))
}
result <- optim(par = params, fn = min.RSS, data = data)
fit <- deSolve::ode(y = ini, times = t, func = LLV, parms = result$par)
matplot(x = fit[,1], y = fit[,-1], type = "l", lwd = 3, add = TRUE)

Любая идея о том, как решить эту проблему, будет очень признательна.


person Thomas    schedule 24.12.2020    source источник
comment
Это сложная проблема. Я согласен с тем, что вы можете подумать, что нелинейная оптимизация должна быть легкой, потому что у вас есть хорошее начальное условие, но вы пытаетесь подогнать нелинейную модель с 20 параметрами к 40 точкам данных. Я посмотрю и посмотрю, могу ли я предложить какие-либо решения, но в целом это будет очень сложно. Может работать что-то вроде моделей MARSS (многомерное авторегрессивное пространство состояний) ...?   -  person Ben Bolker    schedule 24.12.2020
comment
Привет Бен, большое спасибо за ответ. Да, возможно, я не позволяю optim() эффективно выполнять поиск с 40 точками данных. Я мог бы попытаться расширить временные ряды, но обычно это то, что часто приходится делать ученым-рыболовам, чтобы оптимизировать соответствие более сложным моделям, таким как EwE. Я мог бы смоделировать некоторые данные, если вы считаете, что это решит проблему, чтобы соответствовать промежуточным годам, добавив некоторую априорную неопределенность в выборку. Я знаю, что код для модели может быть записан как db.dt=(r+Ab)b, но затем я хотел бы добавить некоторые межспецифические взаимодействия более высокого порядка.   -  person Thomas    schedule 25.12.2020
comment
HOI будет закодирован в виде db1.dt=b1(r1+a11b1+a12b2^2...) Затем я повторно применю оптимизатор, чтобы получить несколько реалистичную (насколько это касается модели LV) модель, которую я мог бы Используйте, чтобы применить алгоритм, который находит скорость сбора урожая равновесия Нэша. Я использовал более сложные модели с этим алгоритмом, но модифицированный LV кажется идеальным для учебника, описывающего весь процесс. Но шаг, с которым я борюсь, - это подгонка, поскольку параметры LV в обычных учебниках обычно довольно загадочны для студентов, и, возможно, наличие реальной системы облегчит процесс понимания.   -  person Thomas    schedule 25.12.2020
comment
Я посмотрю на MARSS и еще раз благодарю вас за то, что вы нашли время, чтобы помочь/предложить решения. Мне очень нравится читать ваши работы :)!   -  person Thomas    schedule 25.12.2020
comment
см. мои правки: я не хочу портить вам настроение, но эта модель достаточно сложна, поэтому использовать ее для прогнозирования того, что произойдет на промысле, крайне нецелесообразно, не говоря уже о том, чтобы усложнять модель путем добавления HOI. ...   -  person Ben Bolker    schedule 01.01.2021


Ответы (2)


У вас лучше подходит, но вы должны быть очень осторожны с этой проблемой. Я немного сошел с ума и использовал (находящийся в разработке) пакет fitode, чтобы решить эту проблему. Я подогнал модель и получил гораздо лучшее соответствие, а также попытался подобрать 100 случайно меняющихся начальных точек вокруг моего наилучшего соответствия. Ваша остаточная сумма квадратов составила 1,19; fitode получил значение 0,29 с первой попытки, а лучшее из 100 совпадений было RSS=0,16. Однако эти соответствия крайне нестабильны. На этом графике показаны соответствия данным и прогнозы на 5 временных шагов в будущем для (1) вашего соответствия (пунктирные линии); 2 – начальная подгонка фитода (пунктир); (3) 100 других подгонок фитодов (те, что в пределах 0,05 RSS от наилучшего соответствия, выделены сплошным цветом, те, что хуже этого, нарисованы очень слабо).

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

Для надежной подгонки этих данных вам либо нужна модель с гораздо меньшим количеством параметров, либо внешняя информация, предоставленная в виде априорных значений, либо регуляризация — какой-то способ наложить штраф на подгонки, подразумевающие «волнистую» детерминированность. траектории или параметры взаимодействия/темпы роста, которые являются необоснованными.

## remotes::install_github("parksw3/fitode")
library(fitode)

## data with tags for fitode
data2 <- setNames(data,paste0(names(data),"_obs"))
data2 <- data.frame(times=seq(nrow(data2)),data2)


## Model formulation (for fitode)
LV_model <- odemodel(
    name="4-species LV",
    model=list(
        Cod ~ Cod*(r1+a11*Cod+a12*Herring+a13*Sprat+a14*Flounder),
        Herring ~ Herring*(r2+a22*Herring+a21*Cod+a23*Sprat+a24*Flounder),
        Sprat ~ Sprat*(r3+a33*Sprat+a31*Cod+a32*Herring+a34*Flounder),
        Flounder ~ Flounder*(r4+a44*Flounder+a41*Cod+a42*Herring+a43*Sprat)
    ),
    observation=list(
        Cod_obs ~ ols(mean=Cod),
        Herring_obs ~ ols(mean=Herring),
        Sprat_obs ~ ols(mean=Sprat),
        Flounder_obs ~ ols(mean=Flounder)
    ),
    initial=list(
        Cod ~ data2$Cod_obs[1],
        Herring ~ data2$Herring_obs[1],
        Sprat ~ data2$Sprat_obs[1],
        Flounder ~ data2$Flounder_obs[1]
    ),
    link=setNames(rep("identity",length(pars)),pars),
    par= pars
)

## plot results
plotres <- function(p,ODEint="rk",lty=1,
                    dt=0.1,
                    tvec=seq(1,10,by=dt),...) {
    par(las=1, bty="l")
    res <- deSolve::ode(ini, tvec, LLV, p, method=ODEint)
    matplot(res[,1],res[,-1],type="l",lty=lty,...)
    return(invisible(res[,-1]))
}

f1 <- fitode(
    LV_model,
    data=data2,
    start=params,
    control=list(maxit=1e5,trace=1000)
)

## fitode with multistart

ranfit <- function(n,fit,range=0.5) {
    ## 
    rpars <- params*runif(length(params),1-range,1+range)
    newfit <- try(update(fit, start=rpars))
    return(newfit)
}

cl <- makeCluster(10)
clusterSetRNGStream(cl = cl, 101)
clusterExport(cl, c("params","LV_model","data2"))
clusterEvalQ(cl,invisible(library(fitode)))
system.time(
    multifit <- parLapply(cl, 1:100, ranfit, fit=f1, tvec=tvec)
)
saveRDS(multifit,file="SO65440448_multifit.rds")

ivec <- seq_along(multifit)
ivec <- ivec[sapply(multifit,function(x) !inherits(x,"try-error"))]
coef <- pred <- vector("list", length=length(ivec))
ll <- conv <- rep(NA,length(ivec))
for (i in seq_along(ivec)) {
    nf <- multifit[[ivec[i]]]
    coef[[i]] <- coef(nf)
    pp <- predict(nf, times=1:10)
    pred[[i]] <- cbind(times=pp[[1]][,1],
                do.call(cbind,lapply(pp,"[",-1)))
    ll[i] <- logLik(nf)
    conv[i] <- nf@mle2@details$convergence
}

par(las=1,bty="l")
matplot(pred[[1]][,1],pred[[1]][,-1],
        type="n",lty=1,ylim=c(0,6),
        xlab="time",ylab="density")
lthresh <- 0.05
for (i in 1:length(pred)) {
    good <- ll[i]>(max(ll)-lthresh)
    alpha <- if (good) 0.8 else 0.1
    lwd <- if (good) 2 else 1
    matlines(pred[[i]][,1],pred[[i]][,-1],lty=1,
             col=adjustcolor(palette()[1:4],alpha.f=alpha),
             lwd=lwd)
}
matpoints(data2[,1],data2[,-1],pch=16,cex=3)
plotres(optimres$par,add=TRUE, lwd=3,lty=2,dt=1)
plotres(coef(f1),add=TRUE, lwd=3,lty=3,dt=1)
person Ben Bolker    schedule 01.01.2021
comment
Уважаемый Бен, спасибо за исчерпывающий ответ. Я поиграю с пакетом fitode, уменьшив размерность задачи до 2 видов с временным рядом в 46 лет. Я знаю, что эта очень простая модель конкуренции LV не подходит для детерминированных прогнозов. Я использую его для представления нового алгоритма, потому что это очень простая (феноменологическая) модель, описывающая spp. взаимодействия (причина, по которой я не тратил много времени на создание модного оптимизатора подгонки). Теперь, когда я знаю, что такие инструменты уже существуют в R, я воспользуюсь ими и скоро вернусь к вам через этот пост. - person Thomas; 06.01.2021
comment
46 лет с 2 видами может работать нормально, особенно если вы начинаете симуляцию достаточно далеко от равновесия (чтобы можно было идентифицировать как параметры роста (r_i), так и параметры конкуренции (alpha_ij)). - person Ben Bolker; 07.01.2021

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

# Optimising parameter fit
LVmse = function(parms) {
  out = as.matrix(deSolve::ode(ini, 1:10, LLV, parms, method="rk")[,-1])
  RSS = sum((spp-out)^2, na.rm = TRUE) # Minimising residual sum of squares
  return(RSS)
}
optimres <- optim(par = params, fn = LVmse)
person Thomas    schedule 30.12.2020
comment
Когда я запускаю это, он останавливается на максимальном количестве итераций = 501 с кодом сходимости 1 («не сходится»)... - person Ben Bolker; 01.01.2021