Искуственные Нейронные сети на R

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

Искусственная нейронная сеть состоит из искусственных нейронов или элементов обработки и организована в три взаимосвязанных слоя: вход, скрытый, который может содержать более одного уровня, и выход.

Искусственная нейронная сеть (Artificial neural network) делится на четыре вида:

Введение в искуственные нейронные сети

Нейронные сети ANN Широкий класс моделей, изначально создавались для моделирования работы мозга Существует много видов нейронных сетей. Они различаются в зависимости от
- Решаемой задачи регрессия/прогнозирование, классификация, кластеризация
- архитектуры модели
- алгоритма обучения сети

Немного биологий • Дендриты – получают информацию
• Тело клетки – обрабатывает информацию
• Аксон – передает обработанную информацию другим нейронам
• Синапсы – соединяют аксон и дендриты других нейронов

• X1 X2 … Xp от других нейронов или от рецепторов
• Входы усиливаются или ослабляются умножением на веса w
• I = линейная комбинация всех входов
• Активационная функция f преобразует сумму в выходной сигнал
• Выходной сигнал поступает к другим нейронам или является выходом нейронной сети

Некоторые активационные функций:

Нейронные сети имеют различные слой, так как работа нейронов организована послойно * Входной слой - каждый нейрон имеет ровно один вход от внешней среды * Скрытый слой - входы — от каждого нейрона предыдущего слоя * Выходной слой - Выход каждого нейрона является реакцией сети

Входной слой и выходной слой обычно не считают так они являются передатчиками переменных, а где происходит вся магия так это в скрытом слое. Слоёв может быть 0, 1 ну или больше. Важно запомнить нейроны передают информацию только следующим слоям

Выбор модели ANN зависит от задачи ну и от аналитика, вот что стоит учитывать при выборе • входные нейроны • скрытые слоя • нейроны в каждом скрытом слое • выходные нейроны • активационная функция каждого нейрона • ВЕСА каждого соединения Обучение нейронной сети = определение значений весов. Остальные параметры задаются аналитиком заранее.

Давайте расмотрим как работают нейронные сети внутри “чёрного ящика”"

Метод обратного распространения (Back Propagation)

Метод обратного распространения (Back Propagation) задает поправки весов. Обучение нейронной сети пройсходит за счёт back propagation. Сначала мы проводим данные через нейроннурю сеть со случайными весами, затем делаем back propagation и корректируем веса, так что бы ошибка уменьшилась. Это выглядит примерно вот так: • Подадим на вход сети 1 наблюдение. Подправим веса так, чтобы ошибка 2 наблюдения уменьшилась • Продолжаем так до последнего наблюдения • Первый цикл обучения закончен. • Выполняем эти циклы, пока общая ошибка Е не станет маленькой.

Формула для изменения весов по методу обратного распространения E(W)= sum(Sigma)???[Yi – Vi(W)]^2

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

Есть проблема в нейронных сетях, это когда сеть запоминает обучающую выборку - это называется Переподгонка (over fitting)

Раньше когда пользовались одним нейроном для решения задач, была задача которую долгое время не могли решить одним нейроном. Были очень спорные времена, давайте рассмотрим эту самую задачу:

Классификация посредством нейронных сетей Задача “Исключающее или”

#  Данные
x1 <- c(0,0,1,1)
x2 <- c(0,1,0,1)
y <-  c(1,0,0,1)
z <- cbind(x1,x2,y)

Подгрузим библиотечку neuralnet

library(neuralnet)

Давайте поставим зерно случайных чисел и постройм модель в r

#  Зерно датчика случайных чисел
i <- 12341
i <- i+1
set.seed(i)

# Модель нейронных сетей
nn <- neuralnet(y ~ x1 +x2, data=z ,hidden = 2, 
                linear.output=F)

Делаем прогноз

res.z <- compute(nn, z[, 1:2])

y.pred <- rep(-9999, 4)
y.pred[res.z$net.result[1:4] > 0.8] <- 1
y.pred[res.z$net.result[1:4] <0.2] <- 0

