问题描述
我试图将这个非常简单的 4 种线性 Lotka-Volterra 竞争模型拟合到观察到的数据中,但由于某种原因,当我尝试 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[,y = results[,-1],type = "l",add = TRUE)
在这里,我继续编写一个函数,该函数最小化残差平方和,当包含在 optim()
中时,使用上述初始参数猜测应该会产生我正在寻找的结果。
min.RSS <- function(data,params) {
output <- deSolve::ode(y = ini,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,parms = result$par)
matplot(x = fit[,y = fit[,lwd = 3,add = TRUE)
任何有关如何解决此问题的想法将不胜感激。
解决方法
你更适合,但你应该非常小心这个问题。我有点疯狂并使用(开发中)fitode package 来解决这个问题。我拟合了模型并得到了更好的拟合,还尝试在我的最佳拟合周围使用 100 个随机变化的起点进行拟合。你的残差平方和是 1.19; fitode
在第一次尝试时达到 0.29,100 次拟合中最好的结果是 RSS=0.16。 然而:这些拟合高度不稳定。该图显示了对未来 5 个时间步长的数据和预测的拟合(1)您的拟合(虚线); (2)fitode初始拟合(虚线); (3) 其他100个fitode拟合(最佳拟合0.05RSS以内的为实心,比这差的画得很轻)。
您可以看到样本外预测大多是疯狂的。你的拟合实际上比一些更好的拟合更稳定 - 在整个社区崩溃之前到达时间步骤 13 - 但底线是在这种情况下非常适合数据绝不保证一个明智的答案。看起来 100 个拟合中的一个在没有崩溃的情况下到达了预测时间序列的末尾(这似乎是基于观察到的时间序列的合理合理的“常识”预测)。
为了可靠地拟合这些数据,您要么需要一个参数少得多的模型,要么需要以先验形式提供的外部信息,要么正则化 - 某种方式进行惩罚拟合,这意味着 '摆动的确定性轨迹,或不合理的交互参数/增长率。p>
## 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]][,do.call(cbind,lapply(pp,"[",-1)))
ll[i] <- logLik(nf)
conv[i] <- nf@mle2@details$convergence
}
par(las=1,bty="l")
matplot(pred[[1]][,pred[[1]][,type="n",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]][,pred[[i]][,col=adjustcolor(palette()[1:4],alpha.f=alpha),lwd=lwd)
}
matpoints(data2[,data2[,pch=16,cex=3)
plotres(optimres$par,add=TRUE,lwd=3,lty=2,dt=1)
plotres(coef(f1),lty=3,dt=1)
,
对于那些感兴趣的人,我设法获得了一个涉及更改 ode 集成方法的解决方案。这是工作优化器:
# Optimising parameter fit
LVmse = function(parms) {
out = as.matrix(deSolve::ode(ini,1:10,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)