рекомендации

четверг, 23 января 2020 г.

Работа с нейронными сетями в R, пакет neuralnet

Перевод. Оригинал: Fitting a Neural Network in R; neuralnet package

Нейронные сети всегда были одной из увлекательных моделей машинного обучения, на мой взгляд, не только из-за причудливого алгоритма обратного распространения, но и из-за их сложности (представьте себе глубокое обучение со многими скрытыми слоями) и структуры, вдохновленной мозгом.

Нейронные сети не всегда были популярны, отчасти потому, что они были и остаются в некоторых случаях вычислительно дорогими, а отчасти потому, что они, по-видимому, не дают лучших результатов по сравнению с более простыми методами, такими как машины опорных векторов (SVM). Тем не менее, нейронные сети вновь привлекли внимание и стали популярными.

В этой статье мы рассмотрим простую нейронную сеть с использованием пакета neuralnet и приведем линейную модель для сравнения.

Набор данных

Мы собираемся использовать набор данных Boston в пакете MASS.
Набор данных Boston представляет собой набор данных о стоимости жилья в пригороде Бостона. Наша цель - спрогнозировать среднюю стоимость домов, занимаемых владельцами (medv), используя все остальные доступные переменные.

set.seed(500)
library(MASS)
data <- Boston

Сначала мы должны убедиться, что ни одна точка данных не отсутствует, в противном случае нам нужно исправить набор данных.


apply(data,2,function(x) sum(is.na(x)))
crim      zn   indus    chas     nox      rm     age     dis     rad     tax ptratio 
   0       0       0       0       0       0       0       0       0       0       0 
black   lstat    medv 
    0       0       0 

Пропущенных данных нет, это хорошо. Далее мы случайным образом разбиваем данные на обучающий и тестовый наборы, затем подгоняем модель линейной регрессии и тестируем ее на тестовом наборе. Обратите внимание, что я использую функцию gml() вместо lm(), это будет полезным позже при перекрестной проверке линейной модели.

index <- sample(1:nrow(data),round(0.75*nrow(data)))
train <- data[index,]
test <- data[-index,]
lm.fit <- glm(medv~., data=train)
summary(lm.fit)
pr.lm <- predict(lm.fit,test)
MSE.lm <- sum((pr.lm - test$medv)^2)/nrow(test)

Функция sample (x, size) просто выводит вектор заданного размера из случайно выбранных выборок из вектора x. По умолчанию выборка производится без замены: index по сути является случайным вектором значений.
Поскольку мы имеем дело с проблемой регрессии, мы будем использовать среднеквадратичную ошибку (MSE) как меру того, насколько наши прогнозы далеки от реальных данных.

Подготовка к обучению нейронной сети

Перед обучением нейронной сети, должна быть сделана некоторая подготовка. Нейронные сети не так просто обучать и настраивать.

В качестве первого шага мы рассмотрим предварительную обработку данных.
Хорошей практикой является нормализация ваших данных перед обучением нейронной сети. Я не могу особо подчеркнуть, насколько важен этот шаг: в зависимости от вашего набора данных, избегание нормализации может привести к бесполезным результатам или к очень трудному процессу обучения (в большинстве случаев алгоритм не сходится до достижения количества разрешенных максимальных итераций). Вы можете выбрать различные методы масштабирования данных (z-нормализация, минимальное-максимальное масштабирование и т. д.). Я решил использовать метод min-max и масштабировать данные в интервале [0,1]. Обычно масштабирование в интервалах [0,1] или [-1,1] имеет тенденцию давать лучшие результаты.

Поэтому мы масштабируем и разделяем данные, прежде чем двигаться дальше:

maxs <- apply(data, 2, max) 
mins <- apply(data, 2, min)
scaled <- as.data.frame(scale(data, center = mins, scale = maxs - mins))
train_ <- scaled[index,]
test_ <- scaled[-index,]

Обратите внимание, что scale возвращает матрицу, которая должна быть приведена к data.frame.