sum(y.pred != y)
## [1] 4

И строим граффик нейронной сети

#  график нейронной сети
plot(nn)

А тут можно посмотреть веса в нейронных сетях

nn$weights
## [[1]]
## [[1]][[1]]
##               [,1]         [,2]
## [1,]  0.9613744085 -1.639694324
## [2,] -0.5866447765  0.699290771
## [3,] -0.1536362394 -1.235043457
## 
## [[1]][[2]]
##              [,1]
## [1,]  1.347678884
## [2,] -2.041921267
## [3,] -0.298992306

Давайте рассмотрим пример нейронной сети с библиотекой “nnet”

library(nnet)

Давайте посмотрим на данные (это встроенные данные в R)

Ежемесячные суммы пассажиров международных авиакомпаний, 1949-1960 годы

data(AirPassengers)
plot(AirPassengers)

Класс таблицы не подходит для анализа, nnet не работает с объектами класса ts

class(AirPassengers)
## [1] "ts"

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

g.series <- log(as.numeric(AirPassengers))

#  Число наблюдений ряда
n.obs <- length(g.series)

#  График ряда после преобразования
plot(1:n.obs, g.series, type = "l")

Ряд сезонный, потому у нейронной сети будет 12 входов 12 первых значений в строке - входные значения, 13-ое - выходное. Будем прогнозировать на 1 наблюдение вперёд

g.2 <- matrix(rep(0, 132*13), nrow = 132, ncol = 13)

# Заполняю матрицу данных отрезками ряда
for (i in 1:132)
{
  g.2[i, ] <- g.series[i:(12 + i)]
}
# зерно датчика случайных чисел
set.seed(12345)

Обучаем нейронную сеть linout = TRUE иначе выходные значения сети будут между 0 и 1 maxit = 1000, так как иногда 100 итераций не хватает

g.net <- nnet(g.2[, 1:12], g.2[, 13], size = 6, 
              linout = TRUE, rang=0.1, decay=0.001, maxit = 1000)
## # weights:  85
## initial  value 4421.801190 
## iter  10 value 21.218283
## iter  20 value 18.437123
## iter  30 value 1.706173
## iter  40 value 0.816669
## iter  50 value 0.607487
## iter  60 value 0.505726
## iter  70 value 0.476954
## iter  80 value 0.437362
## iter  90 value 0.408508
## iter 100 value 0.390039
## iter 110 value 0.369740
## iter 120 value 0.317412
## iter 130 value 0.300061
## iter 140 value 0.280406
## iter 150 value 0.270608
## iter 160 value 0.258549
## iter 170 value 0.247354
## iter 180 value 0.241215
## iter 190 value 0.239779
## iter 200 value 0.237079
## iter 210 value 0.234157
## iter 220 value 0.233405
## iter 230 value 0.232629
## iter 240 value 0.232160
## iter 250 value 0.231809
## iter 260 value 0.231582
## iter 270 value 0.231388
## iter 280 value 0.231197
## iter 290 value 0.231035
## iter 300 value 0.230793
## iter 310 value 0.230138
## iter 320 value 0.229342
## iter 330 value 0.228658
## iter 340 value 0.228195
## iter 350 value 0.227791
## iter 360 value 0.227649
## iter 370 value 0.227262
## iter 380 value 0.226685
## iter 390 value 0.226272
## iter 400 value 0.225948
## iter 410 value 0.225706
## iter 420 value 0.225550
## iter 430 value 0.225378
## iter 440 value 0.225283
## iter 450 value 0.225211
## iter 460 value 0.225174
## iter 470 value 0.225150
## iter 480 value 0.225135
## iter 490 value 0.225125
## iter 500 value 0.225119
## iter 510 value 0.225115
## iter 520 value 0.225112
## iter 530 value 0.225109
## iter 540 value 0.225027
## iter 550 value 0.225016
## iter 560 value 0.225005
## iter 570 value 0.224999
## iter 580 value 0.224997
## iter 590 value 0.224996
## iter 600 value 0.224995
## iter 610 value 0.224994
## iter 620 value 0.224994
## iter 630 value 0.224993
## iter 640 value 0.224993
## final  value 0.224993 
## converged

