Введение в R: часть 3

Pavel Polishchuk, 2014


Примеры построения моделей в R.

  1. Линейная регрессия (lm)
  2. Метод частичных наименьших квадратов (pls)
  3. Метод Random Forest (randomForest)
  4. Пример решения классификационной задачи методом Random Forest

Линейная регрессия (lm)

Построение моделей

Сгенерируем набор данных для моделирования в соответствии с формулой
\[ Y = x_1 + x_2^2 + x_1 \times x_2 + noise \]

Для этого вначале инициализируем генератор случайных чисел, чтобы получать всегда воспроизводимый результат при генерации случайных чисел.

set.seed(50)

Сгенерируем случайные переменные \( x_1 \) и \( x_2 \) в соответствии с законом нормального распределения

x1 <- rnorm(n=200, mean=5, sd=2)
x2 <- rnorm(200, 2, 3)

Сгенерируем зависимую переменную в соответствии с вышеприведенной формулой

y <- x1 + x2^2 + x1*x2 + rnorm(200, 0, 0.5)

Объединим эти параметры в одном data.frame

df <- data.frame(y, x1, x2)

Рассмотрим созданный набор данных. Построим гистограмму распределению значений Y и посмотрим как рапределены значения переменных между собой попарно.

hist(df$y)

plot of chunk unnamed-chunk-5

pairs(df)

plot of chunk unnamed-chunk-5

Посчитаем матрицу взаимных корреляций всех переменных между собой и округлим результат до двух цифр после запятой

round(cor(df), 2)
      y    x1    x2
y  1.00  0.18  0.86
x1 0.18  1.00 -0.05
x2 0.86 -0.05  1.00

Помимо традиционного коэффицента корреляции Пирсона функцией cor можно посчитать ранговый коэффицент корреляции Спирмена.

round(cor(df, method="spearman"), 2)
      y    x1    x2
y  1.00  0.12  0.94
x1 0.12  1.00 -0.02
x2 0.94 -0.02  1.00

И из рисунка и из таблиц видно, что связь между \( y \) и \( x_2 \) довольно тесная.


Альтернативный способ представления данных - использование функции pairs.panels пакета psych, которая возвращает одновременно и диаграммы распределения данных и значения коэффициентов корреляции.

psych::pairs.panels(df)

plot of chunk unnamed-chunk-8


Зададим индексы для объектов обучающей и тестовой выборки

train.ids <- 1:100
test.ids <- 101:200

Построим несколько моделей в соответствии с различными формулами, которые определяют вид искомой зависимости.

Попробуем сперва обычную линейную комбинацию исходных переменных.

m1 <- lm(y ~ x1 + x2, df[train.ids,])
summary(m1)

Call:
lm(formula = y ~ x1 + x2, data = df[train.ids, ])

Residuals:
   Min     1Q Median     3Q    Max 
-15.62  -7.56  -3.43   1.95  54.96 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   -6.379      3.493   -1.83    0.071 .  
x1             3.377      0.625    5.40  4.8e-07 ***
x2             8.567      0.415   20.65  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 12.3 on 97 degrees of freedom
Multiple R-squared:  0.818, Adjusted R-squared:  0.814 
F-statistic:  218 on 2 and 97 DF,  p-value: <2e-16

В результате мы получили уравнение вида \[ Y = 3.377\pm0.625*x_1 + 8,567\pm0,415*x_2 - 6,379\pm3,493 \]
Все коэффициенты в уравнении статистически значимы с высоким уронеем достоверности, кроме свободного члена, который имеет низкий уровень статистической достоверности. Величина \( R^2 = 0.818 \) является статистически достоверной.


Коэффициент детерминации

\[ R^2 = 1 - \frac{\sigma^2}{\sigma_y^2} = 1 - \frac{^{ESS}/_{n}}{^{TSS}/_{n}} = 1 - \frac{ESS}{TSS} = 1 - \frac{\sum_{i=1}^{n}(y_i - \hat{y}_i)^2}{\sum_{i=1}^{n}(y_i - \bar{y})^2} \]

