Наука о данных в R

Распознавание жестов рук

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

Американский язык жестов (ASL) - это законченный естественный язык, который имеет те же лингвистические свойства, что и разговорные языки, с грамматикой, отличной от английского. ASL выражается движениями рук и лица. Это основной язык многих глухих и слабослышащих североамериканцев, но он также используется многими слышащими людьми.

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

Библиотека

Помимо повторяющихся библиотек, которые мы обычно используем в R, мы также будем использовать keras.

Keras - это высокоуровневый API нейронных сетей, разработанный с упором на обеспечение возможности быстрого экспериментирования. Возможность переходить от идеи к результату с минимально возможной задержкой - ключ к проведению хороших исследований.

library(keras)          # neural network
library(dplyr)          # data wrangling
library(caret)          # machine learning functions

Набор данных

Давайте прочитаем набор данных. Этот набор данных доступен на Kaggle.

train <- read.csv('sign_mnist_train.csv')
test <- read.csv('sign_mnist_test.csv')

Формат набора данных подобран в соответствии с классическим MNIST. Обучающие данные (27 455 случаев) и тестовые данные (7 172 случая) составляют примерно половину размера стандартного MNIST, но в остальном похожи со строкой заголовка label, pixel1, pixel2,…, pixel784, которые представляют собой одно изображение размером 28 × 28 пикселей с значения оттенков серого от 0 до 255.

dim(train)
#> [1] 27455   785
dim(test)
#> [1] 7172  785

Каждый обучающий и контрольный пример представляет собой метку (0–25) как взаимно однозначную карту для каждой буквенной буквы A – Z, и нет случаев для 9 = J или 25 = Z, потому что язык жестов для этих букв включает жесты. .

sort(unique(train$label))
#>  [1]  0  1  2  3  4  5  6  7  8 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
sort(unique(test$label))
#>  [1]  0  1  2  3  4  5  6  7  8 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

EDA и предварительная обработка

Во-первых, нам нужно подкорректировать этикетку. Поскольку метки 9 и 25 отсутствуют, мы можем вычесть на 1 все метки больше 9. Таким образом, наша метка станет всеми целыми числами от 0 до 24.

train[train$label > 9, 'label'] <- train[train$label > 9, 'label'] - 1
test[test$label > 9, 'label'] <- test[test$label > 9, 'label'] - 1

Не повредит увидеть, как выглядит наш набор данных. Давайте посмотрим на первые 32 фотографии с train.

vizTrain <- function(input) {
  dimmax <- sqrt(ncol(train[, -1]))
  cols <- 8
  rows <- floor((nrow(input) - 1) / cols) + 1
  par(mfrow = c(rows, cols), mar = c(0.1, 0.1, 0.1, 0.1))
  for (i in 1:nrow(input)) {
    m1 <- matrix(input[i, 2:ncol(input)], nrow = dimmax, byrow = T)
    m1 <- apply(m1, 2, as.numeric)
    m1 <- t(apply(m1, 2, rev))
    image(1:dimmax, 1:dimmax, m1, col = grey.colors(255), xaxt = "n", yaxt = "n")
    text(3, 26, col = "black", cex = 1.2, train[i, 1])
  }
}

vizTrain(train[1:32, ])

Мы выполняем нормализацию оттенков серого, чтобы уменьшить влияние различий в освещении. Более того, модели NN, которые мы будем использовать, сходятся быстрее на [0..1] данных, чем на [0..255]. Для этого просто разделите каждое значение пикселя на 255. Мы также отделяем предиктор и цель от train и test набора данных одновременно, в результате получаем train_x, test_x, train_y, test_y.

train_x <- train %>% 
  select(-label) %>%
  data.matrix()/255
  
test_x <- test %>% 
  select(-label) %>%
  data.matrix()/255

train_y <- train %>% 
  select(label)

test_y <- test %>% 
  select(label)

Модели NN не распознают категориальные особенности. По этой причине нам необходимо выполнить быстрое кодирование меток train_y и test_y. По сути, горячая кодировка генерирует столбцы из единиц и нулей для каждой категории. Таким образом, в нашем случае результатом будет матрица с 24 столбцами, в которой каждая строка будет иметь нулевые значения, за исключением одной ячейки, которая имеет значение 1. Столбец, в котором встречается это значение 1, соответствует метке, которую представляет этот столбец. Например, первые шесть наблюдений в train_y - это метки 3, 6, 2, 2, 12 и 15, как показано в следующей таблице.

train_y_keras <- train_y %>% 
  data.matrix() %>% 
  to_categorical(num_classes = 24)

head(train_y_keras)
#>      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] [,13] [,14] [,15] [,16] [,17] [,18] [,19] [,20] [,21] [,22] [,23]
#> [1,]    0    0    0    1    0    0    0    0    0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#> [2,]    0    0    0    0    0    0    1    0    0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#> [3,]    0    0    1    0    0    0    0    0    0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#> [4,]    0    0    1    0    0    0    0    0    0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#> [5,]    0    0    0    0    0    0    0    0    0     0     0     0     1     0     0     0     0     0     0     0     0     0     0
#> [6,]    0    0    0    0    0    0    0    0    0     0     0     0     0     0     0     1     0     0     0     0     0     0     0
#>      [,24]
#> [1,]     0
#> [2,]     0
#> [3,]     0
#> [4,]     0
#> [5,]     0
#> [6,]     0