Параметры

Насколько я знаю, не существует фиксированного правила относительно количества слоев и нейронов, хотя есть несколько более или менее принятых практических правил. Обычно, если это вообще необходимо, одного скрытого слоя достаточно для огромного числа приложений. Что касается количества нейронов, оно должно быть между размером входного слоя и размером выходного слоя, обычно 2/3 от входного размера. По крайней мере, в моем кратком опыте тестирование снова и снова является лучшим решением, поскольку нет никакой гарантии, что какое-либо из этих правил будет наилучшим образом соответствовать вашей модели.
Так как это игрушечный пример, мы собираемся использовать 2 скрытых слоя с такой конфигурацией: 13: 5: 3: 1. Входной слой имеет 13 входов, два скрытых слоя имеют 5 и 3 нейрона, а выходной слой, конечно, имеет один выход, так как мы делаем регрессию.
Давайте обучим сеть:

library(neuralnet)
n <- names(train_)
f <- as.formula(paste("medv ~", paste(n[!n %in% "medv"], collapse = " + ")))
nn <- neuralnet(f,data=train_,hidden=c(5,3),linear.output=T)
Пара замечаний:

По какой-то причине формула у ~. не принимается в функции neuralnet(). Сначала необходимо написать формулу, а затем передать ее в качестве аргумента в функции обучения.
Аргумент hidden принимает вектор с числом нейронов для каждого скрытого слоя, в то время как аргумент linear.output используется для указания, хотим ли мы сделать регрессию (linear.output = TRUE) или классификацию (linear.output = FALSE)

Пакет neuralnet предоставляет хороший инструмент для построения визуализации модели:

plot(nn)

Это графическое представление модели с весами на каждом соединении:




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

Прогнозирование Medv с использованием нейронной сети

Теперь мы можем попытаться предсказать значения для тестового набора и вычислить MSE. Помните, что сеть будет выдавать нормализованный прогноз, поэтому нам нужно масштабировать его, чтобы провести значимое сравнение (или простое прогнозирование).

pr.nn <- compute(nn,test_[,1:13])
pr.nn_ <- pr.nn$net.result*(max(data$medv)-min(data$medv))+min(data$medv)
test.r <- (test_$medv)*(max(data$medv)-min(data$medv))+min(data$medv)
MSE.nn <- sum((test.r - pr.nn_)^2)/nrow(test_)

Затем мы сравниваем две MSE

print(paste(MSE.lm,MSE.nn))
"21.6297593507225 10.1542277747038"
Очевидно, что сеть делает лучшую работу, чем линейная модель при прогнозировании medv. Еще раз, будьте осторожны, потому что этот результат зависит от разделения данных на обучающую и тестовую выборки, выполненного выше. Ниже, после графика, мы собираемся выполнить быструю перекрестную проверку, чтобы быть более уверенными в результатах.
Первый визуальный подход к производительности сети и линейной модели на тестовом наборе представлен ниже.

par(mfrow=c(1,2))
plot(test$medv,pr.nn_,col='red',main='Real vs predicted NN',pch=18,cex=0.7)
abline(0,1,lwd=2)
legend('bottomright',legend='NN',pch=18,col='red', bty='n')
plot(test$medv,pr.lm,col='blue',main='Real vs predicted lm',pch=18, cex=0.7)
abline(0,1,lwd=2)
legend('bottomright',legend='LM',pch=18,col='blue', bty='n', cex=.95)

График с выходными данными:




Визуально оценивая график, мы можем видеть, что прогнозы, сделанные нейронной сетью (в общем) более сконцентрированы вокруг линии (идеальное выравнивание с линией будет означать MSE 0 и, следовательно, идеальное предсказание), чем те, которые сделаны линейной моделью.

plot(test$medv,pr.nn_,col='red',main='Real vs predicted NN',pch=18,cex=0.7)
points(test$medv,pr.lm,col='blue',pch=18,cex=0.7)
abline(0,1,lwd=2)
legend('bottomright',legend=c('NN','LM'),pch=18,col=c('red','blue'))

