Список работ

Сверточная нейронная сеть на Python

Содержание

Введение

Рассматривается сверточная нейронная сеть (СНС) на Python, реализованная средствами библиотеки TensorFlow, доступ которой выполнен при помощи оболочки Keras.
После обучения сеть способна классифицировать изображения рукописных цифр, выполненные в оттенках серого цвета и имеющие размер 28*28 пикселей.
Обучение и тестирование СНС выполняется на наборе данных MNIST, загрузка которого обеспечивается средствами TensorFlow. Так же набор можно загрузить и средствами Keras. В первом случае набор должен быть предварительно скачен, а во втором он скачивается при начале загрузки, то есть должен быть обеспечен доступ к интернету.
Скачать MNIST можно, например, на [1] или здесь.
Для доступа к MNIST необходимо выполнить установить библиотеку python-mnist. Установка библиотек выполняется в командном окне, которое в Windows открывается после нажатия Win + X.

<path>/Scripts/pip3 install python-mnist

Познакомится со сверточными сетями можно, например, прочитав [2].

Описание MNIST и его загрузка в программу

MNIST содержит образы рукописных цифр (рис. 1).

MNIST

Рис. 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 18126136175 26166255247127  0  0  0  0]
 [  0  0  0  0  0  0  0  0 30 36 94154170253253253253253225172253242195 64  0  0  0  0]
 [  0  0  0  0  0  0  0 49238253253253253253253253253251 93 82 82 56 39  0  0  0  0  0]
 [  0  0  0  0  0  0  0 18219253253253253253198182247241  0  0  0  0  0  0  0  0  0  0]
 [  0  0  0  0  0  0  0  0 80156107253253205 11  0 43154  0  0  0  0  0  0  0  0  0  0]
 [  0  0  0  0  0  0  0  0  0 14  115425390  0  0  0  0  0  0  0  0  0  0  0  0  0  0]
 [  0  0  0  0  0  0  0  0  0  0  0139253190  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 11190253 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 35241225160108  1  0  0  0  0  0  0  0  0  0  0]
 [  0  0  0  0  0  0  0  0  0  0  0  0  0 81240253253119 25  0  0  0  0  0  0  0  0  0]
 [  0  0  0  0  0  0  0  0  0  0  0  0  0  0 45186253253150 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 93252253187  0  0  0  0  0  0  0  0]
 [  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0249253249 64  0  0  0  0  0  0  0]
 [  0  0  0  0  0  0  0  0  0  0  0  0  0  0 46130183253253207  2  0  0  0  0  0  0  0]
 [  0  0  0  0  0  0  0  0  0  0  0  0 39148229253253253250182  0  0  0  0  0  0  0  0]
 [  0  0  0  0  0  0  0  0  0  0 24114221253253253253201 78  0  0  0  0  0  0  0  0  0]
 [  0  0  0  0  0  0  0  0 23 66213253253253253198 81  2  0  0  0  0  0  0  0  0  0  0]
 [  0  0  0  0  0  0 18171219253253253253195 80  9  0  0  0  0  0  0  0  0  0  0  0  0]
 [  0  0  0  0 55172226253253253253244133 11  0  0  0  0  0  0  0  0  0  0  0  0  0  0]
 [  0  0  0  0136253253253212135132 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.

Число 5

Рис. 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

Числа 7, 2, 1 и 0

Рис. 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).

LeNet-5

Рис. 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), таким образом сумма входа остается неизменной.

Dropout

Рис. 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).

Свертка B = A * F

Рис. 7. Свертка B = A * F с шагом 1, в частности, -5 = 3 * 1 + 7 * (-2) + 6

Свертку двумерного массива двумерным фильтром с шагом 1 иллюстрирует рис. 8 (заимствован из [4]).

Свертка с шагом 1

Рис. 8. Свертка с шагом 1

Фильтр показан на рис. 9.

Фильтр

Рис. 9. Фильтр свертки, выполняемой на рис. 8