То же самое делаем с test_y.

test_y_keras <- test_y %>% 
  data.matrix() %>% 
  to_categorical(num_classes = 24)

head(test_y_keras)
#>      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] [,13] [,14] [,15] [,16] [,17] [,18] [,19] [,20] [,21] [,22] [,23]
#> [1,]    0    0    0    0    0    0    1    0    0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#> [2,]    0    0    0    0    0    1    0    0    0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#> [3,]    0    0    0    0    0    0    0    0    0     1     0     0     0     0     0     0     0     0     0     0     0     0     0
#> [4,]    1    0    0    0    0    0    0    0    0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#> [5,]    0    0    0    1    0    0    0    0    0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
#> [6,]    0    0    0    0    0    0    0    0    0     0     0     0     0     0     0     0     0     0     0     0     1     0     0
#>      [,24]
#> [1,]     0
#> [2,]     0
#> [3,]     0
#> [4,]     0
#> [5,]     0
#> [6,]     0

Метрика и проверка

Поскольку мы не предпочитаем один класс другим и все классы сбалансированы, как показано ниже, мы просто будем использовать точность в качестве нашей метрики. Кроме того, поскольку алгоритм классификации изображений NN требует времени для обучения, мы хотим, чтобы проверка модели была как можно более простой. В этом сценарии мы просто будем использовать отдельный test набор данных для проверки модели.

ggplot(train %>% 
         group_by(label) %>% 
         count(name = 'observation_count'), 
       aes(x = label, y = observation_count)) + 
  geom_bar(stat = 'identity') + 
  ggtitle("Number of Observations among Labels")

Моделирование

Плотный с 2 ​​скрытыми слоями

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

train_x_keras <- train_x %>% 
  array_reshape(dim = dim(train_x))

test_x_keras <-  test_x %>% 
  array_reshape(dim = dim(test_x))

Затем постройте модель. А пока создадим архитектуру следующим образом.

  1. Входной уровень: 784 узла
  2. Скрытый уровень 1: 128 узлов, функция активации relu
  3. Скрытый уровень 2: 64 узла, функция активации relu
  4. Выходной уровень: 24 узла, функция активации softmax

Мы используем 784 узла во входном слое, поскольку в каждом изображении в наборе данных всего 784 пикселя. Функция активации Relu выбирается в скрытых слоях, поскольку она подходит для данных изображения (значение каждого пикселя и узла положительно между 0 и 1). Функция активации softmax выбирается в выходном слое, поскольку это проблема многоклассовой классификации.

tensorflow::tf$random$set_seed(42)

model_2hidden <- keras_model_sequential()
model_2hidden %>% 
  layer_dense(input_shape = ncol(train_x_keras),
              units = 128,
              activation = "relu",
              name = "hidden1") %>% 
  layer_dense(units = 64,
              activation = "relu",
              name = "hidden2") %>%
  layer_dense(units = 24,
              activation = "softmax",
              name = "output")
  
summary(model_2hidden)
#> Model: "sequential"
#> _________________________________________________________________
#> Layer (type)                                                  Output Shape                                           Param #              
#> =================================================================
#> hidden1 (Dense)                                               (None, 128)                                            100480               
#> _________________________________________________________________
#> hidden2 (Dense)                                               (None, 64)                                             8256                 
#> _________________________________________________________________
#> output (Dense)                                                (None, 24)                                             1560                 
#> =================================================================
#> Total params: 110,296
#> Trainable params: 110,296
#> Non-trainable params: 0
#> _________________________________________________________________

Скомпилируйте модель. Используйте optimizer_adam в качестве оптимизатора со скоростью обучения по умолчанию 0,001, categorical_crossentropy в качестве функции потерь, поскольку это проблема классификации нескольких классов, и accuracy в качестве показателей, как обсуждалось ранее.

model_2hidden %>% 
  compile(optimizer = optimizer_adam(lr=0.001),
          loss = "categorical_crossentropy",
          metrics = "accuracy")

Затем обучите модель. Мы будем обучать модель партиями по 10 эпох по 32 наблюдения в каждой партии.

history <- model_2hidden %>% 
  fit(train_x_keras, 
      train_y_keras, 
      batch_size = 32, 
      epoch = 10,
      validation_data = list(test_x_keras, test_y_keras))
plot(history)

Как мы видим, есть признак переобучения (точность train слишком хороша по сравнению с точностью test).