Скорректированный коэффициент детерминации с использованием несмещенных оценок

\[ R_{adj}^2 = 1 - \frac{s^2}{s_y^2} = 1 - \frac{^{ESS}/_{n-k}}{^{TSS}/_{n-1}} =1 - (1- R^2)\frac{n-1}{n-k} \leq R^2 \]

\( y_i \) - наблюдаемое значение \( y \)
\( \hat{y}_i \) - предсказанное по модели значение \( y \)
\( \bar{y} \) - среднее значение наблюдаемых значений \( y \)
\( n \) - количество наблюдений
\( k \) - количество параметров


Применим одну из простейших процедур отбора переменных - исключим незначащие члены из уравнения

m2 <- lm(y ~ x1 + x2 + 0, df[train.ids,])
summary(m2)

Call:
lm(formula = y ~ x1 + x2 + 0, data = df[train.ids, ])

Residuals:
   Min     1Q Median     3Q    Max 
-17.73  -8.88  -4.61   2.30  49.03 

Coefficients:
   Estimate Std. Error t value Pr(>|t|)    
x1    2.361      0.290    8.15  1.2e-12 ***
x2    8.267      0.386   21.44  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 12.4 on 98 degrees of freedom
Multiple R-squared:  0.914, Adjusted R-squared:  0.913 
F-statistic:  524 on 2 and 98 DF,  p-value: <2e-16

Коэффициент детерминации существенно увеличился \( R^2 = 0.914 \)


Сравнение моделей

Определим с помощью дисперсионного анализа является ли отличие моделей значимым или нет.

anova(m1, m2)
Analysis of Variance Table

Model 1: y ~ x1 + x2
Model 2: y ~ x1 + x2 + 0
  Res.Df   RSS Df Sum of Sq    F Pr(>F)  
1     97 14603                           
2     98 15105 -1      -502 3.33  0.071 .
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Величина 0.071 > 0.05, поэтому можно утверждать, что с вероятностью 95% отличие моделей не значимо и мы в праве выбрать любую модель.


Попытаемся построить более сложные модели.

Модель учитывающее произведение независимых переменных

m3 <- lm(y ~ x1*x2, df[train.ids,])
summary(m3)

Call:
lm(formula = y ~ x1 * x2, data = df[train.ids, ])

Residuals:
   Min     1Q Median     3Q    Max 
-10.35  -7.51  -3.06   3.77  51.30 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)    5.132      3.623    1.42    0.160    
x1             1.160      0.664    1.75    0.084 .  
x2             3.866      0.890    4.34  3.5e-05 ***
x1:x2          0.962      0.167    5.77  9.5e-08 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 10.6 on 96 degrees of freedom
Multiple R-squared:  0.865, Adjusted R-squared:  0.861 
F-statistic:  205 on 3 and 96 DF,  p-value: <2e-16

Модель второго порядка, учитывающая квадраты независимых переменных

m4 <- lm(y ~ poly(x1, 2) + poly(x2, 2), df[train.ids,])
summary(m4)

Call:
lm(formula = y ~ poly(x1, 2) + poly(x2, 2), data = df[train.ids, 
    ])

Residuals:
    Min      1Q  Median      3Q     Max 
-15.569  -2.232  -0.475   1.683  22.654 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept)    31.056      0.594   52.27  < 2e-16 ***
poly(x1, 2)1   65.297      5.983   10.91  < 2e-16 ***
poly(x1, 2)2  -24.414      5.979   -4.08  9.3e-05 ***
poly(x2, 2)1  253.662      5.991   42.34  < 2e-16 ***
poly(x2, 2)2  105.653      5.971   17.69  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 5.94 on 95 degrees of freedom
Multiple R-squared:  0.958, Adjusted R-squared:  0.956 
F-statistic:  544 on 4 and 95 DF,  p-value: <2e-16