Возможно, более полезное визуальное сравнение приведено ниже:




(Быстрая) перекрестная проверка

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

Разбиение данных на обучающую и тестовую выборки.
Обучение модели под тестовую выборку.
Тестирование модели на тестовой выборке.
Расчет ошибку прогноза.
Повторение процесса K раз.

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

Мы собираемся реализовать быструю перекрестную проверку с использованием цикла for для нейронной сети и функции cv.glm() в пакете boot  для линейной модели.
Насколько я знаю, в R нет встроенной функции для перекрестной проверки в этом типе нейронной сети. Если вы знаете такую функцию, пожалуйста, дайте мне знать в комментариях. Вот 10-кратная перекрестная проверка MSE для линейной модели:

library(boot)
set.seed(200)
lm.fit <- glm(medv~.,data=data)
cv.glm(data,lm.fit,K=10)$delta[1]
23.83560156

Теперь сеть. Обратите внимание, что я делю данные следующим образом: 90% для обучающей выборки и 10% для тестовой выборки случайным образом для 10 раз. Я также инициализирую индикатор выполнения, используя библиотеку plyr, потому что я хочу следить за состоянием процесса, так как настройка нейронной сети может занять некоторое время.

set.seed(450)
cv.error <- NULL
k <- 10
library(plyr) 
pbar <- create_progress_bar('text')
pbar$init(k)
for(i in 1:k){
    index <- sample(1:nrow(data),round(0.9*nrow(data)))
    train.cv <- scaled[index,]
    test.cv <- scaled[-index,]
    nn <- neuralnet(f,data=train.cv,hidden=c(5,2),linear.output=T)   
    pr.nn <- compute(nn,test.cv[,1:13])
    pr.nn <- pr.nn$net.result*(max(data$medv)-min(data$medv))+min(data$medv)   
    test.cv.r <- (test.cv$medv)*(max(data$medv)-min(data$medv))+min(data$medv)   
    cv.error[i] <- sum((test.cv.r - pr.nn)^2)/nrow(test.cv)    
    pbar$step()
}

Через некоторое время процесс завершен, мы рассчитываем среднее значение MSE и выводим результаты в виде коробочного графика.

mean(cv.error)
cv.error
10.32697995
17.640652805  6.310575067 15.769518577  5.730130820 10.520947119  6.121160840
6.389967211  8.004786424 17.369282494  9.412778105

Код для графика:

boxplot(cv.error,xlab='MSE CV',col='cyan',
        border='blue',names='CV error (MSE)',
        main='CV error (MSE) for NN',horizontal=TRUE)


Как вы можете видеть, среднее значение MSE для нейронной сети (10,33) ниже, чем у линейной модели, хотя, по-видимому, существует некоторая степень изменения MSE при перекрестной проверке. Это может зависеть от разделения данных или случайной инициализации весов в сети. Запустив моделирование в разное время с разными начальными значениями, вы можете получить более точную точную оценку для среднего MSE.

Последнее замечание об интерпретируемости модели

Нейронные сети во многом напоминают черные ящики: объяснить их результат гораздо сложнее, чем объяснить результат более простой модели, такой как линейная модель. Поэтому, в зависимости от того, какое приложение вам нужно, вы можете принять во внимание этот фактор. Кроме того, как вы видели выше, при обучении нейронной сети требуется дополнительная осторожность, и небольшие изменения могут привести к различным результатам.

1 комментарий:

  1. Спасибо, очень интересно! Как чайник, пытаюсь освоить на примере с похожим набором данных: четыре (численных) входных значения a, b, c, d, уже нормализованные (-1, 1) и одно (численное) выходное. По идее, должна получиться НС с двумя скрытыми слоями с тремя и двумя нейронами, буду пробовать.

    ОтветитьУдалить