pred_2hidden <- predict_classes(object = model_2hidden, x = test_x_keras)
confusionMatrix(as.factor(pred_2hidden), as.factor(test_y$label))
#> Confusion Matrix and Statistics
#> 
#>           Reference
#> Prediction   0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23
#>         0  310   0   0   0   0   0   0   0   0   0   0   0  42   0   0   0   0   0   0   0   0   0   0   0
#>         1    0 391   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
#>         2    0   0 289   0   0  16   0   0   0   0  30   0   0  23   0   0   0   0   0   0   0   0   0   0
#>         3    0   0   0 156   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0  17   0   0   0   0
#>         4    0   0   0   0 417   0   0   0   0   0   0  21   0   1   0   0   0  21   0   0   0   0   0   0
#>         5    0   0  21   0   0 226   0   0   0  11   0   0   0   0   0   5   0   0   0   0  20   0   0   0
#>         6    0   0   0   1   0   0 307  26   0   0   0   0  21   1   0   0  20   0   0   0  21   0   0   0
#>         7    0   0   0   0   0   0   0 392   0   0   0   0   0   0   0   0   0  17   1   0   0   0   0   0
#>         8    0   0   0   0   0   0   0   0 246  21   0   0   0   0   0   0   0  24   0   0   0   0   0  19
#>         9    0   0   0   0   0   0   0   0   0 149   0   0   0   0   0   0   0   0   0  44   0   0   0   0
#>         10   0   0   0   3   0   0   0   0   0   0 157   0   0   0   0   0   0   0  15   2   0   0   0  41
#>         11   0   0   0   0   2   0   0  10   0   3   0 297   2   0   0   1   0  61   0   0   0   0   0   3
#>         12  21   0   0  19  16   0  21   0   0   0   0  21 184   0   0  20   0   0   0   0   0   0   0   0
#>         13   0   0   0   0   0   0   0   0   0   0   0   0   0 197   0   0   0   0   5   0   0   0   0   0
#>         14   0   0   0   0   0   0   0   0   0  26   0   0   0   0 344   0   0   0   0   0   0   0   2   0
#>         15   0   0   0   0   0   0  20   0   0   0   0  13  18   1   3 138   0   0   0   0   5   0   0   0
#>         16   0   0   0  14   0   0   0   0   0  42   0   0   0   0   0   0 103   0   0  19   0   0   0   0
#>         17   0   0   0   0  63   0   0   0   0   1   0  42  21   0   0   0   0 118   0   0   0   0  10  41
#>         18   0   0   0   0   0   5   0   0   0   0   0   0   0  23   0   0   0   0 165   0  29   0   0  21
#>         19   0  41   0   0   0   0   0   8   0  55   0   0   0   0   0   0   0   0   0 147  31  20   0   0
#>         20   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0  21   0   0  18 218  49   0   0
#>         21   0   0   0   0   0   0   0   0   0   7   0   0   0   0   0   0   0   0   0   0  21 137  19   0
#>         22   0   0   0  52   0   0   0   0  21   0  22   0   3   0   0   0   0   0  62   2   1   0 236   0
#>         23   0   0   0   0   0   0   0   0  21  16   0   0   0   0   0   0   0   5   0  17   0   0   0 207
#> 
#> Overall Statistics
#>                                                
#>                Accuracy : 0.7712               
#>                  95% CI : (0.7613, 0.7809)     
#>     No Information Rate : 0.0694               
#>     P-Value [Acc > NIR] : < 0.00000000000000022
#>                                                
#>                   Kappa : 0.7606               
#>                                                
#>  Mcnemar's Test P-Value : NA                   
#> 
#> Statistics by Class:
#> 
#>                      Class: 0 Class: 1 Class: 2 Class: 3 Class: 4 Class: 5 Class: 6 Class: 7 Class: 8 Class: 9 Class: 10 Class: 11
#> Sensitivity           0.93656  0.90509  0.93226  0.63673  0.83735  0.91498  0.88218  0.89908  0.85417  0.45015   0.75120   0.75381
#> Specificity           0.99386  1.00000  0.98994  0.99755  0.99356  0.99177  0.98681  0.99733  0.99070  0.99357   0.99124   0.98790
#> Pos Pred Value        0.88068  1.00000  0.80726  0.90173  0.90652  0.79859  0.77330  0.95610  0.79355  0.77202   0.72018   0.78364
#> Neg Pred Value        0.99692  0.99395  0.99692  0.98728  0.98793  0.99695  0.99395  0.99349  0.99388  0.97392   0.99252   0.98572
#> Prevalence            0.04615  0.06023  0.04322  0.03416  0.06944  0.03444  0.04852  0.06079  0.04016  0.04615   0.02914   0.05494
#> Detection Rate        0.04322  0.05452  0.04030  0.02175  0.05814  0.03151  0.04281  0.05466  0.03430  0.02078   0.02189   0.04141
#> Detection Prevalence  0.04908  0.05452  0.04992  0.02412  0.06414  0.03946  0.05535  0.05717  0.04322  0.02691   0.03040   0.05284
#> Balanced Accuracy     0.96521  0.95255  0.96110  0.81714  0.91545  0.95337  0.93450  0.94821  0.92243  0.72186   0.87122   0.87085
#>                      Class: 12 Class: 13 Class: 14 Class: 15 Class: 16 Class: 17 Class: 18 Class: 19 Class: 20 Class: 21 Class: 22
#> Sensitivity            0.63230   0.80081   0.99135   0.84146   0.71528   0.47967   0.66532   0.55263   0.63006   0.66505   0.88390
#> Specificity            0.98285   0.99928   0.99590   0.99144   0.98933   0.97430   0.98873   0.97756   0.98711   0.99325   0.97639
#> Pos Pred Value         0.60927   0.97525   0.92473   0.69697   0.57865   0.39865   0.67901   0.48675   0.71242   0.74457   0.59148
#> Neg Pred Value         0.98443   0.99297   0.99956   0.99627   0.99414   0.98138   0.98802   0.98268   0.98136   0.99013   0.99542
#> Prevalence             0.04057   0.03430   0.04838   0.02287   0.02008   0.03430   0.03458   0.03709   0.04824   0.02872   0.03723
#> Detection Rate         0.02566   0.02747   0.04796   0.01924   0.01436   0.01645   0.02301   0.02050   0.03040   0.01910   0.03291
#> Detection Prevalence   0.04211   0.02817   0.05187   0.02761   0.02482   0.04127   0.03388   0.04211   0.04267   0.02566   0.05563
#> Balanced Accuracy      0.80758   0.90005   0.99363   0.91645   0.85230   0.72699   0.82703   0.76509   0.80858   0.82915   0.93014
#>                      Class: 23
#> Sensitivity            0.62349
#> Specificity            0.99137
#> Pos Pred Value         0.77820
#> Neg Pred Value         0.98190
#> Prevalence             0.04629
#> Detection Rate         0.02886
#> Detection Prevalence   0.03709
#> Balanced Accuracy      0.80743