Сравниваем подгонку ряда (красный цвет) и исходный ряд (черный цвет)

plot(1:144, g.series, type = "l")
lines(13:144, g.net$fitted.values, col = "red")

Начинаем вычислять прогнозируемые значения Первый вектор входов == последние 12 наблюдений ряда

g.forecast <- g.2[nrow(g.2), -1] 

#  Горизонт прогноза - 12, по условию
pred.n <- 12

#  Вектор спрогнозированных значений
pred.1 <- rep(-9999, pred.n)

for (i in 1:pred.n)
{
  pred.1[i] <- predict(g.net, g.forecast, type = "raw")
  g.forecast <- c( g.forecast[-1], pred.1[i])
}

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

plot(1:144, exp(g.series), type = "l", xlim = c(0, 144+pred.n), ylim = c(100, 700))
lines(13:144, exp(g.net$fitted.values), col = "red")
lines((144+1):(144+pred.n), exp(pred.1), col = "blue")

Мы расмотрели на примере 2 библиотеки для реализаций Standart NN - neuralnet, nnet

Давайте рассмотрим другие библиотеки и разберём параметры этих библиотек подробнее

Подгрузим библиотечки

library(ggplot2)
library(neuralnet)
library(caret)
## Warning: package 'caret' was built under R version 3.4.4

Загрузим данные и удалим факторные значения

data("diamonds")
head(diamonds)
## # A tibble: 6 x 10
##   carat cut       color clarity depth table price     x     y     z
##   <dbl> <ord>     <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.230 Ideal     E     SI2      61.5  55.0   326  3.95  3.98  2.43
## 2 0.210 Premium   E     SI1      59.8  61.0   326  3.89  3.84  2.31
## 3 0.230 Good      E     VS1      56.9  65.0   327  4.05  4.07  2.31
## 4 0.290 Premium   I     VS2      62.4  58.0   334  4.20  4.23  2.63
## 5 0.310 Good      J     SI2      63.3  58.0   335  4.34  4.35  2.75
## 6 0.240 Very Good J     VVS2     62.8  57.0   336  3.94  3.96  2.48
diamonds$cut <- NULL
diamonds$color <- NULL
diamonds$clarity <- NULL

Данные слишком большие нейронная сеть будет долгоооо думать и комп зависнет. поэтому возьмем 3% данных

ubrat <- createDataPartition(diamonds$price, p = 0.03, list = F)
diamonds <- diamonds[ubrat,]

Нормализуем данные, обязательное условие нейронной сети данные должны быть от 0 до 1

samplesize = 0.70*nrow(diamonds)
set.seed(80)
index = base::sample(seq_len(nrow(diamonds)), size = samplesize )
datatrain = diamonds[ index, ]
datatest = diamonds[ -index, ]
max = apply(diamonds, 2 , max)
min = apply(diamonds, 2 , min)

scaled = as.data.frame(scale(diamonds, center = min, scale = max - min))

Создадим два датасета для нейронной сети

trainNN = scaled[index , ]
testNN = scaled[-index , ]
colnames(trainNN)
## [1] "carat" "depth" "table" "price" "x"     "y"     "z"

И наконец постройм нейронную сеть

NN = neuralnet(price~carat+depth+table+x+y+z, trainNN, hidden = c(5,3) , linear.output = F )
plot(NN)

Наконец долшли до предсказания

predict_testNN = compute(NN, testNN[,c(2:7)])
predict_testNN = (predict_testNN$net.result * (max(diamonds$price) - min(diamonds$price))) + min(diamonds$price)

Постройм график предсказанными значениями и фактическими и вытащим root mean squad error

plot(datatest$price, predict_testNN, col='blue', pch=16, ylab = "predicted NN", xlab = "real")
abline(0,1)