Одной из задач обучения является поиск наилучших фильтров.

Операция ReLU применяется к каждому элементу полученному после свертки массива, заменяя отрицательные значения на нулевые (рис. 10).

ReLU

Рис. 10. ReLU

На pooling-слое (слое подвыборки) уменьшается размер массива данных за счет объединения его смежных ячеек: объединяемые ячейки заменяются одной; значение в новой ячейке определяется по значениям прежних ячеек по заданному правилу.
Так, в случае функции максимума (MaxPooling) новая ячейка будет содержать максимальное значение объединяемых ячеек.
Выбор объединяемых ячеек осуществляется путем наложения на массив заданного фильтра – массива той же формы, что и массив данных, но меньшего размера.
Первое подмножество объединяемых ячеек выделяется фильтром, начиная с левого верхнего угла массива данных. Следующее положение фильтра зависит от заданного шага.
Процесс объединения с шагом (2, 2) иллюстрирует рис. 11.

Pooling

Рис. 11. Объединение с функцией максимума, фильтром 2 * 2 и шагом (2, 2)

Графическая иллюстрация операций свертки, ReLU и подвыборки приведена на рис. 12 (заимствован из [5]).

Conv + ReLU + Pooling

Рис. 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 ShapeParam #
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
Время обучения, сПотериТочностьВремя обучения, сПотериТочность
15010.047680.98435020.049790.9843
210100.036370.988110170.029810.9904
315190.025530.991315150.024620.9915
420280.028950.990220110.023910.9920
525610.030380.989225570.027350.9916

Несколько лучше выглядят результаты при наличии полносвязного слоя с 16-ю нейронами перед последним классифицирующем слоем: точность 99.20% (4-я эпоха) против 99.13%, полученной после трех эпох обучения без слоя FC-16.

Реализация с формами выхода LeNet-5

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

Число эпохПотериТочность
30.05240.9832
50.04220.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
MSEmse
Средняя абсолютная ошибка /
mean absolute error
MAEmae
Средняя абсолютная процентная ошибка /
mean absolute percentage error
MAPEmape
Средняя квадратическая логарифмическая ошибка /
mean squared logarithmic error
MSLEmsle
Квадрат верхней границы /
squared hinge
SHsh
Верхняя граница / hingeHh
Категориальная верхняя граница /
categorical hinge
CHmax(0, neg – pos + 1), где
ch
Логарифм гиперболического косинуса /
logcosh
LClc
Категориальная перекрестная энтропия /
categorical crossentropy
CCEcce
Разреженная категориальная перекрестная энтропия /
sparse categorical crossentropy
SCCEscce
(σ(yi) – softmax-функция, или номализованная экспонента)
Бинарная перекрестная энтропия /
binary crossentropy
BCEbce
Расстояние Кульбака-Лейблера /
kullback leibler divergence
KLDkl
Пуассон /
Poisson
PSSPoisson
Косинусная близость /
cosine proximity
CPcp
Пользовательская функция потерь /
my loss
MLml

Примеры функций расчета потерь на 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. Потери

ЭпохаПотери
MSEMAEMAPEMSLESHHCHLCCCESCCEBCEKLDPSSCPML
10.00280.043332240130.00130.90180.90340.06310.00150.049790.20450.01010.04790.1049-0.98451.1302
20.00190.004820777550.000940.90140.90210.04120.000940.029810.04520.00570.0313 0.1030-0.99021.0021
30.00140.002719319620.000780.90080.90140.03820.000780.024620.03210.00520.02430.1025-0.99090.9576
40.00160.002312036590.000690.90080.90140.03280.000670.023910.02580.00540.02520.1026-0.99190.9164
50.00110.002910964610.000650.90080.90120.03110.000580.027350.02410.00530.02770.1028-0.99050.9709

Таблица 7. Точность