Анализируя приведенную выше матрицу путаницы, многие метки все еще трудно отличить от других, использующих эту модель. Легче всего предсказать следующие метки: 2 = C и 10 = L (идеально). Это неудивительно, поскольку жесты этих двух букв интуитивно понятны и выделяются среди других алфавитов. Таким образом, точность набора данных поезда составляет 96%, а точность набора тестовых данных - 70%.

result <- data.frame(
  'train_acc' = tail(history$metrics$accuracy, n=1),
  'test_acc' = tail(history$metrics$val_accuracy, n=1), 
  row.names = 'Dense with 2 hidden layers')

result
#>                            train_acc  test_acc
#> Dense with 2 hidden layers 0.9844837 0.7711935

Плотный с 3 скрытыми слоями

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

  1. Входной уровень: 784 узла
  2. Скрытый уровень 1: 512 узлов, функция активации relu
  3. Скрытый уровень 2: 256 узлов, функция активации relu
  4. Скрытый уровень 3: 128 узлов, функция активации relu
  5. Выходной уровень: 24 узла, функция активации softmax

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

tensorflow::tf$random$set_seed(42)

model_3hidden <- keras_model_sequential()
model_3hidden %>% 
  layer_dense(input_shape = ncol(train_x_keras),
              units = 512,
              activation = "relu",
              name = "hidden1") %>% 
  layer_dense(units = 256,
              activation = "relu",
              name = "hidden2") %>%
  layer_dense(units = 128,
              activation = "relu",
              name = "hidden3") %>%
  layer_dense(units = 24,
              activation = "softmax",
              name = "output")
  
summary(model_3hidden)
#> Model: "sequential_1"
#> _________________________________________________________________
#> Layer (type)                                                  Output Shape                                           Param #              
#> =================================================================
#> hidden1 (Dense)                                               (None, 512)                                            401920               
#> _________________________________________________________________
#> hidden2 (Dense)                                               (None, 256)                                            131328               
#> _________________________________________________________________
#> hidden3 (Dense)                                               (None, 128)                                            32896                
#> _________________________________________________________________
#> output (Dense)                                                (None, 24)                                             3096                 
#> =================================================================
#> Total params: 569,240
#> Trainable params: 569,240
#> Non-trainable params: 0
#> _________________________________________________________________

Как и раньше, компилируйте и тренируйте.

model_3hidden %>% 
  compile(optimizer = optimizer_adam(lr=0.001),
          loss = "categorical_crossentropy",
          metrics = "accuracy")

history <- model_3hidden %>% 
  fit(train_x_keras, 
      train_y_keras, 
      batch_size = 32, 
      epoch = 10,
      validation_data = list(test_x_keras, test_y_keras))
plot(history)

Как и ожидалось, модель все еще превышает train набор данных. Но посмотрите, точность test набора данных также увеличивается. Давайте посмотрим подробно на матрице путаницы.