RMSE.NN = (sum((datatest$price - predict_testNN)^2) / nrow(datatest)) ^ 0.5
RMSE.NN
## [1] 2435.979904
neuralnet(formula, data, hidden = 1, threshold = 0.01, stepmax = 1e+05, rep = 1, startweights = NULL, learningrate.limit = NULL, learningrate.factor = list(minus = 0.5, plus = 1.2), learningrate=NULL, lifesign = "none", lifesign.step = 1000, algorithm = "rprop+", err.fct = "sse", act.fct = "logistic", linear.output = TRUE, exclude = NULL, constant.weights = NULL, likelihood = FALSE)

Параметры neuralnet:

Библиотека neuralnet позволяет делать множество внутренних слоёв, а библиотека nnet позволяет делать только один скрытый слой

nnet(x, y, weights, size, Wts, mask, linout = FALSE, entropy = FALSE, softmax = FALSE, censored = FALSE, skip = FALSE, rang = 0.7, decay = 0, maxit = 100, Hess = FALSE, trace = TRUE, MaxNWts = 1000, abstol = 1.0e-4, reltol = 1.0e-8, ...)

Параметры nnet:

Пример решения Titanic competition с помощью neuralnet и nnet

Подгрузим данные

library(neuralnet)
library(nnet)
library(ROSE)

df <- read.csv("~/data/Titanic/train.csv")
test <- read.csv("~/data/Titanic/test.csv")
subm <- read.csv("~/data/Titanic/gender_submission.csv")

# Удаляем ненужные колонки
df$PassengerId <- NULL
df$Name <- NULL
df$Ticket <- NULL
df$Cabin <- NULL

# И в тесте тоже
test$PassengerId <- NULL
test$Name <- NULL
test$Ticket <- NULL
test$Cabin <- NULL

# Заменяем пропущенные значения на медиану
df$Age <- ifelse(is.na(df$Age),median(df$Age,na.rm = TRUE),df$Age)

Делаем нормализацию данных что бы нейронная сеть хорошо читала данные :)

# делим данные на 70%
samplesize = 0.70*nrow(df)
set.seed(80) # сеем семя

# переводим в эс.нумерик
df$Embarked <- as.numeric(df$Embarked)
df$Sex <- as.numeric(df$Sex)

test$Embarked <- as.numeric(test$Embarked)
test$Sex <- as.numeric(test$Sex)

index = base::sample(seq_len(nrow(df)), size = samplesize )
datatrain = df
datatest = test
max = apply(df , 2 , max)
min = apply(df, 2 , min)

# скалируем данные
scaled = as.data.frame(scale(df,
                             center = min,
                             scale = max - min))

Подробнее про скалирование Scaling and Centering of Matrix-like Objects (Масштабирование и центрирование матричных объектов) - scale - это общая функция, метод по умолчанию которой центрирует и / или масштабирует столбцы числовой матрицы

Давайте попробуем сделать модель с помощью библиотеки neuralnet

# neuralnet
trainNN = scaled[index , ]
testNN = scaled[-index , ]

NN = neuralnet(Survived~Pclass+Sex+Age+SibSp+Parch+Fare+Embarked, trainNN, hidden = 4, linear.output = F)

plot(NN)

# Делаем predict
predict_testNN = neuralnet::compute(NN, testNN[,c(2:8)])

# чтобы заново нормализовать данные
predict_testNN = (predict_testNN$net.result * (max(df$Survived) - min(df$Survived))) + min(df$Survived)

# Смотрим RMSE (root squad mean error)
RMSE.NN = (sum((testNN$Survived - predict_testNN)^2) / nrow(testNN)) ^ 0.5
RMSE.NN
## [1] 0.3966820739
# и смотрим roc.curve
roc.curve(testNN$Survived,predict_testNN)
## Area under the curve (AUC): 0.801

Теперь давайте попробуем сделать модель с помощью библиотеки nnet

# nnet
tit.class <- class.ind(factor(trainNN[, 1]))