ЭпохаТочность
MSEMAEMAPEMSLESHHCHLCCCESCCEBCEKLDPSSCPML
10.98100.78460.96990.98200.97900.96810.96980.97940.98430.93910.98260.98370.98400.98130.9856
20.98820.97760.97990.98820.98320.98090.98060.98740.99040.98580.99000.98990.99020.98920.9897
30.99030.98690.98190.99020.98980.98640.98210.98920.99150.99000.99040.99100.99100.98900.9907
40.98930.98900.98920.99020.98930.98630.98400.99060.99200.99080.99040.99110.99130.99040.9912
50.99300.98630.98960.99140.99020.98860.98480.99180.99160.99240.99050.99120.99200.98920.9888

Четыре лучших результата получены при употреблении MSE, SCCE, CCE и PSS.

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

Приведенные в табл. 7 оценки результатов обучения сети с различными функциями потерь можно представить в виде диаграммы (рис. 13).

MNIST. Точность обучения для различных функций потерь

Рис. 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:

Скорость обучения

Одним из параметров метода оптимизации является скорость обучения. Важно правильно выбрать начальное значение скорости обучения.
В процессе обучения полезно контролировать значение этого параметра следующим образом:

Влияние скорости обучения на процесс обучения иллюстрирует рис. 14.

Скорость обучения

Рис. 14. Кривые обучения НС при различных значениях скорости обучения

Переобучение

Переобучение иллюстрирует рис. 15.

Переобучение

Рис. 15. Переобучение

Переобучения можно избежать, принимая, например, следующие меры:

Литература

  1. Yann LeCun, Corinna Cortes, Christopher J.C. Burges. The MNIST database of handwritten digits. [Электронный ресурс] URL: http://yann.lecun.com/exdb/mnist/ (дата обращения: 29.06.2018).
  2. Николенко С., Кадурин А., Архангельская Е. Глубокое обучение. – СПб.: Питер, 2018. – 480 с.
  3. Gradient-Based Learning Applied to Document Recognition / Y. LeCun et al. // Proc. IEEE, 1998, vol. 86, no. 11. – P. 2278–2324.
  4. Shafkat I. Intuitively Understanding Convolutions for Deep Learning. [Электронный ресурс] URL:https://towardsdatascience.com/intuitively-understanding-convolutions-for-deep-learning-1f6f42faee1 (дата обращения: 29.06.2018).
  5. Сверточная нейронная сеть, часть 1: структура, топология, функции активации и обучающее множество. [Электронный ресурс] URL:https://habr.com/post/348000/ (дата обращения: 29.06.2018).
  6. Keras-team/keras. [Электронный ресурс] URL: https://github.com/keras-team/keras/blob/master/examples/mnist_cnn.py (дата обращения: 29.06.2018).
  7. TensorFlow losses. [Электронный ресурс] URL: https://github.com/tensorflow/tensorflow/blob/r1.11/tensorflow/python/keras/losses.py (дата обращения: 29.06.2018).
  8. TensorFlow backend. [Электронный ресурс] URL: https://github.com/tensorflow/tensorflow/blob/r1.11/tensorflow/python/keras/backend.py (дата обращения: 29.06.2018).
  9. TensorFlow nn_impl. [Электронный ресурс] URL: https://github.com/tensorflow/tensorflow/blob/r1.11/tensorflow/python/ops/nn_impl.py (дата обращения: 29.06.2018).
  10. Keras. Activations. [Электронный ресурс] URL: https://keras.io/layers/advanced-activations/ (дата обращения: 29.06.2018).
  11. Keras. Advanced activations. [Электронный ресурс] URL: https://keras.io/layers/advanced-activations/ (дата обращения: 29.06.2018).
  12. Николенко С. Линейная регрессия, регуляризация, предсказание, выбор модели. [Электронный ресурс] URL: https://logic.pdmi.ras.ru/~sergey/teaching/mlau12/04-linear3.pdf / (дата обращения: 29.06.2018).

Список работ

Рейтинг@Mail.ru