pred_3hidden <- predict_classes(object = model_3hidden, x = test_x_keras)
confusionMatrix(as.factor(pred_3hidden), as.factor(test_y$label))
#> Confusion Matrix and Statistics
#> 
#>           Reference
#> Prediction   0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23
#>         0  331   0   0   0   0   0   0   0   0   0   0   0  42   0   0   0   0   0   0   0   0   0   0   0
#>         1    0 432   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
#>         2    0   0 305   0   0   0   0   0   0   0   6   0   0   0  15   0   0   0   0   0   0   0   0   0
#>         3    0   0   0 214   0   0   0   0   0   0   0   0   0   0   0   0   0  21   0  19   0   0   0   0
#>         4    0   0   0   0 498   0   0   0   0   0   0   0  15   0   0   0   0  21   0   0   0   0   0   0
#>         5    0   0   5   0   0 247   0   0   0   0   0   0   0  21   0   0   0   0   0   0  20   0   0   0
#>         6    0   0   0   0   0   0 285  20   0   0   0   0   0  10   0   0   0   0   0   0   8   0   0   0
#>         7    0   0   0   0   0   0  22 416   0   0   0   0   0   0   4   0   0   0   0   0   0   0   0   0
#>         8    0   0   0   0   0   0   0   0 249  15   0   0   0   0   0   0   0   0   0   0   0   0   0  20
#>         9    0   0   0   0   0   0   0   0   0 169   0   0   0   0   0   0   0   0   0   2  19   0   0   0
#>         10   0   0   0   0   0   0   0   0   0   0 203   0   0   0   0   0   6   0  20   0   0   0   0  37
#>         11   0   0   0   0   0   0   0   0   0   0   0 323  20   0   0   7   0  42   0   0   0   0   0   0
#>         12   0   0   0   0   0   0  21   0   0   0   0  42 172   0   0   0   0   0   0   0   0   0   0   0
#>         13   0   0   0   0   0   0   0   0   0   0   0   0   0 195   0   0   0   0   0   0   0   0   0   0
#>         14   0   0   0   0   0   0   0   0   0   0   0   0   0   0 309   0   0   0   0   0   0   0   0   0
#>         15   0   0   0   0   0   0  20   0   1   0   0   0  26  10  19 157   0   0   0   0   0   0   0   0
#>         16   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0 103   0   0  17   0  23   0   0
#>         17   0   0   0   0   0   0   0   0   0  17   0  29   0   0   0   0   0 162   0   0   0   0   0   0
#>         18   0   0   0   0   0   0   0   0   0   0   0   0  16  10   0   0   0   0 166   0   0   0   0  21
#>         19   0   0   0   0   0   0   0   0   0  83   0   0   0   0   0   0  20   0   0 194  16  31   0   0
#>         20   0   0   0   0   0   0   0   0   0  21   0   0   0   0   0   0  15   0   0  34 242  21   0  19
#>         21   0   0   0   0   0   0   0   0   0  21   0   0   0   0   0   0   0   0   0   0  21 131  63   0
#>         22   0   0   0  31   0   0   0   0  17   5   0   0   0   0   0   0   0   0  62   0  20   0 204   0
#>         23   0   0   0   0   0   0   0   0  21   0   0   0   0   0   0   0   0   0   0   0   0   0   0 235
#> 
#> Overall Statistics
#>                                                
#>                Accuracy : 0.8285               
#>                  95% CI : (0.8196, 0.8372)     
#>     No Information Rate : 0.0694               
#>     P-Value [Acc > NIR] : < 0.00000000000000022
#>                                                
#>                   Kappa : 0.8205               
#>                                                
#>  Mcnemar's Test P-Value : NA                   
#> 
#> Statistics by Class:
#> 
#>                      Class: 0 Class: 1 Class: 2 Class: 3 Class: 4 Class: 5 Class: 6 Class: 7 Class: 8 Class: 9 Class: 10 Class: 11
#> Sensitivity           1.00000  1.00000  0.98387  0.87347  1.00000  1.00000  0.81897  0.95413  0.86458  0.51057   0.97129   0.81980
#> Specificity           0.99386  1.00000  0.99694  0.99423  0.99461  0.99336  0.99443  0.99614  0.99492  0.99693   0.99095   0.98982
#> Pos Pred Value        0.88740  1.00000  0.93558  0.84252  0.93258  0.84300  0.88235  0.94118  0.87676  0.88947   0.76316   0.82398
#> Neg Pred Value        1.00000  1.00000  0.99927  0.99552  1.00000  1.00000  0.99080  0.99703  0.99434  0.97680   0.99913   0.98953
#> Prevalence            0.04615  0.06023  0.04322  0.03416  0.06944  0.03444  0.04852  0.06079  0.04016  0.04615   0.02914   0.05494
#> Detection Rate        0.04615  0.06023  0.04253  0.02984  0.06944  0.03444  0.03974  0.05800  0.03472  0.02356   0.02830   0.04504
#> Detection Prevalence  0.05201  0.06023  0.04545  0.03542  0.07446  0.04085  0.04504  0.06163  0.03960  0.02649   0.03709   0.05466
#> Balanced Accuracy     0.99693  1.00000  0.99041  0.93385  0.99730  0.99668  0.90670  0.97513  0.92975  0.75375   0.98112   0.90481
#>                      Class: 12 Class: 13 Class: 14 Class: 15 Class: 16 Class: 17 Class: 18 Class: 19 Class: 20 Class: 21 Class: 22
#> Sensitivity            0.59107   0.79268   0.89049   0.95732   0.71528   0.65854   0.66935   0.72932   0.69942   0.63592   0.76404
#> Specificity            0.99084   1.00000   1.00000   0.98916   0.99431   0.99336   0.99321   0.97828   0.98389   0.98493   0.98045
#> Pos Pred Value         0.73191   1.00000   1.00000   0.67382   0.72028   0.77885   0.77934   0.56395   0.68750   0.55508   0.60177
#> Neg Pred Value         0.98285   0.99269   0.99446   0.99899   0.99417   0.98794   0.98822   0.98946   0.98475   0.98919   0.99078
#> Prevalence             0.04057   0.03430   0.04838   0.02287   0.02008   0.03430   0.03458   0.03709   0.04824   0.02872   0.03723
#> Detection Rate         0.02398   0.02719   0.04308   0.02189   0.01436   0.02259   0.02315   0.02705   0.03374   0.01827   0.02844
#> Detection Prevalence   0.03277   0.02719   0.04308   0.03249   0.01994   0.02900   0.02970   0.04796   0.04908   0.03291   0.04727
#> Balanced Accuracy      0.79095   0.89634   0.94524   0.97324   0.85479   0.82595   0.83128   0.85380   0.84165   0.81042   0.87225
#>                      Class: 23
#> Sensitivity            0.70783
#> Specificity            0.99693
#> Pos Pred Value         0.91797
#> Neg Pred Value         0.98597
#> Prevalence             0.04629
#> Detection Rate         0.03277
#> Detection Prevalence   0.03569
#> Balanced Accuracy      0.85238

