在R中“围绕”数据点拟合曲线

时间:2012-11-23 06:26:24

标签: r optimization

我有一个由一组点组成的数据集。这些点以这样一种方式分布在飞机上,即它们可以用抛物线大致界定。我试图找到一种方法将抛物线拟合到点的边界。

这就是我目前所拥有的:

a = 1
b = 2
c = 3

parabola <- function(x) {
    a * x^2 + b * x + c
}

N = 10000

x <- runif(N, -4, 3)
y <- runif(N, 0, 10)

data <- data.frame(x, y)

data <- subset(data, y >= parabola(x))

plot(data, xlim = c(-5, 5), ylim = c(0, 10), col = "grey")

fr <- function(x) {
    PAR = x[1] * data$x^2 + x[2] * data$x + x[3]
    #
    sum((PAR - data$y)^2 + 100 * plogis(PAR - data$y, scale = 0.00001))
}

par = optim(c(0, 0, 0), fr)$par

a = par[1]
b = par[2]
c = par[3]

curve(parabola, add = TRUE, lty = "dashed")

这将创建一个样本数据集,然后将曲线拟合到边界。目标函数包括一个“正常”平方误差项,它适用于抛物线数据,以及第二个逻辑项,它惩罚生活在抛物线下方的点。第二项的参数(100和0.00001)是通过反复试验确定的。

代码绘制点以及拟合的抛物线。

现在这个系统有效...但只有部分时间。有时它产生一个完全错误的拟合,我想在这些情况下,逻辑术语的参数是不合适的。运行代码几次,看看我的意思。

我确信必须有更强大的方法来解决这个问题。想法和建议?

2 个答案:

答案 0 :(得分:4)

我无法提供完整的答案。我唯一的特别想法是为优化算法提供更好的起点 - 希望你更接近你尝试优化的函数的局部最小值。

估算粗略的第一版非常简单。如果你把抛物线写为b*(x-a)^2+c 你可以估计

a <- data$x[which.min(data$y)]
c <- min(data$y)

b1 <- (data$y[which.min(data$x)] - c) / (min(data$x) - a)^2
b2 <- (data$y[which.max(data$x)] - c) / (max(data$x) - a)^2
b <- mean(c(b1, b2))

修改

我的另一个强化测试会议是我的建议和方法“BFGS”。我用以下方法找不到反例:

seed <- floor(runif(1,1,1000))
set.seed(seed)
a = 1
b = 2
c = 3

parabola <- function(x) {
    b * (x-a)^2 + c
}

N = 10000

x <- runif(N, -4, 3)
y <- runif(N, 0, 10)

data <- data.frame(x, y)

data <- subset(data, y >= parabola(x))

plot(data, xlim = c(-5, 5), ylim = c(0, 10), col = "grey")

fr <- function(x) {
    PAR = x[2] * (data$x - x[1])^2 + x[3]
    #
    sum((PAR - data$y)^2 + 100 * plogis(PAR - data$y, scale = 0.00001))
}

a <- data$x[which.min(data$y)]
c <- min(data$y)

b1 <- (data$y[which.min(data$x)] - c) / (min(data$x) - a)^2
b2 <- (data$y[which.max(data$x)] - c) / (max(data$x) - a)^2
b <- mean(c(b1, b2))

par = optim(c(a, b, c), fr, method="BFGS")$par

a = par[1]
b = par[2]
c = par[3]

curve(parabola, add = TRUE, lty = "dashed")

然而,无法保证正确的收敛。我尝试了大约50个案例,一切顺利。您的结果是否已经过审核,或者是否必须在自动化基础上正常运行?


编辑2

我对如何更新目标函数以提高可靠性有一些想法。现在我没有时间制定出完整的解决方案,但也许这些想法可能对您有所帮助:

我们的日期在range(data$x)之内。现在我们想找到一个尽可能好地符合这个数据下边界的抛物线 - 换句话说,找到最大化的值a,b,c

\int_{\range(x)} ax^2 + bx+c dx

(请原谅笨拙的LaTeX - 编写公式有时候会更好)。

现在,可以使用像

这样的惩罚函数来完成抛物线以下的惩罚点
\lambda (ax_i^2+bx_i+c - y_i)^2 if below parabola, 0 otherwise

从间隔中减去该函数应该为您提供合适,平滑的目标函数。简化函数似乎是一个比使用最小二乘方法更好的模型,最小二乘方法试图通过数据点的中间拟合一条线。

但是,你仍然需要选择一个合适的lambda。但这是典型的:您需要在两个不同的目标之间进行折衷(拟合数据,最大化抛物线)。一个更重要的重量必须由您提交。

答案 1 :(得分:0)

进一步感谢thilo提供了非常有用的建议并纠正了我天真的想法。基于thilo的建议,使用抛物线下的区域和适当的惩罚函数,下面的解决方案似乎有效。我也改为L-BFGS-B优化,因为它在小N时表现更好。

parabola.objective <- function(p) {
    d = p[2] * (data$x - p[1])^2 + p[3] - data$y
    #
    area <- function(x) {
        p[2] / 3 * (x - p[1])^3 + p[3] * x
    }
    #
    sum(- area(max(data$x)) + area(min(data$x)) + 100 * ifelse(d > 0, d^2, 0))
}

A <- data$x[which.min(data$y)]
C <- min(data$y)

B1 <- (data$y[which.min(data$x)] - C) / (min(data$x) - A)^2
B2 <- (data$y[which.max(data$x)] - C) / (max(data$x) - A)^2
B <- mean(c(B1, B2))

# the key to getting this working with a small number of points is the
# optimisation method: BFGS works well with around 300 points or more
# but L-BFGS-B seems to perform better down to around 100 points.
#
O = optim(c(A, B, C), parabola.objective, method="L-BFGS-B")

par = O$par

A = par[1]
B = par[2]
C = par[3]

curve(parabola, add = TRUE, lty = "dashed")