Загрузим данные для выполнения задания: загрузим пакет Boston и установим ядро.
my.seed <- 1
data(Boston)
str(Boston)
## 'data.frame': 506 obs. of 14 variables:
## $ crim : num 0.00632 0.02731 0.02729 0.03237 0.06905 ...
## $ zn : num 18 0 0 0 0 0 12.5 12.5 12.5 12.5 ...
## $ indus : num 2.31 7.07 7.07 2.18 2.18 2.18 7.87 7.87 7.87 7.87 ...
## $ chas : int 0 0 0 0 0 0 0 0 0 0 ...
## $ nox : num 0.538 0.469 0.469 0.458 0.458 0.458 0.524 0.524 0.524 0.524 ...
## $ rm : num 6.58 6.42 7.18 7 7.15 ...
## $ age : num 65.2 78.9 61.1 45.8 54.2 58.7 66.6 96.1 100 85.9 ...
## $ dis : num 4.09 4.97 4.97 6.06 6.06 ...
## $ rad : int 1 2 2 3 3 3 5 5 5 5 ...
## $ tax : num 296 242 242 222 222 222 311 311 311 311 ...
## $ ptratio: num 15.3 17.8 17.8 18.7 18.7 18.7 15.2 15.2 15.2 15.2 ...
## $ black : num 397 397 393 395 397 ...
## $ lstat : num 4.98 9.14 4.03 2.94 5.33 ...
## $ medv : num 24 21.6 34.7 33.4 36.2 28.7 22.9 27.1 16.5 18.9 ...
fix(Boston)
names(Boston)
## [1] "crim" "zn" "indus" "chas" "nox" "rm" "age"
## [8] "dis" "rad" "tax" "ptratio" "black" "lstat" "medv"
dim(Boston)
## [1] 506 14
Выполним пошаговое включение регрессоров.
regfit.fwd <- regsubsets(crim ~ ., data = Boston,
nvmax = 13, method = 'forward')
summary(regfit.fwd)
## Subset selection object
## Call: regsubsets.formula(crim ~ ., data = Boston, nvmax = 13, method = "forward")
## 13 Variables (and intercept)
## Forced in Forced out
## zn FALSE FALSE
## indus FALSE FALSE
## chas FALSE FALSE
## nox FALSE FALSE
## rm FALSE FALSE
## age FALSE FALSE
## dis FALSE FALSE
## rad FALSE FALSE
## tax FALSE FALSE
## ptratio FALSE FALSE
## black FALSE FALSE
## lstat FALSE FALSE
## medv FALSE FALSE
## 1 subsets of each size up to 13
## Selection Algorithm: forward
## zn indus chas nox rm age dis rad tax ptratio black lstat medv
## 1 ( 1 ) " " " " " " " " " " " " " " "*" " " " " " " " " " "
## 2 ( 1 ) " " " " " " " " " " " " " " "*" " " " " " " "*" " "
## 3 ( 1 ) " " " " " " " " " " " " " " "*" " " " " "*" "*" " "
## 4 ( 1 ) " " " " " " " " " " " " " " "*" " " " " "*" "*" "*"
## 5 ( 1 ) "*" " " " " " " " " " " " " "*" " " " " "*" "*" "*"
## 6 ( 1 ) "*" " " " " " " " " " " "*" "*" " " " " "*" "*" "*"
## 7 ( 1 ) "*" " " " " "*" " " " " "*" "*" " " " " "*" "*" "*"
## 8 ( 1 ) "*" " " " " "*" " " " " "*" "*" " " "*" "*" "*" "*"
## 9 ( 1 ) "*" "*" " " "*" " " " " "*" "*" " " "*" "*" "*" "*"
## 10 ( 1 ) "*" "*" " " "*" "*" " " "*" "*" " " "*" "*" "*" "*"
## 11 ( 1 ) "*" "*" " " "*" "*" " " "*" "*" "*" "*" "*" "*" "*"
## 12 ( 1 ) "*" "*" "*" "*" "*" " " "*" "*" "*" "*" "*" "*" "*"
## 13 ( 1 ) "*" "*" "*" "*" "*" "*" "*" "*" "*" "*" "*" "*" "*"
round(coef(regfit.fwd, 13), 3)
## (Intercept) zn indus chas nox rm
## 17.033 0.045 -0.064 -0.749 -10.314 0.430
## age dis rad tax ptratio black
## 0.001 -0.987 0.588 -0.004 -0.271 -0.008
## lstat medv
## 0.126 -0.199
Запишем функцию для прогноза функции:
predict.regsubsets <- function(object, newdata, id, ...){
form <- as.formula(object$call[[2]])
mat <- model.matrix(form, newdata)
coefi <- coef(object, id = id)
xvars <- names(coefi)
mat[, xvars] %*% coefi
}
Выполним кросс-валидацию для нахождения оптимальной модели
# k-кратная кросс-валидация
# отбираем 10 блоков наблюдений
k <- 10
set.seed(my.seed)
folds <- sample(1:k, nrow(Boston), replace = T)
# заготовка под матрицу с ошибками
cv.errors <- matrix(NA, k, 13, dimnames = list(NULL, paste(1:13)))
# заполняем матрицу в цикле по блокам данных
for (j in 1:k){
best.fit <- regsubsets(crim ~ ., data = Boston[folds != j, ],
nvmax = 13)
# теперь цикл по количеству объясняющих переменных
for (i in 1:13){
# модельные значения Boston
pred <- predict(best.fit, Boston[folds == j, ], id = i)
# вписываем ошибку в матрицу
cv.errors[j, i] <- mean((Boston$crim[folds == j] - pred)^2)
}
}
# усредняем матрицу по каждому столбцу (т.е. по блокам наблюдений),
# чтобы получить оценку MSE для каждой модели с фиксированным
# количеством объясняющих переменных
mean.cv.errors <- apply(cv.errors, 2, mean)
round(mean.cv.errors, 0)
## 1 2 3 4 5 6 7 8 9 10 11 12 13
## 44 43 43 43 42 42 42 42 41 41 41 41 41
# на графике
plot(mean.cv.errors, type = 'b')
points(which.min(mean.cv.errors), mean.cv.errors[which.min(mean.cv.errors)],
col = 'red', pch = 20, cex = 2)
# перестраиваем модель с 12 объясняющими переменными на всём наборе данных
reg.best <- regsubsets(crim ~ ., data = Boston, nvmax = 13)
round(coef(reg.best, 12), 3)
## (Intercept) zn indus chas nox rm
## 16.986 0.045 -0.064 -0.744 -10.202 0.440
## dis rad tax ptratio black lstat
## -0.994 0.588 -0.004 -0.270 -0.008 0.128
## medv
## -0.199
Наименьшая MSE на тестовой выборке оказалась у модели с 12 предикторами (MSE=41).
Воспльзуемся методом сжатия, а именно лассо-регрессией:
#лассо-регрессия
# из-за синтаксиса glmnet() формируем явно матрицу объясняющих...
x <- model.matrix(crim ~ ., Boston)[, -1]
# и вектор значений зависимой переменной
y <- Boston$crim
set.seed(my.seed)
train <- sample(1:nrow(x), nrow(x)/2)
test <- -train
y.test <- y[test]
# вектор значений гиперпараметра лямбда
grid <- 10^seq(10, -2, length = 100)
lasso.mod <- glmnet(x[train, ], y[train], alpha = 1, lambda = grid)
plot(lasso.mod)
set.seed(my.seed)
cv.out <- cv.glmnet(x[train, ], y[train], alpha = 1)
plot(cv.out)
bestlam <- cv.out$lambda.min
lasso.pred <- predict(lasso.mod, s = bestlam, newx = x[test, ])
#MSE на тестовой
round(mean((lasso.pred - y.test)^2), 0)
## [1] 38
# коэффициенты лучшей модели
out <- glmnet(x, y, alpha = 1, lambda = grid)
lasso.coef <- predict(out, type = 'coefficients',
s = bestlam)[1:13, ]
round(lasso.coef, 3)
## (Intercept) zn indus chas nox rm
## 9.263 0.031 -0.051 -0.513 -3.755 0.041
## age dis rad tax ptratio black
## 0.000 -0.601 0.495 0.000 -0.108 -0.008
## lstat
## 0.118
round(lasso.coef[lasso.coef != 0], 3)
## (Intercept) zn indus chas nox rm
## 9.263 0.031 -0.051 -0.513 -3.755 0.041
## dis rad ptratio black lstat
## -0.601 0.495 -0.108 -0.008 0.118
МSE оказалась ниже чем у метода пошагового включения, MSE=38.