Эта модель однозначно лучше предыдущей. Мы можем идеально предсказать класс 0 = A, 2 = C, 4 = E и 14 = P. Класс 10 = L также хорошо прогнозируется.

temp <- data.frame(
  'train_acc' = tail(history$metrics$accuracy, n=1),
  'test_acc' = tail(history$metrics$val_accuracy, n=1), 
  row.names = 'Dense with 3 hidden layers')

result <- rbind(result, temp)
result
#>                            train_acc  test_acc
#> Dense with 2 hidden layers 0.9844837 0.7711935
#> Dense with 3 hidden layers 1.0000000 0.8284997

Приведенная выше оценка точности показывает, что эта модель чрезвычайно переоснащена. У нас есть идеальный прогноз на train наборе данных! Но опять же, эта модель все еще лучше, чем предыдущая, поскольку точность test набора данных также значительно увеличена, поэтому мы продолжаем использовать эту модель. Итак, что мы можем сделать для борьбы с переобучением, не уменьшая нашу модель?

Плотный с увеличением данных

Проблема с предыдущими моделями заключается в том, что они имеют тенденцию запоминать изображения в train наборе данных, поэтому при поступлении нового test набора данных они не могут его распознать. Увеличение данных - один из многих методов решения этой проблемы. Для данного изображения увеличение данных немного изменит его, чтобы создать несколько новых изображений. Эти новые изображения затем вставляются в модель. Таким образом, модель знает множество версий исходного изображения и, мы надеемся, понимает особенности изображения, а не запоминает его. Мы будем использовать только несколько простых преобразований:

  1. Произвольно повернуть на 10 градусов
  2. Произвольное масштабирование с коэффициентом 0,1
  3. Произвольно сдвинуть по горизонтали на 0,1 доли от общей ширины
  4. Произвольно сдвинуть по горизонтали на 0,1 доли от общей высоты

Мы не используем горизонтальный или вертикальный поворот, поскольку в нашем случае они могут изменить значение изображения. Это увеличение данных может быть выполнено с помощью функции image_data_generator(). Сохраните генератор в объект с именем datagen.

datagen <- image_data_generator(
  rotation_range = 10,
  zoom_range = 0.1,
  width_shift_range = 0.1,
  height_shift_range = 0.1
)

На этот раз мы сделаем моделирование немного по-другому. Вместо подгонки к модели 32 строк, состоящих из 784 пикселей, мы подгоним 32 изображения размером 28 × 28 пикселей за раз. Мы можем использовать функцию flow_images_from_data(), вставив datagen в качестве генератора. Теперь наш генератор готов для набора данных поезда, назовем его train_generator. Для проверки, как и раньше, мы будем использовать все test наблюдения набора данных одновременно для каждой эпохи, считывая строки из 784 значений пикселей.

Теперь, поскольку train_generator принимает изображения в качестве входных данных, нам нужно изменить форму массива входных данных с 784 на (28, 28, 1). Цифра 1 в конце - это количество каналов, которое указывает, что мы используем изображения в градациях серого. Если входные изображения были цветными, то количество каналов обычно было 3 (для красного, зеленого и синего).

train_x_keras <- train_x_keras %>% 
  array_reshape(dim = c(nrow(train_x), 28, 28, 1))

test_x_keras <-  test_x_keras %>% 
  array_reshape(dim = c(nrow(test_x), 28, 28, 1))

train_generator <- flow_images_from_data(
  x = train_x_keras,
  y = train_y_keras,
  generator = datagen,
  batch_size = 32,
  seed = 42
)

Постройте модель по следующей архитектуре:

  1. Входной уровень: (28, 28, 1) узлов
  2. Сглаживание слоя: используется для сглаживания (28, 28, 1) узлов до 784 узлов.
  3. Скрытый уровень 1: 512 узлов, функция активации relu
  4. Скрытый уровень 2: 256 узлов, функция активации relu
  5. Скрытый уровень 3: 128 узлов, функция активации relu
  6. Выходной уровень: 24 узла, функция активации softmax

Обратите внимание, что у нас есть те же скрытые слои и выходной слой, что и у model_3hidden.

tensorflow::tf$random$set_seed(42)

model_3hidden_aug <- keras_model_sequential()
model_3hidden_aug %>% 
  layer_flatten(input_shape = c(28, 28, 1)) %>% 
  layer_dense(units = 512,
              activation = "relu",
              name = "hidden1") %>% 
  layer_dense(units = 256,
              activation = "relu",
              name = "hidden2") %>%
  layer_dense(units = 128,
              activation = "relu",
              name = "hidden3") %>%
  layer_dense(units = 24,
              activation = "softmax",
              name = "output")
  
summary(model_3hidden_aug)
#> Model: "sequential_2"
#> _________________________________________________________________
#> Layer (type)                                                  Output Shape                                           Param #              
#> =================================================================
#> flatten (Flatten)                                             (None, 784)                                            0                    
#> _________________________________________________________________
#> hidden1 (Dense)                                               (None, 512)                                            401920               
#> _________________________________________________________________
#> hidden2 (Dense)                                               (None, 256)                                            131328               
#> _________________________________________________________________
#> hidden3 (Dense)                                               (None, 128)                                            32896                
#> _________________________________________________________________
#> output (Dense)                                                (None, 24)                                             3096                 
#> =================================================================
#> Total params: 569,240
#> Trainable params: 569,240
#> Non-trainable params: 0
#> _________________________________________________________________

Скомпилируйте модель, как раньше.

model_3hidden_aug %>% 
  compile(optimizer = optimizer_adam(lr=0.001),
          loss = "categorical_crossentropy",
          metrics = "accuracy")

Для обучения модели мы не будем использовать обычную fit() функцию. Вместо этого мы воспользуемся функцией fit_generator() и вставим train_generator в качестве генератора. Нам также необходимо указать параметр steps_per_epoch, который представляет собой просто количество шагов в пределах одной эпохи, то есть количество всех train наблюдений, деленное на размер пакета. Наконец, мы обучим модель для 70 эпох, чтобы выжать как можно больше информации. Но учтите, что слишком большое количество эпох также может привести к переобучению.

history <- model_3hidden_aug %>% 
  fit_generator(
    generator = train_generator,
    steps_per_epoch = nrow(train_x_keras) / 32,
    epoch = 70,
    validation_data = list(test_x_keras, test_y_keras))
plot(history)

Сейчас мы говорим! Больше никаких переоснащений!

