Рассматривается сверточная нейронная сеть (СНС) на Python, реализованная средствами библиотеки TensorFlow, доступ которой выполнен при помощи оболочки Keras.
После обучения сеть способна классифицировать изображения рукописных цифр, выполненные в оттенках серого цвета и имеющие размер 28*28 пикселей.
Обучение и тестирование СНС выполняется на наборе данных MNIST, загрузка которого обеспечивается средствами TensorFlow. Так же набор можно загрузить и средствами Keras. В первом случае набор должен быть предварительно скачен, а во втором он скачивается при начале загрузки, то есть должен быть обеспечен доступ к интернету.
Скачать MNIST можно, например, на [1] или здесь.
Для доступа к MNIST необходимо выполнить установить библиотеку python-mnist. Установка библиотек выполняется в командном окне, которое в Windows открывается после нажатия Win + X.
<path>/Scripts/pip3 install python-mnist
Познакомится со сверточными сетями можно, например, прочитав [2].
MNIST содержит образы рукописных цифр (рис. 1).
Рис. 1. Примеры рукописных цифр набора данных MNIST
Каждый образ имеет размер 28*28 пикселей и выполнен в оттенках серого цвета. Показанных на рис. 1 границ образа в его описании нет, на рис. 1 они приведены для наглядности.
Для представления образа используются 28*28 = 784 цифры из диапазона [0, 55]. Например, вариант рукописного числа 5 представлен следующим вектором цифр:
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 18 18 18 126 136 175 26 166 255 247 127 0 0 0 0 0 0 0 0 0 0 0 0 30 36 94 154 170 253 253 253 253 253 225 172 253 242 195 64 0 0 0 0 0 0 0 0 0 0 0 49 238 253 253 253 253 253 253 253 253 251 93 82 82 56 39 0 0 0 0 0 0 0 0 0 0 0 0 18 219 253 253 253 253 253 198 182 247 241 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 80 156 107 253 253 205 11 0 43 154 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 14 1 154 253 90 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 139 253 190 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 11 190 253 70 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 35 241 225 160 108 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 81 240 253 253 119 25 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 45 186 253 253 150 27 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 16 93 252 253 187 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 249 253 249 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 46 130 183 253 253 207 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 39 148 229 253 253 253 250 182 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 24 114 221 253 253 253 253 201 78 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 23 66 213 253 253 253 253 198 81 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 18 171 219 253 253 253 253 195 80 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 55 172 226 253 253 253 253 244 133 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 136 253 253 253 212 135 132 16 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] |
На вход СНС подается массив размера 28*28. Для вышеприведенной версии числа 5 он отображается в следующую таблицу:
Таблица 1. Рукописная версия числа 5
[[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 18 | 18 | 18 | 126 | 136 | 175 | 26 | 166 | 255 | 247 | 127 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 30 | 36 | 94 | 154 | 170 | 253 | 253 | 253 | 253 | 253 | 225 | 172 | 253 | 242 | 195 | 64 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 49 | 238 | 253 | 253 | 253 | 253 | 253 | 253 | 253 | 253 | 251 | 93 | 82 | 82 | 56 | 39 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 18 | 219 | 253 | 253 | 253 | 253 | 253 | 198 | 182 | 247 | 241 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 80 | 156 | 107 | 253 | 253 | 205 | 11 | 0 | 43 | 154 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 14 | 1 | 154 | 253 | 90 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 139 | 253 | 190 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 11 | 190 | 253 | 70 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 35 | 241 | 225 | 160 | 108 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 81 | 240 | 253 | 253 | 119 | 25 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 45 | 186 | 253 | 253 | 150 | 27 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 16 | 93 | 252 | 253 | 187 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 249 | 253 | 249 | 64 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 46 | 130 | 183 | 253 | 253 | 207 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 39 | 148 | 229 | 253 | 253 | 253 | 250 | 182 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 24 | 114 | 221 | 253 | 253 | 253 | 253 | 201 | 78 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 23 | 66 | 213 | 253 | 253 | 253 | 253 | 198 | 81 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 18 | 171 | 219 | 253 | 253 | 253 | 253 | 195 | 80 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 55 | 172 | 226 | 253 | 253 | 253 | 253 | 244 | 133 | 11 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 136 | 253 | 253 | 253 | 212 | 135 | 132 | 16 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0] |
[ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0]] |
Растровое представление образа, масштабированное на 100%, показано на рис. 2.
Рис. 2. Растровое представление рассматриваемого рукописного числа 5
Его можно получить, употребив следующий MaxScript-код:
-- Создаем растровый образ
btmp = bitmap 28 28 color:white
-- Состав файла 5.txt описан в табл. 1
fNm = "G:/100byte/python/MNIST_NN/5.txt"
fs = openFile fNm mode:"r"
for y = 0 to 27 do (
s = readLine fs
s = substituteString s "[" ""
s = substituteString s "]" ""
for x = 0 to 27 do (
while s[1] == " " or s[1] == " " do s = substring s 2 -1
k = 1
while s[k] > " " and k < s.count do k += 1
v = substring s 1 (k - 1) as integer
v = 255 - v
setPixels btmp [x, y] #((color v v v))
s = substring s k -1
)
)
close fs
display btmp
Не покидая Python, растровые изображения рукописных цифр можно получить, применив следующий код:
from mnist import MNIST
import numpy as np
import matplotlib.pyplot as plt
mndata = MNIST('G:/python/MNIST/MNIST_data/')
mndata.gz = True
imagesTrain, labelsTrain = mndata.load_training()
imagesTest, labelsTest = mndata.load_testing()
x_train = np.asarray(imagesTrain)
y_train = np.asarray(labelsTrain)
x_test = np.asarray(imagesTest)
y_test = np.asarray(labelsTest)
img_rows, img_cols = 28, 28
# Выводим 4 первые изображения тестового набора
plt.subplot(2,2,1)
plt.imshow(x_test[0].reshape(img_rows, img_cols), cmap=plt.get_cmap('gray'))
plt.subplot(2,2,2)
plt.imshow(x_test[1].reshape(img_rows, img_cols), cmap=plt.get_cmap('gray'))
plt.subplot(2,2,3)
plt.imshow(x_test[2].reshape(img_rows, img_cols), cmap=plt.get_cmap('gray'))
plt.subplot(2,2,4)
plt.imshow(x_test[3].reshape(img_rows, img_cols), cmap=plt.get_cmap('gray'))
plt.show() # См. рис. 3
Рис. 3. Растровые представления первых 4-х цифр тестового набора (7, 2, 1 и 0)
Чтение и преобразование MNIST обеспечивается следующим кодом (на примере обучающей выборки):
# Работаем с ранее выгруженным в 'G:/python/MNIST/MNIST_data/ архивом MNIST
mndata = MNIST('G:/python/MNIST/MNIST_data/')
# Разрешаем чтение архивированных данных
mndata.gz = True
# Обучающая выборка (данные и метки); imagesTrain и labelsTrain – это списки
imagesTrain, labelsTrain = mndata.load_training()
# Преобразование списков imagesTrain и labelsTrain в одномерные массивы
# Каждый элемент массива - это вектор размера 28*28 с целочисленными данными в диапазоне [0, 255]
x_train = np.asarray(imagesTrain)
y_train = np.asarray(labelsTrain)
print(x_train[0]) # Напечатает 784 цифры, представляющих вариант рукописного числа 5
print(y_train[0]) # Напечатает: 5
#
# Меняем форму входных данных (обучающих и тестовых)
x_train = x_train.reshape(x_train.shape[0], 28, 28, 1)
#
# Преобразование целочисленных данных в float32; данные лежат в диапазоне [0.0, 255.0]
x_train = x_train.astype('float32')
print(x_train[0, :, :, 0]) # Напечатает массив размера 28*28
#
# Приведение к диапазону [0.0, 1.0]
x_train /= 255
#
# Преобразование метки из диапазона [0, 9] в двоичный вектор размера 10
# Так, метка 5 будет преобразована в вектор [0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
y_train = keras.utils.to_categorical(y_train, 10) # 10 – число классов
print(y_train[0]) # Напечатает: [0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
Программируемая ниже СНС имеет те же типы слоев, что и известная сеть LeNet-5 [3] (рис. 4).
Рис. 4. Архитектура LeNet-5
СНС отличается от LeNet формами выхода слоев, функциями активации и механизмом фильтрации данных на сверточных слоях.
Архитектура СНС на показана на рис. 5 и описана в табл. 2.
Рис. 5. Архитектура СНС
Таблица 2. Описание архитектуры СНС
Тип слоя | Форма ядра (окна) | Шаг | Форма выхода | Число обучаемых весов |
---|---|---|---|---|
Conv / ReLU | (5, 5) | (1, 1) | (None, 28, 28, 32) | 832 |
MaxPool | (2, 2) | (2, 2) | (None, 14, 14, 32) | 0 |
Conv / ReLU | (5, 5) | (1, 1) | (None, 10, 10, 64) | 51'264 |
MaxPool | (2, 2) | (2, 2) | (None, 5, 5, 64) | 0 |
FC / ReLU | (None, 1024) | 1'639'424 | ||
FC / Linear | (None, 16) | 16'400 | ||
FC / Softmax | (None, 10) | 170 |
Используются слои следующих типов:
Сеть содержит два сверточных слоя, два слоя подвыборки и три полносвязных слоя.
Операция свёртки, выполняемая на сверточных слоях, заключается в вычислении скалярных произведений входного массива признаков и ядра свёртки (массива, называемого также фильтром) при различных положениях последнего: ядро свертки позиционируется с заданным шагом на входном массиве признаков. В используемой СНС ядро свёртки – это массив формы (2, 2).
На слоях подвыборки осуществляется уплотнение признаков в результате замены группы признаков, охватываемых окном (массивом) подвыборки, одним признаком этой группы (в используемой СНС берется признак с максимальным значением). Окно подвыборки передвигается с заданным шагом по входному массиву признаков. В используемой СНС окно подвыборки – это массив формы (2, 2). Шаг перемещения окна также задается в виде массива формы (2, 2).
На входе СНС вектор из 784 признаков преобразуется в массив формы (28, 28). Каждый элемент массива – это значение соответствующего пикселя изображения рукописной цифры.
Два сверточных слоя позволяют «выразить» взаимосвязь между пикселями изображения, расположенными далеко друг от друга.
На первом сверточном слое обучаются 32 фильтра, на втором – 64. Выходные данные каждого сверточного слоя фильтруются функцией ReLU и передаются на слой подвыборки; окно и шаг подвыборки имеют форму (2, 2).
Слои подвыборки снижают размерность обрабатываемых сетью данных.
Три полносвязных слоя с последовательно уменьшающимся числом нейронов совмещают выделенные сверточными слоями признаки и выдают ответ [2].
Последний (полносвязный) слой СНС определяет вероятности принадлежности изображения к классам в зависимости от значений, выделенных на предыдущем, также полносвязном, слое признаков.
Передача признаков от второго слоя подвыборки первому полносвязному слою предваряется операцией Flatten, выполняющей преобразование многомерного выхода предшествующего слоя в одномерный; на форма выхода Flatten – (None, 1600).
Сеть, получая на входе изображение, извлекает вектор из 16 признаков, по значениям которых изображение относится к одному из десяти классов.
Дополнительно может быть задан и Dropout-слой, который на каждом шаге обучения обнуляет часть входных единиц. Обнуляемые единицы (рис. 6) выбираются на каждом шаге (пакете) случайным образом, и их число определяется коэффициентом разреживания 0 <= rate < 1. Необнуляемые единицы маcштабируются величиной 1/(1 - rate), таким образом сумма входа остается неизменной.
Рис. 6. До и после слоя Dropout
Пример.
import numpy as np
import tensorflow as tf
from keras import backend as K
d_layer = tf.keras.layers.Dropout(0.2, input_shape = (2,))
data = np.arange(10, dtype = 'float32').reshape(5, 2)
print(data)
#array([[0., 1.],
# [2., 3.],
# [4., 5.],
# [6., 7.],
# [8., 9.]], dtype=float32)
outputs = d_layer(data, training = True) # <tf.Tensor 'dropout/dropout/mul:0' shape=(5, 2) dtype=float32>
sess = tf.Session()
print(sess.run(outputs))
#[[ 0. 1.25]
# [ 2.5 3.75]
# [ 0. 0. ]
# [ 7.5 8.75]
# [10. 11.25]]
Слой позволяет избежать переобучения сети, которое проявляется в том, что сеть показывает гораздо лучшие результаты на обучающей выборке, чем на тестовой.
Слой Flatten – преобразует многомерный выход предшествующего слоя в одномерный, например:
model = Sequential()
model.add(Conv2D(64, 3, 3,
border_mode = 'same',
input_shape = (3, 32, 32)))
# Теперь: model.output_shape = (None, 64, 32, 32)
model.add(Flatten())
# Теперь: model.output_shape = (None, 65536), где 65536 = 64 * 32 * 32
Свертку входного вектора A фильтром F можно проиллюстрировать следующим образом: фильтр F скользит по входному вектору А, каждой позиции фильтра отвечает элемент выходного вектора B, значение которого равно скалярному произведению F и области вектора A, закрываемой фильтром F (рис. 7).
Рис. 7. Свертка B = A * F с шагом 1, в частности, -5 = 3 * 1 + 7 * (-2) + 6
Свертку двумерного массива двумерным фильтром с шагом 1 иллюстрирует рис. 8 (заимствован из [4]).
Рис. 8. Свертка с шагом 1
Фильтр показан на рис. 9.
Рис. 9. Фильтр свертки, выполняемой на рис. 8
Одной из задач обучения является поиск наилучших фильтров.
Операция ReLU применяется к каждому элементу полученному после свертки массива, заменяя отрицательные значения на нулевые (рис. 10).
Рис. 10. ReLU
На pooling-слое (слое подвыборки) уменьшается размер массива данных за счет объединения его смежных ячеек: объединяемые ячейки заменяются одной; значение в новой ячейке определяется по значениям прежних ячеек по заданному правилу.
Так, в случае функции максимума (MaxPooling) новая ячейка будет содержать максимальное значение объединяемых ячеек.
Выбор объединяемых ячеек осуществляется путем наложения на массив заданного фильтра – массива той же формы, что и массив данных, но меньшего размера.
Первое подмножество объединяемых ячеек выделяется фильтром, начиная с левого верхнего угла массива данных. Следующее положение фильтра зависит от заданного шага.
Процесс объединения с шагом (2, 2) иллюстрирует рис. 11.
Рис. 11. Объединение с функцией максимума, фильтром 2 * 2 и шагом (2, 2)
Графическая иллюстрация операций свертки, ReLU и подвыборки приведена на рис. 12 (заимствован из [5]).
Рис. 12. Иллюстрация операций свертки, ReLU и подвыборки
Код построен на основе программ, приведенных в [2 и 6].
В качестве функции потерь берется перекрестная энтропии – categorical_crossentropy.
Метод оптимизации функции потерь – Adam.
Прочие сведения о сети приведены выше, а также в сопутствующем комментарии.
from mnist import MNIST
import numpy as np
import keras
import time
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
#from keras.layers.embeddings import Embedding
from keras import backend as K
import sys # Для sys.exit()
#
#from keras.datasets import mnist
#
# Задание затравки датчика случайных чисел обеспечит повторяемость результата
np.random.seed(348)
#
# Параметры модели
# Число экземпляров данных обучающей выборки между пересчетами весовых коэффициентов
# Чем меньше batch_size, тем дольше процесс обучения
batch_size = 256
# Число классов (по числу цифр)
num_classes = 10
# Число эпох обучения
epochs = 5
#
# Виды функцции потерь:
# mean_squared_error;
# mean_absolute_error;
# mean_absolute_percentage_error;
# mean_squared_logarithmic_error;
# squared_hinge;
# hinge;
# categorical_hinge;
# logcosh;
# categorical_crossentropy;
# sparse_categorical_crossentropy;
# binary_crossentropy;
# kullback_leibler_divergence;
# cosine_proximity
#
# Проверенные функции потерь:
# mean_squared_error; mean_absolute_error; mean_absolute_percentage_error
# mean_squared_logarithmic_error; squared_hinge; hinge; categorical_hinge
# logcosh; categorical_crossentropy; sparse_categorical_crossentropy
# binary_crossentropy; kullback_leibler_divergence
#
loss = keras.losses.categorical_crossentropy
print(loss)
categorical = True # False только для sparse_categorical_crossentropy
#
# Виды методов оптимизации:
# SGD; RMSprop; Adagrad; Adadelta; Adam; Adamax; Nadam
# Вариант задания метода оптимизации
# optKind = optimizers.SGD(lr = 0.01, decay = 1e-6, momentum = 0.9, nesterov = True)
#
optimizer = keras.optimizers.Adam()
#
# Читаем в списки imagesTrain, labelsTrain, imagesTest и labelsTest данные и метки
# из ранее скаченных файлов - набора данных MNIST
# Данные - это преставление числа (метки) на рисунке размера 28*28 пикселей
# Метка - это число из диапазона [0, 9]
# В каждом пикселе представлен оттенок серрого цвета. Он задается числом из диапазона [0, 255]
mndata = MNIST('G:/python/MNIST/MNIST_data/')
# Разрешаем чтение архивированных данных
mndata.gz = True
# Обучающая выборка (данные и метки)
imagesTrain, labelsTrain = mndata.load_training()
# Тестовая выборка (данные и метки)
imagesTest, labelsTest = mndata.load_testing()
# Преобразование списков в одномерные массивы
# Каждый элемент массива - это вектор размера 28*28 с целочисленными данными в диапазоне [0, 255]
x_train = np.asarray(imagesTrain)
y_train = np.asarray(labelsTrain)
x_test = np.asarray(imagesTest)
y_test = np.asarray(labelsTest)
print(x_train[0]) # Напечатает 784 цифры, представляющих вариант рукописного числа 5
print(y_train[0]) # Напечатает: 5
#
# Данные можно ввести, используя класс mnist, определенный в keras.datasets, но это медленнее
#(x_train, y_train), (x_test, y_test) = mnist.load_data()
#
# Размер входного образа
img_rows, img_cols = 28, 28
#
print(x_train.shape) # (60000, 784)
# Меняем форму входных данных (обучающих и тестовых)
if K.image_data_format() == 'channels_first':
x_train = x_train.reshape(x_train.shape[0], 1, img_rows, img_cols)
x_test = x_test.reshape(x_test.shape[0], 1, img_rows, img_cols)
input_shape = (1, img_rows, img_cols)
else: # channels_last
x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1)
x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1)
input_shape = (img_rows, img_cols, 1)
# Реально имеем channels_last
#
print(x_train.shape) # (60000, 28, 28, 1)
# Преобразование целочисленных данных в float32; данные лежат в диапазоне [0.0, 255.0]
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
print(x_train[0, :, :, 0]) # Напечатает массив размера 28*28
#
# Приведение к диапазону [0.0, 1.0]
x_train /= 255
x_test /= 255
#
# Преобразование метки - числа из диапазона [0, 9] в двоичный вектор размера 10
# Так, метка 5 (соответствует цифре 5) будет преобразована в вектор [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
if categorical:
print(y_train[0]) # Напечатает: 5
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
print(y_train[0]) # Напечатает: [0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
#
print('Форма x_train:', x_train.shape) # Напечатает: Форма x_train: (60000, 28, 28, 1)
print(x_train.shape[0], 'обучающих образов') # Напечатает: 60000 обучающих образов
print(x_test.shape[0], 'тестовых образов') # Напечатает: 10000 тестовых образов
#
start_time = time.time()
#
# Создаем модель сверточной нейронной сети
model = Sequential()
#model.add(Embedding(y_train.shape[0], 32))
model.add(Conv2D(32, kernel_size = (5, 5), strides = (1, 1), padding = 'same',
activation = 'relu', input_shape = input_shape))
trainable_count = int(np.sum([K.count_params(p) for p in set(model.trainable_weights)]))
non_trainable_count = int(np.sum([K.count_params(p) for p in set(model.non_trainable_weights)]))
trainable_countConv2D_32 = trainable_count
non_trainable_countConv2D_32 = non_trainable_count
print('Input shape: ', model.input_shape) # Input shape: (None, 28, 28, 1)
print('Conv2D_32. Форма выхода: ', model.output_shape) # Conv2D_32: (None, 28, 28, 32)
print('Conv2D_32. Число обучаемых весов: ', trainable_countConv2D_32) # 832
print('Conv2D_32. Число необучаемых весов: ', non_trainable_countConv2D_32) #
#
model.add(MaxPooling2D(pool_size = (2, 2), strides = (2, 2), padding = 'same'))
trainable_count = int(np.sum([K.count_params(p) for p in set(model.trainable_weights)]))
non_trainable_count = int(np.sum([K.count_params(p) for p in set(model.non_trainable_weights)]))
trainable_countMaxPooling2D_1 = trainable_count - trainable_countConv2D_32
non_trainable_countMaxPooling2D_1 = non_trainable_count - non_trainable_countConv2D_32
print('MaxPooling2D_1. Форма выхода: ', model.output_shape) # MaxPooling2D_1: (None, 14, 14, 32)
print('MaxPooling2D_1. Число обучаемых весов: ', trainable_countMaxPooling2D_1) # 0
print('MaxPooling2D_1. Число необучаемых весов: ', non_trainable_countMaxPooling2D_1) # 0
#
model.add(Conv2D(64, kernel_size = (5, 5), strides = (1, 1), activation = 'relu'))
trainable_count = int(np.sum([K.count_params(p) for p in set(model.trainable_weights)]))
non_trainable_count = int(np.sum([K.count_params(p) for p in set(model.non_trainable_weights)]))
trainable_countConv2D_64 = trainable_count - trainable_countConv2D_32 \
- trainable_countMaxPooling2D_1
non_trainable_countConv2D_64 = non_trainable_count - non_trainable_countConv2D_32 \
- non_trainable_countMaxPooling2D_1
print('Conv2D_64. Форма выхода: ', model.output_shape) # Conv2D_64: (None, 10, 10, 64)
print('Conv2D_64. Число обучаемых весов: ', trainable_countConv2D_64) # 51264
print('Conv2D_64. Число необучаемых весов: ', non_trainable_countConv2D_64) # 0
#
model.add(MaxPooling2D(pool_size = (2, 2), strides = (2, 2)))
trainable_count = int(np.sum([K.count_params(p) for p in set(model.trainable_weights)]))
non_trainable_count = int(np.sum([K.count_params(p) for p in set(model.non_trainable_weights)]))
trainable_countMaxPooling2D_2 = trainable_count - trainable_countConv2D_32 \
- trainable_countMaxPooling2D_1 - trainable_countConv2D_64
non_trainable_countMaxPooling2D_2 = non_trainable_count - non_trainable_countConv2D_32 \
- non_trainable_countMaxPooling2D_1 - non_trainable_countConv2D_64
print('MaxPooling2D_2. Форма выхода: ', model.output_shape) # MaxPooling2D_2: (None, 5, 5, 64)
print('MaxPooling2D_2. Число обучаемых весов: ', trainable_countMaxPooling2D_2) # 0
print('MaxPooling2D_2. Число необучаемых весов: ', non_trainable_countMaxPooling2D_2) # 0
#
#model.add(Dropout(0.25))
#print('Dropout_0.25: ', model.output_shape) # Dropout_0.25: (None, 5, 5, 64)
#
model.add(Flatten())
trainable_count = int(np.sum([K.count_params(p) for p in set(model.trainable_weights)]))
non_trainable_count = int(np.sum([K.count_params(p) for p in set(model.non_trainable_weights)]))
trainableFlatten = trainable_count - trainable_countConv2D_32 \
- trainable_countMaxPooling2D_1 - trainable_countConv2D_64 \
- trainable_countMaxPooling2D_2
non_trainableFlatten = non_trainable_count - non_trainable_countConv2D_32 \
- non_trainable_countMaxPooling2D_1 - non_trainable_countConv2D_64 \
- non_trainable_countMaxPooling2D_2
print('Flatten. Форма выхода: ', model.output_shape) # (None, 1600)
print('Flatten. Число обучаемых весов: ', trainableFlatten) # 0
print('Flatten. Число необучаемых весов: ', non_trainableFlatten) # 0
#
model.add(Dense(1024, activation = 'relu'))
trainable_count = int(np.sum([K.count_params(p) for p in set(model.trainable_weights)]))
non_trainable_count = int(np.sum([K.count_params(p) for p in set(model.non_trainable_weights)]))
trainableDense_1024 = trainable_count - trainable_countConv2D_32 \
- trainable_countMaxPooling2D_1 - trainable_countConv2D_64 \
- trainable_countMaxPooling2D_2 - trainableFlatten
non_trainableDense_1024 = non_trainable_count - non_trainable_countConv2D_32 \
- non_trainable_countMaxPooling2D_1 - non_trainable_countConv2D_64 \
- non_trainable_countMaxPooling2D_2 - non_trainableFlatten
print('Dense_1024. Форма выхода: ', model.output_shape) # (None, 1024)
print('Dense_1024. Число обучаемых весов: ', trainableDense_1024) # 1639424
print('Dense_1024. Число необучаемых весов: ', non_trainableDense_1024) # 0
#
model.add(Dense(16, activation = 'linear'))
trainable_count = int(np.sum([K.count_params(p) for p in set(model.trainable_weights)]))
non_trainable_count = int(np.sum([K.count_params(p) for p in set(model.non_trainable_weights)]))
trainableDense_16 = trainable_count - trainable_countConv2D_32 \
- trainable_countMaxPooling2D_1 - trainable_countConv2D_64 \
- trainable_countMaxPooling2D_2 - trainableFlatten \
- trainableDense_1024
non_trainableDense_16 = non_trainable_count - non_trainable_countConv2D_32 \
- non_trainable_countMaxPooling2D_1 - non_trainable_countConv2D_64 \
- non_trainable_countMaxPooling2D_2 - non_trainableFlatten \
- non_trainableDense_1024
print('Dense_16. Форма выхода: ', model.output_shape) # (None, 16)
print('Dense_16. Число обучаемых весов: ', trainableDense_16) # 16400
print('Dense_16. Число необучаемых весов: ', non_trainableDense_16) # 0
#
#model.add(Dropout(0.5))
#print('Dropout_0.5: ', model.output_shape) # (None, 16)
#
model.add(Dense(num_classes, activation = 'softmax'))
trainable_count = int(np.sum([K.count_params(p) for p in set(model.trainable_weights)]))
non_trainable_count = int(np.sum([K.count_params(p) for p in set(model.non_trainable_weights)]))
trainableDense_10 = trainable_count - trainable_countConv2D_32 \
- trainable_countMaxPooling2D_1 - trainable_countConv2D_64 \
- trainable_countMaxPooling2D_2 - trainableFlatten \
- trainableDense_1024 - trainableDense_16
non_trainableDense_10 = non_trainable_count - non_trainable_countConv2D_32 \
- non_trainable_countMaxPooling2D_1 - non_trainable_countConv2D_64 \
- non_trainable_countMaxPooling2D_2 - non_trainableFlatten \
- non_trainableDense_1024 - non_trainableDense_16
print('Dense_10. Форма выхода: ', model.output_shape) # (None, 10)
print('Dense_10. Число обучаемых весов: ', trainableDense_10) # 170
print('Dense_10. Число необучаемых весов: ', non_trainableDense_10) # 0
#
trainable_count = int(np.sum([K.count_params(p) for p in set(model.trainable_weights)]))
non_trainable_count = int(np.sum([K.count_params(p) for p in set(model.non_trainable_weights)]))
print('Всего обучаемых весов: ', trainable_count) # 1708090
print('Всего необучаемых весов: ', non_trainable_count) # 0
#
#sys.exit()
#
model.compile(loss = loss, optimizer = optimizer, metrics = ['accuracy'])
#
# Обучение
model.fit(x_train, y_train, batch_size = batch_size, epochs = epochs, verbose = 1, validation_data = (x_test, y_test))
#
print('Число эпох: ', epochs)
print('batch_size = ', batch_size)
#print('Нет Dense 16')
print('Есть Dense 16')
print('Время обучения: ', (time.time() - start_time))
#
# Проверка
start_time = time.time()
score = model.evaluate(x_test, y_test, verbose = 0)
#
# Вывод потерь и точности
print('Потери при тестировании: ', score[0])
print('Точность при тестировании:', score[1])
# Время тестирования
print('Время тестирования: ', (time.time() - start_time))
Замечание. Вместо промежуточной печати сведений о сети можно после создания сети употребить
model.summary()
from mnist import MNIST
import numpy as np
import keras
import time
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K
np.random.seed(348)
batch_size = 256
num_classes = 10
epochs = 5
mndata = MNIST('G:/python/MNIST/MNIST_data/')
mndata.gz = True
imagesTrain, labelsTrain = mndata.load_training()
imagesTest, labelsTest = mndata.load_testing()
x_train = np.asarray(imagesTrain)
y_train = np.asarray(labelsTrain)
x_test = np.asarray(imagesTest)
y_test = np.asarray(labelsTest)
img_rows, img_cols = 28, 28
x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1)
x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1)
input_shape = (img_rows, img_cols, 1)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
model = Sequential()
model.add(Conv2D(32, kernel_size = (5, 5), strides = (1, 1), padding = 'same', activation = 'relu', input_shape = input_shape))
model.add(MaxPooling2D(pool_size = (2, 2), strides = (2, 2), padding = 'same'))
model.add(Conv2D(64, kernel_size = (5, 5), strides = (1, 1), activation = 'relu'))
model.add(MaxPooling2D(pool_size = (2, 2), strides = (2, 2)))
model.add(Flatten())
model.add(Dense(1024, activation = 'relu'))
model.add(Dense(16, activation = 'linear'))
model.add(Dense(num_classes, activation = 'softmax'))
model.summary() # Описание модели
model.compile(loss = keras.losses.categorical_crossentropy, optimizer = keras.optimizers.Adam(), metrics = ['accuracy'])
model.fit(x_train, y_train, batch_size = batch_size, epochs = epochs, verbose = 1, validation_data = (x_test, y_test))
score = model.evaluate(x_test, y_test, verbose = 0)
print('Потери при тестировании: ', score[0])
print('Точность при тестировании:', score[1])
Результат употребленния model.summary():
Layer(type) | Output Shape | Param # |
conv2d_1(Conv2D) | (None, 28, 28, 32) | 832 |
max_pooling2d_1(MaxPooling2) | (None, 14, 14, 32) | 0 |
conv2d_2(Conv2D) | (None, 10, 10, 64) | 51264 |
max_pooling2d_2(MaxPooling2) | (None, 5, 5, 64) | 0 |
flatten_1(Flatten) | (None, 1600) | 0 |
dense_1(Dense) | (None, 1024) | 1639424 |
dense_2(Dense) | (None, 16) | 16400 |
dense_3(Dense) | (None, 10) | 170 |
Total params: 1,708,090
Trainable params: 1,708,090
Non-trainable params: 0
Лучший результат по точности классификации изображений рукописных цифр на обучающей выборке MNIST, обнаруженный в [1], равен 0.9977.
На рассматриваемой сети получена точность классификации при тестировании на MNIST, равная 0.9925 (6 эпох, batch_size = 256, оптимизатор Adadelta, есть предпоследний полносвязный слой с 16-ю нейронами).
При замере зависимости результата от числа эпох использованы СНС двух следующих архитектур:
Во всех измерениях при обучении использованы:
Замечание. Метка изображения должна быть преобразована в двоичный вектор размера 10.
# Преобразование метки – числа из диапазона [0, 9] в двоичный вектор размера 10
# Так, метка 5 (соответствует цифре 5) будет преобразована в вектор [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
print(y_train[0]) # Напечатает: 5
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
print(y_train[0]) # Напечатает: [0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
Результаты измерений приведены в табл. 3.
Таблица 3. Зависимость результата обучения от числа эпох
Эпоха | Нет FC-16 | Есть FC-16 | ||||
---|---|---|---|---|---|---|
Время обучения, с | Потери | Точность | Время обучения, с | Потери | Точность | |
1 | 501 | 0.04768 | 0.9843 | 502 | 0.04979 | 0.9843 |
2 | 1010 | 0.03637 | 0.9881 | 1017 | 0.02981 | 0.9904 |
3 | 1519 | 0.02553 | 0.9913 | 1515 | 0.02462 | 0.9915 |
4 | 2028 | 0.02895 | 0.9902 | 2011 | 0.02391 | 0.9920 |
5 | 2561 | 0.03038 | 0.9892 | 2557 | 0.02735 | 0.9916 |
Несколько лучше выглядят результаты при наличии полносвязного слоя с 16-ю нейронами перед последним классифицирующем слоем: точность 99.20% (4-я эпоха) против 99.13%, полученной после трех эпох обучения без слоя FC-16.
from mnist import MNIST
import numpy as np
import keras
import time
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K
np.random.seed(348)
batch_size = 256
num_classes = 10
epochs = 5
mndata = MNIST('G:/python/MNIST/MNIST_data/')
mndata.gz = True
imagesTrain, labelsTrain = mndata.load_training()
imagesTest, labelsTest = mndata.load_testing()
x_train = np.asarray(imagesTrain)
y_train = np.asarray(labelsTrain)
x_test = np.asarray(imagesTest)
y_test = np.asarray(labelsTest)
img_rows, img_cols = 28, 28
x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1)
x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1)
input_shape = (img_rows, img_cols, 1)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
model = Sequential()
model.add(Conv2D(6, kernel_size = (5, 5), strides = (1, 1), padding = 'same', activation = 'relu', input_shape = input_shape))
model.add(MaxPooling2D(pool_size = (2, 2), strides = (2, 2), padding = 'same'))
model.add(Conv2D(16, kernel_size = (5, 5), strides = (1, 1), activation = 'relu'))
model.add(MaxPooling2D(pool_size = (2, 2), strides = (2, 2)))
model.add(Flatten())
model.add(Dense(120, activation = 'relu'))
model.add(Dense(84, activation = 'linear'))
model.add(Dense(num_classes, activation = 'softmax'))
model.compile(loss = keras.losses.categorical_crossentropy, optimizer = keras.optimizers.Adam(), metrics = ['accuracy'])
model.fit(x_train, y_train, batch_size = batch_size, epochs = epochs, verbose = 1, validation_data = (x_test, y_test))
score = model.evaluate(x_test, y_test, verbose = 0)
print('Потери при тестировании: ', score[0])
print('Точность при тестировании:', score[1])
Результаты обучения этой сети приведены в табл. 4.
Таблица 4. Результаты обучения СНС с формами выхода LeNet-5
Число эпох | Потери | Точность |
---|---|---|
3 | 0.0524 | 0.9832 |
5 | 0.0422 | 0.9876 |
Результаты по понятной причине уступают предыдущим: число обучаемых весов этой сети равно 61'706, что существенно меньше числа обучаемых весов прежней СНС, равного 1'708'090.
Замечание. Число обучаемых весов сети определяется следующим образом:
trainable_count = int(np.sum([K.count_params(p) for p in set(model.trainable_weights)]))
Условия проведения эксперимента:
Использованы приведенные в табл. 5 функции потерь [2, 7-9]:
Таблица 5. Функции потерь.
В таблице:
yi – прогнозируемое значение;
xi – истинное значение;
n – размер вектора xi (yi).
Функция | Обозначение | Формула |
---|---|---|
Средняя квадратическая ошибка / mean squared error | MSE | |
Средняя абсолютная ошибка / mean absolute error | MAE | |
Средняя абсолютная процентная ошибка / mean absolute percentage error | MAPE | |
Средняя квадратическая логарифмическая ошибка / mean squared logarithmic error | MSLE | |
Квадрат верхней границы / squared hinge | SH | |
Верхняя граница / hinge | H | |
Категориальная верхняя граница / categorical hinge | CH | max(0, neg – pos + 1), где |
Логарифм гиперболического косинуса / logcosh | LC | |
Категориальная перекрестная энтропия / categorical crossentropy | CCE | |
Разреженная категориальная перекрестная энтропия / sparse categorical crossentropy | SCCE | (σ(yi) – softmax-функция, или номализованная экспонента) |
Бинарная перекрестная энтропия / binary crossentropy | BCE | |
Расстояние Кульбака-Лейблера / kullback leibler divergence | KLD | |
Пуассон / Poisson | PSS | |
Косинусная близость / cosine proximity | CP | |
Пользовательская функция потерь / my loss | ML |
Примеры функций расчета потерь на Python:
Вариант 1. Функция, поскольку axis = -1, вернет тензор с числом измерениий на 1 меньше, чем входные тензоры.
import keras.backend as K
# Вычисление Mean squared error (MSE)
def mse_(y_true, y_pred):
# Форма y_true, y_pred: (?, 10); форма выхода: (?,)
return K.mean(K.square(y_pred - y_true), axis = -1)
Вариант 2. Функция вернет тензор с shape = ().
import keras.backend as K
# Вычисление Mean squared error (MSE). Возвращает тензор с shape = ()
def mse_(y_true, y_pred):
return K.mean(K.square(y_pred - y_true))
# Пользовательская функция потерь. На входе тензоры
def myLoss(y_true, y_pred):
dist = K.sqrt(K.sqrt(K.sum(K.square(K.square(y_pred - y_true)))))
err = dist
return err # Вернет тензор с shape=(?,))
Результаты измерений приведены в табл. 6 и 7.
Таблица 6. Потери
Эпоха | Потери | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
MSE | MAE | MAPE | MSLE | SH | H | CH | LC | CCE | SCCE | BCE | KLD | PSS | CP | ML | |
1 | 0.0028 | 0.0433 | 3224013 | 0.0013 | 0.9018 | 0.9034 | 0.0631 | 0.0015 | 0.04979 | 0.2045 | 0.0101 | 0.0479 | 0.1049 | -0.9845 | 1.1302 |
2 | 0.0019 | 0.0048 | 2077755 | 0.00094 | 0.9014 | 0.9021 | 0.0412 | 0.00094 | 0.02981 | 0.0452 | 0.0057 | 0.0313 | 0.1030 | -0.9902 | 1.0021 |
3 | 0.0014 | 0.0027 | 1931962 | 0.00078 | 0.9008 | 0.9014 | 0.0382 | 0.00078 | 0.02462 | 0.0321 | 0.0052 | 0.0243 | 0.1025 | -0.9909 | 0.9576 |
4 | 0.0016 | 0.0023 | 1203659 | 0.00069 | 0.9008 | 0.9014 | 0.0328 | 0.00067 | 0.02391 | 0.0258 | 0.0054 | 0.0252 | 0.1026 | -0.9919 | 0.9164 |
5 | 0.0011 | 0.0029 | 1096461 | 0.00065 | 0.9008 | 0.9012 | 0.0311 | 0.00058 | 0.02735 | 0.0241 | 0.0053 | 0.0277 | 0.1028 | -0.9905 | 0.9709 |
Таблица 7. Точность
Эпоха | Точность | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
MSE | MAE | MAPE | MSLE | SH | H | CH | LC | CCE | SCCE | BCE | KLD | PSS | CP | ML | |
1 | 0.9810 | 0.7846 | 0.9699 | 0.9820 | 0.9790 | 0.9681 | 0.9698 | 0.9794 | 0.9843 | 0.9391 | 0.9826 | 0.9837 | 0.9840 | 0.9813 | 0.9856 |
2 | 0.9882 | 0.9776 | 0.9799 | 0.9882 | 0.9832 | 0.9809 | 0.9806 | 0.9874 | 0.9904 | 0.9858 | 0.9900 | 0.9899 | 0.9902 | 0.9892 | 0.9897 |
3 | 0.9903 | 0.9869 | 0.9819 | 0.9902 | 0.9898 | 0.9864 | 0.9821 | 0.9892 | 0.9915 | 0.9900 | 0.9904 | 0.9910 | 0.9910 | 0.9890 | 0.9907 |
4 | 0.9893 | 0.9890 | 0.9892 | 0.9902 | 0.9893 | 0.9863 | 0.9840 | 0.9906 | 0.9920 | 0.9908 | 0.9904 | 0.9911 | 0.9913 | 0.9904 | 0.9912 |
5 | 0.9930 | 0.9863 | 0.9896 | 0.9914 | 0.9902 | 0.9886 | 0.9848 | 0.9918 | 0.9916 | 0.9924 | 0.9905 | 0.9912 | 0.9920 | 0.9892 | 0.9888 |
Четыре лучших результата получены при употреблении MSE, SCCE, CCE и PSS.
Приведенные в табл. 7 оценки результатов обучения сети с различными функциями потерь можно представить в виде диаграммы (рис. 13).
Рис. 13. Точность обучения на пяти эпохах для различных функций потерь
Код, обеспечивающий построение диаграммы:
import numpy as np
import matplotlib.pyplot as plt
#
# Вывод диграммы "Точность обучения для различных функций потерь"
# Набор данных: MNIST
# Вид модели НС: conv
# Код модели НС: Г
imgType = 'MNIST' # MNIST EMNIST CIFAR10
nnType = 'conv' # conv mlp lstm
modelCode = 'Г'
ttl = imgType + ' / ' + nnType + ' / ' + modelCode
acc = []
acc.append([9930., 'MSE'])
acc.append([9890., 'MAE'])
acc.append([9892., 'MAPE'])
acc.append([9914., 'MSLE'])
acc.append([9902., 'SH'])
acc.append([9886., 'H'])
acc.append([9848., 'CH'])
acc.append([9918., 'LC'])
acc.append([9920., 'CCE'])
acc.append([9924., 'SCCE'])
acc.append([9905., 'BCE'])
acc.append([9912., 'KLD'])
acc.append([9920., 'PSS'])
acc.append([9904., 'CP'])
acc.append([9912., 'ML'])
# Сортируем список по первому элементу каждого подсписка
acc.sort(key = lambda r:r[0])
n = len(acc)
accV = []
accF = []
for k in range(n):
accV.append(acc[k][0] / 100.0)
accF.append(acc[k][1])
accMin = min(accV) - 0.1
accMax = max(accV) + 0.1
ind = np.arange(n)
width = 0.15 # Ширина столбца диаграммы
##plt.subplot(111)
plt.figure(figsize = (8, 4))
plt.ylim(accMin, accMax)
plt.bar(ind, accV, width)
plt.xlabel('Функция потерь')
plt.ylabel('Точность')
plt.title(ttl)
plt.xticks(ind, accF)
##plt.yticks(np.arange(accMin, accMax, 0.1))
##plt.legend(('Точность'), loc = 'upper left')
plt.show()
Перечислим функции активации, которые можно задать, работая с keras:
Способы задания функций активации:
from keras.layers import Activation, Dense
model.add(Dense(64))
model.add(Activation('tanh'))
или
model.add(Dense(64, activation = 'tanh'))
или
from keras import backend as K
model.add(Dense(64, activation = K.tanh))
Возможный способ задания функции активации (на примере LeakyReLU):
from keras.layers import LeakyReLU
hidden2_3 = Dense(12)(hidden2_2)
hidden2_4 = LeakyReLU(alpha = 0.4) (hidden2_3)
Одним из параметров метода оптимизации является скорость обучения. Важно правильно выбрать начальное значение скорости обучения.
В процессе обучения полезно контролировать значение этого параметра следующим образом:
Влияние скорости обучения на процесс обучения иллюстрирует рис. 14.
Рис. 14. Кривые обучения НС при различных значениях скорости обучения
Переобучение иллюстрирует рис. 15.
Рис. 15. Переобучение
Переобучения можно избежать, принимая, например, следующие меры: