Skip to content

模拟:Fig. 5.9

原文 5.5 光滑参数的自动选择
作者 szcf-weiya
发布 2018-03-30

生成数据

模型为

其中 $X\sim U[0,1], \varepsilon\sim N(0, 1)$。

## generate dataset
func <- function(x)
{
  return(sin(12*(x+0.2))/(x+0.2))
}
genXY <- function(N = 100)
{
  x = runif(N)
  noise = rnorm(N)
  y = func(x) + noise
  return(list(X = x,
              Y = y))
}

计算 CV

错误的做法

R 中,可以用smooth.spline() 来拟合三次光滑样条。一开始,注意到该函数返回值有 LOOCV (或 GCV),于是觉得只用一个循环就可以得到交叉验证随自由度变化的曲线(原图中的交叉验证便是舍一法),即

nrep = 15
df.seq = (1:nrep)+1
models = vector("list", nrep)
cvs = numeric(nrep)
for (i in 1:nrep){
    models[[i]] = smooth.spline(X, Y, df = df.seq[i], cv=TRUE, all.knots = TRUE)
    cvs[i] = models[[i]]$cv.crit
}

但这样得到的图象为

这与图 5.9 的图象完全不一样,曲线走势还是相反的,所以这样理解是有问题的。详细阅读帮助文档,我的结论是,虽然我们指定了有效自由度 $\df_\lambda$,但模型参数中的交叉验证还是用来进行模型选择,因为还有一个参数 spar 可以调整样条的光滑度,这一点书上似乎没有提到。所以便根据定义自己编写适用的计算 CV 的函数

自定义函数

直接根据舍一法交叉验证公式进行计算

mysmooth.spline <- function(X, Y, df){
  nY = length(Y)
  nX = length(X)
  cv.i = as.numeric(nX)
  if (nX != nY)
    return(0)
  # calculate LOOCV
  for (i in 1:nX){
    x = X[-i]
    y = Y[-i]
    model = smooth.spline(x, y, df = df, all.knots = TRUE)
    cv.i[i] = (Y[i] - predict(model, X[i])$y)^2
  }
  cv = mean(cv.i)
  # calculate EPE
  # ...
}

计算 CV

定义式为

问题是怎么求期望呢?

第一次尝试

第一次尝试是与上述计算 CV 时的错误做法一起发生的,因为注意到函数有个返回值 pens.crit,帮助文档给出的解释为

the penalized criterion, a non-negative number; simply the (weighted) residual sum of squares (RSS), sum(.$w * residuals(.)^2)

于是用

crits[i] = models[[i]]$crit

来估计 EPE,但发现这样还是与原图有很多差异,非常重要的一点是这个准则是随着 $\lambda$ 变大而单调降低的。

第二次尝试

观察 (5.25) 的最后一行,认为 $\sigma^2=\sigma_\varepsilon^2=1$,然后估计 MSE ,最后效果也不好

Y.pred = model.full$y
Y.true = func(model.full$yin) # DO NOT use Y directly, because they have reordered.
epe = 1 + mean(( Y.true - Y.pred)^2)

效果也和原书有差异,遂放弃。

第三次尝试

这时,意识到计算 EPE 时应该不能在测试集上计算,所以这次尝试和下面的尝试都是在测试集上进行。这一次,测试时重新生成测试集,然后计算 EPE(直接用定义式来估计),即

data.test = genXY(N = 500)
x.test = data.test$X
y.test = data.test$Y
y.pred = predict(model.full, x.test)$y
epe = mean((y.test - y.pred)^2)

此时的效果已经非常接近图 5.9 了,但突然意识到一个问题,我们应该是不知道真实数据的生成过程,不然还需要拟合么?所以测试集还是只来自仅有的 $N=100$ 个观测值。

第四次尝试

这一次,将 $N=100$ 划分为测试集和训练集,在测试集上计算 EPE,即

n2 = 100
epe.i = numeric(n2)
for (i in 1:n2){
ind = sample(N, 50)
model = smooth.spline(X[ind], Y[ind], df = df)
y.pred = predict(model, X[-ind])$y
epe.i[i] = mean((y.pred - Y[-ind])^2)
}
epe = mean(epe.i)

此时的效果已经非常接近原图了,如下图

虽然个别误差比原图稍大,但趋势与原图完全一致,最小值也在 $9$ 附近取得。

给定 $\lambda$

现在已经重现了左上图,对于剩下三幅图,我们需要计算平均的拟合值以及方差,这可以直接在上述求 EPE 的过程(或者求 CV 时)中计算,

yhat = array(dim = c(n2, nY))
epe.i = numeric(n2)
for (i in 1:n2){
    # ...
    # predict the whole dataset
    yhat[i, ] = predict(model, sort(X))$y
}
## calculate the mean and sd of fitted value
yhat.mean = apply(yhat, 2, mean)
yhat.sd = apply(yhat, 2, sd)

然后在编写下面的绘图函数

## plot function for (b)-(d)
plot.lambda <- function(X, Y, lambda)
{
  res = mysmooth.spline(X, Y, lambda)
  fitted = res$fitted
  sd = res$sd
  plot(X, Y, xlab = expression(X), ylab = "y",
       main = substitute(paste(df[lambda]," = ", l), list(l=lambda)))
  lines(0.01*0:100, func(0.01*0:100), col = "blue", type = "l", lwd=3)
  lines(sort(X), fitted, lwd = 3)
  polygon(c(rev(sort(X)), sort(X)), c(rev(fitted-2*sd), fitted+2*sd), 
          col=rgb(1, 1, 0, 0.5), border = NA)
}

则依次取 $\lambda=5,9,15$,可以得到

这与原图是一致的!

练习 5.10

Ex. 5.10 要求添加 $\hat f(\lambda)$ 的逐点分位数,直接在上面的代码中加入计算分位数的代码即可

yhat75 = apply(yhat, 2, function(x) quantile(x, 0.75))
yhat25 = apply(yhat, 2, function(x) quantile(x, 0.25))

得到

Comments