pred_3hidden_aug <- predict_classes(object = model_3hidden_aug, x = test_x_keras)
confusionMatrix(as.factor(pred_3hidden_aug), as.factor(test_y$label))
#> Confusion Matrix and Statistics
#> 
#>           Reference
#> Prediction   0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23
#>         0  311   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
#>         1    0 424   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
#>         2    0   0 310   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
#>         3    0   7   0 245   0   0   0   0   0   0   0   0   0   0   0   0   0   0  17   0   1   0   3   0
#>         4    0   0   0   0 477   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
#>         5    0   0   0   0   0 247  20   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
#>         6    0   0   0   0   0   0 309   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
#>         7    0   0   0   0   0   0   0 436   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
#>         8    0   0   0   0   0   0   0   0 288   0   0   0   0   0   0   0   0   0   0   0   0   0   0  39
#>         9    0   1   0   0   0   0   0   0   0 331   0   0   0   0   0   0   0   0   0   4   0   0   0   2
#>         10   0   0   0   0   0   0   0   0   0   0 209   0   0   0   0   0   0   0   0   0   0   0   0   0
#>         11   0   0   0   0  21   0   0   0   0   0   0 394   0   0   0   0   0  23   0   0   0   0   0   0
#>         12  20   0   0   0   0   0   0   0   0   0   0   0 291   0   0   0   0   0   0   0   0   0   0   0
#>         13   0   0   0   0   0   0   0   0   0   0   0   0   0 246   0   0   0   0   0   0   0   0   0   0
#>         14   0   0   0   0   0   0   0   0   0   0   0   0   0   0 347   0   0   0   0   0   0   0   0   0
#>         15   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0 164   0   0   0   0   0   0   0   0
#>         16   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0 109   0   0   0   0   0   0   0
#>         17   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   4 223   0   0   0   0   0   0
#>         18   0   0   0   0   0   0  19   0   0   0   0   0   0   0   0   0   0   0 230   0   0   0   0   0
#>         19   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0  31   0   0 262  19   0   2   0
#>         20   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0 306   0   0   1
#>         21   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0  19 206   2   0
#>         22   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   1   0   0   0 260   0
#>         23   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   1   0   0 290
#> 
#> Overall Statistics
#>                                                
#>                Accuracy : 0.9642               
#>                  95% CI : (0.9596, 0.9683)     
#>     No Information Rate : 0.0694               
#>     P-Value [Acc > NIR] : < 0.00000000000000022
#>                                                
#>                   Kappa : 0.9625               
#>                                                
#>  Mcnemar's Test P-Value : NA                   
#> 
#> Statistics by Class:
#> 
#>                      Class: 0 Class: 1 Class: 2 Class: 3 Class: 4 Class: 5 Class: 6 Class: 7 Class: 8 Class: 9 Class: 10 Class: 11
#> Sensitivity           0.93958  0.98148  1.00000  1.00000  0.95783  1.00000  0.88793  1.00000  1.00000  1.00000   1.00000   1.00000
#> Specificity           1.00000  1.00000  1.00000  0.99596  1.00000  0.99711  1.00000  1.00000  0.99433  0.99898   1.00000   0.99351
#> Pos Pred Value        1.00000  1.00000  1.00000  0.89744  1.00000  0.92509  1.00000  1.00000  0.88073  0.97929   1.00000   0.89954
#> Neg Pred Value        0.99708  0.99881  1.00000  1.00000  0.99686  1.00000  0.99432  1.00000  1.00000  1.00000   1.00000   1.00000
#> Prevalence            0.04615  0.06023  0.04322  0.03416  0.06944  0.03444  0.04852  0.06079  0.04016  0.04615   0.02914   0.05494
#> Detection Rate        0.04336  0.05912  0.04322  0.03416  0.06651  0.03444  0.04308  0.06079  0.04016  0.04615   0.02914   0.05494
#> Detection Prevalence  0.04336  0.05912  0.04322  0.03806  0.06651  0.03723  0.04308  0.06079  0.04559  0.04713   0.02914   0.06107
#> Balanced Accuracy     0.96979  0.99074  1.00000  0.99798  0.97892  0.99856  0.94397  1.00000  0.99717  0.99949   1.00000   0.99675
#>                      Class: 12 Class: 13 Class: 14 Class: 15 Class: 16 Class: 17 Class: 18 Class: 19 Class: 20 Class: 21 Class: 22
#> Sensitivity            1.00000    1.0000   1.00000   1.00000   0.75694   0.90650   0.92742   0.98496   0.88439   1.00000   0.97378
#> Specificity            0.99709    1.0000   1.00000   1.00000   1.00000   0.99942   0.99726   0.99247   0.99985   0.99699   0.99986
#> Pos Pred Value         0.93569    1.0000   1.00000   1.00000   1.00000   0.98238   0.92369   0.83439   0.99674   0.90749   0.99617
#> Neg Pred Value         1.00000    1.0000   1.00000   1.00000   0.99504   0.99669   0.99740   0.99942   0.99417   1.00000   0.99899
#> Prevalence             0.04057    0.0343   0.04838   0.02287   0.02008   0.03430   0.03458   0.03709   0.04824   0.02872   0.03723
#> Detection Rate         0.04057    0.0343   0.04838   0.02287   0.01520   0.03109   0.03207   0.03653   0.04267   0.02872   0.03625
#> Detection Prevalence   0.04336    0.0343   0.04838   0.02287   0.01520   0.03165   0.03472   0.04378   0.04281   0.03165   0.03639
#> Balanced Accuracy      0.99855    1.0000   1.00000   1.00000   0.87847   0.95296   0.96234   0.98872   0.94212   0.99849   0.98682
#>                      Class: 23
#> Sensitivity            0.87349
#> Specificity            0.99985
#> Pos Pred Value         0.99656
#> Neg Pred Value         0.99390
#> Prevalence             0.04629
#> Detection Rate         0.04044
#> Detection Prevalence   0.04057
#> Balanced Accuracy      0.93667

Мы можем заметить, что многие классы предсказываются идеально или почти идеально. Некоторые классы все еще трудно различить, например, 4 = E и 17 = S. Это связано с похожими жестами рук между алфавитами E и S.

temp <- data.frame(
  'train_acc' = tail(history$metrics$accuracy, n=1),
  'test_acc' = tail(history$metrics$val_accuracy, n=1), 
  row.names = 'Dense with 3 hidden layers and data augmentation')

result <- rbind(result, temp)
result
#>                                            train_acc     test_acc
#> Dense with 2 hidden layers                 0.9844837    0.7711935
#> Dense with 3 hidden layers                 1.0000000    0.8284997
#> Dense with 3 hidden layers and data aug    0.9588302    0.9641662

Мы получаем лучшую модель с одинаковой точностью train и test, около 95-96%.

Заключение

Нейронная сеть (NN) очень подходит для задач классификации изображений. Это связано с тем, что сложно извлекать элементы из изображений вручную, и NN может сделать это внутренне, не беспокоясь о том, какие функции следует извлекать. Что касается нашей проблемы, мы видим, что сама по себе модель NN может привести к переобучению. Следовательно, вводится расширение данных, которое может значительно повысить производительность модели и уменьшить переобучение. Однако многое еще можно улучшить:

  1. Настройте скорость обучения, при необходимости используйте планировщик скорости обучения
  2. Попробуйте другие подходы для борьбы с проблемами переобучения, такие как слой исключения или регуляризация.
  3. Перейти на сверточную нейронную сеть

Сноска

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



Если вы хотите поддержать мои статьи, подумайте о том, чтобы стать участником Medium. Всего за 5 долларов в месяц вы получите полный доступ ко всем историям на Medium. Ваш членский взнос напрямую поддерживает меня и других писателей, которых вы читаете.