Come eseguire i minimi quadrati non lineari con parametri condivisi in R?
Vorrei eseguire la regressione dei minimi quadrati non lineari in R dove minimizzo simultaneamente i residui al quadrato di tre modelli (vedi sotto). Ora, i tre modelli condividono alcuni dei parametri, nel mio esempio, parametri b
e d
.
C'è un modo per farlo con uno nls()
, o, i pacchetti minpack.lm
o nlsr
?
Così, idealmente, vorrei generare la funzione obiettivo (la somma dei minimi quadrati di tutti i modelli insieme) e regredire tutti i parametri in una volta: a1
, a2
, a3
, b
, c1
, c2
, c3
e d
.
(Sto cercando di evitare di eseguire tre regressioni indipendenti e quindi di eseguire una media su b
e d
.)
my_model <- function(x, a, b, c, d) {
a * b ^ (x - c) + d
}
# x values
x <- seq(0, 10, 0.2)
# Shared parameters
b <- 2
d <- 10
a1 <- 1
c1 <- 1
y1 <- my_model(x,
a = a1,
b = b,
c = c1,
d = d) + rnorm(length(x))
a2 <- 2
c2 <- 5
y2 <- my_model(x,
a = a2,
b = b,
c = c2,
d = d) + rnorm(length(x))
a3 <- -2
c3 <- 3
y3 <- my_model(x,
a = a3,
b = b,
c = c3,
d = d) + rnorm(length(x))
plot(
y1 ~ x,
xlim = range(x),
ylim = d + c(-50, 50),
type = 'b',
col = 'red',
ylab = 'y'
)
lines(y2 ~ x, type = 'b', col = 'green')
lines(y3 ~ x, type = 'b', col = 'blue')
Risposte
Di seguito eseguiamo nls
(utilizzando un modello leggermente modificato) e nlxb
(da nlsr) ma si nlxb
ferma prima della convergenza. Desidero che questi problemi entrambi diano comunque risultati che si adattano visivamente bene ai dati. Questi problemi suggeriscono che ci sono problemi con il modello stesso, quindi nella sezione Altro , guidati nlxb
dall'output, mostriamo come correggere il modello fornendo un sottomodello del modello originale che si adatta facilmente ai dati con entrambi nls
e nlxb
e dà anche un buon adattamento . Alla fine nella sezione Note forniamo i dati in forma riproducibile.
nls
Assumendo la configurazione mostrata riproducibile nella Nota alla fine, riformulare il problema per l'algoritmo plineare nls definendo una matrice di destra le cui colonne moltiplicano ciascuno dei parametri lineari, rispettivamente a1, a2, a3 e d. plinear non richiede valori iniziali per chi semplifica il setup. Li riporterà rispettivamente come .lin1, .lin2, .lin3 e .lin4.
Per ottenere i valori di partenza abbiamo utilizzato un modello più semplice senza raggruppamento e una griglia di ricerca su b da 1 a 10 ec anche da 1 a 10 utilizzando nls2
nel pacchetto con lo stesso nome. Abbiamo anche scoperto che nls
produceva ancora errori ma, utilizzando abs
nella formula, come mostrato, è stato completato.
I problemi con il modello suggeriscono che c'è un problema fondamentale con esso e nella sezione Altro si discute come risolverlo.
xx <- c(x, x, x)
yy <- c(y1, y2, y3)
# startingi values using nls2
library(nls2)
fo0 <- yy ~ cbind(b ^ abs(xx - c), 1)
st0 <- data.frame(b = c(1, 10), c = c(1, 10))
fm0 <- nls2(fo0, start = st0, alg = "plinear-brute")
# run nls using starting values from above
g <- rep(1:3, each = length(x))
fo <- yy ~ cbind((g==1) * b ^ abs(xx - c[g]),
(g==2) * b ^ abs(xx - c[g]),
(g==3) * b ^ abs(xx - c[g]),
1)
st <- with(as.list(coef(fm0)), list(b = b, c = c(c, c, c)))
fm <- nls(fo, start = st, alg = "plinear")
plot(yy ~ xx, col = g)
for(i in unique(g)) lines(predict(fm) ~ xx, col = i, subset = g == i)
fm
dando:
Nonlinear regression model
model: yy ~ cbind((g == 1) * b^abs(xx - c[g]), (g == 2) * b^abs(xx - c[g]), (g == 3) * b^abs(xx - c[g]), 1)
data: parent.frame()
b c1 c2 c3 .lin1 .lin2 .lin3 .lin4
1.997 0.424 1.622 1.074 0.680 0.196 -0.532 9.922
residual sum-of-squares: 133
Number of iterations to convergence: 5
Achieved convergence tolerance: 5.47e-06
(continua dopo la trama)