g.net <- nnet(trainNN, tit.class, size = 4, linout = TRUE, rang=0.1, decay=0.001, maxit = 1000)
## # weights:  46
## initial  value 743.364576 
## iter  10 value 100.001132
## iter  20 value 0.271174
## iter  30 value 0.207477
## iter  40 value 0.179151
## iter  50 value 0.152756
## iter  60 value 0.110144
## iter  70 value 0.090045
## iter  80 value 0.072690
## iter  90 value 0.061709
## iter 100 value 0.055012
## iter 110 value 0.043034
## iter 120 value 0.028745
## iter 130 value 0.023004
## iter 140 value 0.019471
## iter 150 value 0.017579
## iter 160 value 0.016843
## iter 170 value 0.015953
## iter 180 value 0.015302
## iter 190 value 0.015043
## iter 200 value 0.014865
## iter 210 value 0.014408
## iter 220 value 0.013971
## iter 230 value 0.013889
## iter 240 value 0.013841
## iter 250 value 0.013760
## iter 260 value 0.013628
## iter 270 value 0.013563
## iter 280 value 0.013491
## iter 290 value 0.013471
## iter 300 value 0.013209
## iter 310 value 0.012983
## iter 320 value 0.012946
## iter 330 value 0.012890
## iter 340 value 0.012865
## iter 350 value 0.012827
## iter 360 value 0.012814
## iter 370 value 0.012772
## iter 380 value 0.012760
## iter 390 value 0.012713
## iter 400 value 0.012661
## iter 410 value 0.012638
## iter 420 value 0.012621
## iter 430 value 0.012606
## iter 440 value 0.012587
## iter 450 value 0.012579
## iter 460 value 0.012567
## iter 470 value 0.012562
## iter 480 value 0.012559
## iter 490 value 0.012542
## iter 500 value 0.012539
## iter 510 value 0.012531
## iter 520 value 0.012525
## iter 530 value 0.012520
## iter 540 value 0.012518
## iter 550 value 0.012514
## iter 560 value 0.012513
## iter 570 value 0.012513
## iter 580 value 0.012511
## iter 590 value 0.012510
## iter 600 value 0.012510
## iter 610 value 0.012509
## iter 620 value 0.012508
## iter 630 value 0.012508
## iter 640 value 0.012508
## iter 650 value 0.012508
## final  value 0.012508 
## converged
#Готовлю вектор с результатами
a.2 <- rep(0, nrow(g.net$fitted.values))
for(i in 1:nrow(g.net$fitted.values)){
  a.2[i]<- which.max(g.net$fitted.values[i, ])
}
#Таблица сопряженности, можно посмотреть сколько неправильно предсказанных значений
table(a.2, trainNN[,1])
##    
## a.2   0   1
##   1 375   0
##   2   0 248
# Видим что нейронная сеть предсказала все правильно

# так как nnet работает с матрицей, нужно заново все перевести в нормальный результат
nnet_pred <- as.data.frame(predict(g.net, testNN))
nnet_pred$Survived <- ifelse(nnet_pred$`0`>0.6, 1, 0)
nnet_pred$`0` <- NULL
nnet_pred$`1` <- NULL

# и смотрим ошибки, ошибок нету очень странно
RMSE.NN = (sum((testNN$Survived - nnet_pred)^2) / nrow(testNN)) ^ 0.5
RMSE.NN
## [1] 1
roc.curve(testNN$Survived, nnet_pred$Survived)

## Area under the curve (AUC): 1.000

Тут ниже делаем туже самую нормализацию для тестовых данных и вставляем в сабмит и дальше сабмитим резульат

max = apply(datatest , 2 , max)
min = apply(datatest, 2 , min)

scaled = as.data.frame(scale(datatest,
                             center = min,
                             scale = max - min))
test_nn <- scaled

predict_testNN = neuralnet::compute(NN, test_nn)
subm <- read.csv("~/data/Titanic/gender_submission.csv")

subm$Survived <- predict_testNN$net.result

subm$Survived <- ifelse(subm$Survived>0.5, 1, 0)

write.csv(subm, "subm_nn.csv", row.names = F)