Наука о данных в R
Распознавание жестов рук
Построение модели нейронной сети для понимания языка жестов и способов его улучшения
Американский язык жестов (ASL) - это законченный естественный язык, который имеет те же лингвистические свойства, что и разговорные языки, с грамматикой, отличной от английского. ASL выражается движениями рук и лица. Это основной язык многих глухих и слабослышащих североамериканцев, но он также используется многими слышащими людьми.
Нам даны изображения жестов рук ASL, каждая представляет собой классический латинский алфавит. Можем ли мы классифицировать каждую картинку как алфавит? Это проблема многоклассовой классификации, которая будет решена путем разработки модели нейронной сети (NN).
Библиотека
Помимо повторяющихся библиотек, которые мы обычно используем в R, мы также будем использовать keras.
Keras - это высокоуровневый API нейронных сетей, разработанный с упором на обеспечение возможности быстрого экспериментирования. Возможность переходить от идеи к результату с минимально возможной задержкой - ключ к проведению хороших исследований.
library(keras) # neural network library(dplyr)
# data wranglinglibrary(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))
Затем постройте модель. А пока создадим архитектуру следующим образом.
- Входной уровень: 784 узла
- Скрытый уровень 1: 128 узлов, функция активации relu
- Скрытый уровень 2: 64 узла, функция активации relu
- Выходной уровень: 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 скрытыми слоями
Несмотря на признаки переобучения в предыдущей модели, давайте просто расширим модель и посмотрим, в какой степени происходит переобучение. На этот раз мы будем использовать более крупную модель со следующей архитектурой.
- Входной уровень: 784 узла
- Скрытый уровень 1: 512 узлов, функция активации relu
- Скрытый уровень 2: 256 узлов, функция активации relu
- Скрытый уровень 3: 128 узлов, функция активации relu
- Выходной уровень: 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
набора данных они не могут его распознать. Увеличение данных - один из многих методов решения этой проблемы. Для данного изображения увеличение данных немного изменит его, чтобы создать несколько новых изображений. Эти новые изображения затем вставляются в модель. Таким образом, модель знает множество версий исходного изображения и, мы надеемся, понимает особенности изображения, а не запоминает его. Мы будем использовать только несколько простых преобразований:
- Произвольно повернуть на 10 градусов
- Произвольное масштабирование с коэффициентом 0,1
- Произвольно сдвинуть по горизонтали на 0,1 доли от общей ширины
- Произвольно сдвинуть по горизонтали на 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
)
Постройте модель по следующей архитектуре:
- Входной уровень: (28, 28, 1) узлов
- Сглаживание слоя: используется для сглаживания (28, 28, 1) узлов до 784 узлов.
- Скрытый уровень 1: 512 узлов, функция активации relu
- Скрытый уровень 2: 256 узлов, функция активации relu
- Скрытый уровень 3: 128 узлов, функция активации relu
- Выходной уровень: 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 может привести к переобучению. Следовательно, вводится расширение данных, которое может значительно повысить производительность модели и уменьшить переобучение. Однако многое еще можно улучшить:
- Настройте скорость обучения, при необходимости используйте планировщик скорости обучения
- Попробуйте другие подходы для борьбы с проблемами переобучения, такие как слой исключения или регуляризация.
- Перейти на сверточную нейронную сеть
Сноска
Привет! Спасибо, что дошли до конца. Это шестая моя статья из серии Наука о данных в R. Пожалуйста, найдите предыдущий здесь для любопытных умов, а следующий здесь:
Если вы хотите поддержать мои статьи, подумайте о том, чтобы стать участником Medium. Всего за 5 долларов в месяц вы получите полный доступ ко всем историям на Medium. Ваш членский взнос напрямую поддерживает меня и других писателей, которых вы читаете.