В последнем случае видно, что все коэффициенты в уравнении имеют высокую значимость и коэффициент детерминации также высокий \( R^2 = 0.958 \)

Сравнение моделей указывает на значимое отличие последней модели от предыдущей.

anova(m2, m4)
Analysis of Variance Table

Model 1: y ~ x1 + x2 + 0
Model 2: y ~ poly(x1, 2) + poly(x2, 2)
  Res.Df   RSS Df Sum of Sq   F Pr(>F)    
1     98 15105                            
2     95  3354  3     11751 111 <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Для сравнения построим модель с использованием правильной функциональной зависимости

m5 <- lm(y ~ x1 + x1:x2 + I(x2^2), df[train.ids,])
summary(m5)

Call:
lm(formula = y ~ x1 + x1:x2 + I(x2^2), data = df[train.ids, ])

Residuals:
    Min      1Q  Median      3Q     Max 
-1.0893 -0.3216  0.0275  0.3209  1.0808 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -0.15588    0.15289   -1.02     0.31    
x1           1.03061    0.02750   37.48   <2e-16 ***
I(x2^2)      1.00983    0.00459  219.78   <2e-16 ***
x1:x2        0.99062    0.00436  226.95   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.518 on 96 degrees of freedom
Multiple R-squared:     1,  Adjusted R-squared:     1 
F-statistic: 9.97e+04 on 3 and 96 DF,  p-value: <2e-16

Свободный членя является не значимым, следовательно зависимость проходит через начало отсчета. Построим модель без свободного члена.

m6 <- lm(y ~ x1 + x1:x2 + I(x2^2) + 0, df[train.ids,])
summary(m6)

Call:
lm(formula = y ~ x1 + x1:x2 + I(x2^2) + 0, data = df[train.ids, 
    ])

Residuals:
    Min      1Q  Median      3Q     Max 
-1.0777 -0.3465  0.0152  0.3057  1.1711 

Coefficients:
        Estimate Std. Error t value Pr(>|t|)    
x1       1.00601    0.01319    76.2   <2e-16 ***
I(x2^2)  1.00769    0.00409   246.3   <2e-16 ***
x1:x2    0.99165    0.00425   233.5   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.518 on 97 degrees of freedom
Multiple R-squared:     1,  Adjusted R-squared:     1 
F-statistic: 2.2e+05 on 3 and 97 DF,  p-value: <2e-16

Прогноз значений для тестового набора данных

pred.m4 <- predict(m4, df[test.ids,])

Сопоставим качество прогноза с истинным значением \( y \)

cor(pred.m4, df$y[test.ids])
[1] 0.9673
cor(pred.m4, df$y[test.ids]) ^ 2
[1] 0.9357

Визуализация моделей

Зададим вид вывода результатов и применим функцию plot к моделям m4 и m6.

# 2 x 2 pictures on one plot and square plotting region, independent of device size
par(mfrow = c(2, 2), pty = "s")  
plot(m4)

plot of chunk unnamed-chunk-20

plot(m6)

plot of chunk unnamed-chunk-20

Вернем вид вывода результатов к виду по умолчанию

par(mfrow = c(1, 1), pty = "m")       

Метод частичных наименьших квадратов (pls)

require(pls)
pls.m1 <- plsr(y ~ ., data=df[train.ids, ], ncomp=2, validation="CV", segments=5, segment.type="random")

Воспользуемся функцией summary для вывода статистики модели

summary(pls.m1)
Data:   X dimension: 100 2 
    Y dimension: 100 1
Fit method: kernelpls
Number of components considered: 2

VALIDATION: RMSEP
Cross-validated using 5 random segments.
       (Intercept)  1 comps  2 comps
CV            28.6    13.87    14.00
adjCV         28.6    13.76    13.79

TRAINING: % variance explained
   1 comps  2 comps
X    69.23   100.00
y    78.68    81.78