nlsr
Con nlsr sarebbe stato fatto così. Non era necessaria alcuna ricerca sulla griglia per i valori iniziali e abs
nemmeno l' aggiunta . I valori b e d sembrano simili alla soluzione nls ma gli altri coefficienti differiscono. Visivamente entrambe le soluzioni sembrano adattarsi ai dati.
D'altra parte dalla colonna JSingval vediamo che il giacobiano è carente di rango che lo ha fatto fermare e non produrre valori SE e la convergenza è dubbia (sebbene possa essere sufficiente dato che visivamente il grafico, non mostrato, buona vestibilità). Discuteremo come risolvere questo problema nella sezione Altro.
g1 <- g == 1; g2 <- g == 2; g3 <- g == 3
fo2 <- yy ~ g1 * (a1 * b ^ (xx - c1) + d) +
g2 * (a2 * b ^ (xx - c2) + d) +
g3 * (a3 * b ^ (xx - c3) + d)
st2 <- list(a1 = 1, a2 = 1, a3 = 1, b = 1, c1 = 1, c2 = 1, c3 = 1, d = 1)
fm2 <- nlxb(fo2, start = st2)
fm2
dando:
vn: [1] "yy" "g1" "a1" "b" "xx" "c1" "d" "g2" "a2" "c2" "g3" "a3" "c3"
no weights
nlsr object: x
residual sumsquares = 133.45 on 153 observations
after 16 Jacobian and 22 function evaluations
name coeff SE tstat pval gradient JSingval
a1 3.19575 NA NA NA 9.68e-10 4097
a2 0.64157 NA NA NA 8.914e-11 662.5
a3 -1.03096 NA NA NA -1.002e-09 234.9
b 1.99713 NA NA NA -2.28e-08 72.57
c1 2.66146 NA NA NA -2.14e-09 10.25
c2 3.33564 NA NA NA -3.955e-11 1.585e-13
c3 2.0297 NA NA NA -7.144e-10 1.292e-13
d 9.92363 NA NA NA -2.603e-12 3.271e-14
Possiamo calcolare gli SE usando nls2 come secondo stadio, ma questo ancora non risolve il problema con tutto ciò che suggeriscono i valori singolari.
summary(nls2(fo2, start = coef(fm2), algorithm = "brute-force"))
dando:
Formula: yy ~ g1 * (a1 * b^(xx - c1) + d) + g2 * (a2 * b^(xx - c2) + d) +
g3 * (a3 * b^(xx - c3) + d)
Parameters:
Estimate Std. Error t value Pr(>|t|)
a1 3.20e+00 5.38e+05 0.0 1
a2 6.42e-01 3.55e+05 0.0 1
a3 -1.03e+00 3.16e+05 0.0 1
b 2.00e+00 2.49e-03 803.4 <2e-16 ***
c1 2.66e+00 9.42e-02 28.2 <2e-16 ***
c2 3.34e+00 2.43e+05 0.0 1
c3 2.03e+00 8.00e+05 0.0 1
d 9.92e+00 4.42e+05 0.0 1
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 0.959 on 145 degrees of freedom
Number of iterations to convergence: 8
Achieved convergence tolerance: NA
Altro
Quando nls
ha problemi ad adattare un modello, spesso suggerisce che c'è qualcosa che non va nel modello stesso. Giocandoci un po ', guidati dalla colonna JSingval nell'output nlsr sopra che suggerisce quei c
parametri o d
potrebbe essere il problema, scopriamo che se fissiamo tutti c
i valori dei parametri a 0, il modello è facile da adattare dati valori di partenza sufficientemente buoni e dà ancora una somma residua bassa dei quadrati.
library(nls2)
fo3 <- yy ~ cbind((g==1) * b ^ xx, (g==2) * b ^ xx, (g==3) * b ^ xx, 1)
st3 <- coef(fm0)["b"]
fm3 <- nls(fo3, start = st3, alg = "plinear")
dando:
Nonlinear regression model
model: yy ~ cbind((g == 1) * b^xx, (g == 2) * b^xx, (g == 3) * b^xx, 1)
data: parent.frame()
b .lin1 .lin2 .lin3 .lin4
1.9971 0.5071 0.0639 -0.2532 9.9236
residual sum-of-squares: 133
Number of iterations to convergence: 4
Achieved convergence tolerance: 1.67e-09
che la seguente anova indica è paragonabile a quanto fm
sopra nonostante abbia 3 parametri in meno:
anova(fm3, fm)
dando:
Analysis of Variance Table
Model 1: yy ~ cbind((g == 1) * b^xx, (g == 2) * b^xx, (g == 3) * b^xx, 1)
Model 2: yy ~ cbind((g == 1) * b^abs(xx - c[g]), (g == 2) * b^abs(xx - c[g]), (g == 3) * b^abs(xx - c[g]), 1)
Res.Df Res.Sum Sq Df Sum Sq F value Pr(>F)
1 148 134
2 145 133 3 0.385 0.14 0.94
Possiamo rifare fm3
usando in nlxb
questo modo:
fo4 <- yy ~ g1 * (a1 * b ^ xx + d) +
g2 * (a2 * b ^ xx + d) +
g3 * (a3 * b ^ xx + d)
st4 <- list(a1 = 1, a2 = 1, a3 = 1, b = 1, d = 1)
fm4 <- nlxb(fo4, start = st4)
fm4
dando:
nlsr object: x
residual sumsquares = 133.45 on 153 observations
after 24 Jacobian and 33 function evaluations
name coeff SE tstat pval gradient JSingval
a1 0.507053 0.005515 91.94 1.83e-132 8.274e-08 5880
a2 0.0638554 0.0008735 73.11 4.774e-118 1.26e-08 2053
a3 -0.253225 0.002737 -92.54 7.154e-133 -4.181e-08 2053
b 1.99713 0.002294 870.6 2.073e-276 -2.55e-07 147.5
d 9.92363 0.09256 107.2 3.367e-142 -1.219e-11 10.26
Nota
L'input assunto di seguito è lo stesso della domanda, tranne per il fatto che impostiamo ulteriormente il seme per renderlo riproducibile.
set.seed(123)
my_model <- function(x, a, b, c, d) a * b ^ (x - c) + d
x <- seq(0, 10, 0.2)
b <- 2; d <- 10 # shared
a1 <- 1; c1 <- 1
y1 <- my_model(x, a = a1, b = b, c = c1, d = d) + rnorm(length(x))
a2 <- 2; c2 <- 5
y2 <- my_model(x, a = a2, b = b, c = c2, d = d) + rnorm(length(x))
a3 <- -2; c3 <- 3
y3 <- my_model(x, a = a3, b = b, c = c3, d = d) + rnorm(length(x))
Non sono sicuro che questo sia davvero il modo migliore, ma potresti ridurre al minimo la somma dei quadrati residui utilizzando optim()
.
#start values
params <- c(a1=1, a2=1, a3=1, b=1, c1=1, c2=1, c3=1,d=1)
# minimize total sum of squares of residuals
fun <- function(p) {
sum(
(y1-my_model(x, p["a1"], p["b"], p["c1"], p["d"]))^2 +
(y2-my_model(x, p["a2"], p["b"], p["c2"], p["d"]))^2 +
(y3-my_model(x, p["a3"], p["b"], p["c3"], p["d"]))^2
)
}
out <- optim(params, fun, method="BFGS")
out$par
# a1 a2 a3 b c1 c2 c3
# 0.8807542 1.0241804 -2.8805848 1.9974615 0.7998103 4.0030597 3.5184600
# d
# 9.8764917
E possiamo aggiungere le trame sopra l'immagine
curve(my_model(x, out$par["a1"], out$par["b"], out$par["c1"], out$par["d"]), col="red", add=T) curve(my_model(x, out$par["a2"], out$par["b"], out$par["c2"], out$par["d"]), col="green", add=T) curve(my_model(x, out$par["a3"], out$par["b"], out$par["c3"], out$par["d"]), col="blue", add=T)
