|
Machine Learning
|
Para demonstrar o uso funcionamento da validação cruzada, será usado um conjunto de dados simulado apenas pela conveniência de serem conhecidos parâmetros.
As observações serão provenientes do modelo \[ y = \beta_{0} + \beta_{1} x + \beta_{3} \sin(x/\beta_{4}) + \epsilon, \]
em que \(\beta = [\beta_0, \beta_1, \beta_2, \beta_3]\) são os parâmetros de regressão e \(\epsilon\) um erro normal de média 0 e variância \(\sigma^2\). Os reais valores usados para esses parâmetros na simulação estão expostos dentro do código.
#-----------------------------------------------------------------------
# Pacotes.
rm(list = objects())
library(lattice)
library(latticeExtra)
#-----------------------------------------------------------------------
# Gerando dados.
curve(10 + 0.05 * x + 2 * sin(x/10), 0, 100)
# Retorna os valores determinados pelo modelo.
eta <- function(x, beta = c(10, 0.05, 2)) {
beta[1] + beta[2] * x + beta[3] * sin(x/10)
}
# Retorna um ruído normal padrão.
epsilon <- function(x) {
rnorm(n = length(x), mean = 0, sd = 1)
}
# Simulando do modelo.
da <- data.frame(x = runif(100, 0, 100))
da$y <- eta(da$x) + epsilon(da$x)
# Exibindo os dados.
plot(y ~ x, data = da)
curve(eta, add = TRUE, col = "orange", lwd = 3)
legend("bottomright",
bty = "n",
legend = c("Sinal verdadeiro", "Valores observados"),
lty = c(1, 0),
lwd = c(3, 1),
col = c("orange", "black"),
pch = c(NA, 1))
Na validação cruzada holdout é deixado de fora, para avaliação do desempenho de predição, uma porporção dos dados. Com a porção complementar, um modelo é ajustado aos dados, ocasião na qual são estimadas as quantidade livres do modelo, ou seja, os parâmetros.
#-----------------------------------------------------------------------
# Partindo os dados em treino e validação.
# Criando a variável para separar os dados em treino e validação.
ntra <- 80
nval <- 20
i <- sample(rep(c("tra", "val"),
times = c(ntra, nval)))
# Dividindo os dados em treino e validação.
das <- split(da, f = i)
str(das)
## List of 2
## $ tra:'data.frame': 80 obs. of 2 variables:
## ..$ x: num [1:80] 75.9 80.3 71.6 24.5 63 ...
## ..$ y: num [1:80] 15.4 17.8 15.1 12 14.5 ...
## $ val:'data.frame': 20 obs. of 2 variables:
## ..$ x: num [1:20] 40.5 89.9 88.8 36.6 95.7 ...
## ..$ y: num [1:20] 10.7 15.4 14.6 10.7 16 ...
# Criando uma lista de fórmulas para repetir os dados nos paineis.
dmax <- 15
form <- replicate(dmax, y ~ x)
names(form) <- sprintf("pol. grau: %d", 1:dmax)
# Diagramas de dispersão dos dados um ajuste variando o grau.
xyplot.list(form,
data = das[["tra"]],
cex = 0.6,
layout = c(3, 5),
as.table = TRUE) +
layer(panel.smoother(form = y ~ poly(x, degree = panel.number()),
method = lm)) +
layer(panel.curve(eta, col = "orange", lwd = 3)) +
layer(panel.points(x = x,
y = y,
pch = 19,
cex = 0.6,
col = "red"),
data = das[["val"]])
# Calculação a validação cruzada do handout.
deg <- 1:dmax
cv <- sapply(deg,
FUN = function(d) {
# Ajuste com os dados de treino.
m0 <- lm(y ~ poly(x, degree = d), data = das[["tra"]])
# Predição com os dados de treino e de teste.
yhat_inter <- predict(m0, newdata = das[["tra"]]) # Dados de treino.
yhat_exter <- predict(m0, newdata = das[["val"]]) # Dados de teste.
# Erros de predição
Etra <- crossprod(das[["tra"]]$y - yhat_inter)/ntra
Eval <- crossprod(das[["val"]]$y - yhat_exter)/ntra
# Retorno.
c(ErrTra = Etra,
ErrVal = Eval)
})
cv <- cbind(deg, as.data.frame(t(cv)))
# Capacidade de predição contra o grau do polinômio usado.
xyplot(ErrTra + ErrVal ~ deg,
data = cv,
type = "o",
auto.key = list(corner = c(0.95, 0.95)))
Na validação cruzada do tipo \(k\)-folds, os dados são dividos nos registros em 5 conjuntos de tamanho aproximadamente igual (igual quando \(n\) for múltiplo de \(k\)).
Ao todo são \(k\) situações no qual se faz exatamente o que foi feito no procedimento holdout. Todavia, com essa divisão, cada registro ficará como observação no conjunto de teste pelo menos uma vez. Isso faz com que a avaliação do modelo tenha maior cobertura em termos de condições.
#-----------------------------------------------------------------------
# Método k-fold.
# Gerando um conjunto maior de observações do mesmo modelo.
da <- data.frame(x = runif(207, 0, 100))
da$y <- eta(da$x) + epsilon(da$x)
nrow(da)
## [1] 207
# Número de observações e quantidade de folds para dividir os dados.
n <- nrow(da)
k <- 10
da$i <- ceiling((1:n)/(n/k))
# Número de registros por fold.
table(da$i)
##
## 1 2 3 4 5 6 7 8 9 10
## 20 21 21 20 21 21 20 21 21 21
# Parte os dados em k conjuntos disjuntos (gera uma lista).
das <- split(da, f = da$i)
str(das)
## List of 10
## $ 1 :'data.frame': 20 obs. of 3 variables:
## ..$ x: num [1:20] 34.7 84.2 61.4 55.9 68 ...
## ..$ y: num [1:20] 11.1 14.9 11.7 11.2 12.2 ...
## ..$ i: num [1:20] 1 1 1 1 1 1 1 1 1 1 ...
## $ 2 :'data.frame': 21 obs. of 3 variables:
## ..$ x: num [1:21] 12.5 32.5 100 70.5 52.2 ...
## ..$ y: num [1:21] 13.6 11.1 12.4 14.1 10 ...
## ..$ i: num [1:21] 2 2 2 2 2 2 2 2 2 2 ...
## $ 3 :'data.frame': 21 obs. of 3 variables:
## ..$ x: num [1:21] 25.4 26.2 63.7 51.8 29.5 ...
## ..$ y: num [1:21] 11.56 13 14.27 10.57 9.39 ...
## ..$ i: num [1:21] 3 3 3 3 3 3 3 3 3 3 ...
## $ 4 :'data.frame': 20 obs. of 3 variables:
## ..$ x: num [1:20] 24.731 88.563 0.686 0.377 91.049 ...
## ..$ y: num [1:20] 13.84 14.69 9.56 9.31 14.06 ...
## ..$ i: num [1:20] 4 4 4 4 4 4 4 4 4 4 ...
## $ 5 :'data.frame': 21 obs. of 3 variables:
## ..$ x: num [1:21] 85.4 60.6 69.7 87.2 87 ...
## ..$ y: num [1:21] 15.1 12.5 15.2 15.8 15.3 ...
## ..$ i: num [1:21] 5 5 5 5 5 5 5 5 5 5 ...
## $ 6 :'data.frame': 21 obs. of 3 variables:
## ..$ x: num [1:21] 94.04 28.34 51.76 99.39 4.23 ...
## ..$ y: num [1:21] 14.68 9.66 10.02 13.39 11.15 ...
## ..$ i: num [1:21] 6 6 6 6 6 6 6 6 6 6 ...
## $ 7 :'data.frame': 20 obs. of 3 variables:
## ..$ x: num [1:20] 75.9 74.2 70.9 30.4 76 ...
## ..$ y: num [1:20] 16.1 14.5 14.8 11.5 16.8 ...
## ..$ i: num [1:20] 7 7 7 7 7 7 7 7 7 7 ...
## $ 8 :'data.frame': 21 obs. of 3 variables:
## ..$ x: num [1:21] 76.3 58.1 41.5 62 34 ...
## ..$ y: num [1:21] 16.4 11.7 10.2 13.9 11.9 ...
## ..$ i: num [1:21] 8 8 8 8 8 8 8 8 8 8 ...
## $ 9 :'data.frame': 21 obs. of 3 variables:
## ..$ x: num [1:21] 76.62 3.76 29.88 86.4 56.36 ...
## ..$ y: num [1:21] 16.6 10.4 12.5 15.5 12.2 ...
## ..$ i: num [1:21] 9 9 9 9 9 9 9 9 9 9 ...
## $ 10:'data.frame': 21 obs. of 3 variables:
## ..$ x: num [1:21] 65.8 18.9 45.4 59.1 51.1 ...
## ..$ y: num [1:21] 13.8 14.4 12.2 13 11.3 ...
## ..$ i: num [1:21] 10 10 10 10 10 10 10 10 10 10 ...
# Replica a fórmula para exibir os dados.
form <- replicate(k, y ~ x)
names(form) <- sprintf("fold %d", 1:k)
xyplot.list(form,
data = da,
type = "n",
xlab = "Grau do polinômio",
ylab = "Erro",
cex = 0.6,
as.table = TRUE) +
# Curva do modelo verdadeiro.
layer(panel.curve(eta, col = "orange", lwd = 3)) +
# Observações do conjunto de treinamento.
layer(panel.points(x = x[da$i != packet.number()],
y = y[da$i != packet.number()])) +
# Observações do conjunto de avaliação.
layer(panel.points(x = x[da$i == packet.number()],
y = y[da$i == packet.number()],
pch = 19,
cex = 0.6,
col = "red")) +
# # Ajuste do modelo aos dados de treinamento.
# layer(panel.smoother(x = x[da$i != packet.number()],
# y = y[da$i != packet.number()],
# form = y ~ poly(x, degree = 5),
# method = lm)) +
# Linhas de referência.
layer(panel.grid(), under = TRUE)
# Avaliando cenários varrendo os fold e variando o grau do polinômio.
cen <- expand.grid(fold = 1:k, deg = 1:15)
# Avaliando cada cenário.
kfol <- mapply(f = cen$fold,
d = cen$deg,
FUN = function(f, d) {
# Ajuste do modelo aos dados de treinamento.
j <- da$i != f
m0 <- lm(y ~ poly(x, degree = d), data = subset(da, j))
# Predição com os dados de treino e de teste.
yhat_inter <- predict(m0, newdata = subset(da, j)) # Dados de treino.
yhat_exter <- predict(m0, newdata = das[[f]]) # Dados de teste.
# Erros de predição
Etra <- crossprod(subset(da, j)$y - yhat_inter)/length(yhat_inter)
Eval <- crossprod(das[[f]]$y - yhat_exter)/length(yhat_exter)
# Retorno.
c(ErrTra = Etra, ErrVal = Eval)
})
kfol <- cbind(cen, as.data.frame(t(kfol)))
# Curva de erro em cada fold.
xyplot(ErrTra + ErrVal ~ deg | factor(fold),
data = kfol,
xlab = "Grau do polinômio",
ylab = "Erro",
as.table = TRUE,
auto.key = list(corner = c(0.95, 0.95)),
type = "o")
# Os folds juntos para um mesmo tipo de erro.
xyplot(ErrTra + ErrVal ~ deg,
xlab = "Grau do polinômio",
ylab = "Erro",
groups = fold,
data = kfol,
type = "o") +
layer(panel.xyplot(x = x,
y = y,
type = "a",
col = "black",
lwd = 2))
#-----------------------------------------------------------------------
# k-fold ou leave-one-out (n-fold)?
# Cenários.
cen <- expand.grid(fold = 1:n, deg = 1:15)
# Obtendo os erros para cada cenário.
nfol <- mapply(f = cen$fold,
d = cen$deg,
FUN = function(f, d) {
# Ajuste do modelo aos dados de treinamento.
m0 <- lm(y ~ poly(x, degree = d),
data = da[-f, ])
# Erro de treinamento.
ErrTra <- deviance(m0)/(n - 1)
# Erro de validação.
yhat <- predict(m0, newdata = da[f, ])
ErrVal <- (yhat - da$y[f])^2
return(c(ErrTra = ErrTra, ErrVal = ErrVal))
})
nfol <- cbind(cen, as.data.frame(t(nfol)))
names(nfol)[4] <- "ErrVal"
xyplot(ErrTra + ErrVal ~ deg,
groups = fold,
data = nfol,
scales = list(y = "free"),
type = "o") +
layer(panel.xyplot(x = x,
y = y,
type = "a",
col = "black",
lwd = 2))
# Leave-one-out vs k-fold.
xyplot(ErrVal ~ deg,
data = nfol,
xlab = "Grau do polinômio",
ylab = "Erro",
type = c("p", "a")) +
as.layer(xyplot(ErrVal ~ (deg + 0.15),
data = kfol,
col = "red",
type = c("p", "a")))
Quando usar o leave-one-out?
Vamos fazer a decomposição do erro quadrático médio de predição de uma nova observação \(Y\) cujo vetor de covariáveis é \(x_0\). O verdadeiro modelo para a média de \(Y\) é representado pela função \(\eta(x)\), conforme mostra a igualdade abaixo, \[ Y_{x_0} = \eta(x_0) + e, \] em que \(e\) é um termo aleatório de média 0 e variância constante \(\sigma^2\).
Então o erro quadrático médio é função da variância do erro, da variância das predições usando o modelo estimado denotado por \(\hat{h}(x)\) e do vício, que é função da discrepância entre \(\eta(x_0)\) e a média de \(\hat{h}(x_0)\).
\[ \begin{align*} \mathbb{E}[(Y_{x_0} - \hat{h}(x_0))^2] &= \mathbb{E}[Y^2 + \hat{h}^2 - 2Y\hat{h}]\\ &= \underbrace{\mathbb{E}[Y^2]}_{ \mathbb{V}[Y] = \mathbb{E}[Y^2] - \mathbb{E}^2[Y]} + \mathbb{E}[\hat{h}^2] - \mathbb{E}[2Y\hat{h}]\\ &= \mathbb{V}[Y] + \mathbb{E}^2[Y] + \mathbb{V}[\hat{h}] + \mathbb{E}^2[\hat{h}] - 2\mathbb{E}[Y\hat{h}] \\ &= \mathbb{V}[Y] + \mathbb{V}[\hat{h}] + (\eta^2 - 2h\mathbb{E}[\hat{h}] + \mathbb{E}^2[\hat{h}])\\ &= \mathbb{V}[Y] + \mathbb{V}[\hat{h}] + (\eta - \mathbb{E}[\hat{h}])^2\\ &= \sigma^2 + \mathbb{V}[\hat{h}] + \mathbb{B}^2[\hat{h}] \end{align*} \]
#-----------------------------------------------------------------------
# Decomposição em variância e vício.
# Valor considerado para avaliar a predição (x_0).
x0 <- 50
# Gerando dados artificiais.
da <- data.frame(x = runif(100, 0, 100))
da$y <- eta(da$x) + epsilon(da$x)
# Observação futura a ser prevista pelo modelo.
y <- eta(x0) + epsilon(x0)
# Ajuste do modelo.
m0 <- lm(y ~ x, data = da)
h <- predict(m0, newdata = list(x = x0))
# Quadrado do erro de predição.
(y - h)^2
## 1
## 3.144327
#-----------------------------------------------------------------------
# Repetindo o processo várias vezes variando o grau do polinômio.
# Valor da covariável e estimativa de Y pelo modelo verdadeiro.
x0 <- 50
eta_x0 <- eta(x0)
# Graus do polinômio a serem avaliados e quantidade de repetições.
deg <- 1:15
rpt <- 100
# deg <- c(1, 7)
# rpt <- 5000
# Aplica para cada grau.
resul <- lapply(deg,
FUN = function(d) {
t(replicate(rpt, {
da <- data.frame(x = sample(0:100, size = 30))
da$y <- eta(da$x) + epsilon(da$x)
m0 <- lm(y ~ poly(x, degree = d),
data = da)
y <- eta_x0 + epsilon(x0)
h <- predict(m0,
newdata = list(x = x0))
names(h) <- NULL
return(c(yobs = y,
hfit = h))
}))
})
# Calcula os termos da decomposição do EQM.
eqm_decom <- function(m) {
c(eqm = mean((m[, "yobs"] - m[, "hfit"])^2),
Vy = var(m[, "yobs"]) * (rpt - 1)/rpt,
Vh = var(m[, "hfit"]) * (rpt - 1)/rpt,
Bh = (eta_x0 - mean(m[, "hfit"]))^2)
}
resul <- t(sapply(resul, eqm_decom))
resul <- cbind(deg = deg, as.data.frame(resul))
resul
## deg eqm Vy Vh Bh
## 1 1 6.103615 1.0511001 0.08176052 5.320882e+00
## 2 2 3.716172 0.8337139 0.16464649 2.493738e+00
## 3 3 4.351430 1.0059225 0.17915714 2.651481e+00
## 4 4 1.763831 1.2664840 0.14194413 1.779472e-01
## 5 5 1.456391 1.1950296 0.15341861 1.441929e-01
## 6 6 1.046582 0.8814642 0.17189460 1.277271e-02
## 7 7 1.325178 1.0732927 0.25014617 7.429692e-04
## 8 8 1.403162 1.0330854 0.27993645 6.131462e-03
## 9 9 1.096374 1.0049492 0.31254871 2.976572e-07
## 10 10 1.667162 1.1013899 0.35280593 5.732081e-05
## 11 11 1.591532 1.1700133 0.32756392 2.486194e-03
## 12 12 1.343440 0.9537399 0.32944838 1.151621e-03
## 13 13 1.279023 0.7870739 0.41499978 1.454529e-02
## 14 14 1.580736 1.0510679 0.63860877 7.219584e-03
## 15 15 1.645274 1.0178021 0.59534610 3.064336e-03
xyplot(eqm + Vy + Vh + Bh ~ deg,
data = resul,
type = c("p", "o"),
xlab = "Grau do polinômio",
ylab = "Componentes do erro quadrático médio",
auto.key = list(corner = c(0.95, 0.95),
text = c(expression(EQM(hat(y))),
expression(Var(y)),
expression(Var(hat(y))),
expression(Bias(hat(y))))))# +
# glayer(panel.smoother(..., se = FALSE))
#-----------------------------------------------------------------------
# Visualizando este conceito.
# Simulando dados.
da <- data.frame(x = sample(0:100, size = 30))
da$y <- eta(da$x) + epsilon(da$x)
# Cria lista com fórmulas.
dmax <- 15
form <- replicate(dmax, y ~ x)
names(form) <- sprintf("Pol. grau: %d", 1:dmax)
xyplot.list(form,
data = da,
type = "n",
as.table = TRUE,
panel = function(...) {
# Gráficos dos pontos originais (não exibidos).
panel.xyplot(...)
# Simulando a resposta e ajustando o modelo.
d <- panel.number()
xseq <- 0:100
resul <- replicate(30, {
xnew <- sample(0:100, size = 30)
ynew <- eta(xnew) + epsilon(xnew)
panel.points(x = xnew,
y = ynew,
pch = 20,
cex = 0.8,
alpha = 0.25)
m0 <- lm(ynew ~ poly(xnew, degree = d))
yfit <- predict(m0,
newdata = list(xnew = xseq))
panel.lines(x = xseq,
y = yfit,
col = "black",
alpha = 0.7)
return(yfit)
})
# Ajuste médio ponto a ponto.
# yfitm <- rowMeans(resul)
# panel.lines(x = xseq,
# y = yfitm,
# col = "green2",
# lwd = 3)
# O verdadeiro modelo.
panel.curve(eta,
col = "orange",
lwd = 3)
panel.abline(v = x0, lty = 2, col = "gray")
})
Para um material mais completo, visite: http://leg.ufpr.br/~walmes/cursoR/fiocruz/fiocruz04diagn.html.
# Dados.
plot(dist ~ speed, data = cars)
# Ajuste do modelo.
m0 <- lm(dist ~ speed + I(speed^2), data = cars)
# Índice das observações.
i <- 1:nrow(cars)
# Artefatos do modelo ajustado.
X <- model.matrix(m0)
iXlX <- solve(t(X) %*% X)
siXlX <- sqrt(diag(iXlX))
Hi <- sqrt(diag(X %*% iXlX %*% t(X)))
fit0 <- fitted(m0)
beta0 <- coef(m0)
loo <- lapply(i,
FUN = function(ii) {
m1 <- update(m0, data = cars[-ii, ])
fit <- predict(m1,
newdata = cars[ii, ])
s <- sqrt(deviance(m1)/df.residual(m1))
se_beta <- s * siXlX
se_fit <- s * Hi[ii]
list(dfbetas = (beta0 - coef(m1))/se_beta,
dffits = (fit0[ii] - fit)/se_fit)
})
head(dfbetas(m0))
## (Intercept) speed I(speed^2)
## 1 -0.274891030 0.246714807 -0.218599848
## 2 0.109198833 -0.098005996 0.086837495
## 3 -0.188270827 0.147739655 -0.117691200
## 4 0.158683853 -0.124522200 0.099195894
## 5 -0.002372816 0.001681285 -0.001215252
## 6 -0.080020109 0.046278022 -0.025675268
head(t(sapply(loo, "[[", "dfbetas")))
## (Intercept) speed I(speed^2)
## [1,] -0.274891030 0.246714807 -0.218599848
## [2,] 0.109198833 -0.098005996 0.086837495
## [3,] -0.188270827 0.147739655 -0.117691200
## [4,] 0.158683853 -0.124522200 0.099195894
## [5,] -0.002372816 0.001681285 -0.001215252
## [6,] -0.080020109 0.046278022 -0.025675268
head(dffits(m0))
## 1 2 3 4 5
## -0.281893050 0.111980344 -0.223308547 0.188215355 -0.003221695
## 6
## -0.137841851
head(sapply(loo, "[[", "dffits"))
## 1 2 3 4 5
## -0.281893050 0.111980344 -0.223308547 0.188215355 -0.003221695
## 6
## -0.137841851
Machine Learning | Prof. Eduardo V. Ferreira & Prof. Walmes M. Zeviani |