Чтобы посчитать коэффициент детерминации для обучающей выборки и для случая кросс-валидации воспользуемся следующими функциями. Следует отметить, что модель pls в качестве прогнозированных значений возвращает массив!

# train
cor(pls.m1$fitted.values[,,2], df$y[train.ids]) ^ 2
[1] 0.8178
# cross-validation
cor(pls.m1$validation$pred[,,2], df$y[train.ids]) ^ 2
[1] 0.7604

Для pls модель также возможно использовать функцию plot, которая возвращает диаграмму зависимости предсказанных значений от наблюдаемых

plot(pls.m1)
abline(0, 1) # добавляет линию выходящую из начала координат и идущей под углом 45 градусов

plot of chunk unnamed-chunk-25

Спрогнозируем значения тестовой выборки и оценим качество прогноза

pred.pls.m1 <- predict(pls.m1, df[test.ids,])
cor(pred.pls.m1[,,2], df$y[test.ids]) ^ 2
[1] 0.775

Используем другой вид зависимости, который был успешно применен ранее в случае линейной регрессии

pls.m2 <- plsr(y ~ poly(x1, 2) + poly(x2, 2), data=df[train.ids, ], ncomp=2, validation="CV", segments=5, segment.type="random")
summary(pls.m2)
Data:   X dimension: 100 4 
    Y dimension: 100 1
Fit method: kernelpls
Number of components considered: 2

VALIDATION: RMSEP
Cross-validated using 5 random segments.
       (Intercept)  1 comps  2 comps
CV            28.6    8.976    6.848
adjCV         28.6    8.246    6.711

TRAINING: % variance explained
   1 comps  2 comps
X    24.62    50.23
y    94.86    95.81
cor(pls.m2$validation$pred[,,2], df$y[train.ids]) ^ 2
[1] 0.9415

Как видно качество модели значительно уеличилось. Подтвердим это оценив прогнозирующую способность модели на тестовом наборе данных.

pred.pls.m2 <- predict(pls.m2, df[test.ids,])
cor(pred.pls.m2[,,2], df$y[test.ids]) ^ 2
[1] 0.9344
plot(pls.m2)
abline(0, 1)

plot of chunk unnamed-chunk-29


Метод Random Forest (randomForest)

require(randomForest)
rf.m <- randomForest(y ~ ., data=df[train.ids, ], do.trace=50, mtry=2)
     |      Out-of-bag   |
Tree |      MSE  %Var(y) |
  50 |    37.52     4.68 |
 100 |    36.98     4.61 |
 150 |    36.74     4.58 |
 200 |     35.9     4.48 |
 250 |    36.21     4.52 |
 300 |    36.34     4.53 |
 350 |    36.35     4.54 |
 400 |    36.82     4.59 |
 450 |    35.87     4.47 |
 500 |    35.72     4.46 |

Следует отметить, что большинство функций имеют несколько способов вызова через определение функции как в вышеприведенном случае, либо через указание матрицы X и вектора Y. Так построение модели можно осуществить через
rf.m <- randomForest(x=df[train.ids, -1], y=df[train.ids, "y"], do.trace=50, mtry=2)

Вызовем статистику модели

rf.m

Call:
 randomForest(formula = y ~ ., data = df[train.ids, ], do.trace = 50,      mtry = 2) 
               Type of random forest: regression
                     Number of trees: 500
No. of variables tried at each split: 2

          Mean of squared residuals: 35.72
                    % Var explained: 95.54

Рассчитаем значения коэффициента детерминации для out-of-bag выборки (внутренняя валидация моделей)

cor(rf.m$predicted, df$y[train.ids]) ^ 2
[1] 0.9556

Функция plot в случае моделей случайного леса возвращает зависимость величины среднеквадратичной ошибки прогноза out-of-bag выборки от количества деревьев в лесе

plot(rf.m)

plot of chunk unnamed-chunk-33

Чтобы оценить качество модели предскажем тестовый набор данных

pred.rf <- predict(rf.m, df[test.ids,])
cor(pred.rf, df$y[test.ids]) ^ 2
[1] 0.9644

Построим график зависимости между предсказанными и наблюдаемыми значениями

plot(pred.rf, df$y[test.ids])
abline(0, 1)

plot of chunk unnamed-chunk-35


Решение классификационных задач методом Random Forest

Конвертируем значения y из df в два класса по заданному граничному значению

df$yclass <- factor(ifelse(df$y > 30, 1, 0))
sapply(df, class)
        y        x1        x2    yclass 
"numeric" "numeric" "numeric"  "factor" 

Построение классификационной модели ничем не отличается от регресссионных моделей. Алгоритм сам выбирает тип модели (скласссификационная или регрессионная) в зависимости от типа данных передаваемого Y. Если Y представлен числовыми значениями, то по умолчанию будет построена регрессионная модель, если Y представлен как factor, то будет построена классификационная модель.

rf.m2 <- randomForest(yclass ~ . - y, data=df[train.ids, ], do.trace=50, mtry=2, importance=TRUE, nPerm=3)
ntree      OOB      1      2
   50:   4.00%  3.57%  4.55%
  100:   4.00%  3.57%  4.55%
  150:   4.00%  3.57%  4.55%
  200:   4.00%  3.57%  4.55%
  250:   4.00%  3.57%  4.55%
  300:   4.00%  3.57%  4.55%
  350:   4.00%  3.57%  4.55%
  400:   4.00%  3.57%  4.55%
  450:   4.00%  3.57%  4.55%
  500:   4.00%  3.57%  4.55%

Обратите внимание на способ представления формулы. Знак минус указывает, что эти переменные должны быть исключены из расммотрения при построении модели.

Выведем статистику модели

rf.m2

Call:
 randomForest(formula = yclass ~ . - y, data = df[train.ids, ],      do.trace = 50, mtry = 2, importance = TRUE, nPerm = 3) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 2

        OOB estimate of  error rate: 4%
Confusion matrix:
   0  1 class.error
0 54  2     0.03571
1  2 42     0.04545

Проверим прогнозирующую способность на тестовом наборе данных

pred.rf.m2 <- predict(rf.m2, df[test.ids,])

Следует помнить, что результатом прогноза будет factor

class(pred.rf.m2)
[1] "factor"

Выведем матрицу ошибок классификации

table(pred.rf.m2, df$yclass[test.ids])

pred.rf.m2  0  1
         0 60  6
         1  1 33

Выведем значения важности переменных в последней модели

rf.m2$importance
         0        1 MeanDecreaseAccuracy MeanDecreaseGini
x1 0.02934 0.002637              0.01767            2.659
x2 0.40098 0.476636              0.42748           46.118
rf.m2$importance[,"MeanDecreaseGini"]
    x1     x2 
 2.659 46.118 

Заключение

Следует помнить, что в R реализовано множество различных алгоритмов построения регрессионных и классификационных моделей. Выбор метода построения модели лежит полностью на исследователе и требует некоторого опыта и навыков. Познакомится с некоторыми другими методами машинного обучения, которые присутствуют в R можно по ссылке http://cran.r-project.org/web/views/MachineLearning.html


Домашнее задание

  1. Реализовать собственную функцию create.folds для случайного деления выборки на фолды с целью кросс-валидации. Результатом должен быть список, каждый элемент которого содержит индексы соединений, входящих в обучающую выборку отдельного фолда.
  2. Осуществить кросс-валидацию для модели randomForest на данных представленных в уроке (df), чтобы сравнить ее с остальными моделями.
  3. Использовать файл ENB2012_data.xlsx в качестве источника данных (для загрузки данных можно использовать пакет xlsx). Построить модели зависимости heating.load и Cooling.load от остальных переменных методами, описанными в уроке (можно также поискать другие методы по приведенной в конце занятия ссылке и использовать их).